/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.properties;
import gw.lang.reflect.IType;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.IGosuObject;
import gw.util.GosuClassUtil;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* A node in a tree representation of an underlying {@link PropertySet}. Any compound names, such
* as a.b.c and a.b.d, in the keys of the property set are split into a tree representation. In the
* a.b.c/a.b.d example there would be a property node for a, with a child node b with two further
* leaf children c and d.
*/
public class PropertyNode implements IGosuObject {
private final PropertyNode _parent;
private final String _name;
private final String _path;
private final PropertySet _propertySet;
private final Map<String, PropertyNode> _children = new TreeMap<String, PropertyNode>();
public static PropertyNode buildTree(PropertySet propertySet) {
final PropertyNode root = new PropertyNode(null, "", propertySet);
for (String propertyPath : propertySet.getKeys()) {
PropertyNode currentNode = root;
for (String pathPart : propertyPath.split("\\.")) {
PropertyNode childNode = currentNode.getChild(pathPart);
if (childNode == null) {
if (!isGosuIdentifier(pathPart)) {
break; // don't consider the whole property
}
childNode = new PropertyNode(currentNode, pathPart, propertySet);
currentNode.addChild(childNode);
}
currentNode = childNode;
}
}
root.removeUseless();
return root;
}
private PropertyNode(PropertyNode parent, String name, PropertySet propertySet) {
_name = name;
_parent = parent;
_path = parent != null ? join(parent.getPath(), name) : name;
_propertySet = propertySet;
}
/**
* The full property name, for example a.b
* @return a non null name, which must be one or more valid Gosu identifiers separated by periods
*/
public String getFullName() {
return join(_propertySet.getName(), _path);
}
/**
* The last part of the property name, for example b if the full name is a.b
* @return a non null name, which must be a valid Gosu identifier
*/
public String getRelativeName() {
return GosuClassUtil.getShortClassName(getFullName());
}
/**
* Return the name that should be used for the type based on this property node
* @return a non null type name
*/
public String getTypeName() {
return isRoot() ? getFullName() : getFullName() + "$Type";
}
/**
* Return the intrinsic type based on this property node
* @return intrinsic type
*/
@Override
public IType getIntrinsicType() {
return TypeSystem.getByFullName(getTypeName());
}
/**
* Does this property node have a value in the underlying {@link PropertySet}
* @return true if the node has an underlying value, false otherwise
*/
public boolean hasValue() {
return _propertySet.getKeys().contains(_path);
}
/**
* Return the value for this property as given by the underlying {@link PropertySet}
* @return the property value, or null if it doesn't have one
*/
public String getValue() {
return hasValue() ? _propertySet.getValue(_path) : null;
}
/**
* Is this a leaf node - that is, does it have no children?
* @return true if this node has no children, false otherwise
*/
public boolean isLeaf() {
return _children.isEmpty();
}
/**
* Is this the root of a property node tree?
* @return true if this is the root, false otherwise
*/
public boolean isRoot() {
return _parent == null;
}
/**
* The direct children of this property node
* @return a non null, though possibly empty, list of children
*/
public List<PropertyNode> getChildren() {
return new ArrayList<PropertyNode>(_children.values()); // for API backward compatibility returns list
}
/**
* Return the value for the named child property; this is just like doing lookup on the underlying
* {@link PropertySet} except that the name is prefixed with the full name of this property. For
* example if this property is a then getting the child value b.c will return the value of a.b.c
* in the original property set
* @param name non null name of child property
* @return the child property value, or null if there is no such child property
*/
public String getChildValue(String name) {
return _propertySet.getValue(join(_path, name));
}
/**
* If this node has a property value, returns the value of that property. Otherwise returns
* a string describing the property name.
*/
@Override
public String toString() {
return hasValue() ? getValue() : String.format("Property <%s>", _path);
}
// returns child node if such one with the specified name exists, otherwise <code>null</code>
private PropertyNode getChild(String name) {
return _children.get(name);
}
private void addChild(PropertyNode node) {
_children.put(node.getName(), node);
}
private boolean isUseless() {
return _children.isEmpty() && !hasValue();
}
// removes all useless nodes in the tree represented by this node as a root
private void removeUseless() {
Set<Map.Entry<String, PropertyNode>> entries = _children.entrySet();
for (Iterator<Map.Entry<String, PropertyNode>> it = entries.iterator(); it.hasNext(); ) {
Map.Entry<String, PropertyNode> entry = it.next();
PropertyNode child = entry.getValue();
child.removeUseless();
if (child.isUseless()) {
it.remove();
}
}
}
static boolean isGosuIdentifier(String name) {
boolean result = name.length() > 0 && isGosuIdentifierStart(name.charAt(0));
for (int i = 1; i < name.length() && result; i++) {
if (!isGosuIdentifierPart(name.charAt(i))) {
result = false;
}
}
return result;
}
private static boolean isGosuIdentifierStart(char ch) {
return Character.isJavaIdentifierStart(ch) && ch != '$';
}
private static boolean isGosuIdentifierPart(char ch) {
return Character.isJavaIdentifierPart(ch) && ch != '$';
}
private static String join(String head, String tail) {
if (head.isEmpty()) {
return tail;
} else if (tail.isEmpty()) {
return head;
} else {
return head + "." + tail;
}
}
private String getName() {
return _name;
}
public String getPath() {
return _path;
}
public PropertyNode getParent() {
return _parent;
}
}