Pojo Container Code

After looking around some more, it looks like I should just post the code here.

This is the “main” class, I had it about 85% done, when I hit a stumbling block with the internal properties. Found the solution in the hbncontainer, thanks Matti?

PojoContainer.java


package container.pojo;

import com.itmill.toolkit.data.Container;
import com.itmill.toolkit.data.Item;
import com.itmill.toolkit.data.Property;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Container that handles Pojo's. It uses reflection to obtain the
 * getters() names. These names are then used as the "names" or IDs.
 * <p>
 * This also uses reflection to call the pojo to access the get()
 * or set() methods when a call is made to get a property value. It's
 * like FM, no static at all.
 * </p>
 * @author Andy Thomson
 */
public class PojoContainer implements Container {

    // hold the container item map
    private PojoContainerMap pojoContainerMap = new PojoContainerMap();

    // hold the property types: classes and values
    private PojoClassMap pojoClassMap;

    // safer? index counter
    private AtomicInteger containerEntryIndex = new AtomicInteger(0);

    public class PojoItem implements Item {

        private ConcurrentMap<Object, Property> properties =
                new ConcurrentHashMap<Object, Property>();

        private Object pojo;

        public PojoItem(Object pojo) {
            this.pojo = pojo;
        }

        // from the hbncontainer article - works :-)
        public Property getItemProperty(Object id) {
            Property p = properties.get(id);
            if (p == null) {
                p = new PojoItemProperty(id.toString());
                properties.putIfAbsent(id, p);
            }
            return p;
        }

        public Collection getItemPropertyIds() {
            return getContainerPropertyIds();
        }

        public boolean addItemProperty(Object id, Property property)
                throws UnsupportedOperationException {
            return false;
        }

        public boolean removeItemProperty(Object id)
                throws UnsupportedOperationException {
            return false;
        }

        public class PojoItemProperty implements Property {

            private String propertyName;

            boolean readOnly = false;

            public PojoItemProperty(String propertyName) {
                this.propertyName = propertyName;
            }

            public Class getType() {
                return pojoClassMap.getProperty((String) propertyName).getType();
            }

            public Object getValue() {
                //LOG.debug("getValue() for " + propertyName);
                try {
                    Method m = null;
                    if (propertyName.startsWith("is") ||
                            propertyName.startsWith("has") ) {
                        m = pojo.getClass().getMethod(propertyName);
                    } else {
                        // search for the getter() with the propertyName
                        m = pojo.getClass().getMethod("get" + propertyName);
                    }
                    // m.setAccessible(true);
                    // invoke the method to get the value
                    Object obj = m.invoke(pojo);
                    return obj;
                } catch (NoSuchMethodException ex) {
                    ex.printStackTrace();
                } catch (IllegalAccessException ex) {
                    ex.printStackTrace();
                } catch (InvocationTargetException ex) {
                    ex.printStackTrace();
                }
                return null;
            }

            public void setValue(Object newValue) throws ReadOnlyException,
                    ConversionException {
                try {
                    // create the newValue Class "parameter"
                    Class paramClass = Class.forName(
                            newValue.getClass().getName());

                    // look for a setter() that uses the 
                    // "newValue" Class-type parameter
                    Method m = pojo.getClass().getMethod(
                            "set" + propertyName, paramClass);

                    // invoke the setter() passing the newValue object
                    m.invoke(pojo, new Object[]{newValue});
                } catch (NoSuchMethodException ex) {
                    ex.printStackTrace();
                } catch (IllegalAccessException ex) {
                    ex.printStackTrace();
                } catch (InvocationTargetException ex) {
                    ex.printStackTrace();
                } catch (ClassNotFoundException ex) {
                    ex.printStackTrace();
                }
            }

            public boolean isReadOnly() {
                return readOnly;
            }

            public void setReadOnly(boolean newStatus) {
                this.readOnly = newStatus;
            }

