• home
  • forum
  • my
  • kt
  • download
  • Intelligent data keeps Swing simple

    Author: 2007-08-03 11:42:48 From:

    The Swing architecture allows Java developers to create complex displays that present a lot of data. Unfortunately, maintaining that data within large Swing components can be a coding nightmare. In this article, Jonathan Simon presents a technique called iData, or intelligent data. You can use the iData architecture to create a central repository of data within your application. By doing so, you'll more fully separate data from presentation and produce code that's cleaner and easier to maintain. There's even an open source toolkit available with sample code to help you get started. Read on to learn more and see a sample implementation of the iData technique. Share your thoughts on this article with the author and other readers in the discussion forum by clicking Discuss at the top or bottom of the article.

    The advanced Swing architecture enables developers to design more intricate displays than ever before. Such displays often require a significant amount of logic that is error prone and difficult to maintain. For the advanced Swing components (for example, JTable and JTree), the difficulties often arise when program logic uses cell-based data storage, editing, and rendering when more global knowledge is required. Intelligent data, or data with advanced knowledge, can be persisted into component models as cell data providing the necessary knowledge to develop advanced applications. The iData technique described in this article establishes a generic architecture for integrating intelligent data with Swing components while preserving the Model-View-Controller architecture. This is accomplished through a tightly integrated indirection scheme using intelligent data for data storage, data retrieval indirection, and display-setting indirection. The resulting indirection objects create a flexible and extensible central location for implementing complex business display logic and interaction functionality with minimum complexity.

    An open source iData toolkit is available to assist developers in integrating the iData architecture into their own projects. The toolkit consists of a collection of interfaces defining the indirection layers, as well as default implementations, optimizations, custom editors and renderers, and numerous examples. See the Resources section for a link to this toolkit.

    The three layers of the iData technique

    The iData technique consists of three layers.

    • Data storage: The iData technique assumes that an application is storing data in DataObjects. A DataObject is loosely defined as a JavaBean-compliant object containing a number of fields with corresponding get[FieldName]() and set[FieldName]() methods.

    • Indirection of data values to display components: The data indirection layer consists of an interface defining an object that contains the DataObject. This is known as the intelligent data, or iData layer. (Note that the iData layer is not to be confused with the iData technique, the name of the architecture as a whole.) The iData layer interfaces define generic methods to access and mutate fields in DataObjects. Each concrete iData layer class implements these generic accessor and mutator methods for a specific requirement. Usually, the iData layer implementations will simply get and set values in the DataObject. As you will see in the examples, however, this indirection creates a centralized location for implementing complex logic, including editing validation, virtual data, and data decoration. The iData level is further subdivided into functionality for immutable (read only) and mutable (read/write) data. This distinction is made to simplify interfaces with complex noneditable data with no need for editing logic.

    • Display indirection to customize the editing and rendering components based on data: The intelligent display, or iDisplay layer, accomplishes intelligent displays using indirection similar to the iData layer. The iDisplay layer defines an interface for components that edit and render iData layer objects. Examples of this iDisplay layer customization include displaying error conditions by changing cell background colors, and creating generic editors that allow an iData layer implementation to determine the component that is most suitable to edit its data. As with the iData layer, the iDisplay layer is also subdivided into functionality for immutable and mutable data.

    These three layers combine to create a tightly integrated set of indirection objects that are added to the component models rather than the data itself. The architecture makes cell-based knowledge possible while preserving the Model-View-Controller architecture in Swing. Logic for retrieving, displaying, and editing data is encapsulated in an intelligent data object inside each cell. The result is a functionally flexible and extensible technique for implementing complex user interface displays and interaction.


    Figure 1. Class diagram of the complete architecture for the iData technique
    Class diagram of the complete architecture for the iData technique

    In the following sections, we'll look at each layer of the architecture for the iData technique. Along the way, we'll build pieces of a hypothetical Bike Shop application to demonstrate the technique.



    Back to top


    DataObjects

    As noted above, a DataObject is defined as a JavaBean-compliant object containing a number of fields with corresponding get[FieldName]() and set[FieldName]() methods. The data fields are typically combined in a DataObject by business area. Our example Bike Shop application has a DataObject called Bicycle with numerous fields (modelName, manufacturer, price, cost, and so on) and corresponding get and set methods. Other potential DataObjects in the Bike Shop: a BicycleComponent with fields similar to Bicycle's, and a Purchase DataObject with fields like purchasorName, price, dateOfPurchase, and so on. The following is an example of a portion of the Bicycle DataObject from the Bike Shop application.


    Listing 1. Sample DataObject
    public class Bicycle
    {
         //fields
         double price = ...
         String manufacturer = ...
         ...
    
    
         //default constructor
         public Bicycle(){}
    
    
    
         //accessors
         public Double getPrice()
         {
              //sometimes its necessary to wrap primitives in related 
              //Object types...
              return new Double(this.price);
         }
    
         public String getManufacturer()
         {
              return this.manufacturer;
         }
    
    
         ...
    
    
         //mutators
         public void setPrice(Double price)
         {
              this.price = price.doubleValue();
         }
    
         public void setManufacturer(String manufacturer)
         {
              this.manufacturer = manufacturer;
         }
    
         ...
    
    }     
    



    Back to top


    Indirection: The iData layer

    As noted above, the iData layer is subdivided into functionality for immutable and mutable data. Since the MutableIData interface extends the ImmutableIData interface, we'll start by examining the immutable functionality.

    Data indirection layer for read-only intelligent data (ImmutableIData)

    The ImmutableIData interface is a part of the iData layer; it represents the immutable iData indirection. It consists of two methods and one recommended method overriding:

    • getData() returns a typed data value from a DataObject.

    • getSource() returns the DataObject itself.

    • Overriding the toString() method returns the string representation of the getData() result.

    As an example, let's look at an ImmutableIData implementation for the Manufacturer field.


    Listing 2. ImmutableIData implementation for Bicycle Manufacturer
    public class BicycleManufacturerIData implements ImmutableIData
    {
      //the DataObject
      Bicycle bicycle = null;
    
      public BicycleManufacturerIData(Bicycle bicycle)
      {
        this.bicycle = bicycle;  //cache the DataObject 
      }
    
      public Object getSource()
      {
        return this.bicycle; //this simply returns the DataObject
      }
    
      public Object getData()
      {
        //returns the manufacturer field from the DataObject.  
        //This is the main logical method of the indirection layer.
        return bicycle.getManufacturer(); 
      }
    
      public String toString()
      {
        //create a safe to String method to avoid null pointer exceptions 
        //while painting...
        Object data = this.getData();
        if (data != null)     
          return data.toString();
        else
          return "";
      }
    }
    

    The iData toolkit provides an abstract class implementing ImmutableIData called DefaultImmutableIData. It overrides the toString() method in Object to safely return getData().toString(). The rest of our examples will extend default implementations of the iData layer interfaces. These default implementations are included in the toolkit as well.

    Integration with JTable
    Let's continue with our Bike Shop example and integrate the iData technique into a JTable. The table has columns for manufacturer, modelName, modelID, price, cost, and inventory. Assume that the rest of the ImmutableIData implementations have been written along the lines of the manufacturer iData.

    The data actually added to the JTable DataModel are the ImmutableIData implementations, each containing a DataObject. This notion of adding the iData layer implementation containing a DataObject is the realization of the indirection layer referred to earlier.


    Figure 2. JTable cells with ImmutableIData implementations containing DataObjects
    JTable cells with ImmutableIData implementations containing DataObjects

    I find it useful to have a helper method I call createRow() to create an entire row at once. When using a subclass of AbstractTableModel, this row can be added to the model in its entirety. The createRow() method takes a DataObject as a parameter and instantiates the appropriate ImmutableIData implementations for the particular table.


    Listing 3. createRow() method
    protected Vector createRow(Bicycle bicycle)
      {
        Vector vec = new Vector();
          vec.add(new BicycleModelNameImmutableIData(bicycle));
          vec.add(new BicycleModelIDImmutableIData(bicycle));
          vec.add(new BicycleManufacturerImmutableIData(bicycle));
          vec.add(new BicyclePriceAndCostImmutableIData(bicycle));
          vec.add(new BicycleProfitImmutableIData(bicycle));
          vec.add(new BicycleInventoryImmutableIData(bicycle));
        return vec;
      }
    

    Additionally, the createRow() method is a centralized location for the logic that determines which ImmutableIData implementations are placed in the model. It can also be useful from a class management perspective to use anonymous inner classes declared directly in the createRow() method for simple ImmutableIData implementations.

    Rendering sequence
    Default renderers create string representations of the objects they are to display by calling the objects' toString() methods. This is one of the reasons it is important for ImmutableIData implementations to have a useful toString() method. At rendering time, the renderer receives an ImmutableIData implementation from the JTable. To render the iData, the toString() method is called. The following represents the entire rendering sequence:

    • toString() on the iData implementation
    • getData() on the iData implementation
    • get[FieldName]() on the DataObject

    Figure 3. Rendering sequence
    Rendering sequence

    Figure 4. Read-only table
    Read-only table

    Dynamic persistence
    Using DataObjects as the iData's data not only provides flexibility for the iData indirection, but also adds a useful layer of data indirection providing dynamic persistence. Consider the example of a displayed table whose values are being updated externally. Ordinarily, the client would need to implement complex logic to deduce the location within the model in which to persist the updated value. This logic is entirely unnecessary when using the iData and DataObject indirection, as new values are automatically persisted. This is a result of populating models with multiple iData objects containing the same DataObject instance. When a DataObject's internal values are changed, the DataObject itself remains unchanged, since all iData objects point to the same instance. Requerying the DataObject using its get and set methods always returns the most updated results without any manual persistence. The only action the client must perform upon updates is a repaint, which forces the renderers to re-render the updated cells, which in turn retrieves and displays the new data values.

    One result of this indirection is the ability to have a unified client data cache. Assuming components use iData indirection and DataObjects from the central client cache, all data edits are dynamically persisted across the entire application. This can greatly simplify trading systems and other clients responsible for displaying dynamic data.

    Example: Virtual columns
    Virtual columns represent one example of the flexibility of the iData technique. A virtual column is a column that contains data that is not explicitly in the model, but rather is a composite of multiple fields. Consider a column called profit that would display the difference between price and cost. To create such a column, create an ImmutableIData implementation where getData() returns the difference between price and cost.


    Listing 4. Profit virtual column
    public class BicycleProfitImmutableIData extends DefaultImmutableIData
    {
      ...
      
      public Object getData()
      {
        //return the difference of the price and cost field from the DataObject
        return new Double(bicycle.getPrice() - bicycle.getCost());
      }
    }
    

    Creating this type of virtual column with the standard model would require a significant amount of logic. Initially, the model would be populated with the correct values. Problems would occur when price or cost were edited, requiring complex and often error-prone logic to update the profit values across the application. With the iData technique, no updating is necessary when a price or cost is edited. Persistence occurs dynamically.

    Using iData objects as building blocks
    The indirection layer is implemented as group of iData objects, resulting in substantial benefits. For example, the PriceAndCost display, which appends two data values, can also be implemented using composition. Rather then retrieving both data values directly from the DataObject within the new CompositePriceAndCost display, we can use the previously written BicyclePriceImmutableIData and BicycleCostImmutableIData objects. The getData() method creates the return string by appending the values retrieved from the two iData layer implementations delineated by a separator -- a slash, in this case. The resulting getData() method looks like this:


    Listing 5. Composite implementation of PriceAndCostImmutableIData

    public Object getData()
    {
        // append the price, a slash, and the cost using pre-built iData 
        // implementations     
        return new String( (String)priceIData.getData() + " / " + 
            (String)costIData.getData() );  
    }
    

    The ability to combine the different iData implementations promotes code reuse and flexibility. You can start to develop new iData implementations by composing different combinations of pre-existing iData implementations. This means fewer concrete classes as well as more flexibility as the composition facilitates the dynamic composition of iData implementations at runtime. The toolkit contains a number of helper classes for implementing simple composition-based iData objects, including a prefix and suffix string decorator iData implementation that takes an arbitrary iData object with a suffix and/or prefix to decorate the iData's string representation.

    A reflection-based ImmutableIData implementation (UniversalImmutableIData)
    One of the main drawbacks of the iData approach is class explosion. The number of iData classes can quickly get out of hand in large applications. The majority of iData layer implementations repeat the same sequence in which the getData() request is redirected to a get[FieldName]() method in the DataObject. This can be generically implemented using reflection. The toolkit contains a default implementation of a reflection-based ImmutableIData implementation called UniversalImmutableIData. UniversalImmutableIData takes a DataObject and a field name as initialization parameters. Internally, it takes the field name and retrieves the get[FieldName]() method, which is invoked when either the getData() or the toString() method is called. This approach simplifies development and reduces class explosion at the cost of the slight performance reduction involved with using reflection. Most applications will not be affected by this performance reduction, but developers of large or real-time applications should keep it in mind.


    Listing 6. createRow() method using UniversalImmutableIData

    protected Vector createRow(Bicycle bicycle)
    {
      Vector vec = new Vector();
        vec.add(new UniversalImmutableIData(bicycle, "modelName"));
        vec.add(new UniversalImmutableIData(bicycle, "modelID"));
        vec.add(new UniversalImmutableIData(bicycle, "manufacturer"));
        vec.add(new UniversalImmutableIData(bicycle, "priceAndCost"));
        vec.add(new UniversalImmutableIData(bicycle, "inventory"));
      return vec;
    }
    


    Listing 7. Sample from Reflection-based UniversalImmutableIData
      protected String field = ...  //the field name 
      protected Method accessorMethod = ... //the accessor method
      protected Object source = ... //the DataObject
    
      ...
    
      protected void setMethods()
      {
        if (field == null || field.equals(""))
          return;
    
        //capitalize the first letter of the field, so you get getName, 
        //not getname...
        String firstChar = field.substring(0,1).toUpperCase();
        //remove first letter
        String restOfField = field.substring(1);
        //add together the string "get" + the capitalized first letter, 
        //plus the remaining
        String fieldAccessor = "get" + firstChar + restOfField;
        //cache the method object for future use
        this.setAccessorMethod(fieldAccessor);
      }
    
    
      ...
    
    
      protected void setAccessorMethod(String methodName)
      {
        try
        {
          accessorMethod = source.getClass().getMethod(methodName, null);
        }
        catch ( ... )
        {
          ...
        }
      }
    
      ...
    
    
      public Object getData()
      {
        try
        {
          return accessorMethod.invoke(source, null);    
        }
        catch ( ... )
        {
          ...
        }
      }
    

    Data indirection layer for editable intelligent data (MutableIData)

    MutableIData extends ImmutableIData with the addition of a setData() method to make it mutable. The setData() method takes the new data value as a parameter and returns a boolean reflecting the success of the edit. It is usually necessary to cast the new data value in the setData() method to match the data type that is cached for that field in the DataObject. The standard implementation safely tests the object type, returning false if the class types do not match.


    Listing 8. MutableIData for Bicycle Manufacturer

      public boolean setData(Object data)
      {
         if (!data instanceof String) 
             return false;
         ((Bicycle)this.getSource()).setManufacturer((String)data);
             return true;
      }
    

    Once all of the new MutableIData objects are written, update the getRow() method to instantiate MutableIData instances instead of their Immutable counterparts. I find that I only make an ImmutableIData object when a field is specifically immutable. Otherwise, I make a MutableIData and simply use it in read-only tables, knowing that the setData() method is not going to be called.

    Custom editors
    Now that you've made your data mutable, there is a major change: editing data requires custom editors. If a default editor is used, the JTable will retrieve the editor for data of type Object, which is actually a String editor. Once the editing is stopped, the editor returns a String value that is persisted to the model, replacing the iData layer implementation with a String value. The following diagram depicts the sequence an edit must follow to preserve the integrity of the iData indirection.


    Figure 5. Editing sequence
    Editing sequence

    Although existing editors can be extended to follow this sequence, such an approach is impractical; it leads to class explosion and causes unnecessary complexity. Custom editors are practical in unique situations, but the majority of editors will follow the same sequence, which can be encapsulated in a separate class. The iData toolkit contains an implementation of this class, called UniversalTableCellEditor.

    The UniversalTableCellEditor uses TableCellEditor containment rather than extension. At edit time, the UniversalTableCellEditor strips the data value from the iData layer implementation and initializes the contained TableCellEditor with that value. When editing is stopped, the UniversalTableCellEditor retrieves the value from the TableCellEditor and sets the data in the iData implementation accordingly. If no editor is initially specified, the UniversalTableCellEditor retrieves the default editor in the JTable for the type of the iData implementation's data.

    The entire editing sequence described above is completely encapsulated within the UniversalTableCellEditor. This means that any editor, even a third-party editor, can be used without requiring extensions to implement the iData logic.

    I suggest setting the editor in the JTable by setting the default editor of each TableColumn to a UniversalTableCellEditor. The iData toolkit contains a utility class with a number of static helper methods. The configureTable() method in the utility class iterates through the TableColumns and sets each current editor to a new instance of UniversalTableCellEditor containing the previous cell editor for that column.

    The toolkit has a UniversalTableCellRenderer with similar functionality as it relates to renderers. Universal editor and renderer combinations for JTree and JComboBox/JList are also included in the toolkit.

    Example: In-cell validation guaranteeing that price is greater than cost
    A standard difficulty with editing is in-cell validation, where the data is validated before the cell editing is stopped. The setData() method creates a centralized location for in-cell validation. Consider an example in which the user should be notified if the price is less than the cost after one of those values is edited. In this case, we want to present the user with the following options:

    • Leave both values as is.
    • Raise price to equal cost.
    • Change the unedited value so that the original price difference between it and the recently edited value is maintained.

    This is all done with relative ease in the setData() method. A JOptionPane is presented to the user to identify the preferred option. Once the option is selected, the calculation is performed to set the appropriate values. The knowledge of all of the data values as well as the centralized location for implementing this business logic are the key to the flexibility to the iData technique.


    Listing 9. In-cell validation

    String doNotEdit = "Do Not Edit";
    String priceEqualsCost = "Price = Cost";
    String keepProfitDifference = "Keep Profit Difference";
    String keepProfitPercentage = "Keep Profit Percentage";
    
    ...
    
    public boolean setData(Object data)
    {
        double newCost = new Double(data.toString()).doubleValue();
        double oldCost = this.bicycle.getCost();
        double price = bicycle.getPrice();
    
        ((Bicycle)this.getSource()).setCost(newCost);
    
        if (price < newCost)
        {
          Object result = JOptionPane.showInputDialog
          (
            null,
            "Cost you have entered is more than the set price for this bicycle"
            + "\nPlease select from the following options",
            "",
            JOptionPane.QUESTION_MESSAGE,
            null,
            new Object[]{doNotEdit, priceEqualsCost, keepProfitDifference, 
                keepProfitPercentage},
            priceEqualsCost
          );
    
          if (result != null)
          {
            //persist the data
            if (result.equals(priceEqualsCost))
              this.bicycle.setPrice(bicycle.getCost());  
            //keep the delta between price and cost
            else if (result.equals(keepProfitDifference))
              this.bicycle.setPrice( newCost + (oldPrice - oldCost) ); 
            //keep the same profit percentage
            else if (result.equals(keepProfitPercentage))
              this.bicycle.setPrice( newCost * (oldPrice / oldCost) ); 
          }
        }
        return true;
      }
    

    Using non-JTable components
    Although for the sake of consistency we've been using a JTable in our examples, it's worth examining a simple example using another Swing component. Let's create a JList containing bicycle names. Simply iterate through a collection of Bicycle objects, wrap them in BicycleModelNameImmutableIData objects, and add them to the JList. Note that the same iData instances that are used in the JTable are also used in the JList. These instances can be used in any other components in the same way.


    Listing 10. JList initialization

    protected void initList()
    {
        ...
    
        while ( ... )
        {
           //wrap the bicycle in an iData object and add it to the list model
          Bicycle bicycle =  ...
          model.addElement(new BicycleModelNameMutableIData(bicycle)); 
        }
        //wrap the lists renderer in the iData toolkit universal renderer 
        //for JLists
        list.setCellRenderer(new 
          UniversalListCellRenderer(list.getCellRenderer())); 
    }
    


    Figure 6. JList example
    JList example


    Back to top


    Intelligent display indirection (iDisplay)

    The iDisplay structure is the layer for creating custom displays based on iData. For example, consider an interface where a preferred Manufacturer should be displayed with red text to notify the user of its preferred status. This would normally require complex logic to retrieve data values not passed in by the renderer, resulting in intricate code that would make the implementation of data-centric custom displays impractical. The iDisplays tightly integrate with the iData to make such scenarios simple, and in doing so create a centralized location for extension as well.

    Display indirection layer for read-only intelligent data (ImmutableIDisplay)

    ImmutableIDisplay encapsulates the display-specific logic. This is accomplished by the ImmutableIDisplay having get[Component]CellRendererComponent() methods for each of the three main renderer types: TableCellRenderer, TreeCellRenderer, and ListCellRenderer. ImmutableIDisplayIData integrates ImmutableIDisplay with the ImmutableIData by containing an ImmutableIDisplay.

    When the JTable calls getCellRendererComponent() in UniversalTableCellRenderer and passes in an object of type ImmutableIDisplayIData, the UniversalTableCellRenderer then forwards the getCellRendererComponent request to the corresponding get[Component]CellRendererComponent in the ImmutableIDisplayIData's contained ImmutableIDisplay.

    Let's look at another example from our Bike Shop demo, in which a user enters a bicycle price that is less than the cost and the background of the price and cost cells turn red. The getTableCellRenderer() method of the ImmutableIDisplay retrieves the DataObject and checks to see if the price is less then the cost. If it is, the background is set to red; otherwise, the background is set to white. It is important to remember to set the background explicitly to the default color when a special case is not present. Swing uses the flyweight pattern for rendering, repeatedly painting the same component. Unpredictable results occur if the standard settings are altered for special cases and not reset for standard cases.


    Listing 11. getTableCellRenderer() method for Bicycle cost showing data-based cell coloring

    public TableCellRenderer getTableCellRenderer(JTable table, Object value, 
      boolean isSelected, boolean hasFocus, int row, int column)
    {
          //cache old background for change comparisons
          Color oldColor = renderer.getBackground(); 
          //cache old background for change comparisons
          Color newColor = null;
          //check to see if Object is a MutableIData
          if (value instanceof MutableIData)
          {
            MutableIData arg = (MutableIData)value;  //cast it.
            Bicycle bicycle = (Bicycle)arg.getSource();
            if (arg.getData() instanceof Number)  //check the data type
            {
            // retrieve price and cost from the DataObject
              double cost = ((Number)arg.getData()).doubleValue();
              double price = bike.getPrice();
              //make comparisons
              if (price > cost)
                newColor = Color.cyan;
              else
                newColor = Color.red;
            }
          }
          // check and see if color changed
          if (!newColor.equals(oldColor))
              this.setBackground(newColor);
    }
    


    Figure 7. Table with price and cost background color validation
    Table with price and cost background color validation

    Display indirection layer for editable intelligent data (MutableIDisplay)

    The immutable/mutable distinction holds for iDisplay implementations as well. The MutableIDisplay is responsible for editors, while the ImmutableIDisplay is responsible for renderers. As with the ImmutableIDisplayIData, there is a MutableIDisplayIData that extends MutableIData and contains a MutableIDisplay. Its usage is identical to that of the ImmutableIDisplay except that it implements the get[Component]CellEditor() methods rather than the get[Component]CellRenderer() methods. The toolkit contains custom editors for JTable, JTree, and JComboBox.

    The forwarding of the get[Component]CellRenderer() and get[Component]CellEditor() methods to the iDisplay creates a useful layer of indirection. The primary result is a centralized, encapsulated location for customizing the display settings and functionality. The iData use of containment for the iDisplay rather than extension promotes flexibility and extensibility in addition to limiting class explosion. Most importantly, it nearly eliminates the need for custom editors and renderers, which often contain very intricate display logic. Although complete custom editors and renderers are required, the majority of displays can be implemented using the indirection layer provided by the iDisplay.



    Back to top


    Drawbacks

    There are a few drawbacks to keep in mind when implementing the iData technique:

    • Performance: The iData technique does not present significant performance overhead for most applications. The technique specifies a significant amount of indirection, not logic or processing. Problems will arise, however, if implementations of getData()/setData() methods or the get[Component]CellRenderer()/Editor() methods have too much logic. Any logic in these methods is called for every cell in a component every time the component is painted. Try to keep these methods as concise as possible.

    • Classes added to codebase: There is no doubt that using the iData technique requires a significant number of classes. This is to be expected with any object-oriented technique, and has certain benefits. In fact, much of the application-specific business logic resides in these extra classes, forcing a level of encapsulation that might not otherwise occur. If classes need to be kept to an absolute minimum, this may not be the best choice. A number of optimizations have been presented for these size-critical applications, though there is typically an associated performance cost. Application requirements should be considered when making decisions about code complexity, number of classes, and performance costs.

    • Learning curve: This is the most prominent drawback. The iData technique was designed with flexibility and extensibility in mind. This requires a certain amount of abstraction, which is at first confusing, if not daunting. I believe the architecture is approachable after some exploration, but it does require consistent attention.



    Back to top


    Conclusions

    Populating component models with intelligent data combined with the iData indirection layer helps create a flexible and extensible centralized location for implementing advanced UI functionality. Additionally, this functionality can be implemented with relatively simple logic in fully encapsulated classes to promote flexibility and reuse. The addition of the open source toolkit eases the transition to integrating the iData technique as the much of the code is already written and tested. Each application needs only its own implementation of the iData indirection layer to successfully use the techniques discussed. There are no major Swing component customizations, no custom models, and no changes in standard Swing functionality -- only carefully placed indirection. The result is a system that simplifies implementation of complex display functionality and customization in a straightforward, flexible, and extensible manner.



    Back to top


    Open source note

    I believe the toolkit is essential to successfully implementing the iData approach. The toolkit minimizes integration time by supplying user-tested code in addition to optimizations and helper classes developed over time. The design and implementation are influenced by the projects I have worked on. Obviously, you may come across projects with demands that might be more easily implemented with modifications to the toolkit, and its usefulness will be greatly enhanced as developers help to refine its design, creating a more accessible and robust implementation.

    The toolkit is distributed through SourceForge.net under the Artistic License. The project home page (see Resources) contains the full source, documentation, and binary distributions, as well as links to a listserv and other information. You are encouraged to contribute code enhancements to be included in future releases. Usage is free for all applications according to the provisions of the open source agreement.

    discuss this topic to forum

    relation tutorial

    No relevant information

    Category

      Applet Building (2)
      Application Building (3)
      Communication (1)
      Database Related (8)
      Development (12)
      EJB (14)
      Game Programming (2)
      General Java (38)
      Javabeans (4)
      JSP and Servlets (8)
      Miscellaneous (23)
      Networking (1)
      Security (2)
      Swing (13)
      WAP and WML (1)
      XML and Java (0)

    New

    Hot