/* * Copyright (c) 2006,2009 Declarative Engineering LLC. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Declarative Engineering LLC * verson 1 which accompanies this distribution, and is available at * http://declarativeengineering.com/legal/DE_Developer_License_v1.txt */ package com.foundation.view; import java.math.BigDecimal; import java.util.Date; import com.common.debug.Debug; import com.common.util.IHashSet; import com.common.util.IIterator; import com.foundation.event.EventSupport; import com.foundation.event.IEventEmitter; import com.foundation.event.IEventHandler; import com.foundation.util.IManagedCollection; import com.foundation.view.IAbstractContainer; import com.foundation.view.IAssociationHandler; import com.foundation.view.IValueHolder; import com.foundation.view.resource.ResourceReference; /** * The base class for the single and multi association classes. */ public abstract class Association implements IResourceAssociationTypes { /** The container that contains this association. */ private AssociationContainer associationContainer = null; /** The parent association which may be null if this is a root association. */ private Association parent = null; /** The name of the value holder whose value will be used as input to the association. This may be null if a row value is to be passed to the association. */ private String valueHolderName = null; /** The actual value holder instance at runtime. This will be null if the value holder has not yet been found, or if the value holder name is not set. */ private IValueHolder valueHolder = null; /** The optional name of the type the association applies to. If there is a value holder name then this should be the value holder's type or a specialization thereof. */ private Class valueHolderHeldType = null; /** The optional name of the type the association applies to. This can be used by single or multi associations and is non-null only if the association will be passed a row value. */ private Class rowType = null; /** The view unique number for this association. A value of -1 indicates an invalid value. An invalid value may be used if the association refers to the input value as the output value. */ private int associationNumber = -1; /** The handler which is invoked to get and set the attribute value. */ private IAssociationHandler handler = null; /** Whether the association should invert boolean logic when getting and setting. This is ignored for associations not resulting in a boolean value (may be null). */ private boolean invertLogic = false; //TODO: Are the attributes applied to the result (output) of the association or the input? /** The set of attributes to listen to on the result of the association and whose values are passed to the association nodes. This should be null except for indirect link associations and method target associations. */ private AssociationAttribute[] attributes = null; //TODO: What are the initial inputs for the nodes? Are the attributes' values the inputs? Is the association's result (output) the input here? How about the association's input? /** The set of nodes that define an arbitrary tree of listeners which indicate that this association's result may have changed. This should be null except for indirect link associations and method target associations. */ private AssociationNode[] nodes = null; /** A reusable array containing one value. */ protected Object[] oneArray = null; /** A reusable array containing two values. */ protected Object[] twoArray = null; /** * Association constructor. * @param rowType The type of row the association applies to. In the case of a root association in a single association this is the type held by the value holder. * @param associationNumber The association number which will be passed to the handler when the assocation is invoked. This number allows fast indexing of the methods that the handler handles. This value may be -1 if the input value should be used as the output value for the association (short circuiting). * @param handler The object that handles the method invocations to get and set the value. * @param invertLogic Whether the result of the association will be inverted. This is ignored unless the result is a Boolean value. * @param attributes The set of attributes to listen to and whose values are placed into the nodes to listen for change notification. * @param nodes The optional node data used to set up an arbitrary tree of event listeners for notification when the association's result changes. * @param valueHolderName The name of the optional value holder whose value holds the getter and setter methods used by the association. If no value holder is defined then the row object passed to the association must define the getter and setter methods. */ public Association(Class rowType, int associationNumber, IAssociationHandler handler, boolean invertLogic, AssociationAttribute[] attributes, AssociationNode[] nodes, String valueHolderName) { this.rowType = rowType; this.associationNumber = associationNumber; this.handler = handler; this.invertLogic = invertLogic; this.attributes = attributes; this.nodes = nodes; this.valueHolderName = valueHolderName; }//Association()// /** * Initializes the association. * @param associationContainer The container that contains this association. */ public void initialize(AssociationContainer associationContainer) { this.associationContainer = associationContainer; }//initialize()// /** * Releases the association. */ public void release() { this.associationContainer = null; }//release()// /** * Gets the association number used to index the associations by the association handler. * @return The number passed to the handler that identifies this association. */ protected int getAssociationNumber() { return associationNumber; }//getAssociationNumber()// /** * Gets the handler that is invoked when the get or set operations are required by the association. * @return The handler that calls the getter and setter methods for the association. */ protected IAssociationHandler getHandler() { return handler; }//getHandler()// /** * Inverts the logic of a boolean result. * @return Whether boolean results for the association should be inverted. */ public boolean invertLogic() { return invertLogic; }//invertLogic()// /** * Gets the parent association. * @return The parent association or null if this is a root association. */ protected Association getParent() { return parent; }//getParent()// /** * Sets the parent association. * @param parent The parent association or null if this is a root association. */ protected void setParent(Association parent) { this.parent = parent; }//setParent()// /** * Gets the container for this association. * @return The container containing this association. */ protected AssociationContainer getAssociationContainer() { return associationContainer; }//getAssociationContainer()// /** * Gets whether the value holder association is connected to a value holder by name. * @return Whether there is a potential value holder connection. */ public boolean getIsValueHolderAssociated() { return valueHolderName != null; }//getIsValueHolderAssociated()// /** * Gets the value holder associated with the component. * @return The associated value holder, or null if one was not located with the proper name. */ public IValueHolder getValueHolder() { if(valueHolder == null) { valueHolder = locateValueHolder(); if(valueHolder == null) { Debug.log("Warning: ValueHolder named " + valueHolderName + " could not be found for the view component named '" + getAssociationContainer().getResourceAssociation().getComponent().getName() + "'."); }//if// }//if// return valueHolder; }//getValueHolder()// /** * Gets the class of object held by the value holder for this association. * @return The class of object expected to be held by the value holder when using this association. */ public Class getValueHolderHeldType() { return valueHolderHeldType; }//getValueHolderHeldType()// /* (non-Javadoc) * @see com.foundation.view.IValueHolderAssociation#getRowType() */ public Class getRowType() { return rowType; }//getRowType()// /** * Locates the value holder by looking in the component's parents (and the component its self if it is a container) until a value holder with the given name is found. *
Note: The name is not case sensitive.
* @return The found value holder, or null if one could not be found. */ protected IValueHolder locateValueHolder() { IValueHolder result = null; IAbstractContainer container = getAssociationContainer().getResourceAssociation().getContainer(); while((result == null) && (container != null)) { IIterator iterator = container.getComponents().iterator(); while((result == null) && (iterator.hasNext())) { Object component = iterator.next(); if((component instanceof IValueHolder) && (((IValueHolder) component).getName().equalsIgnoreCase(valueHolderName))) { result = (IValueHolder) component; }//if// }//while// container = container.getContainer(); }//while// return result; }//locateValueHolder()// /** * Determines whether the given row value is applicable to this association. * @param row The row value to compare with. * @return Whether this association supports the given row. */ public boolean isApplicable(Object row) { return row != null && getRowType().isAssignableFrom(row.getClass()); }//isApplicable()// /** * Gets the set of attributes to listen to on the result of the association and whose values are passed to the association nodes. * This should be null except for indirect link associations and method target associations. * @return The set of attributes the result should look at when registering with the nodes. */ protected AssociationAttribute[] getAttributes() { return attributes; }//getAttributes()// /** * Gets the set of nodes that define an arbitrary tree of listeners which indicate that this association's result may have changed. * This should be null except for indirect link associations and method target associations. * @return The set of nodes that the result's attribute values and node attribute values will be registered with to create an arbitrary tree of event listeners. */ protected AssociationNode[] getNodes() { return nodes; }//getNodes()// /** * Registers listeners with the result. * @param eventSupport The event support used to register for events on the value. * @param trackedValues The values already registered. This is used to prevent a value from being registered multiple times. * @param handler The handler called if any listener fires. * @param value The value that should be plugged into the nodes. */ protected void registerListeners(EventSupport eventSupport, IHashSet trackedValues, IEventHandler handler, Object value) { AssociationNode[] nodes = getNodes(); if(nodes != null) { for(int nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) { if(nodes[nodeIndex].isApplicable(value)) { int[] events = nodes[nodeIndex].getEvents(); AssociationAttribute[] attributes = nodes[nodeIndex].getAttributes(); if(events != null) { //Iterate over the defined events and register for changes on the value.// for(int eventIndex = 0; eventIndex < events.length; eventIndex++) { eventSupport.register((IEventEmitter) value, events[eventIndex], handler, true); }//for// }//if// if(attributes != null) { //Iterate over the defined attributes and for each get the value and recursively call this registerListeners method.// for(int attributeIndex = 0; attributeIndex < attributes.length; attributeIndex++) { Object attributeValue = attributes[attributeIndex].getValue(value); if(!trackedValues.containsValue(attributeValue)) { trackedValues.add(attributeValue); //TODO: Should we support other collections? If so we would need to be notified when there are changes... if(attributeValue instanceof IManagedCollection && attributes[attributeIndex].getNavigateCollectionValues()) { IIterator iterator = ((IManagedCollection) attributeValue).iterator(); //Register for changes in the collected values.// while(iterator.hasNext()) { registerListeners(eventSupport, trackedValues, handler, iterator.next()); }//while// //Register for changes in the collection.// eventSupport.register((IManagedCollection) attributeValue, IManagedCollection.EVENT, handler, true); }//if// //Register for changes in the attribute value.// registerListeners(eventSupport, trackedValues, handler, attributeValue); }//if// }//for// }//if// }//if// }//for// }//if// }//registerListeners()// /** * Ensures that the current thread is the view system thread. */ public void verifyThread() { associationContainer.verifyThread(); }//verifyThread()// /** * Converts the given value into a properly typed value for the control, otherwise returns null if that was not possible. *Note: The default value for the association is converted in the ResourceAssociation class.
* @param value The value which may not match the display value type identifier. * @return The matching value for the display value type. */ protected Object convertValueToControl(Object value) { Object currentValue = null; if(value instanceof ResourceReference) { currentValue = value; }//if// else { switch(getAssociationContainer().getResourceAssociation().getValueType()) { case TYPE_TEXT: { if(value instanceof Number) { currentValue = getAssociationContainer().getResourceAssociation().defaultFormat.format(value); }//if// else { currentValue = value != null ? value.toString() : null; }//else// break; }//case// case TYPE_BOOLEAN: { if(value instanceof Boolean) { currentValue = (Boolean) value; }//if// else if((value instanceof Byte) || (value instanceof Short) || (value instanceof Integer) || (value instanceof Long)) { currentValue = ((Number) value).longValue() == 0 ? Boolean.FALSE : Boolean.TRUE; }//else if// else if((value instanceof Float) || (value instanceof Double)) { currentValue = ((Number) value).doubleValue() == 0 ? Boolean.FALSE : Boolean.TRUE; }//else if// else if(value instanceof IManagedCollection) { currentValue = ((IManagedCollection) value).getSize() > 0 ? Boolean.TRUE : Boolean.FALSE; }//else if// else { currentValue = value == null ? Boolean.FALSE : Boolean.TRUE; }//else// if(invertLogic()) { currentValue = ((Boolean) currentValue).booleanValue() ? Boolean.FALSE : Boolean.TRUE; }//if// break; }//case// case TYPE_COLOR: { if(value instanceof JefColor) { currentValue = (JefColor) value; }//if// else if(value instanceof String) { currentValue = new JefColor((String) value); }//else if// break; }//case// case TYPE_FONT: { if(value instanceof JefFont[]) { currentValue = (JefFont[]) value; }//if// else if(value instanceof JefFont) { currentValue = new JefFont[] {(JefFont) value}; }//else if// else if(value instanceof String) { currentValue = JefFont.getJefFonts((String) value); }//else if// break; }//case// case TYPE_IMAGE: { if(value instanceof JefImage) { currentValue = (JefImage) value; }//if// else if(value instanceof String) { currentValue = new JefImage((String) value); }//else if// break; }//case// case TYPE_IMAGES: { if(value instanceof JefImage[]) { currentValue = (JefImage[]) value; }//if// else if(value instanceof JefImage) { currentValue = new JefImage[] {(JefImage) value}; }//else if// break; }//case// case TYPE_INTEGER: { if(value instanceof Integer) { currentValue = (Integer) value; }//if// else if(value instanceof String) { try { currentValue = Integer.valueOf((String) value); }//try// catch(Throwable e) { Debug.log(new RuntimeException("Could not convert the string '" + value.toString() + "' to an integer value.")); }//catch// }//else if// else if(value instanceof Number) { currentValue = new Integer(((Number) value).intValue()); }//else if// break; }//case// case TYPE_LONG: { if(value instanceof Long) { currentValue = (Long) value; }//if// else if(value instanceof String) { try { currentValue = Long.valueOf((String) value); }//try// catch(Throwable e) { Debug.log(new RuntimeException("Could not convert the string '" + value.toString() + "' to an long value.")); }//catch// }//else if// else if(value instanceof Number) { currentValue = new Long(((Number) value).longValue()); }//else if// break; }//case// case TYPE_OBJECT: { currentValue = value; break; }//case// case TYPE_FLOAT: { if(value instanceof Float) { currentValue = (Float) value; }//if// else if(value instanceof String) { try { currentValue = Float.valueOf((String) value); }//try// catch(Throwable e) { Debug.log(new RuntimeException("Could not convert the string '" + value.toString() + "' to a float value.")); }//catch// }//else if// else if(value instanceof Number) { currentValue = new Float(((Number) value).floatValue()); }//else if// break; }//case// case TYPE_DOUBLE: { if(value instanceof Double) { currentValue = (Double) value; }//if// else if(value instanceof String) { try { currentValue = Double.valueOf((String) value); }//try// catch(Throwable e) { Debug.log(new RuntimeException("Could not convert the string '" + value.toString() + "' to a double value.")); }//catch// }//else if// else if(value instanceof Number) { currentValue = new Double(((Number) value).doubleValue()); }//else if// break; }//case// case TYPE_DATE: { if(value instanceof Date) { currentValue = (Date) value; }//if// break; }//case// case TYPE_GRADIENT: { if(value instanceof JefColor) { currentValue = new JefGradient((JefColor) value, null, JefGradient.DIAGONAL); }//if// else if(value instanceof String) { currentValue = new JefGradient((String) value); }//else if// else if(value instanceof JefGradient) { currentValue = (JefGradient) value; }//else if// break; }//case// case TYPE_BIG_DECIMAL: { if(value instanceof BigDecimal) { currentValue = (BigDecimal) value; }//if// else { try { currentValue = new BigDecimal(value.toString()); }//try// catch(Throwable e) { Debug.log(new RuntimeException("Could not convert the string '" + value.toString() + "' to a BigDecimal value.")); }//catch// }//else// break; }//case// default: { throw new RuntimeException("Error: Invalid value type."); }//default// }//switch// }//else// return currentValue; }//convertValueToControl()// /** * Converts the given value into a properly typed value for the model, otherwise returns null if that was not possible. *Note: We don't currently store the model type so we will not try to convert to the model's type, but instead assume that it is the same as the control's type.
* @param value The value from the control. * @return The matching value for the model. */ protected Object convertValueToModel(Object value) { switch(getAssociationContainer().getResourceAssociation().getValueType()) { case TYPE_BOOLEAN: { if(invertLogic()) { value = ((Boolean) value).booleanValue() ? Boolean.FALSE : Boolean.TRUE; }//if// break; }//case// default: { //Do nothing.// }//default// }//switch// return value; }//convertValueToModel()// }//Association//