            /**
             * This is a MANDATORY method. The 'pos' documentation, nor
             * any place else mentions this, you can spend hours and hours
             * debugging what appears to be bad code, as the table
             * cell's are not correct. Your code is fine, it's the
             * toString() calls buried in the toolkit to fill in the
             * cell content that bit you.
             *
             *    pos - abbreviation
             *      1  positive
             *      2  position
             *      3  a pile of stuff that a dog leaves behind
             *
             * @return String
             *         return the property value as a string
             */
            @Override
            public String toString() {
                // use string-buffer! its safe, friendly, and works properly
                StringBuffer sb = new StringBuffer();
                sb.append(this.getValue());
                return sb.toString();
            }
        }

    }

    /**
     * Constructor, requires the full class name of the pojo. The default is
     * to build a sorted list.
     * <pre>
     *
     *   PojoContainer pc =
     *              new PojoContainer("org.company.common.service");
     * 
     * </pre>
     * <p>
     *
     * To disable the sorting calling the constructor with a second option,
     * a boolean false.
     * 
     * <code>
     *  PojoContainer pc =
     *              new PojoContainer("org.company.common.service", false);
     * </code>
     * </p>
     * @param clsName, String
     *        The full class name of the pojo
     */
    public PojoContainer(String clsName) {
        pojoClassMap = getClassInformation(clsName);
    }

    public PojoContainer(String clsName, boolean sorted) {
        pojoClassMap = getClassInformation(clsName, sorted);
    }

    /**
     * Convenience method to add the Pojo object in one step vs
     * the two or three steps normally used. This also takes care
     * of the indexing, washes the dishes, and cleans the house.
     * You just supply the pojo, it does the rest.
     * 
     * @param pojo
     *
     */
    public void populatePojoItem(Object pojo) {
        this.pojoContainerMap.addContainerItem(
                containerEntryIndex.getAndIncrement(), new PojoItem(pojo));
    }

    private PojoClassMap getClassInformation(String clsString) {
        return getClassInformation(clsString, true);
    }

