Implementing a new data source

This section is based on the sample located in <installdir>/samples/datasource/explorer .

Implementing the data source

IltDefaultDataSource includes support for any IlpClass instances and handles tables mapping IlpObject instances with identifiers. Deriving FileDataSource from that class allows you to benefit from this functionality.

How to implement a data source

Extend the IltDefaultDataSource class as follows:
public class FileDataSource extends IltDefaultDataSource {
Write the constructor on the following model:
public FileDataSource() {
    super();
    // Retrieve the File Java class as an IlpClass
    IlpMutableClassManager classManager = getContext().getClassManager();
   fileClass = (IlpMutableClass)classManager.getClass(File.class.getName());
    // Create a subclass of the java.io.File IlpClass for root objects,
   // so as to apply different styles to them.
   classOfRoots = new IlpDefaultClass("DirectoryRoot",
                                      fileClass,
                                      Collections.EMPTY_LIST);
   fileClass.addSubClass(classOfRoots);
   classManager.addClass(classOfRoots);
   // loads the roots
   File[] rootdirs = File.listRoots();
   addFiles(rootdirs, classOfRoots);
   }
This data source is meant to read in objects of type File which, in Java™, are instances of the class java.io.File . In this sample code, java.io.File objects are defined by the IlpClass automatically generated by the class manager. Here, the class java.io.File is considered as a JavaBeans™ class (which is acceptable, as most of its methods comply with JavaBeans conventions). As an alternative, you could create a dedicated IlpClass implementation and the corresponding IlpObject implementation. See Defining the business model with dynamic classes and Adding dynamic business objects.
There is a known limitation in Microsoft® Windows® environments: the getName method of the class java.io.File does not display labels correctly. This is why it is necessary here to subclass the IlpClass defining java.io.File to represent the roots of the file system.
Once the required IlpClass objects are known, the data source is initialized with the root objects returned by the listRoots method of the class java.io.File .

How to insert objects into a data source by reading from an array of files

The addFiles method, detailed below, iterates over an array of Files , creates the corresponding IlpObject instances if they are not yet present in the data source, and inserts these objects into the data source.
protected synchronized void addFiles(File[] files, IlpClass ilpClass) {
      List filesAsIlpObjects = new ArrayList(files.length);
      for (int i = 0; i < files.length; i++) {
        // use the file itself as the object identifier
          Object id = files[i];
          if (getObject(id) == null) {
            IlpBeansObject ilpObject = new IlpBeansObject(files[i],
              ilpClass,files[i]);
            filesAsIlpObjects.add(ilpObject);
          }
      }
      if (filesAsIlpObjects.size() != 0)
        addObjects(filesAsIlpObjects);
    }

How to implement load-on-demand in a data source

The current implementation of IltDefaultDataSource handles structural information (that is, child-parent relationships) pertaining to IlpObject instances independently of the objects themselves.
This implementation makes it possible for you to load objects on demand in two different ways:
  • with the getObject(Object id) method
or
  • with the getContainerInterface method
    This method returns an implementation of the IlpContainer interface. This interface contains a getChildren method that returns the identifiers of the children of the IlpObject .
In our example, we have implemented load-on-demand by redefining the getContainerInterface of the data source (see Implementing the getContainerInterface method). Since we do not use the access to structural information provided by the default data source in order not to duplicate it from the File class where it is already present, we also provide a specific implementation of the getChildInterface method (see Implementing the getChildInterface method and the IlpChild interface).
Note
You could also create IlpObject instances dynamically by reimplementing the method getObject(Object id) .

Implementing the getContainerInterface method

The method getContainerInterface returns an IlpContainer. If the object is a container, the method should return a non-null value, whether this container has children or not, as shown in the following example.

How to get a container

public synchronized IlpContainer getContainerInterface(Object idOrIlpObject) {
   IlpObject object = idOrIlpObject instanceof IlpObject ? 
(IlpObject)idOrIlpObject : getObject(idOrIlpObject);
    if (object != null && fileClass.isAssignableFrom(object.getIlpClass()))  {
     File file = (File)((IlpBeansObject)object).getBean();
     if (file.isDirectory())
       return DIRECTORY_LOADER;
     else
       return null;
   }
   return super.getContainerInterface(idOrIlpObject);
  }
The getContainerInterface method takes an IlpObject or an identifier as parameter. It retrieves the IlpObject instance from its parameter and checks whether the returned object is an instance of the IlpClass corresponding to the File class and whether File is a directory. If it is not a directory, it returns null or the default implementation of the data source, if the object is not of the File IlpClass . If it is a directory, the method returns a specific implementation of the IlpContainer interface (see Implementing the IlpContainer interface).
Note that in this example, the IlpObject is downcast to an instance of IlpBeansObject and that the Bean it contains is in turn downcast to a File instance. We could have used the directory attribute of the IlpClass directly as follows:

How to use the directory attribute of an IlpClass directly

Boolean isDirectory = 
    Boolean)object.getAttributeValue(fileClass.getAttribute("directory")) ;
