/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.query.mapping.xml; import java.io.PrintStream; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.teiid.designer.runtime.version.spi.ITeiidServerVersion; import org.teiid.designer.xml.IMappingNode; import org.teiid.query.parser.TeiidNodeFactory; import org.teiid.query.parser.TeiidNodeFactory.ASTNodes; import org.teiid.query.sql.lang.LanguageObject; import org.teiid.query.sql.symbol.ElementSymbol; import org.teiid.runtime.client.Messages; /** * There will be a mapping node * for every entity (element or attribute) in a target XML document. * @see MappingNodeConstants */ public abstract class MappingNode implements Cloneable, Serializable, IMappingNode { private static final long serialVersionUID = 6761829541871178451L; /** The parent of this node, null if root. */ private MappingNode parent; /** Child nodes, usually just 1 or 2, but occasionally more */ private List<MappingNode> children = new LinkedList<MappingNode>(); /** node properties, as defined in NodeConstants.Properties. */ private Map<MappingNodeConstants.Properties, Object> nodeProperties; /** * Version used for constructing ast nodes */ private final ITeiidServerVersion teiidVersion; /** * @param teiidVersion */ public MappingNode(ITeiidServerVersion teiidVersion){ this.teiidVersion = teiidVersion; } protected ITeiidServerVersion getTeiidVersion() { return teiidVersion; } protected <T extends LanguageObject> T createASTNode(ASTNodes nodeType) { return TeiidNodeFactory.getInstance().create(getTeiidVersion(), nodeType); } /** * Get the parent of this node. */ public MappingNode getParent() { return this.parent; } public static MappingNode findNode(MappingNode root, String partialName) { return findNode(root, partialName, 0); } public static MappingNode findNode(MappingNode root, String partialName, int start) { String canonicalName = root.getName(); if (canonicalName != null) { int offset = partialName.charAt(start)=='@'?1:0; if (partialName.startsWith(canonicalName, start + offset)) { if (partialName.length() - start > canonicalName.length() + 1 && partialName.charAt(start + canonicalName.length()) == '.') { start += canonicalName.length() + 1; } else if (partialName.length() - start == canonicalName.length() + offset) { return root; } else { return null; } } else { return null; } } for (Iterator<MappingNode> i = root.getChildren().iterator(); i.hasNext();) { MappingNode child = i.next(); MappingNode found = findNode(child, partialName, start); if (found != null) { return found; } } return null; } /** * Set the parent of this node. This method is restricted, as * it should be called only when {@link #addChild adding a child node} */ void setParent( MappingNode parent ) { this.parent = parent; } /** * Get the children contained by this node, or an empty List * @return children; if no children, return empty List (never null) */ public List<MappingNode> getChildren(){ return this.children; } /** * Get all children of this node of a specified target node type. The value * of node type should be one of {@link #ATTRIBUTE} or {@link #ELEMENT}. */ public List<MappingNode> getChildren( String type ) { List<MappingNode> subset = new ArrayList<MappingNode>(); Iterator<MappingNode> iter = children.iterator(); while ( iter.hasNext() ) { MappingNode node = iter.next(); if ( node.getProperty(MappingNodeConstants.Properties.NODE_TYPE).equals(type) ) { subset.add( node ); } } return subset; } public List<MappingNode> getNodeChildren() { List<MappingNode> subset = new ArrayList<MappingNode>(); Iterator<MappingNode> iter = children.iterator(); while ( iter.hasNext() ) { MappingNode node = iter.next(); if ( !node.getProperty(MappingNodeConstants.Properties.NODE_TYPE).equals(MappingNodeConstants.ATTRIBUTE) ) { subset.add( node ); } } return subset; } /** * Add a child mapping node to this one. * @param MappingNode to add as a child of this node * @return the added node, with parent set to this node */ public void addChild( MappingNode node ) { this.children.add( node ); node.setParent(this); } /** * Retrieve one of the Object values, keyed off the * Integer property keys defined in * {@link MappingNodeConstants.Properties} * @param propertyID Integer property key * @return Object value */ public Object getProperty(MappingNodeConstants.Properties propertyID) { Object value = null; if(nodeProperties != null) { value = nodeProperties.get(propertyID); } if (value == null){ value = MappingNodeConstants.Defaults.DEFAULT_VALUES.get(propertyID); } return value; } /** * Sets one of the Object values, keyed off the * Integer property keys defined in * {@link MappingNodeConstants.Properties} * @param propertyID Integer property key * @param value Object property value */ void setProperty(MappingNodeConstants.Properties propertyID, Object value) { if (value != null){ // Get the default for the property ... final Object defaultValue = MappingNodeConstants.Defaults.DEFAULT_VALUES.get(propertyID); final Map<MappingNodeConstants.Properties, Object> props = getNodeProperties(); // props is never null if ( !value.equals(defaultValue) ) { // we know value is not null // Set the value only if different than the default; note that the 'getProperty' // method is returning the default if there isn't a value props.put(propertyID, value); } else { // The value is equal to the default, so because we didn't set it // we have to make sure to remove any existing value ... props.remove(propertyID); } } } void removeProperty(MappingNodeConstants.Properties propertyID) { getNodeProperties().remove(propertyID); } /** * Returns the actual local properties object, instantiates if necessary. * The Map returned is the basis for equality for a MappingNode. * <b>Note:</b> Use {@link #getProperties} unless absolutely necessary. * @return the actual properties (not including defaults) stored at this * object. * @see #getProperties */ public Map<MappingNodeConstants.Properties, Object> getNodeProperties(){ if(nodeProperties == null) { nodeProperties = new LinkedHashMap<MappingNodeConstants.Properties, Object>(); } return nodeProperties; } /** * <p>Gets the fully qualified name of this node, this is obtained by concatenating * the nodes name to the parents qualified name with a delimiter in between them.</p> * @return The fully qualified name of this node. */ public String getFullyQualifiedName() { String myName = getPathName(); String parentName = (getParent() == null) ? "" : getParent().getFullyQualifiedName(); //$NON-NLS-1$ if(myName == null || myName.equals("")) { //$NON-NLS-1$ return parentName; } else if(parentName == null || parentName.equals("")) { //$NON-NLS-1$ return myName; } else { return parentName + MappingNodeConstants.PATH_DELIM + myName; } } public String getName() { return (String) this.getProperty(MappingNodeConstants.Properties.NAME); } public String getPathName() { return getName(); } // ========================================================================= // OVERRIDE Object METHODS // ========================================================================= /** * Compare the symbol based ONLY on properties (including name), NOT on * parent node and children nodes (or lack thereof). * @param obj Other object * @return True if other obj is a MappingNode (or subclass) and properties * are equal */ public boolean equals(Object obj) { if(obj == this) { return true; } if(obj != null && obj instanceof MappingNode) { return ((MappingNode)obj).getNodeProperties().equals(getNodeProperties()); } return false; } /** * Return a hash code for this symbol. * @return Hash code */ public int hashCode() { return this.getNodeProperties().hashCode(); } /** * Returns a string representation of an instance of this class. */ public String toString() { Object name = getProperty(MappingNodeConstants.Properties.NAME); Object criteria = getProperty(MappingNodeConstants.Properties.CRITERIA); Object prefix = getProperty(MappingNodeConstants.Properties.NAMESPACE_PREFIX); Object defaultValue = getProperty(MappingNodeConstants.Properties.DEFAULT_VALUE); Object fixedValue = getProperty(MappingNodeConstants.Properties.FIXED_VALUE); Object namespaces = getProperty(MappingNodeConstants.Properties.NAMESPACE_DECLARATIONS); return "[" + getProperty(MappingNodeConstants.Properties.NODE_TYPE) + "]" //$NON-NLS-1$ //$NON-NLS-2$ + " name='" + ((prefix != null) ? prefix + ":" : "") + ((name != null) ? name : "") + "'" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //+ " path='" + ((name != null) ? getSchemaPath() : "undefined") + "'" + ((defaultValue != null) ? " default='" + defaultValue + "'" : "") //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + ((fixedValue != null) ? " fixed='" + fixedValue + "'" : "") //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + " minOccurs=" + getProperty(MappingNodeConstants.Properties.CARDINALITY_MIN_BOUND) //$NON-NLS-1$ + " maxOccurs=" + getProperty(MappingNodeConstants.Properties.CARDINALITY_MAX_BOUND) //$NON-NLS-1$ + ((criteria != null) ? (" constraint=\"" + criteria + "\"") : "") //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + ((namespaces != null) ? (" namespaces=\"" + namespaces + "\"") : ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } /** * Prints the whole tree of MappingNodes to the provided PrintStream. */ public static void printMappingNodeTree(MappingNode root, PrintStream output){ output.print(toStringNodeTree(root)); } public static String toStringNodeTree(MappingNode root){ StringBuffer str = new StringBuffer(); buildTreeString(root, str, 0); return str.toString(); } // Define a single tab private static final String TAB = " "; //$NON-NLS-1$ private static void buildTreeString(MappingNode node, StringBuffer str, int tabLevel){ setTab(str, tabLevel++); str.append(node.toString()); str.append(node.getNodeProperties()); str.append("\n"); //$NON-NLS-1$ Iterator<MappingNode> i = node.getChildren().iterator(); while (i.hasNext()){ buildTreeString(i.next(), str, tabLevel); } } private static void setTab(StringBuffer str, int tabStop) { for(int i=0; i<tabStop; i++) { str.append(TAB); } } /** * Starting at a point in a mapping document and traversing either upward toward the root or * downward (depth or breadth first), this method finds and returns * the first MappingNode instance that has the indicated property key and value. A <code>null</code> * can be passed in for the value parameter, indicating the first node should be returned which has * <i>any</i> non-null value for the property key. * @param propertyKey any of the properties defined in {@link MappingNodeConstants.Properties} * @param value an Object value that is checked for. <code>null</code> can be passed in, indicating * the first node with any non-null value for that property should be returned * @param node a MappingNode in a mapping document - this is the beginning point of the search * @param searchDirection indicates whether to search upward or downward (depth or breadth first) from the * node parameter; use either {@link MappingNodeConstants#SEARCH_UP}, * {@link MappingNodeConstants#SEARCH_DOWN} or {@link MappingNodeConstants#SEARCH_DOWN_BREADTH_FIRST} * @return MappingNode first node found that has the indicated property and value, or null if none found */ static MappingNode findFirstNodeWithProperty(MappingNodeConstants.Properties propertyKey, Object value, MappingNode node, int searchDirection) { return findFirstNodeWithPropertyValue(propertyKey, value, false, node, searchDirection); } /** * Starting at a point in a mapping document and traversing either upward toward the root or * downward (depth or breadth first), this method finds and returns * the first MappingNode instance that has the indicated property key and value. A <code>null</code> * can be passed in for the value parameter, indicating the first node should be returned which has * <i>any</i> non-null value for the property key. * @param propertyKey any of the properties defined in {@link MappingNodeConstants.Properties} * for which the value is of type String * @param value an Object value that is checked for. <code>null</code> can be passed in, indicating * the first node with any non-null value for that property should be returned * @param node a MappingNode in a mapping document - this is the beginning point of the search * @param searchDirection indicates whether to search upward or downward (depth or breadth first) from the * node parameter; use either {@link MappingNodeConstants#SEARCH_UP}, * {@link MappingNodeConstants#SEARCH_DOWN} or {@link MappingNodeConstants#SEARCH_DOWN_BREADTH_FIRST} * @return MappingNode first node found that has the indicated property and value, or null if none found */ static MappingNode findFirstNodeWithPropertyString(MappingNodeConstants.Properties propertyKey, String value, MappingNode node, int searchDirection) { return findFirstNodeWithPropertyValue(propertyKey, value, true, node, searchDirection); } private static MappingNode findFirstNodeWithPropertyValue(MappingNodeConstants.Properties propertyKey, Object value, boolean isStringValue, MappingNode node, int searchDirection) { if (node == null || propertyKey == null){ return null; } if (searchDirection == MappingNodeConstants.SEARCH_UP){ return traverseUpForFirstNodeWithPropertyString(propertyKey, value, isStringValue, node); } else if (searchDirection == MappingNodeConstants.SEARCH_DOWN){ return traverseDownForFirstNodeWithPropertyString(propertyKey, value, isStringValue, node, false); } else if (searchDirection == MappingNodeConstants.SEARCH_DOWN_BREADTH_FIRST){ // Check root node first if (checkThisNodeForPropertyValue(propertyKey, value, isStringValue, node)){ return node; } return traverseDownForFirstNodeWithPropertyString(propertyKey, value, isStringValue, node, true); } else { throw new IllegalArgumentException(Messages.getString(Messages.ERR.ERR_015_002_0009, searchDirection )); } } private static MappingNode traverseDownForFirstNodeWithPropertyString(MappingNodeConstants.Properties propertyKey, Object value, boolean isStringValue, MappingNode node, boolean breadthFirst) { if (breadthFirst) { Iterator<MappingNode> children = node.getChildren().iterator(); while (children.hasNext()){ MappingNode child = children.next(); if (checkThisNodeForPropertyValue(propertyKey, value, isStringValue, child)){ return child; } } } else { if (checkThisNodeForPropertyValue(propertyKey, value, isStringValue, node)){ return node; } } Iterator<MappingNode> children = node.getChildren().iterator(); while (children.hasNext()){ MappingNode child = children.next(); //recursive call to this method MappingNode result = traverseDownForFirstNodeWithPropertyString(propertyKey, value, isStringValue, child, breadthFirst); if (result != null){ return result; } } return null; } private static boolean checkThisNodeForPropertyValue(MappingNodeConstants.Properties propertyKey, Object value, boolean isStringValue, MappingNode node) { Object thisValue = node.getProperty(propertyKey); if (thisValue != null){ if (value == null){ return true; } if (isStringValue && ((String)thisValue).equalsIgnoreCase((String)value)) { return true; } if (thisValue.equals(value)) { return true; } } return false; } private static MappingNode traverseUpForFirstNodeWithPropertyString(MappingNodeConstants.Properties propertyKey, Object value, boolean isStringValue, MappingNode node) { while (node != null){ if (checkThisNodeForPropertyValue(propertyKey, value, isStringValue, node)){ return node; } node = node.getParent(); } return null; } public void setExclude(boolean exclude) { setProperty(MappingNodeConstants.Properties.IS_EXCLUDED, Boolean.valueOf(exclude)); } public boolean isExcluded() { Boolean exclude = (Boolean)getProperty(MappingNodeConstants.Properties.IS_EXCLUDED); if (exclude != null) { return exclude.booleanValue(); } return false; } public abstract void acceptVisitor(MappingVisitor visitor); /** * Get the source node for this Mapping Node; note that only mapping elements and * mapping attributes have the source nodes; that too ones with specified with * NameInSource specified attribute. * * If not find nearest one looking up the tree. * @return */ public MappingSourceNode getSourceNode() { if (getParent() != null) { return getParent().getSourceNode(); } return null; } public String getNameInSource() { return null; } public MappingNode clone() { try { MappingNode clone = (MappingNode) super.clone(); clone.children = new ArrayList<MappingNode>(children); for (int i = 0; i < clone.children.size(); i++) { MappingNode childClone = clone.children.get(i).clone(); childClone.setParent(clone); clone.children.set(i, childClone); } if (this.nodeProperties != null) { this.nodeProperties = new HashMap<MappingNodeConstants.Properties, Object>(this.nodeProperties); } return clone; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } public ElementSymbol getElementSymbol() { return null; } }