/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.xmlcode; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; /** * <p> * <code>ModelProperty</code> is internally used in {@link org.openflexo.xmlcode.XMLMapping} * <p> * You never need to used it directly, this class maps <code><property><code> tags in <i>model file</i>. * * @author <a href="mailto:Sylvain.Guerin@enst-bretagne.fr">Sylvain Guerin</a> * @see XMLMapping */ public class ModelProperty { public enum PropertyType { SINGLE_PROPERTY_TYPE, ARRAY_PROPERTY_TYPE, VECTOR_PROPERTY_TYPE, HASHTABLE_PROPERTY_TYPE, PROPERTIES_PROPERTY_TYPE, SAFE_PROPERTIES_PROPERTY_TYPE, UNMAPPED_ATTRIBUTES_TYPE, COMPLEX_PROPERTY_TYPE; } /** Stores name of this property */ private String name = null; /** Stores xmlTag(s) of this property */ private String[] xmlTag; /** Stores contains value of this property */ private String contains = null; /** Stores contains class of this property */ private Class<?> containsClass = null; /** Stores boolean indicating if this property is attribute or not */ private boolean isAttribute = false; /** Stores boolean indicating if this property is a text attribute or not */ private boolean isText = false; /** * Stores String indicating what attribute to use as a key to store hashtable (if this property is a HASHTABLE_PROPERTY_TYPE): this is * not specified if this property use built-in key scheme */ private String keyToUse = null; /** * Stores context for this property */ private String context = null; /** * Stores boolean indicating if this property corresponds to the primary relation relative to embedding perspective of the model */ private boolean isPrimary = true; /** * Stores boolean indicating if this property must be duplicated while objects are cloned */ private boolean isCloneable = true; /** * Stores boolean indicating if this property must be copied while objects are cloned */ private boolean isCopyable = true; /** * Stores type of this property, which could be: * <ul> * <li>{@link #SINGLE_PROPERTY_TYPE}: this property matches a single object or primitive</li> * <li>{@link #ARRAY_PROPERTY_TYPE}: this property matches a array of objects or primitives</li> * <li>{@link #VECTOR_PROPERTY_TYPE}: this property matches a list of objects stored in a {@link java.util.Vector} or a sub-class of * {@link java.util.Vector}.</li> * <li>{@link #HASHTABLE_PROPERTY_TYPE}: this property matches a list of objects stored in a {@link java.util.Hashtable} or a sub-class * of {@link java.util.Hashtable}.</li> * <li>{@link #PROPERTIES_PROPERTY_TYPE}: this property matches a dynamic list of objects stored in a {@link java.util.Hashtable} or a * sub-class of {@link java.util.Hashtable} where key is a String obtained and parsed from/as XML element name: therefore, there is no * need to implement model for contained data</li> * </ul> */ private PropertyType propertyType; /** Stores related model entity */ private ModelEntity modelEntity; /** Stores related KeyValueProperty */ private KeyValueProperty keyValueProperty; /** * Handled xml tags (it could have many handled tags is this property represents a list of values (vector or hashtable) */ private String[] handledXMLTags = null; /** Description of this property */ private String description; /** Default value to be ignored */ protected String ignoreDefaultValue = null; /** * Creates a new <code>ModelEntity</code> instance<br> * This constructor should be called for dynamically XMLMapping building purposes.<br> * Use {@link ModelEntity#registerNewModelProperty(ModelProperty)} to register this new <code>ModelProperty</code> object in an * <code>ModelEntity</code> instance. * * @param aModelEntity * a <code>ModelEntity</code> value * @param aPropertyType * a <code>int</code> value, which could be: * <ul> * <li>{@link #SINGLE_PROPERTY_TYPE}: this property matches a single object or primitive</li> * <li>{@link #ARRAY_PROPERTY_TYPE}: this property matches a array of objects or primitives</li> * <li>{@link #VECTOR_PROPERTY_TYPE}: this property matches a list of objects stored in a {@link java.util.Vector} or a * sub-class of {@link java.util.Vector}.</li> * <li>{@link #HASHTABLE_PROPERTY_TYPE}: this property matches a list of objects stored in a {@link java.util.Hashtable} or a * sub-class of {@link java.util.Hashtable}.</li> * <li>{@link #PROPERTIES_PROPERTY_TYPE}: this property matches a dynamic list of objects stored in a * {@link java.util.Hashtable} or a sub-class of {@link java.util.Hashtable} where key is a String obtained and parsed * from/as XML element name: therefore, there is no need to implement model for contained data</li> * </ul> * @exception InvalidModelException * if an error occurs */ public ModelProperty(ModelEntity aModelEntity, String aName, String someXMLTags, String aContainsTag, String aKeyToUse, PropertyType aPropertyType, boolean isAttributeFlag, boolean isTextFlag) throws InvalidModelException { super(); modelEntity = aModelEntity; name = aName; parseXMLTags(someXMLTags); contains = aContainsTag; keyToUse = aKeyToUse; isAttribute = isAttributeFlag; isText = isTextFlag; propertyType = aPropertyType; if (modelEntity == null) { throw new InvalidModelException("Specified ModelEntity object is null"); } init(); } /** * Internaly used to initialize ModelProperty */ private void init() { if (contains != null) { try { containsClass = Class.forName(contains); } catch (ClassNotFoundException e) { throw new InvalidModelException("Invalid attribute contains found: matches no known class: " + contains); } } if (isAttribute) { if (isText) { throw new InvalidModelException( "Invalid attribute tag found: only one of 'text' and 'attribute' tags could be set to true ('YES' value)'"); } if (propertyType != PropertyType.SINGLE_PROPERTY_TYPE) { throw new InvalidModelException( "Invalid attribute tag found: 'attribute' tag could not be set to true ('YES' value)' while attribute type is not single."); } } if (propertyType != PropertyType.SINGLE_PROPERTY_TYPE) { if (isText) { throw new InvalidModelException( "Invalid attribute tag found: 'text' tag could not be set to true ('YES' value)' while attribute type is not single."); } } if (name == null) { throw new InvalidModelException("No attribute 'name' defined for tag 'property' in model file"); } if (xmlTag == null && !isText && propertyType != PropertyType.PROPERTIES_PROPERTY_TYPE && propertyType != PropertyType.SAFE_PROPERTIES_PROPERTY_TYPE && contains == null) { throw new InvalidModelException( "No attribute 'xmlTag' or 'contains' defined for tag 'property' in model file while xml tag 'text' is not set to true."); } if (xmlTag != null && getDefaultXmlTag() != null) { if (getDefaultXmlTag().equalsIgnoreCase(XMLMapping.classNameLabel)) { // throw new InvalidModelException("Invalid xml property name: // "+xmlTag+" is a reserved keyword"); } if (getDefaultXmlTag().equalsIgnoreCase(XMLMapping.keyLabel)) { // throw new InvalidModelException("Invalid xml property name: // "+xmlTag+" is a reserved keyword"); } } if (name.lastIndexOf(".") > -1) { if (!getModel().serializeOnly) { StringBuilder sb = new StringBuilder(); for (String s : xmlTag) { if (sb.length() > 0) { sb.append(','); } sb.append(s); } throw new InvalidModelException("Invalid xml property name: " + sb + " compound keys are allowed only in 'serializeOnly' models"); } } if (propertyType == PropertyType.SINGLE_PROPERTY_TYPE) { if (ParameteredKeyValueProperty.isParameteredKeyValuePropertyPattern(name)) { propertyType = PropertyType.COMPLEX_PROPERTY_TYPE; keyValueProperty = new ParameteredKeyValueProperty(modelEntity.getRelatedClass(), name, !getModel().serializeOnly); } else { keyValueProperty = new SingleKeyValueProperty(modelEntity.getRelatedClass(), name, !getModel().serializeOnly); } } if (propertyType == PropertyType.ARRAY_PROPERTY_TYPE) { keyValueProperty = new ArrayKeyValueProperty(modelEntity.getRelatedClass(), name, !getModel().serializeOnly); } else if (propertyType == PropertyType.VECTOR_PROPERTY_TYPE) { keyValueProperty = new VectorKeyValueProperty(modelEntity.getRelatedClass(), name, !getModel().serializeOnly); } else if (propertyType == PropertyType.HASHTABLE_PROPERTY_TYPE) { keyValueProperty = new HashtableKeyValueProperty(modelEntity.getRelatedClass(), name, !getModel().serializeOnly); } else if (propertyType == PropertyType.PROPERTIES_PROPERTY_TYPE) { keyValueProperty = new PropertiesKeyValueProperty(modelEntity.getRelatedClass(), name, !getModel().serializeOnly); handledXMLTags = null; } else if (propertyType == PropertyType.SAFE_PROPERTIES_PROPERTY_TYPE) { keyValueProperty = new PropertiesKeyValueProperty(modelEntity.getRelatedClass(), name, !getModel().serializeOnly); ((PropertiesKeyValueProperty) keyValueProperty).setSafe(true); handledXMLTags = null; } else if (propertyType == PropertyType.UNMAPPED_ATTRIBUTES_TYPE) { keyValueProperty = new PropertiesKeyValueProperty(modelEntity.getRelatedClass(), name, !getModel().serializeOnly); handledXMLTags = null; } } protected XMLMapping getModel() { return modelEntity.getModel(); } /** * Creates a new <code>ModelProperty</code> instance, given a node <code>aPropertyNode</code> * * @param aPropertyNode * a <code>Node</code> value * @param aModelEntity * a <code>ModelEntity</code> value */ public ModelProperty(Node aPropertyNode, ModelEntity aModelEntity) { super(); Node tempAttribute; NamedNodeMap attributes; boolean nameIsSpecified = false; boolean xmlTagIsSpecified = false; isAttribute = false; isText = false; propertyType = PropertyType.SINGLE_PROPERTY_TYPE; modelEntity = aModelEntity; if (modelEntity == null) { throw new InvalidModelException("Specified ModelEntity object is null"); } if (!aPropertyNode.getNodeName().equals(XMLMapping.propertyLabel)) { throw new InvalidModelException("Invalid tag '" + aPropertyNode.getNodeName() + "' found in model file"); } // end of if () attributes = aPropertyNode.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { tempAttribute = attributes.item(i); if (tempAttribute.getNodeName().equals(XMLMapping.nameLabel)) { nameIsSpecified = true; name = tempAttribute.getNodeValue(); } else if (tempAttribute.getNodeName().equals(XMLMapping.xmlTagLabel)) { xmlTagIsSpecified = true; parseXMLTags(tempAttribute.getNodeValue()); } else if (tempAttribute.getNodeName().equals(XMLMapping.descriptionLabel)) { setDescription(tempAttribute.getNodeValue()); } else if (tempAttribute.getNodeName().equals(XMLMapping.containsLabel)) { contains = tempAttribute.getNodeValue(); } else if (tempAttribute.getNodeName().equals(XMLMapping.attributeLabel)) { isAttribute = tempAttribute.getNodeValue().equalsIgnoreCase("yes"); } else if (tempAttribute.getNodeName().equals(XMLMapping.primaryLabel)) { isPrimary = tempAttribute.getNodeValue().equalsIgnoreCase("yes"); } else if (tempAttribute.getNodeName().equals(XMLMapping.cloneableLabel)) { isCloneable = tempAttribute.getNodeValue().equalsIgnoreCase("yes"); } else if (tempAttribute.getNodeName().equals(XMLMapping.copyableLabel)) { isCopyable = tempAttribute.getNodeValue().equalsIgnoreCase("yes"); } else if (tempAttribute.getNodeName().equals(XMLMapping.ignoreDefaultValueLabel)) { ignoreDefaultValue = tempAttribute.getNodeValue(); } else if (tempAttribute.getNodeName().equals(XMLMapping.keyLabel)) { keyToUse = tempAttribute.getNodeValue(); } else if (tempAttribute.getNodeName().equals(XMLMapping.typeLabel)) { if (tempAttribute.getNodeValue().equalsIgnoreCase(XMLMapping.singleLabel)) { propertyType = PropertyType.SINGLE_PROPERTY_TYPE; } else if (tempAttribute.getNodeValue().equalsIgnoreCase(XMLMapping.arrayLabel)) { propertyType = PropertyType.ARRAY_PROPERTY_TYPE; } else if (tempAttribute.getNodeValue().equalsIgnoreCase(XMLMapping.vectorLabel)) { propertyType = PropertyType.VECTOR_PROPERTY_TYPE; } else if (tempAttribute.getNodeValue().equalsIgnoreCase(XMLMapping.hashtableLabel)) { propertyType = PropertyType.HASHTABLE_PROPERTY_TYPE; } else if (tempAttribute.getNodeValue().equalsIgnoreCase(XMLMapping.propertiesLabel)) { propertyType = PropertyType.PROPERTIES_PROPERTY_TYPE; } else if (tempAttribute.getNodeValue().equalsIgnoreCase(XMLMapping.safePropertiesLabel)) { propertyType = PropertyType.SAFE_PROPERTIES_PROPERTY_TYPE; } else if (tempAttribute.getNodeValue().equalsIgnoreCase(XMLMapping.unmappedAttributesLabel)) { propertyType = PropertyType.UNMAPPED_ATTRIBUTES_TYPE; } else { throw new InvalidModelException("Invalid attribute value '" + tempAttribute.getNodeValue() + "' found in model file for tag '" + XMLMapping.typeLabel + "'"); } } else if (tempAttribute.getNodeName().equals(XMLMapping.textLabel)) { isText = tempAttribute.getNodeValue().equalsIgnoreCase("yes"); } else if (tempAttribute.getNodeName().equals(XMLMapping.contextLabel)) { context = tempAttribute.getNodeValue(); } else { throw new InvalidModelException("Invalid attribute '" + tempAttribute.getNodeName() + "' found in model file for tag 'property'"); } } init(); } /** * Returns <code>name</code> of this <code>ModelProperty</code> * * @return a <code>String</code> value */ public String getName() { return name; } /** * Returns related KeyValueProperty */ public KeyValueProperty getKeyValueProperty() { return keyValueProperty; } /** * Returns String indicating what attribute to use as a key to store hashtable (if this property is a HASHTABLE_PROPERTY_TYPE): this is * not specified if this property use built-in key scheme * * @return a <code>String</code> value */ public String getKeyToUse() { return keyToUse; } /** * Return default value to be ignored, if any * * @return null when none defined */ public String getIgnoreDefaultValue() { return ignoreDefaultValue; } /** * Returns <code>isAttribute</code> flag of this <code>ModelProperty</code> * * @return a <code>String</code> value */ public boolean getIsAttribute() { return isAttribute; } /** * Returns <code>propertyType</code> value of this <code>ModelProperty</code> * * @return a <code>int</code> value */ public PropertyType getPropertyType() { return propertyType; } /** * Returns <code>isText</code> flag of this <code>ModelProperty</code> * * @return a <code>String</code> value */ public boolean getIsText() { return isText; } /** * Returns a String representation of this object suitable for debugging purposes * * @return a <code>String</code> value */ @Override public String toString() { String returnedString = " <property name=" + '"' + getName() + '"'; if (getXmlTags() != null) { returnedString += " " + XMLMapping.xmlTagLabel + "=" + '"' + getConcatenedXmlTag() + '"'; } if (isAttribute) { returnedString += " " + XMLMapping.attributeLabel + "=" + '"' + "yes" + '"'; } if (propertyType != PropertyType.SINGLE_PROPERTY_TYPE) { if (propertyType == PropertyType.ARRAY_PROPERTY_TYPE) { returnedString += " " + XMLMapping.typeLabel + "=" + '"' + XMLMapping.arrayLabel + '"'; } else if (propertyType == PropertyType.VECTOR_PROPERTY_TYPE) { returnedString += " " + XMLMapping.typeLabel + "=" + '"' + XMLMapping.vectorLabel + '"'; } else if (propertyType == PropertyType.HASHTABLE_PROPERTY_TYPE) { returnedString += " " + XMLMapping.typeLabel + "=" + '"' + XMLMapping.hashtableLabel + '"'; } else if (propertyType == PropertyType.PROPERTIES_PROPERTY_TYPE) { returnedString += " " + XMLMapping.typeLabel + "=" + '"' + XMLMapping.propertiesLabel + '"'; } else if (propertyType == PropertyType.SAFE_PROPERTIES_PROPERTY_TYPE) { returnedString += " " + XMLMapping.typeLabel + "=" + '"' + XMLMapping.safePropertiesLabel + '"'; } else if (propertyType == PropertyType.UNMAPPED_ATTRIBUTES_TYPE) { returnedString += " " + XMLMapping.typeLabel + "=" + '"' + XMLMapping.unmappedAttributesLabel + '"'; } else { returnedString += " " + XMLMapping.typeLabel + "=" + '"' + "???" + '"'; } } if (isText) { returnedString += " text=" + '"' + "yes" + '"'; } if (getKeyToUse() != null) { returnedString += " " + XMLMapping.keyLabel + "=" + '"' + getKeyToUse() + '"'; } if (containsClass != null) { returnedString += " " + XMLMapping.containsLabel + "=" + '"' + containsClass.getName() + '"'; } if (context != null) { returnedString += " " + XMLMapping.contextLabel + "=" + '"' + context + '"'; } returnedString += "/>\n"; return returnedString; } /** * Return boolean indicating if XML tag <code>aTagName</code> is handled by this property. */ public boolean handlesXMLTag(String aTagName) { if (xmlTag == null) { return false; } else { for (int i = 0; i < xmlTag.length; i++) { if (xmlTag[i] != null && xmlTag[i].equals(aTagName)) { return true; } } return false; } } /** * Return handled xml tags (it could have many handled tags is this property represents a list of values (vector or hashtable) or not * well-defined values */ public String[] getXmlTags() { if ((xmlTag == null || handledXMLTagsNeedsUpdate) && containsClass != null) { XMLMapping model = modelEntity.getModel(); List<String> v = new ArrayList<String>(); for (Map.Entry<String, ModelEntity> e : model.modelEntitiesStoredByClassName.entrySet()) { String key = e.getKey(); Class<?> type = null; try { type = Class.forName(key); } catch (ClassNotFoundException e2) { continue; } ModelEntity entity = e.getValue(); if (containsClass.isAssignableFrom(type)) { if (context == null) { if (entity.getXmlTags() != null) { for (int i = 0; i < entity.getXmlTags().length; i++) { v.add(entity.getXmlTags()[i]); } } } else { if (entity.getXmlTags(context) != null) { for (int i = 0; i < entity.getXmlTags(context).length; i++) { v.add(entity.getXmlTags(context)[i]); } } } } } xmlTag = v.toArray(new String[v.size()]); handledXMLTagsNeedsUpdate = false; } return xmlTag; } protected boolean handledXMLTagsNeedsUpdate = true; public void updateHandledXMLTags() { handledXMLTagsNeedsUpdate = true; } /** * Used if this property represent a list of values (vector or hashtable) to parse xmlTag (using ',') and extract handled xml tag */ private void parseXMLTags(String someXMLTags) { StringTokenizer st = new StringTokenizer(someXMLTags, ","); List<String> temp = new ArrayList<String>(); while (st.hasMoreElements()) { String anXMLTag = (String) st.nextElement(); temp.add(anXMLTag); } if (temp.size() == 0) { throw new InvalidModelException("No XML tags specified in model file for entity " + getName()); } else { xmlTag = temp.toArray(new String[temp.size()]); } } public boolean hasXmlTag() { return xmlTag != null && xmlTag.length > 0; } /** * Returns default <code>xmlTag</code> of this <code>ModelEntity</code> * * @return a <code>String</code> value */ public String getDefaultXmlTag() { if (xmlTag != null && xmlTag.length > 0) { return xmlTag[0]; } else { throw new InvalidModelException("No XML tag defined for property '" + getName() + "'. Is it an abstract entity ?"); // return null; } } /** * Returns <code>xmlTag</code> of this <code>ModelEntity</code> * * @return a <code>String</code> value */ public String getConcatenedXmlTag() { String returned = ""; if (xmlTag != null) { for (int i = 0; i < xmlTag.length; i++) { if (i > 0) { returned += ","; } returned += xmlTag[i]; } return returned; } else { return "null"; } } public boolean isInherited(ModelEntity entity) { return entity.inheritedModelProperties.get(getName()) != null; } public ModelEntity getInheritedEntity(ModelEntity entity) { return entity.inheritedModelProperties.get(getName()); } public boolean isPrimitive() { return getType().isPrimitive() || StringEncoder.isConvertable(getType()) || propertyType == PropertyType.PROPERTIES_PROPERTY_TYPE || propertyType == PropertyType.UNMAPPED_ATTRIBUTES_TYPE; } public boolean isSingle() { return propertyType == PropertyType.SINGLE_PROPERTY_TYPE || propertyType == null; } public boolean isArray() { return propertyType == PropertyType.ARRAY_PROPERTY_TYPE; } public boolean isVector() { return propertyType == PropertyType.VECTOR_PROPERTY_TYPE; } public boolean isHashtable() { return propertyType == PropertyType.HASHTABLE_PROPERTY_TYPE; } public boolean isProperties() { return propertyType == PropertyType.PROPERTIES_PROPERTY_TYPE; } public boolean isSafeProperties() { return propertyType == PropertyType.SAFE_PROPERTIES_PROPERTY_TYPE; } public boolean isUnmappedAttributes() { return propertyType == PropertyType.UNMAPPED_ATTRIBUTES_TYPE; } public boolean isComplex() { return propertyType == PropertyType.COMPLEX_PROPERTY_TYPE; } /** * Wheter this property should be serialized as an attribute of the XML node or as child node of the XML node * * @return */ public boolean isAttribute() { return isAttribute; } /** * If there are several references to this object, specifies if this one should be the one that performs the serialization or if another * node should do it and this one should rather use a reference * * @return */ public boolean isPrimary() { return isPrimary; } /** * Wheter the value of this property should be cloned or just copies a reference of it * * @return */ public boolean isCloneable() { return isCloneable; } public boolean isCopyable() { return isCopyable; } public Class getType() { return getKeyValueProperty().getType(); } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public ModelEntity getModelEntity() { return modelEntity; } }