Skip to content.

Overview

This page describes the design and technical details of "Save/Open workspace" functionality. We have used XStream library for implementing this functionality. XStream library can convert an object in memory to a XML file, and vice versa. Thus, when the user saves a workspace, we store all the objects that are present in the workspace into a XML file using XStream. We also store other state information such as active workspaces and plugin names in a separate metadata file. Using these two files, we can recreate the workspace state when user wants to restore workspace from a file. The design for save/open workspace can be extended easily to suit the needs of new layer classes. Following sections describe the serialization and deserialization in details.

Architecture

Following diagram displays the classes involved in implementing save/open workspace functionality. The new methods added for this functionality are highlighted in red.

Serialization

Serialization process starts in WorkspaceViewer::storeState function. The whole process can be summarized in following steps:
  1. Find the active plugins and active workspace names. Create a list of these names.
  2. Using Xstream, store the above list to the metadata file. We need to instantiate these workspaces and plugins before trying to load the XML data file. (This is required for XStream. See the XStream section for details.)
  3. Loop through each layer in every viewport and convert it into an object of mbSerializedLayer class. Store all the mbSerializedLayer objects in a vector. Following are the intermediate steps for converting a layer into mbSerializedLayer:
    1. Create a dlLayerFeatures object for the layer by calling createLayerFeaturesObject method on the layer object.
    2. Now call the serialize method for the layer object, and pass the dlLayerFeaturesObject created in step 1 as an argument. The serialize method will store the features of this layer into the dlLayerFeaturesObject passed as argument.
    3. Create an instance of mbSerializedLayer object to hold the current layer in serialized form. Now store the dlLayerFeaturesObject from step 2 in this object. Other information about the layer such as viewport, mbObject contained in the layer, parent-plugin name etc. is also stored in the mbSerializedLayer object.
    4. mbSerializedLayer objects created for all the layers are stored in a vector.
    5. Using Xstream, store the vector generated in previous step to the data file.

In serialization process, every layer object is converted into an object of mbSerializedLayer class. The code for mbSerializedLayer class looks like the following:

   public class mbSerializedLayer 
   {
   
      // Stores the name of the plugin that loaded this layer.
      private String parentPluginName;

      //Stores the object that was loaded in this layer.
      private Object layerObject;

      //Stores the viewportId in which this layer was loaded.
      private int viewportId;

      //Stores the object containing details about this layer.
      private Object layerFeatures;
      
      public mbSerializedLayer() {}
   
      public mbSerializedLayer(String parentPluginName, Object layerFeatures) {
         this.parentPluginName = parentPluginName;
         this.layerFeatures = layerFeatures;
      }
   
      // getters and setters to access the private member variables
      // follow from this point onwards.
          ....
   }

mbSerializedLayer class represents a condensed form of a layer. MBAT supports loading of different types of layer objects into Viewports. All the layers have properties such as parent plugin name, viewport in which the layer was loaded, and the mbObject being displayed. These common properties of the layer are stored in parentPluginName, viewportId and layerObject member variables of mbSerializedLayer class. For capturing other required information about the layer object, layerFeatures member variable is used.

Serialization is done using a chaining approach. Every class in the hierarchy (JLayerImage2D,dlLayerImage2D & dlLayer) implements serialize method. Each of these methods accepts an object as input. The values of class's member variables are stored in this object, and then the object is passed to the parent's serialize method to store parent's member variable values. Following diagram explains this with an example:

Following is an example of the chaining approach used in the serialization process. While serializing a JLayerImage2D object, serialize methods in JLayerImage2D, dlLayerImage2D and dlLayer are called in a chained manner. Following is the sequence of operation:

  • WorkspaceViewer::storeState method calls the createLayerFeaturesObject for a layer. This object is passed on to serialize method for JLayerImage2D class.
  • serialize method in JLayerImage2D class is responsible for storing member variables from JLayerImage2D class that are used to display an object of the class. JLayerImage2D class extends dlLayerImage2D class. It inherits all the display defining members from dlLayerImage2D class, and hence the serialize method in this class is not required to do anything other than just calling super.serialize.
  • dlLayerImage2D::serialize method stores values for translation, rotation and scaling fields into the layerFeatures object passed in as argument. After this, it calls the parent's serialize method(in this case dlLayer::serialize) and passes layerFeatures object to it. dlLayerImage2D::serialize method looks like the following:
      public void serialize(dlLayerFeatures layerFeatures) 
      {
          // Store rotation, scaling and translation information.
          layerFeatures.setRotation(this.getTextureRotation());
          layerFeatures.setScale(this.getTextureScale());
          layerFeatures.setTranslation(this.getTextureTranslation());
       
          ... storing other member variables specific to dlLayerImage2D class...


          // Call the parent serialize function to serialize parent details.
          super.serialize(layerFeatures);
      }
  • dlLayer::serialize method stores the value for opacity into the object passed in as argument. Since dlLayer class is the root of the hierarchy, it does not do a super call. dlLayer::serialize method looks like the following:
       public void serialize(dlLayerFeatures layerFeatures)
       {
           // Store member variables specific to this layer in
           // layerFeatures object passed in as argument
           layerFeatures.setOpacity(this.getOpacity());
       }