    /**
     * The reflection method to obtain the getter() names. This can be
     * modified to exclude methods, or even changed to use annotation
     * to parse-out the names.
     * 
     * @param clsString
     * @return
     */
    private PojoClassMap getClassInformation(String clsString, boolean sorted) {

        // define the class
        Class cls = null;

        // define the return Collection
        PojoClassMap classMap = new PojoClassMap(sorted);

        // load the class
        try {
            cls = Class.forName(clsString);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // look for the getters
        for (Method m : cls.getDeclaredMethods()) {
            // look only for "public" getters
            if (Modifier.isPublic(m.getModifiers())) {
                if (m.getName().startsWith("get")) {
                    classMap.addProperty(m.getName().substring(3),
                            (Property) new PojoClassProperty(m.getReturnType(),
                            m.getName().substring(3)));
                }
                if (m.getName().startsWith("is")) {
                    classMap.addProperty(m.getName(),
                            (Property) new PojoClassProperty(m.getReturnType(),
                            m.getName()));
                }
                if (m.getName().startsWith("has")) {
                    classMap.addProperty(m.getName(),
                            (Property) new PojoClassProperty(m.getReturnType(),
                            m.getName()));
                }
            }
        }

        return classMap;
    }

    /**
     * Gets the ID's of all Properties stored in the Container. The ID's are
     * returned as a unmodifiable collection.
     *
     * @return unmodifiable collection of Property IDs
     */
    public Collection getContainerPropertyIds() {
        return pojoClassMap.listPropertyValues();
    }

    /**
     * Gets the data type of all Properties identified by the given Property ID.
     * This is the Java object type, ie, class, String, Integer, Date, or
     * some other Java object.
     *
     * @param propertyId
     *            ID identifying the Properties
     * @return data type of the Properties
     */
    public Class getType(Object propertyId) {
        return pojoClassMap.getProperty((String) propertyId).getType();
    }

    /**
     * Gets the Property identified by the given itemId and propertyId from the
     * Container. If the Container does not contain the Property,
     * <code>null</code> is returned.
     *
     * @param itemId
     *            ID of the Item which contains the Property     [PojoItem]

     * @param propertyId
     *            ID of the Property to retrieve                 [Method Name]

     * @return Property with the given ID or <code>null</code>
     */
    public Property getContainerProperty(Object itemId, Object propertyId) {
        return pojoContainerMap.getContainerItem(itemId).
                getItemProperty(propertyId);
    }

    /**
     * Gets the Item with the given Item ID from the Container. If the Container
     * does not contain the requested Item, <code>null</code> is returned.
     *
     * @param itemId
     *            ID of the Item to retrieve
     * @return the Item with the given ID or <code>null</code> if the Item is
     *         not found in the Container
     */
    public Item getItem(Object itemId) {
        return pojoContainerMap.getContainerItem(itemId);
    }

    /**
     * Gets the ID's of all Items stored in the Container. The ID's are returned
     * as a unmodifiable collection.
     *
     * @return unmodifiable collection of Item IDs
     */
    public Collection getItemIds() {
        return Collections.unmodifiableCollection(
                pojoContainerMap.listContainerIds());
    }

    /**
     * Gets the number of Items in the Container.
     *
     * @return number of Items in the Container
     */
    public int size() {
        return pojoContainerMap.getSize();
    }

    /**
     * Tests if the Container contains the specified Item
     *
     * @param itemId
     *            ID the of Item to be tested
     * @return boolean indicating if the Container holds the specified Item
     */
    public boolean containsId(Object itemId) {
        return pojoContainerMap.hasContainerItem((Integer) itemId);
    }

    /**
     * Creates a new Item with the given ID into the Container. The new
     * <p>
     * Item is returned, and it is ready to have its Properties modified.
     * Returns <code>null</code> if the operation fails or the Container already
     * contains a Item with the given ID.
     * </p>
     *
     * <p>
     * This functionality is optional.
     * </p>
     *
     * @param itemId
     *            ID of the Item to be created
     * @return Created new Item, or <code>null</code> in case of a failure
     */
    public Item addItem(Object itemId) throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void addItem(Object itemId, Item item) {
        if ((item instanceof PojoItem)) {
            pojoContainerMap.addContainerItem(itemId, item);
        }
    }

    /**
     * Creates a new Item into the Container, and assign it an automatic ID.
     *
     * <p>
     * The new ID is returned, or <code>null</code> if the operation fails.
     * After a successful call you can use the {@link #getItem(Object ItemId)
     * <code>getItem</code>}method to fetch the Item.
     * </p>
     *
     * <p>
     * This functionality is optional.
     * </p>
     *
     * @return ID of the newly created Item, or <code>null</code> in case of a
     *         failure
     */
    public Object addItem() throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * Removes the Item identified by <code>ItemId</code> from the Container.
     * This functionality is optional.
     *
     * @param itemId
     *            ID of the Item to remove
     * @return <code>true</code> if the operation succeeded, <code>false</code>
     *         if not
     */
    public boolean removeItem(Object itemId)
            throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * Adds a new Property to all Items in the Container. The Property ID, data
     * type and default value of the new Property are given as parameters.
     *
     * This functionality is optional.
     *
     * @param propertyId
     *            ID of the Property
     * @param type
     *            Data type of the new Property
     * @param defaultValue
     *            The value all created Properties are initialized to
     * @return <code>true</code> if the operation succeeded, <code>false</code>
     *         if not
     */
    public boolean addContainerProperty(Object propertyId, Class type,
            Object defaultValue) throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * Removes a Property specified by the given Property ID from the Container.
     * Note that the Property will be removed from all Items in the Container.
     *
     * This functionality is optional.
     *
     * @param propertyId
     *            ID of the Property to remove
     * @return <code>true</code> if the operation succeeded, <code>false</code>
     *         if not
     */
    public boolean removeContainerProperty(Object propertyId)
            throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * Removes all Items from the Container.
     *
     * <p>
     * Note that Property ID and type information is preserved. This
     * functionality is optional.
     * </p>
     *
     * @return <code>true</code> if the operation succeeded, <code>false</code>
     *         if not
     */
    public boolean removeAllItems() throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Not supported yet.");
    }
}

