/* * (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.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * <p> * <code>XMLMapping</code> class represent a mapping that will be used for xml coding/decoding process. * </p> * Instance of this class will be instanciated from an XML file called a <i>model file</i> which contains all informations to map data from * xml data to object (xml decoding) and from object to xml data (xml coding).<br> * Suppose that you have following XML data: * * <pre> * <?xml version="1.0" encoding="UTF-8"?> * <command quantity="3" commandIsAlreadyPaid="yes"> * <customer> * <name>Ringle</name> * </customer> * <movie name="Alien"> * here comes a description of the movie * <date>2001.09.18 AD at 09:54:58 AM CEST</date> * <cat>Horror</cat> * <role name="Brett"></role> * <role name="Kane"></role> * <role name="Dallas"></role> * <role name="Parker"></role> * </movie> * </command> * </pre> * * and that you will map it to following <code>objects</code>: * * <pre> * public class Command { * public int qty; * * public boolean commandIsAlreadyPaid; * * public Movie movie; * * public Customer customer; * } * * public class Movie { * public String title; * * public String description; * * public Date dateReleased; * * public Vector roles; * * public String category; * } * * public class Customer { * public String name; * } * * public class Role { * public String roleName; * } * </pre> * * you will define a <i>model file</i> defining this mapping such as: * * <pre> * <?xml version="1.0" encoding="UTF-8"?> * <model> * <entity name="yourpackage.Command" xmlTag="command"> * <property name="qty" xmlTag="quantity" attribute="YES"/> * <property name="commandIsAlreadyPaid" xmlTag="commandIsAlreadyPaid" attribute="YES"/> * <property name="movie" xmlTag="movie"/> * <property name="customer" xmlTag="customer"/> * </entity> * <entity name="yourpackage.Customer" xmlTag="customer"> * <property name="name" xmlTag="name"/> * </entity> * <entity name="yourpackage.Movie" xmlTag="movie"> * <property name="title" xmlTag="name" attribute="YES"/> * <property name="description" text="YES"/> * <property name="dateReleased" xmlTag="date"/> * <property name="roles" xmlTag="role" forceList="YES"/> * <property name="category" xmlTag="cat"/> * </entity> * <entity name="yourpackage.Role" xmlTag="role"> * <property name="roleName" xmlTag="name" attribute="YES"/> * </entity> * </model> * </pre> * * All valid tags are used in this example model file.<br> * NB1: Only one of those three tags <code>text</code> <code>forceList</code> and <code>attribute</code> could be set to true (which is * normal).<br> * NB2: All valid types are all the java primitives (<code>int</code>, <code>long</code>, <code>short</code>, <code>double</code>, * <code>float</code>, <code>boolean</code>, <code>byte</code>, <code>char</code>) and {@link java.util.Date} and {@link java.lang.String} * objects.<br> * NB3: Tag <code>xmlTag</code> could be ommited if and only if <code>text</code> is specified and set to <code>true</code> ( * <code>YES</code> value). * * @author <a href="mailto:Sylvain.Guerin@enst-bretagne.fr">Sylvain Guerin</a> * @see XMLCoder * @see XMLDecoder */ public class XMLMapping { /** ModelEntity objects stored in a dictionary where the key is the xml tag */ protected Map<String, ModelEntity> modelEntitiesStoredByXMLTag; /** * ModelEntity objects stored in a dictionary where the key is the class name */ protected Map<String, ModelEntity> modelEntitiesStoredByClassName; /** Boolean indicating if references must be handled during encoding */ protected boolean handlesReferences; /** Serialization mode */ protected int serializationMode = DEFAULT_SERIALIZATION_MODE; /** Stores custom id mapping */ protected XMLMapId mapId = null; /** * Boolean indicating if this mapping will be used only for encoding (does not require set methods and valid constructors ) */ protected boolean serializeOnly; /** Builder class defined for this model, if any */ protected Class<?> builderClass; /** Description of this model */ protected String description; /** Defines 'model' label */ public static final String modelLabel = "model"; /** Defines 'entity' label */ public static final String entityLabel = "entity"; /** Defines 'mapId' label */ public static final String mapIdLabel = "mapId"; /** Defines 'map' label */ public static final String mapLabel = "map"; /** Defines 'entityClass' label */ public static final String entityClassLabel = "entityClass"; /** Defines 'identifierAccessor' label */ public static final String identifierAccessorLabel = "identifierAccessor"; /** Defines 'name' label */ public static final String nameLabel = "name"; /** Defines 'xmlTag' label */ public static final String xmlTagLabel = "xmlTag"; /** Defines 'property' label */ public static final String propertyLabel = "property"; /** Defines 'attribute' label */ public static final String attributeLabel = "attribute"; /** Defines 'abstract' label */ public static final String abstractLabel = "abstract"; /** Defines 'type' label */ public static final String typeLabel = "type"; /** Defines 'single' label */ public static final String singleLabel = "single"; /** Defines 'array' label */ public static final String arrayLabel = "array"; /** Defines 'vector' label */ public static final String vectorLabel = "vector"; /** Defines 'hashtable' label */ public static final String hashtableLabel = "hashtable"; /** Defines 'className' label */ public static final String classNameLabel = "className"; /** Defines 'className' label */ public static final String entryLabel = "Entry"; /** Defines 'key' label */ public static final String keyLabel = "key"; /** Defines 'key' label */ public static final String valueLabel = "value"; /** Defines 'text' label */ public static final String textLabel = "text"; /** Defines 'id' label */ public static final String idLabel = "id"; /** Defines 'idref' label */ public static final String idrefLabel = "idref"; /** Defines 'ignoreDefaultValue' label */ public static final String ignoreDefaultValueLabel = "ignoreDefaultValue"; /** Defines 'handlesReferences' label */ public static final String handlesReferencesLabel = "handlesReferences"; /** Defines 'serializationMode' label */ public static final String serializationModeLabel = "serializationMode"; /** Defines 'builder' label */ public static final String builderLabel = "builder"; /** Defines 'properties' label */ public static final String propertiesLabel = "properties"; /** Defines 'properties' label */ public static final String safePropertiesLabel = "safeproperties"; /** Defines 'unmappedAttributes' label */ public static final String unmappedAttributesLabel = "unmappedAttributes"; /** Defines 'finalizer' label */ public static final String finalizerLabel = "finalizer"; /** Defines 'initializer' label */ public static final String initializerLabel = "initializer"; /** Defines 'contexts' label */ public static final String contextsLabel = "contexts"; /** Defines 'context' label */ public static final String contextLabel = "context"; /** Defines 'contains' label */ public static final String containsLabel = "contains"; /** Defines 'serializeOnly' label */ public static final String serializeOnlyLabel = "serializeOnly"; /** Defines 'description' label */ public static final String descriptionLabel = "description"; /** Defines 'primary' label */ public static final String primaryLabel = "primary"; /** Defines 'cloneable' label */ public static final String cloneableLabel = "cloneable"; /** Defines 'copyable' label */ public static final String copyableLabel = "copyable"; /** Defines 'deepFirst' label */ public static final String deepFirstLabel = "deepFirst"; /** Defines 'pseudoTree' label */ public static final String pseudoTreeLabel = "pseudoTree"; /** Defines 'structureThenValues' label */ public static final String orderedPseudoTreeLabel = "orderedPseudoTree"; /** Defines 'default' label */ public static final String DefaultLabel = "default"; /** Defines 'genericTypingStoredIn' label */ public static final String genericTypingStoredIn = "genericTypingStoredIn"; /** Defines 'genericTypingClassName' label */ public static final String genericTypingClassName = "genericTypingClassName"; /** Defines 'deepFirst' serialization mode */ public static final int DEEP_FIRST = 0; /** Defines 'pseudoTree' serialization mode */ public static final int PSEUDO_TREE = 1; /** Defines 'structureThenValues' serialization mode */ public static final int ORDERED_PSEUDO_TREE = 2; /** Defines default serialization mode */ public static final int DEFAULT_SERIALIZATION_MODE = DEEP_FIRST; /** * Creates a new <code>XMLMapping</code> instance<br> * This constructor should be called for dynamically XMLMapping building purposes.<br> * Use {@link #registerNewModelEntity(ModelEntity)} to register new <code>ModelEntity</code> objects. * */ public XMLMapping() { super(); modelEntitiesStoredByXMLTag = new Hashtable<String, ModelEntity>(); modelEntitiesStoredByClassName = new Hashtable<String, ModelEntity>(); mapId = null; } /** * Register new <code>ModelEntity</code> object in mapping<br> * This method MUST be use to dynamically handle <code>XMLMapping</code> instances. * * @exception InvalidModelException * if an error occurs during mapping construction (invalid model) */ public void registerNewModelEntity(ModelEntity aModelEntity) { System.out.println("registerNewModelEntity " + aModelEntity); updateAndRegisterNewModelEntity(aModelEntity); } /** * Creates a new <code>XMLMapping</code> instance given a <code>File</code> object * * @param modelFile * a <code>File</code> value * @exception IOException * if an I/O error occurs (file not found, etc...) * @exception SAXException * if parse error occurs * @exception ParserConfigurationException * if a parser configuration error occurs * @exception InvalidModelException * if an error occurs during mapping construction (invalid model) */ public XMLMapping(File modelFile) throws IOException, SAXException, ParserConfigurationException, InvalidModelException { super(); initWithModelFile(modelFile); } /** * Creates a new <code>XMLMapping</code> instance given a <code>InputStream</code> object * * @param modelInputStream * a <code>InputStream</code> value * @exception IOException * if an I/O error occurs (file not found, etc...) * @exception SAXException * if parse error occurs * @exception ParserConfigurationException * if a parser configuration error occurs * @exception InvalidModelException * if an error occurs during mapping construction (invalid model) */ public XMLMapping(InputStream modelInputStream) throws IOException, SAXException, ParserConfigurationException, InvalidModelException { super(); initWithModelInputStream(modelInputStream); } /** * Initialize a <code>XMLMapping</code> object given a <code>File</code> object * * @param modelFile * a <code>File</code> value * @exception IOException * if an I/O error occurs (file not found, etc...) * @exception SAXException * if parse error occurs * @exception ParserConfigurationException * if a parser configuration error occurs * @exception InvalidModelException * if an error occurs during mapping construction (invalid model) */ protected void initWithModelFile(File modelFile) throws IOException, SAXException, ParserConfigurationException, InvalidModelException { FileInputStream in = new FileInputStream(modelFile); try { initWithModelInputStream(in); } finally { in.close(); } } /** * Initialize a <code>XMLMapping</code> object given a <code>modelInputStream</code> object * * @param modelInputStream * a <code>InputStream</code> value * @exception IOException * if an I/O error occurs (file not found, etc...) * @exception SAXException * if parse error occurs * @exception ParserConfigurationException * if a parser configuration error occurs * @exception InvalidModelException * if an error occurs during mapping construction (invalid model) */ protected void initWithModelInputStream(InputStream modelInputStream) throws IOException, SAXException, ParserConfigurationException, InvalidModelException { Document modelDocument = null; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { // ByteArrayInputStream Stream=new // ByteArrayInputStream(buf,0,length); DocumentBuilder builder = factory.newDocumentBuilder(); modelDocument = builder.parse(modelInputStream); initWithModelDocument(modelDocument); } catch (SAXException e) { throw e; } catch (ParserConfigurationException e) { throw e; } catch (IOException e) { throw e; } } /** * Initialize a <code>XMLMapping</code> object given a <code>Document</code> object representing the model file. * * @param modelDocument * a <code>Document</code> value * @exception InvalidModelException * if an error occurs during mapping construction (invalid model) */ protected void initWithModelDocument(Document modelDocument) throws InvalidModelException { NodeList modelNodeList; Node modelNode; NodeList entitiesNodeList; Node tempNode; modelNodeList = modelDocument.getElementsByTagName(modelLabel); if (modelNodeList.getLength() == 0) { throw new InvalidModelException("No tag 'model' found in model file"); } else if (modelNodeList.getLength() > 1) { throw new InvalidModelException("More than one tag 'model' found in model file"); } modelNode = modelNodeList.item(0); if (modelNode.hasAttributes()) { NamedNodeMap attributes = modelNode.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Node tempAttribute = attributes.item(i); if (tempAttribute.getNodeName().equals(XMLMapping.handlesReferencesLabel)) { handlesReferences = tempAttribute.getNodeValue().equalsIgnoreCase("yes"); } else if (tempAttribute.getNodeName().equals(XMLMapping.serializationModeLabel)) { if (tempAttribute.getNodeValue().equalsIgnoreCase(deepFirstLabel)) { serializationMode = DEEP_FIRST; } else if (tempAttribute.getNodeValue().equalsIgnoreCase(pseudoTreeLabel)) { serializationMode = PSEUDO_TREE; } else if (tempAttribute.getNodeValue().equalsIgnoreCase(orderedPseudoTreeLabel)) { serializationMode = ORDERED_PSEUDO_TREE; } else if (tempAttribute.getNodeValue().equalsIgnoreCase(DefaultLabel)) { serializationMode = DEFAULT_SERIALIZATION_MODE; } } else if (tempAttribute.getNodeName().equals(XMLMapping.serializeOnlyLabel)) { serializeOnly = tempAttribute.getNodeValue().equalsIgnoreCase("yes"); } else if (tempAttribute.getNodeName().equals(XMLMapping.builderLabel)) { try { builderClass = Class.forName(tempAttribute.getNodeValue()); } catch (ClassNotFoundException e) { throw new InvalidModelException("Builder defined for this model matches no known class: '" + tempAttribute.getNodeValue() + "'"); } } else { throw new InvalidModelException("Invalid attribute '" + tempAttribute.getNodeName() + "' found in model file for tag 'model'"); } } } modelEntitiesStoredByXMLTag = new Hashtable<String, ModelEntity>(); modelEntitiesStoredByClassName = new Hashtable<String, ModelEntity>(); entitiesNodeList = modelNode.getChildNodes(); for (int i = 0; i < entitiesNodeList.getLength(); i++) { tempNode = entitiesNodeList.item(i); if (tempNode.getNodeType() == Node.ELEMENT_NODE) { if (tempNode.getNodeName().equals(XMLMapping.descriptionLabel)) { if (tempNode.getChildNodes().getLength() == 1 && tempNode.getFirstChild().getNodeType() == Node.TEXT_NODE) { setDescription(tempNode.getFirstChild().getNodeValue()); } } else if (tempNode.getNodeName().equals(XMLMapping.entityLabel)) { updateAndRegisterNewModelEntity(new ModelEntity(tempNode, this)); } else if (tempNode.getNodeName().equals(XMLMapping.mapIdLabel)) { mapId = new XMLMapId(tempNode, this); } else { throw new InvalidModelException("Invalid tag " + tempNode.getNodeName() + " found in model file"); } } else if (tempNode.getNodeType() == Node.TEXT_NODE) { // Non significative text will be simply ignored if (tempNode.getNodeValue().trim().length() > 0) { throw new InvalidModelException("Invalid text found in model file"); } } else if (tempNode.getNodeType() == Node.COMMENT_NODE) { // Simply ignore it } else { throw new InvalidModelException("Invalid xml tag found as child of 'model' tag in model file"); } } } public boolean implementsCustomIdMappingScheme() { return mapId != null; } /** * Internally used to register new <code>ModelEntity</code> object in mapping<br> * Inheritance is managed at this level. * * @param aModelEntity * a <code>ModelEntity</code> value */ private void updateAndRegisterNewModelEntity(ModelEntity aModelEntity) { for (Map.Entry<String, ModelEntity> e : modelEntitiesStoredByClassName.entrySet()) { ModelEntity tempModelEntity = e.getValue(); if (tempModelEntity.inheritsFrom(aModelEntity)) { tempModelEntity.takeParentUnderAccount(aModelEntity); updateEntitiesStoredByXMLTags(tempModelEntity); } if (aModelEntity.inheritsFrom(tempModelEntity)) { aModelEntity.takeParentUnderAccount(tempModelEntity); } } storesNewModelEntity(aModelEntity); updateProperties(); } private void updateProperties() { for (Map.Entry<String, ModelEntity> e : modelEntitiesStoredByClassName.entrySet()) { ModelEntity tempModelEntity = e.getValue(); tempModelEntity.updateProperties(); } } /** * Internally used to register new <code>ModelEntity</code> object in mapping * * @param aModelEntity * a <code>ModelEntity</code> value */ private void storesNewModelEntity(ModelEntity aModelEntity) { updateEntitiesStoredByXMLTags(aModelEntity); if (modelEntitiesStoredByClassName.get(aModelEntity.getName()) != null && modelEntitiesStoredByClassName.get(aModelEntity.getName()) != aModelEntity) { throw new InvalidModelException("Duplicated entity matching class name " + aModelEntity.getName()); } modelEntitiesStoredByClassName.put(aModelEntity.getName(), aModelEntity); } private void updateEntitiesStoredByXMLTags(ModelEntity aModelEntity) { if (aModelEntity.getXmlTags() != null) { for (int i = 0; i < aModelEntity.getXmlTags().length; i++) { String xmlTag = aModelEntity.getXmlTags()[i]; if (modelEntitiesStoredByXMLTag.get(xmlTag) != null && modelEntitiesStoredByXMLTag.get(xmlTag) != aModelEntity) { throw new InvalidModelException("Duplicated entity with XML tag " + xmlTag); } modelEntitiesStoredByXMLTag.put(xmlTag, aModelEntity); } } } /** * Returns <code>ModelEntity</code> object with XML tag name matching <code>aTagName</code> value, <code>null</code> if such an object * doesn't exist. * * @param aTagName * a <code>String</code> value * @return a <code>ModelEntity</code> value */ public ModelEntity entityWithXMLTag(String aTagName) { return modelEntitiesStoredByXMLTag.get(aTagName); } /** * Returns first <code>ModelProperty</code> object with XML tag name matching <code>aTagName</code> value, <code>null</code> if such an * object doesn't exist. * * @param aTagName * a <code>String</code> value * @return a <code>ModelEntity</code> value */ public ModelProperty propertyWithXMLTag(String aTagName) { for (Map.Entry<String, ModelEntity> e : modelEntitiesStoredByXMLTag.entrySet()) { ModelEntity tempModelEntity = e.getValue(); ModelProperty tempModelProperty = tempModelEntity.getModelPropertyWithXMLTag(aTagName); if (tempModelProperty != null) { return tempModelProperty; } } return null; } /** * Returns <code>ModelEntity</code> object with class name matches <code>aClassName</code> value, <code>null</code> if such an object * doesn't exist. * * @param aClassName * a <code>String</code> value * @return a <code>ModelEntity</code> value */ public ModelEntity entityWithClassName(String aClassName) { ModelEntity returnedModelEntity; returnedModelEntity = modelEntitiesStoredByClassName.get(aClassName); return returnedModelEntity; } /** * Returns <code>ModelEntity</code> object with class matches <code>aClass</code> value, <code>null</code> if such an object doesn't * exist. Look in inheritance tree. * * @param aClass * a <code>Class</code> value * @return a <code>ModelEntity</code> value */ public ModelEntity entityForClass(Class<?> aClass) { Class<?> currentClass = aClass; ModelEntity returned = entityWithClassName(currentClass.getName()); while (currentClass != null && currentClass.getSuperclass() != null && returned == null) { currentClass = currentClass.getSuperclass(); returned = entityWithClassName(currentClass.getName()); } return returned; } /** * Returns a String representation of this object suitable for debugging purposes * * @return a <code>String</code> value */ @Override public String toString() { String returnedString = "<model"; if (hasBuilderClass()) { returnedString += " " + builderLabel + "=" + '"' + builderClass().getName() + '"'; } if (handlesReferences()) { returnedString += " " + handlesReferencesLabel + "=" + '"' + "YES" + '"'; } returnedString += " " + serializationModeLabel + "=" + '"' + serializationMode + '"'; returnedString += ">\n"; for (Map.Entry<String, ModelEntity> e : modelEntitiesStoredByClassName.entrySet()) { ModelEntity tempModelEntity = e.getValue(); returnedString += tempModelEntity.toString(); } returnedString += "</model>\n"; return returnedString; } /** * Returns all <code>ModelEntity</code> objects stored in an Enumeration */ public Iterator<ModelEntity> allModelEntities() { if (modelEntitiesStoredByXMLTag != null) { return modelEntitiesStoredByXMLTag.values().iterator(); } else { return null; } } /** * Appends to this <code>XMLMapping</code> all entries concerning the other mapping <code>aMapping</code>, so that this mapping will be * able to maps the two old mappings. */ public void appendXMLMapping(XMLMapping aMapping) { Iterator<ModelEntity> i = aMapping.allModelEntities(); if (i != null) { while (i.hasNext()) { storesNewModelEntity(i.next()); } } } /** * Return Boolean indicating if references must be handled during encoding */ public boolean handlesReferences() { return handlesReferences; } /** * Return Builder class defined for this model, if any, null if not defined */ public Class<?> builderClass() { return builderClass; } /** * Return boolean indicating if a builder class has been defined for this model */ public boolean hasBuilderClass() { return builderClass != null; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } /** * Return the most specialized registered entity matching all supplied XML tags * * @param xmlTags * @return */ public ModelEntity mostSpecializedEntityMatchingAllXMLTags(String[] xmlTags) { /* * String concatenedXMLTags = ""; for (int i=0; i<xmlTags.length; i++) { * concatenedXMLTags += xmlTags[i]; } System.out.println * ("mostSpecializedEntityMatchingAllXMLTags() with * "+concatenedXMLTags); */ if (xmlTags.length == 0) { // System.out.println ("mostSpecializedEntityMatchingAllXMLTags() // not found, supplied tag is null !"); return null; } else if (xmlTags.length == 1) { return entityWithXMLTag(xmlTags[0]); } else { Hashtable potentialEntities = null; if (xmlTags.length > 0) { potentialEntities = entityWithXMLTag(xmlTags[0]).getAncestors(); } for (int i = 1; i < xmlTags.length; i++) { String xmlTag = xmlTags[i]; ModelEntity tempEntity = entityWithXMLTag(xmlTag); Hashtable<ModelEntity, Integer> tempAncestors = tempEntity.getAncestors(); Hashtable<ModelEntity, Integer> keptEntities = new Hashtable<ModelEntity, Integer>(); for (Enumeration e = tempAncestors.keys(); e.hasMoreElements();) { ModelEntity ent = (ModelEntity) e.nextElement(); if (potentialEntities.get(ent) != null) { int potentialLevel = ((Integer) potentialEntities.get(ent)).intValue(); int tempLevel = tempAncestors.get(ent).intValue(); keptEntities.put(ent, new Integer(potentialLevel + tempLevel)); } } potentialEntities = keptEntities; } if (potentialEntities.size() > 0) { ModelEntity returned = null; int bestLevel = 0; for (Enumeration e = potentialEntities.keys(); e.hasMoreElements();) { ModelEntity ent = (ModelEntity) e.nextElement(); int entLevel = ((Integer) potentialEntities.get(ent)).intValue(); // System.out.println ("I have "+ent.getName()+" with level // "+entLevel); if (returned == null || entLevel < bestLevel) { returned = ent; bestLevel = entLevel; } } // System.out.println ("Returning "+returned.getName()+" with // level "+bestLevel); return returned; } else { // System.out.println // ("mostSpecializedEntityMatchingAllXMLTags() not found !"); return null; } } } public int getSerializationMode() { return serializationMode; } public void setSerializationMode(int serializationMode) { this.serializationMode = serializationMode; } /** * Finds all objects embedded by the object object and of the class klass. This method is not recursive. * * @param <T> * @param object * @param klass * @return */ public <T> Collection<T> getEmbeddedObjectsForObject(Object object, Class<T> klass) { return getEmbeddedObjectsForObject(object, klass, false); } /** * Finds all objects embedded by the object object and of the class klass. The method will maintain the natural order of objects if * specified by the flag. This method is not recursive. * * @param <T> * @param object * @param klass * @param maintainNaturalOrder * @return */ public <T> Collection<T> getEmbeddedObjectsForObject(Object object, Class<T> klass, boolean maintainNaturalOrder) { return getEmbeddedObjectsForObject(object, klass, maintainNaturalOrder, false); } /** * Finds all objects embedded by the object object and of the class klass. The method will maintain the natural order of objects if * specified by the flag. The method will go into objects recursively if specified by the flag. * * @param <T> * @param object * @param klass * @param maintainNaturalOrder * @param recursive * @return */ public <T> Collection<T> getEmbeddedObjectsForObject(Object object, Class<T> klass, boolean maintainNaturalOrder, boolean recursive) { return getEmbeddedObjectsForObject(object, maintainNaturalOrder ? new Vector<T>() : new HashSet<T>(), klass, recursive); } /** * Finds all objects embedded by the object object and of the class klass. The method will go into objects recursively if specified by * the flag. All objects will be added into the passed collection. * * @param <T> * @param object * @param v * @param klass * @param recursive * @return */ public <T> Collection<T> getEmbeddedObjectsForObject(Object object, Collection<T> v, Class<T> klass, boolean recursive) { return getEmbeddedObjectsForObject(object, v, klass, recursive, false, new HashSet<Object>()); } /** * Finds all objects embedded by the object object and of the class klass. This method is not recursive. * * @param <T> * @param object * @param klass * @return */ public <T> Collection<T> getRestrictedEmbeddedObjectsForObject(Object object, Class<T> klass) { return getRestrictedEmbeddedObjectsForObject(object, klass, false); } public <T> Collection<T> getRestrictedEmbeddedObjectsForObject(Object object, Class<T> klass, boolean maintainNaturalOrder) { return getRestrictedEmbeddedObjectsForObject(object, klass, maintainNaturalOrder, false); } public <T> Collection<T> getRestrictedEmbeddedObjectsForObject(Object object, Class<T> klass, boolean maintainNaturalOrder, boolean recursive) { return getRestrictedEmbeddedObjectsForObject(object, maintainNaturalOrder ? new Vector<T>() : new HashSet<T>(), klass, recursive); } public <T> Collection<T> getRestrictedEmbeddedObjectsForObject(Object object, Collection<T> v, Class<T> klass, boolean recursive) { return getEmbeddedObjectsForObject(object, v, klass, recursive, true, new HashSet<Object>()); } private <T> Collection<T> getEmbeddedObjectsForObject(Object object, Collection<T> v, Class<T> klass, boolean recursive, boolean followPrimaryAndCopiableOnly, Collection<Object> visited) { // First we add the object to the visited objects! visited.add(object); if (klass.isAssignableFrom(object.getClass()) && !v.contains(object)) { v.add((T) object); } ModelEntity entity = entityForClass(object.getClass()); if (entity == null) { return v; } Enumeration<ModelProperty> en = entity.getModelProperties(); while (en.hasMoreElements()) { ModelProperty property = en.nextElement(); if (followPrimaryAndCopiableOnly && (!property.isPrimary() || !property.isCopyable())) { continue; } KeyValueProperty keyValueProperty = property.getKeyValueProperty(); if (keyValueProperty instanceof SingleKeyValueProperty && keyValueProperty.getType().isPrimitive()) { continue; } Object o = keyValueProperty.getObjectValue(object); if (o != null) { if (recursive) { if (o instanceof Map) { Map<?, ?> hash = (Map<?, ?>) o; for (Object oInHash : hash.values()) { if (!visited.contains(oInHash)) { v.addAll(getEmbeddedObjectsForObject(oInHash, v, klass, recursive, followPrimaryAndCopiableOnly, visited)); } } } else if (o instanceof List) { List<?> vector = (List<?>) o; for (Object oInVector : vector) { if (!visited.contains(oInVector)) { v.addAll(getEmbeddedObjectsForObject(oInVector, v, klass, recursive, followPrimaryAndCopiableOnly, visited)); } } } else { if (!visited.contains(o)) { v.addAll(getEmbeddedObjectsForObject(o, v, klass, recursive, followPrimaryAndCopiableOnly, visited)); } } } else { if (!v.contains(o) && klass.isAssignableFrom(o.getClass())) { v.add((T) o); } } } } return v; } }