We can see from above code that as we move up the hierarchy, more and more information gets added to dlLayerFeatures object. At the end of the chain process, the resultant dlLayerFeatures object contains all the information to required to recreate the corresponding layer. This object gets stored as layerFeatures member variable of mbSerializedLayer object.

Deserialization

Deserialization process starts in WorkspaceViewer::restoreState function. This process can be summarized into following steps:
  1. Using Xstream, deserialize the metadata file, and reload the workspaces and plugins that were active when the workspace was saved.
  2. Using Xstream, deserialize the data file. This results into a vector of mbSerializedLayer objects.
  3. For every element in the vector from step 2, call the deserialize method. In deserialization, all the member variables for the layer class get initialized and transformations are applied.
  4. Call the repaint method for the GLCanvas object, to display the deserialized contents into the viewports.

Following diagram shows how a JLayerImage2D layer gets recreated from the serialized form:

As in the serialization, deserialization is also done using the chaining technique. The layerFeatures object from each of the mbSerializedLayer object is passed to the deserialize method of layer. Inside this method, the member variables specific to this class are instantiated, and then the request for further deserialization is passed on to the parent class by calling super.deserialize and passing the layerFeatures object. For example, consider the following code pieces:

In class dlLayerImage2D::deserialize,

    public void deserialize(dlLayerImage2DFeatures layerFeatures) 
    {
       // Get information about translation, scaling and rotation
       Point2D.Float translation = layerFeatures.getTranslation();
       Point2D.Float scale = layerFeatures.getScale();
       Float rotation = layerFeatures.getRotation();
      
       // Apply the transformations to this layer
       this.setTextureTranslation(translation.x, translation.y);
       this.setTextureScale(scale.x, scale.y);
       this.setTextureRotationRel(rotation);
   
        ... Set other member variables for the dlLayerImage2D class...
   
       // Delegate to parent class de-serialization function
       super.deserialize(layerFeatures);
    }
dlLayerImage2D class inherits from dlLayer class, thus deserialize method in dlLayer class gets called from the last line in above code. dlLayer::deserialize method code looks as below:
   public void deserialize(dlLayerFeatures layerFeatures)
   {
       // Set opacity value for the layer. 
       // Note opacity is defined in dlLayer class.
       this.setOpacity(layerFeatures.getOpacity());
   }

The advantage of this design is that we can easily create new types of layers based on either dlLayer or dlLayerImage2D classes without having to worry about how to serialize/deserialize the information stored in these base classes.

XStream

XStream is a simple library to serialize objects to XML and back again. XMLSerializer class in 'net.nbirn.common.xml' package encapsulates the calls made to XStream functions.

XStream and JPF

MBAT uses JPF to load plugins dynamically. When loading a plugin, JPF creates a new "ClassLoader" object and loads all the classes for the plugin using this new classloader. Thus, classes for different plugins get loaded into different class loaders. This makes sure that there are no namespace collisions. But, this has a side effect while using XStream.

When saving an object to XML file, XStream retrieves the class definition from the object itself. But while loading an object from XML file, we need to provide all the required classloaders to XStream. XStream will use this to get the class definitions. Since, in our case, different plugin classes have been loaded using different class loaders, the situation becomes a little more tricky. Fortunately, XStream provides a CompositeClassLoader?, which is basically combination of several class loaders. When a function is called on it, it interrogates each registered class loader to work out which one can service the request. The information about which class loaders are required is stored in the metadata file that is created in the serialization process. Using this information, the code loads all the workspaces and plugins, and then starts loading individual layer objects.

Attachment sort Action Size Date Who Comment
im2.jpg manage 80.0 K 25 Mar 2009 - 22:08 NikhilSane  
serialization_flow.jpg manage 43.3 K 26 Mar 2009 - 17:57 NikhilSane  
deserialization_flow.jpg manage 37.1 K 26 Mar 2009 - 17:57 NikhilSane  
serialize_seq.jpg manage 24.5 K 26 Mar 2009 - 23:10 NikhilSane Sequence diagram for serialization
deserialize_seq.jpg manage 36.7 K 26 Mar 2009 - 23:42 NikhilSane  
save_design.jpg manage 100.1 K 26 Mar 2009 - 23:55 NikhilSane  
save_design_new.JPG manage 100.1 K 26 Mar 2009 - 23:56 NikhilSane  
save_design_updated.jpg manage 168.0 K 20 May 2009 - 18:12 NikhilSane