PojoContainerMap.java

package container.pojo;

import com.itmill.toolkit.data.Item;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Holds the container items, property objects
 *
 * @author Andy Thomson
 */
public class PojoContainerMap {

    ConcurrentMap<Object, Item> items = new ConcurrentHashMap<Object, Item>();

    public PojoContainerMap() {
    }

    public int getSize() {
        return items.size();
    }

    public void addContainerItem(Object id, Item item) {
        if (item != null) {
            items.putIfAbsent(id, item);
        }
    }

    public boolean hasContainerItem(Object id) {
        return items.containsKey(id);
    }

    public Item getContainerItem(Object itemId) {
        return items.get(itemId);
    }

    public List<Object> listContainerIds() {
        List<Object> idList = new ArrayList<Object>();
        Iterator it = items.keySet().iterator();
        while (it.hasNext()) {
            // Get key
            Object key = it.next();
            if (key != null) {
                idList.add(key);                
            }
        }
        return idList;
    }

    public final boolean removeContainerItem(Object id) {
        boolean removed = false;
        Iterator<Map.Entry<Object, Item>> entries = items.entrySet().iterator();
        while (entries.hasNext()) {
            Map.Entry<Object, Item> entry = entries.next();
            if (entry != null) {
                if (entry.getKey() == id) {
                    removed = items.remove(id, entry);
                }
            }
        }
        return removed;
    }
}

PojoClassProperty.java

package container.pojo;

import com.itmill.toolkit.data.Property;

/**
 * Just like a toolkit Property, except this one holds
 * the Pojo class information.  Pojo class getter() name [Value]
 and
 * return class type [Type]
.
 *
 * @author Andy Thomson
 */
public class PojoClassProperty implements Property {

    private Class type;

    private Object value;

    private boolean readOnly;

    public PojoClassProperty(Class type, Object value) {
        this.type = type;
        this.value = value;
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object newValue) throws ReadOnlyException,
            ConversionException {
        this.value = newValue;
    }

    public Class getType() {
        return type;
    }

    public void setType(Class type) {
        this.type = type;
    }

    public boolean isReadOnly() {
        return readOnly;
    }

    public void setReadOnly(boolean newStatus) {
        this.readOnly = newStatus;
    }
}

PojoClassMap.java

package container.pojo;

import com.itmill.toolkit.data.Property;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * This class holds the Pojo class Property objects
 *
 * @author Andy Thomson
 */
public class PojoClassMap {

    final ConcurrentMap<String, Property> classMap;

    private boolean sorted = true;

    public PojoClassMap() {
        classMap = new ConcurrentHashMap<String, Property>();
    }

    public PojoClassMap(boolean sorted) {
        classMap = new ConcurrentHashMap<String, Property>();
        this.sorted = sorted;
    }

    public boolean isSorted() {
        return sorted;
    }

    public void setSorted(boolean sorted) {
        this.sorted = sorted;
    }

    public final void addProperty(String id, Property property) {
        if (id != null && property != null) {
            // Add the file-system
            classMap.putIfAbsent(id, property);
        }
    }

    public final Property getProperty(String propertyId) {
        return classMap.get(propertyId);
    }

