[DGD] Java or LPC (DGD)?

Albert Deinbeck albert-deinbeck at albert-deinbeck.de
Sat Aug 16 14:42:28 CEST 2003


----- Original Message -----
From: "Felix A. Croes" <felix at dworkin.nl>
To: <dgd at list.imaginary.com>
Sent: Thursday, August 14, 2003 4:57 PM
Subject: Re: [DGD] Java or LPC (DGD)?


> "Albert Deinbeck" <albert-deinbeck at albert-deinbeck.de> wrote:
>
> > From: "Felix A. Croes" <felix at dworkin.nl>
>
> >[...]
> > > The only way to accomplish this is to split each class in two: a
> > > wrapper class which contains the values, and a wrapped class for
> > > the program code which may be updated.
> >
> > An interesting approach, but of course not the only way. You presume the
use
> > of standard java serialization, which won't work with modified classes
> > because a checksum of the class (i.e. the class bytecode) is saved along
> > with the serialized instance.
> > You can, however, use another form of serialization, either using the
> > externalizable interface or some external serialization library like
Java
> > Serialization to XML (JSX). The latter would also allow for other
programs
> > or simple editors to modify the serialized data.
>
> Actually, I was not presuming any sort of serialisation at all -- I was
> treating the problem of saving and restoring data for an object as
> already solved.  I wasn't aware that ordinary Java serialization
> includes a checksum for the bytecode  -- how silly!

I do not think it's silly. Java was designed to be secure in a context of
wordwide interchange and use of classes and serialized data. In this context
it is a security hole to let classes be deserialized from sources other than
produced by the origin class.
Imagine you use a class made by another guy from Japan. Changes he makes to
private methods or variables are invisible to users of
his class. However, you may serialize and store an instance, then update to
a new version and reload it and crash.
What would you do if an object is stored, then the class is changed and
class variables modified so that they don't match any more and then the
object is tried to be reloaded again?

> >   The wrapper class must create
> > > the wrapped class using a private ClassLoader instance.  To upgrade
> > > the wrapped class, a new ClassLoader instance must also be created.
> >
> > This is not necessary if you use a compiling classloader which checks
every
> > time the class is accessed, whether the class file has been modified an
> > compiles and reloads the class if necessary. This way, you can use one
> > classloader and circumvent the problems described below....
>
> You could get it to work that way by giving each wrapped class a new
> private name, every time you recompile it (since the "official" name
> is in the simulated global namespace anyhow).  Unfortunately, that would
> keep the old classes around, no longer used.  A memory leak, basically.

You needn't use a new name for the class. You can call Class.forName() with
the name of the changed class and the class will be replaced. The old class
object will remain in memory as long as it is referenced from anywhere, i.e.
as long as there are instances of the old class around.

> > > What I previously referred to as "the problem of dependencies" is not
> > > generally solvable.  Old instances referred to by the thread of
> > > execution will continue to exist side by side with new ones, resulting
> > > in possible problems until the old instances are no longer used.
> >
> > This problem is of course solvable. If you have a wrapper class
containing
> > the real class instance, the wrapper could have a method:
> > public void update(){
> >     store(myObject,fileName);
> >     myObject = null;
> >     getClassLoader().loadClass("MyObjectClass");
> >     myObject = (MyObjectClass) load(fileName);
> > }
> > where store serializes the class to a file and load unserializes it and
> > returns an Object.
>
> This won't work because "myObject = null;" does not actually unload
> the object, even if it is the single remaining reference.  Java does not
> allow recompilation of an already loaded class, and does not have a way
> to force unloading of an unreferenced object, other than by unreferencing
> the class loader (which is why I suggested using one classloader per
> class).

Every time a class is referenced, the classloader is asked to retrieve the
class. If you write
class Outer {
 public MyClass myInstance = new MyClass();
...
then the compiler will insert a logic reference. As soon as Outer class is
loaded, the classloader
checks all logic references and also loads the referenced classes, in this
case MyClass. Then the logic
reference is replaced by a physical reference (a position on the heap).
So as long as Outer class exists, it points to the version of MyClass which
was present when Outer was loaded.
You can, however, delay the resolution of logical references.

class Outer {
 public MyInterface myInstance =
(MyInterface)Class.forName('MyClass').newInstance();
...
will do the same, but the class MyClass is loaded exactly at the time and
EVERY time the line above is executed.
The classloader loading Outer has no chance to load MyClass in advance.
Every time you call this line the classloader will be run.
All you have to do then is to write a CompilingClassLoader which first
checks for a new MyClass.java before he looks if there is
already a class object loaded.
The classloader calls findLoadedClass() to retrieve an already loaded class
object. all you have to do is:

class CompilingClassLoader extends ClassLoader {
public Class loadClass(String name, boolean resolve)
    Class cls = null;
    cls = findLoadedClass(name);
    filename = name.replace('.','/'); // java.lang.String =>
java/lang/String
    classname = filename+".class";
    javaname = filename+".java";
    File classFile = new File(classname);
    File javaFile = new File(javaname);
    if(javaFile.exists() && !classFile.exists() || javaFile.lastModified() >
classFile.lastModified()) {
        ... compile the class and load the new class into a byte array ...
       cls = defineClass(name,bytes,0,bytes.length);
    }
}

I have left out some parts not necessary for understanding.
This way you can change classes on the fly. All you have to do then is to
make every object holding a reference
to refresh it. This is where a wrapper could be useful which is the only one
to hold a reference to the actual object.

> > Interfaces can be very useful in this case, used to define the static
> > interface of modifyable classes. If your class is:
> > class Living implements LivingInterface { // important Living methods}
> > class Living2 implements LivingInterface { // modified Living methods}
> > then you can make
> > new Living.store("Living.dat");
> > new Living2.store("Living2.dat");
> > LivingInterface myLiving = (LivingInterface) load("Living.dat");
> > LivingInterface myLiving = (LivingInterface) load("Living2.dat");
> >
> > So myLiving can in effect store different classes transparently.
>
> This is what I myself suggested as an alternative :)  But of course,
> in a persistent environment interfaces have to be as mutable as
> class bytecode.

There is something developers have to rely on, as well as Virtual Machines.
You do expect every LPC object to possess certain
functions. Interfaces have to be generic, to allow interchange without
hindering extensions.
For example: Every command in DGD implements the command interface:
interface Command {
    public void do_command(String string);
}
Interfaces should not and cannot be as mutable, as they are what others rely
on. You can however make new interfaces and subclass them. If you make
interface SuperCommand extends Command {
   public void do_command(String string, Permission perm);
}
you can use advanced functionality but keep backwards compatibility as every
class implementing SuperCommand also isInstanceOf Command.

Regards,
Albert


_________________________________________________________________
List config page:  http://list.imaginary.com/mailman/listinfo/dgd



More information about the DGD mailing list