package org.javabuilders; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; /** * Represents a node being currently processed * @author Jacek Furmankiewicz */ public class Node { private Object mainObject = null; private Node parent = null; private String key = null; private Map<String,List<Object>> childValues = new HashMap<String,List<Object>>(); private Set<Node> childNodes = new LinkedHashSet<Node>(); private Map<String,Object> properties = new HashMap<String,Object>(); private Set<String> consumedKeys = new HashSet<String>(); private boolean usePreInstantiatedRoot = false; private Map<String,Object> customProperties = new HashMap<String, Object>(); /** * Constructor (for use by Builder only) * @param mainObject Main object being processed * @param properties The list of properties for this object */ Node(Node parent, String key) { this(parent,key,null); } /** * Constructor * @param mainObject Main object being processed * @param properties The list of properties for this object */ public Node(String key, Map<String,Object> properties) { this(null,key, properties); } /** * Constructor * @param parent Parent node (null is accepted if root) * @param mainObject Main object being processed */ public Node(Node parent, String key, Map<String,Object> properties) { this(parent,key, properties, null); } /** * Constructor * @param parent Parent node (null is accepted if root) * @param mainObject Main object being processed */ public Node(Node parent, String key, Map<String,Object> properties, Object mainObject) { if (key == null) { throw new NullPointerException("key cannot be null"); } this.key = key; if (properties != null) { this.properties = properties; } //automatically add to parent if there is one if (parent != null) { parent.addChildNode(this); this.parent = parent; } if (mainObject != null) { this.setMainObject(mainObject); } } /** * Returns the optional main object that was created by this node * @return Main object */ public Object getMainObject() { return mainObject; } /** * Sets the optional main object that was created by this node */ public void setMainObject(Object object) { mainObject = object; } /** * @return The parent node (null if root) */ public Node getParent() { return parent; } /** * Attempts to find the first parent of a particular type * @return The parent node (null if none found) */ public Object getParentObject(Class<?>...parentTypes) { Node parentNode = null; Class<?> type = getMainObject().getClass(); Node currentNode = this; root: while (type != null) { for(Class<?> parentType : parentTypes) { if (parentType.isAssignableFrom(type)) { parentNode = currentNode; break root; } } //go up one level currentNode = currentNode.getParent(); if (currentNode != null) { type = currentNode.getMainObject().getClass(); } else { type = null; } } if (parentNode != null) { return parentNode.getMainObject(); } else { return null; } } /** * Returns the list of all the children nodes belonging to this parent * @return */ public Map<String,List<Object>> getChildValues() { return childValues; } /** * Returns the list of all the children for a specific key * @return */ public List<Object> getChildValues(String key) { return childValues.get(key); } /** * Returns the first child value for a specific key * @return */ public Object getFirstChildValue(String key) { return childValues.get(key).get(0); } /** * Registers the child with this parent, grouped under a common key */ public void addChildValue(String key,Object value) { List<Object> list = childValues.get(key); if (list == null) { list = new ArrayList<Object>(); childValues.put(key, list); } list.add(value); } /** * Returns all child nodes * @return All child nodes */ public Set<Node> getChildNodes() { return childNodes; } /** * Returns all child nodes, filtered by class type that was created * @param classFilter Class filter * @return All child nodes */ public Set<Node> getChildNodes(Class<?>... classFilter) { Set<Node> nodes = new LinkedHashSet<Node>(); for(Node child : getChildNodes()) { for(Class<?> type : classFilter) { if (type.isAssignableFrom(child.getMainObject().getClass())) { nodes.add(child); } } } return nodes; } /** * Helper method to quickly get helper node * @return */ public Node getContentNode() { return getChildNode(Builder.CONTENT); } /** * Helper method to quickly get children in the content node * @return List of content nodes (or null if none) */ public Set<Node> getContentNodes() { Node content = getContentNode(); if (content != null) { return content.getChildNodes(); } else { return null; } } /** * Helper method to quickly get children in the content node of a particular * type * @param classFilter Class type to filter on * @return List of relevant child nodes */ public Set<Node> getContentNodes(Class<?>... classFilter) { Node content = getContentNode(); if (content != null) { return content.getChildNodes(classFilter); } else { return new HashSet<Node>(); } } /** * Helper method to quickly get children in the content node of a particular * type * @param classFilter Class type to filter on * @return List of relevant child objects */ @SuppressWarnings("unchecked") public <C> Set<C> getContentObjects(Class<? extends C> classFilter) { Set<Node> contents = getContentNodes(classFilter); Set<C> objects = new LinkedHashSet<C>(); for(Node node : contents) { objects.add((C) node.getMainObject()); } return objects; } /** * Helper method to quickly get child objects of a particular type * @param <C> * @param classFilter * @return */ @SuppressWarnings("unchecked") public <C> Set<C> getChildObjects(Class<? extends C> classFilter) { Set<Node> contents = getChildNodes(classFilter); Set<C> objects = new LinkedHashSet<C>(); for(Node node : contents) { objects.add((C) node.getMainObject()); } return objects; } /** * Returns sibling objects of a particular type * @param <C> * @param classFilter * @return */ public <C> Set<C> getSiblingObjects(Class<? extends C> classFilter) { return getParent().getChildObjects(classFilter); } /** * Looks at the raw YAML data, regardless of whether it's been turned into a * node yet or not * @param <C> Class * @param classFilter Class filter * @return */ @SuppressWarnings("unchecked") public <C> List<Map<String,Object>> getContentData(Class<? extends C> classFilter) { List<Map<String,Object>> data = new LinkedList<Map<String,Object>>(); String name = classFilter.getSimpleName(); Object ct = getProperty(Builder.CONTENT); if (ct instanceof List) { List<Object> list = (List<Object>) ct; for (Object entry : list) { if (entry instanceof Map) { Map<String,Map<String,Object>> row = (Map<String, Map<String, Object>>) entry; for(String key : row.keySet()) { if (name.equals(key)) { data.add(row.get(key)); } } } } } return data; } /** * Returns the child node identified by a particular key * @param key Key * @return Child node (or null if none found) */ public Node getChildNode(String key) { for(Node node: childNodes) { if (node.getKey().equals(key)) { return node; } } return null; } /** * Adds a child node * @param node Node */ public void addChildNode(Node node) { childNodes.add(node); } /** * @return The properties */ public Map<String, Object> getProperties() { return properties; } /** * Shortcut to getProperties().containsKey() for simpler API * @param key * @return True if contains property, false if not */ public boolean containsProperty(String key) { return getProperties().containsKey(key); } /** * Shortcut to quickly get a string representation of a property value * @param property A list of property aliases * @throws Thrown if the same aliased property is defined multiple times * @return String property value (null if not found) */ public String getStringProperty(String...propertyAliases) throws BuildException { String value = null; for(String alias : propertyAliases) { if (getProperties().containsKey(alias)) { //if the same property's aliases are defined multiple times, we need to throw exception if (value == null) { Object temp = getProperties().get(alias); value = (temp instanceof String) ? (String)temp : String.valueOf(temp); } else { throw new BuildException("Found multiple alias values for the same property: " + propertyAliases); } } } return value; } /** * Shortcut to quickly get a Long representation of a property value * @param property A list of property aliases * @throws Thrown if the same aliased property is defined multiple times * @return String property value (null if not found) */ public Long getLongProperty(String...propertyAliases) throws BuildException { Long value = null; for(String alias : propertyAliases) { if (getProperties().containsKey(alias)) { //if the same property's aliases are defined multiple times, we need to throw exception if (value == null) { value = (Long)getProperties().get(alias); } else { throw new BuildException("Found multiple alias values for the same property: " + propertyAliases); } } } return value; } /** * Shortcut to quickly get an Object representation of a property value * @param property A list of property aliases * @throws Thrown if the same aliased property is defined multiple times * @return String property value (null if not found) */ public Object getProperty(String...propertyAliases) throws BuildException { Object value = null; for(String alias : propertyAliases) { if (getProperties().containsKey(alias)) { //if the same property's aliases are defined multiple times, we need to throw exception if (value == null) { value = getProperties().get(alias); } else { throw new BuildException("Found multiple alias values for the same property: " + propertyAliases); } } } return value; } /** * Returns if the list of created objects has at least one instance of the * requested class (or its subclasses) * @param type Class type * @return true if found, false if not */ public boolean containsType(Class<?> type) { boolean contains = false; for(Node child : getChildNodes()) { if (child.getMainObject() != null && type.isAssignableFrom(child.getMainObject().getClass())) { contains = true; break; } } return contains; } /** * Return the key that this node corresponds to * @return Key */ public String getKey() { return key; } /** * @return The list of keys/properties of the node that have been consumed by the various property handlers up till now */ public Set<String> getConsumedKeys() { return consumedKeys; } /** * For internal Builder use (when processing collection nodes) * @param consumedKeys */ void setConsumedKeys(Set<String> consumedKeys) { this.consumedKeys = consumedKeys; } /** * If true, the node should use the preinstantiated root object instead * of creating a new instance * @return the usePreInstantiatedRoot */ public boolean isUsePreInstantiatedRoot() { return usePreInstantiatedRoot; } /** * Sets a flag telling the node to use the preinstantiated root from * the BuildResult instead of creating a new instance * @param usePreInstantiatedRoot the usePreInstantiatedRoot to set */ public void setUsePreInstantiatedRoot(boolean usePreInstantiatedRoot) { this.usePreInstantiatedRoot = usePreInstantiatedRoot; } /** * @return Domain-specific custom properties that can be set on a node */ public Map<String, Object> getCustomProperties() { return customProperties; } /** * @param key Key * @return Custom property value */ public Object getCustomProperty(String key) { return customProperties.get(key); } /** * Checks if a custom property is equal to a particular value * @param key Key * @param value Value to compare * @return True if equal, false if not */ public boolean isCustomPropertyEqualTo(String key,Object value) { Object pValue =customProperties.get(key); if (pValue == null) { return false; } else { return pValue.equals(value); } } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return MessageFormat.format("{0} node: {1}",getKey(), properties); } }