    public List<Class> listPropertyTypes() {
        final List<String> keys = new ArrayList<String>(classMap.keySet());
        if (isSorted()) {
            Collections.sort(keys);
        }
        final List<Class> propertyList = new ArrayList<Class>();
        for (String key : keys) {
            // key could have been removed by another thread
            if (classMap.get(key) != null) {
                propertyList.add(classMap.get(key).getType());
            }
        }
        return Collections.unmodifiableList(propertyList);
    }

    public List<Object> listPropertyValues() {
        final List<String> keys = new ArrayList<String>(classMap.keySet());
        if (isSorted()) {
            Collections.sort(keys);
        }
        final List<Object> propertyList = new ArrayList<Object>();
        for (String key : keys) {
            // key could have been removed by another thread
            if (classMap.get(key) != null) {
                propertyList.add(classMap.get(key).getValue());
            }
        }
        return Collections.unmodifiableList(propertyList);
    }

    public final boolean removeProperty(String id) {
        boolean removed = false;
        Iterator<ConcurrentMap.Entry<String, Property>> entries =
                classMap.entrySet().iterator();
        while (entries.hasNext()) {
            ConcurrentMap.Entry<String, Property> entry = entries.next();
            if (entry != null) {
                if (entry.getKey().equalsIgnoreCase(id)) {
                    removed = classMap.remove(id, entry);
                }
            }
        }
        return removed;
    }

    public int size() {
        return classMap.size();
    }
}

This can be used anywhere in the toolkit as a datasource. The TableDemo code could be changed in the init() section to use this pojocontainer.

TableDemo

    init() {
        ...

        // create a new pojo data source, disable sorting of names
        final PojoContainer pc = new PojoContainer(File.class.getName(), false);
        File root = new File("/");

        pc.populatePojoItem(root);

        table.setContainerDataSource(pc);
        
        ...
    }

Oops!

Posted the TableDemo example in the wrong place. See the “Pojo Container” topic to get the demo.

Maybe a moderator could move it here?

Andy

Can’t seem to move a sing post, just whole threads, so here is a link instead:

http://forum.itmill.com/posts/list/632.page#2053

Oh, and good job, Andy! :smiley:

Hi!

Great work!

We have something similar in almost all of our client projects. There have been some pressures to implement similar Containers to Toolkit for a long, but we haven’t got time to test and finish them enough. Toolkit should really have more general containers like yours for common use cases. We’d also need a set of generic validators.

The right place for this kind of work is our incubator area in svn:
http://dev.itmill.com/browser/incubator (trac browser) or
http://dev.itmill.com/svn/incubator/ (svn access)

There are also some bean/pojo related helpers as well as some unfinished demo applications. I also suggest to check out HbnContainer in case you are using hibernate/database as your persistency layer.

If you want to share your code there, please contact me with email (firstname.lastname ät itmill.com) or private message and I’ll deal you accounts to the svn server.

cheers,
matti

Matti,

After I posted this I discovered the BeanContainer, it is very similar in nature and far more complete. I am thinking it may make sense to re-use the BeanContainer, ie, make a copy, call it PojoContainer and transfer over the pertinent code sections [being lazy :-), you did most of the hard work in BeanContainer]
. I just need to add the reflection portions, with a slightly more advanced class-mapping algorithm than what the Pojo Container now has.


I did look at the hbnContainer, I created a newer version that made it engine independent [MySql, Hsqldb, H2]
. Also provided the ability to dynamically add new tables [same table with new name]
: MyTable [entity]
→ MyTable_Feb, MyTable_Mar, and so forth. It can also rename the columns dynamically if needed. All of this without creating a mapping xml template :-).

As soon as I iron out some issues with the changes I made for the hibernate->container->content separation, I’ll post it, or have it checked into the incubator.

Andy

Hi Andy!

Great work with HbnContainer, I’m happy to include your patches when you get them finished.

Good helpers to connect to databases would be a great help for IT Mill Toolkit developers. Most of real project really do use DB. I hope we can concentrate more on developing that side this year after spending most of 2008 with GWT and client side code.

cheers,
matti