if (isDirectory.booleanValue())
   return DIRECTORY_LOADER;
else
   return null;

Implementing the IlpContainer interface

The default data source provides its own implementations of all the structural interfaces (such as IlpChild or IlpContainer). These implementations ensure that the information they contain is coherent. For example, if A is the child of B, B is the parent of A.
These implementations do not support load-on-demand. To support load on demand, you have to redefine the IlpContainer interface.
It is quite easy to retrieve the children of a File object and load them. Following is an implementation of IlpContainer for the class java.io.File . It uses a static instance of the class since the entire context of the getChildren method is contained in its parameter, which is the IlpObject instance itself.

How to retrieve child objects

IlpContainer DIRECTORY_LOADER = new IlpContainer(){
      public Collection getChildren(IlpObject object) {
        // the file is supposed to be a directory
        File directory = (File)((IlpBeansObject)object).getBean();
        File[] files = directory.listFiles();
        List filesIds = new ArrayList(files.length);
        for (int i = 0; i < files.length; i++) {
          // 1st get all the identifiers
          filesIds.add(files[i]);
        }

        // then add the files
        addFiles(files, fileClass);
        return filesIds;
      }
    };
Note that to avoid recreating the same IlpObject instance twice, no record is kept as to whether a directory has already been loaded. The test in the addFiles method (described in Implementing the data source) is used to check whether an IlpObject instance exists before creating it and adding it to the data source.

Implementing the getChildInterface method and the IlpChild interface

Since you do not use the access to structural information provided by the default data source, you have to provide specific implementations of the getChildInterface method and of the IlpChild interface. This is quite easy, since the java.io.File class has a getParentFile method.

How to provide specific implementations of getChildInterface and IlpChild

public synchronized IlpChild getChildInterface(Object idOrIlpObject) {
      IlpObject object = idOrIlpObject instanceof IlpObject ? 
(IlpObject)idOrIlpObject : getObject(idOrIlpObject);
      if (object != null && fileClass.isAssignableFrom(object.getIlpClass()))  
{
        File file = (File)((IlpBeansObject)object).getBean();
        File parentFile = file.getParentFile();
        if (parentFile != null) {
          return FILE_PARENT;
        } else {
          return null;
        }
      }
      return super.getChildInterface(idOrIlpObject);
    }
The method gets the IlpObject and checks whether that object is a file. It then checks whether it has a parent file and returns the specific implementation of the IlpChild interface, described below.
Like the other structural interfaces, the IlpChild interface returns an identifier. Here, since the identifiers of the objects are the files themselves, it returns the parent file.

How to get the parent file

IlpChild FILE_PARENT = new IlpChild() {
      public Object getParent(IlpObject object) {
        File file = (File)((IlpBeansObject)object).getBean();
        File parentFile = file.getParentFile();
         return parentFile;

      }