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);
...
}