/*
* Beanfabrics Framework Copyright (C) by Michael Karneim, beanfabrics.org
* Use is subject to license terms. See license.txt.
*/
package org.beanfabrics.meta;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.beanfabrics.Path;
import org.beanfabrics.util.GenericType;
/**
* The {@link PathNode} represents the meta data for a specific node inside a PM's tree-like structure, reachable by a
* given {@link Path} relative to a arbitratily defined root node.
* <p>
* With a {@link PathNode} you can navigate from a specific node upwards until it's defined root node and downwards
* until it's leaf nodes.
* <p>
* Note: There is <i>no natural root node</i> for any given PM, because there is no top-level PM class. PM classes can
* always be combined into greater structures. In this sense any node inside a PMs structure can be arbitrarily choosen
* to be a root node.
*
* @author Michael Karneim
*/
public class PathNode {
private final PathNode parent;
private final TypeInfo modelType;
private final String name;
private final Path path;
private final GenericType genericType;
/**
* Constructs a {@link PathNode} with the given attributes.
*
* @param elementName
* the path element name
* @param elementTypeInfo
* the type info of the element
* @param parent
* the path element info of the parent element (may be <code>null</code>)
* @param elementGenericType
* the generic type represented by this element
*/
PathNode(String elementName, TypeInfo elementTypeInfo, PathNode parent, GenericType elementGenericType) {
super();
this.name = elementName;
this.parent = parent;
this.modelType = elementTypeInfo;
this.path = parent == null ? new Path() : Path.concat(this.parent.getPath(), new Path(elementName));
this.genericType = elementGenericType;
}
/**
* Constructs a {@link PathNode} with the given type info as root element.
*
* @param aTypeInfo
*/
PathNode(TypeInfo aTypeInfo) {
this(Path.THIS_PATH_ELEMENT, aTypeInfo, null, new GenericType(aTypeInfo.getJavaType()));
}
/**
* Returns the path element info of this element's parent.
*
* @return the path element info of this element's parent
*/
public PathNode getParent() {
return parent;
}
/**
* Returns the type info of this element.
*
* @return the type info of this element
*/
public TypeInfo getTypeInfo() {
return modelType;
}
/**
* Returns the element name of this path element.
*
* @return the element name of this path element
*/
public String getName() {
return name;
}
/**
* Returns whether this element's type info has children.
*
* @return whether this element's type info has children
*/
public boolean hasChildren() {
return this.modelType.hasProperties();
}
/**
* Returns the path element infos for for all children.
*
* @return the path element infos for for all children
*/
public Collection<PathNode> getChildren() {
Collection<PropertyInfo> props = this.modelType.getProperties();
List<PathNode> result = new ArrayList<PathNode>();
for (PropertyInfo prop : props) {
GenericType childGT = getGenericTypeOfChild(prop);
PathNode child = new PathNode(prop.getName(), prop.getTypeInfo(), this, childGT);
result.add(child);
}
return result;
}
/**
* Returns the path element info for the child with he given name.
*
* @param name
* the property name of the child
* @return the path element info for the child with he given name
*/
public PathNode getChild(String name) {
PropertyInfo prop = this.modelType.getProperty(name);
if (prop == null) {
return null;
} else {
GenericType childGT = getGenericTypeOfChild(prop);
PathNode child = new PathNode(prop.getName(), prop.getTypeInfo(), this, childGT);
return child;
}
}
/**
* Returns a new {@link PathNode} with this node defined as the new root node.
*
* @return a new {@link PathNode} with this node defined as the new root node
*/
public PathNode asRoot() {
return new PathNode("this", getTypeInfo(), null, getGenericType());
}
/**
* Returns the path element info for the node at the end of the given path.
*
* @param pathToNode
* @return the path element info for the node at the end of the given path
*/
public PathNode getNode(Path pathToNode) {
if (pathToNode == null) {
return null;
} else if (this.path.equals(pathToNode)) {
return this;
} else {
String nextChildName = pathToNode.getElement(0);
PathNode nodeDesc = this.getChild(nextChildName);
if (nodeDesc == null) {
return null;
} else if (pathToNode.length() == 1) {
return nodeDesc;
} else {
return nodeDesc.getNode(pathToNode.getSubPath(1));
}
}
}
/**
* Returns the relative path from the root element to this path element.
*
* @return the relative path from the root element to this path element
*/
public Path getPath() {
return this.path;
}
/**
* Returns the generic type representing the Java class of this path element.
*
* @return the generic type representing the Java class of this path element
*/
public GenericType getGenericType() {
return genericType;
}
/**
* Returns the root element.
*
* @return the root element
*/
public PathNode getRoot() {
if (this.parent == null) {
return this;
} else {
return this.parent.getRoot();
}
}
/**
* Returns the generic type of the child represented by the given property info.
*
* @param childPropertyInfo
* the property info of the child
* @return the generic type of the child represented by the given property info
*/
private GenericType getGenericTypeOfChild(PropertyInfo childPropertyInfo) {
if (childPropertyInfo.getMember() instanceof Field) {
Field f = (Field) childPropertyInfo.getMember();
GenericType result = genericType.getFieldType(f.getName());
return result;
} else if (childPropertyInfo.getMember() instanceof Method) {
Method m = (Method) childPropertyInfo.getMember();
GenericType result = genericType.getMethodReturnType(m.getName());
return result;
} else {
throw new Error("Unexpected member type: " + childPropertyInfo.getMember().getClass().getName());
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((path == null) ? 0 : path.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PathNode other = (PathNode) obj;
if (path == null) {
if (other.path != null)
return false;
} else if (!path.equals(other.path))
return false;
return true;
}
}