// $HeadURL$ // $Id$ // // Copyright © 2006, 2010, 2011, 2012 by the President and Fellows of Harvard College. // // Screensaver is an open-source project developed by the ICCB-L and NSRB labs // at Harvard Medical School. This software is distributed under the terms of // the GNU General Public License. package edu.harvard.med.screensaver.model.meta; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import org.apache.log4j.Logger; import edu.harvard.med.screensaver.model.Entity; import edu.harvard.med.screensaver.util.CollectionUtils; import edu.harvard.med.screensaver.util.ParallelIterator; import edu.harvard.med.screensaver.util.StringUtils; /** * Defines the path of relationships between a root entity and a related entity * <i>instance<i>. Given a root entity instance, the RelationshipPath can be * used to resolve the related entity instance. * <p> * Since some relationships in the path may be "to-many", a single entity on the * "many" side of the relationship must be selected by restricting the * relationship collection to only one of its elements. To this end, * RelationshipPath supports adding a "restriction" predicate to each of its * "to-many" path node. (Currently, the only type of predicate supported is * entity ID equality.) * * @author <a mailto="andrew_tolopko@hms.harvard.edu">Andrew Tolopko</a> * @author <a mailto="john_sullivan@hms.harvard.edu">John Sullivan</a> */ public class RelationshipPath<E extends Entity/* ,L */> { private static Logger log = Logger.getLogger(RelationshipPath.class); private static Pattern collectionIndexPattern = Pattern.compile("(.+)\\[(.+)\\]"); private static final Cardinality DEFAULT_CARDINALITY = Cardinality.TO_MANY; private static final String ID_PROPERTY = "id"; private Class<E> _rootEntityClass; protected List<String> _path = Lists.newArrayList(); protected List<Class<? extends Entity>> _entityClasses = Lists.newArrayList(); protected List<String> _inversePath = Lists.newArrayList(); protected List<PropertyNameAndValue> _restrictions = Lists.newArrayList(); protected List<Cardinality> _cardinality = Lists.newArrayList(); protected Integer _hashCode; protected String _asFormattedPath; protected String _asString; public static <E extends Entity> RelationshipPath<E> from(Class<E> entityClass) { return new RelationshipPath<E>(entityClass); } RelationshipPath(Class<E> rootEntityClass) { _rootEntityClass = rootEntityClass; } public RelationshipPath<E> restrict(String restrictionPropertyName, Object restrictionValue) { List<PropertyNameAndValue> newRestrictions = Lists.newArrayList(_restrictions); newRestrictions.set(newRestrictions.size() - 1, new PropertyNameAndValue(restrictionPropertyName, restrictionValue)); return new RelationshipPath<E>(_rootEntityClass, _entityClasses, _path, _inversePath, newRestrictions, _cardinality); } public RelationshipPath<E> to(String relatedEntityName) { return to(relatedEntityName, DEFAULT_CARDINALITY); } public RelationshipPath<E> to(String relatedEntityName, Cardinality cardinality) { return to(relatedEntityName, null, null, cardinality); } public RelationshipPath<E> to(String relatedEntityName, Class<? extends Entity> relatedEntityClass, String inverseEntityName, Cardinality cardinality) { List<Class<? extends Entity>> newEntityClasses = Lists.newArrayList(_entityClasses); newEntityClasses.add(relatedEntityClass); List<String> newPath = Lists.newArrayList(_path); newPath.add(relatedEntityName); List<String> newInversePath = Lists.newArrayList(_inversePath); newInversePath.add(inverseEntityName); List<PropertyNameAndValue> newRestrictions = Lists.newArrayList(_restrictions); newRestrictions.add(null); List<Cardinality> newCardinality = Lists.newArrayList(_cardinality); newCardinality.add(cardinality); return new RelationshipPath<E>(_rootEntityClass, newEntityClasses, newPath, newInversePath, newRestrictions, newCardinality); } public RelationshipPath<E> to(RelationshipPath<? extends Entity> relationship) { assert !!!(this instanceof PropertyPath); List<Class<? extends Entity>> newEntityClasses = Lists.newArrayList(Iterables.concat(_entityClasses, relationship._entityClasses)); List<String> newPath = Lists.newArrayList(Iterables.concat(_path, relationship._path)); List<String> newInversePath = Lists.newArrayList(Iterables.concat(_inversePath, relationship._inversePath)); List<PropertyNameAndValue> newRestrictions = Lists.newArrayList(Iterables.concat(_restrictions, relationship._restrictions)); List<Cardinality> newCardinality = Lists.newArrayList(Iterables.concat(_cardinality, relationship._cardinality)); return new RelationshipPath<E>(_rootEntityClass, newEntityClasses, newPath, newInversePath, newRestrictions, newCardinality); } public PropertyPath<E> to(PropertyPath<? extends Entity> path) { RelationshipPath<E> relPath = to(path.getAncestryPath()); if (path.isCollectionOfValues()) { return relPath.toCollectionOfValues(path.getPropertyName()); } return relPath.toProperty(path.getPropertyName()); } public PropertyPath<E> toProperty(String propertyName) { return new PropertyPath<E>(this, propertyName, false); } public PropertyPath<E> toId() { return toProperty(ID_PROPERTY); } public PropertyPath<E> toCollectionOfValues(String collectionName) { return new PropertyPath<E>(this, collectionName, true); } public PropertyPath<E> toFullEntity() { return toProperty(PropertyPath.FULL_ENTITY); } protected RelationshipPath(Class<E> rootEntityClass, List<Class<? extends Entity>> entityClasses, List<String> path, List<String> inversePath, List<PropertyNameAndValue> restrictions, List<Cardinality> cardinality) { _rootEntityClass = rootEntityClass; _entityClasses.addAll(entityClasses); _path.addAll(path); _inversePath.addAll(inversePath); _restrictions.addAll(restrictions); _cardinality.addAll(cardinality); } public Class<E> getRootEntityClass() { return _rootEntityClass; } public int getPathLength() { return _path.size(); } /** * @return the path as a String of dot-separated concatenation of the path * nodes. Does not contain any restriction information, as does * {@link #toString()}. * @deprecated code that needs a string representation of a RelationshipPath * should be updated to use RelationshipPath directly to inspect * the nodes comprising the path */ @Deprecated public String getPath() { if (_asFormattedPath == null) { _asFormattedPath = StringUtils.makeListString(_path, "."); } return _asFormattedPath; } public Iterator<String> pathIterator() { return Iterators.unmodifiableIterator(_path.iterator()); } public Iterator<String> inversePathIterator() { return Iterators.unmodifiableIterator(_inversePath.iterator()); } public Iterator<Class<? extends Entity>> entityClassIterator() { return Iterators.unmodifiableIterator(_entityClasses.iterator()); } public RelationshipPath<E> getAncestryPath() { if (_path.size() == 0) { return null; } int i = _path.size() - 1; return new RelationshipPath<E>(_rootEntityClass, _entityClasses, _path.subList(0, i), _inversePath.subList(0, i), _restrictions.subList(0, i), _cardinality.subList(0, i)); } public RelationshipPath<E> getUnrestrictedPath() { List<PropertyNameAndValue> newRestrictions = Lists.newArrayList(); CollectionUtils.fill(newRestrictions, null, _restrictions.size()); return new RelationshipPath<E>(_rootEntityClass, _entityClasses, _path, _inversePath, newRestrictions, _cardinality); } public String getLeaf() { if (_path.size() > 0) { return _path.get(_path.size() - 1); } return ""; } public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof RelationshipPath) { RelationshipPath other = (RelationshipPath) obj; return hashCode() == other.hashCode(); } return false; } @Override public int hashCode() { if (_hashCode == null) { _hashCode = toString().hashCode(); } return _hashCode; } public String toString() { if (_asString == null) { StringBuilder s = new StringBuilder(); s.append('<').append(_rootEntityClass.getSimpleName()).append('>'); ParallelIterator<String,PropertyNameAndValue> iter = new ParallelIterator<String,PropertyNameAndValue>(_path.iterator(), _restrictions.iterator()); while (iter.hasNext()) { iter.next(); appendPathElement(s, iter.getFirst(), iter.getSecond()); } _asString = s.toString(); } return _asString; } public boolean hasRestrictions() { return Iterables.any(_restrictions, Predicates.notNull()); } public Iterator<PropertyNameAndValue> restrictionIterator() { return Iterators.unmodifiableIterator(_restrictions.iterator()); } public PropertyNameAndValue getLeafRestriction() { if (_path.size() > 0) { return _restrictions.get(_path.size() - 1); } return null; } public Cardinality getCardinality() { ParallelIterator<Cardinality,PropertyNameAndValue> iterator = new ParallelIterator<Cardinality,PropertyNameAndValue>(_cardinality.iterator(), _restrictions.iterator()); while (iterator.hasNext()) { iterator.next(); if (iterator.getFirst() == Cardinality.TO_MANY && iterator.getSecond() == null) { return Cardinality.TO_MANY; } } return Cardinality.TO_ONE; } private void appendPathElement(StringBuilder s, String pathElementName, PropertyNameAndValue propertyNameAndValue) { s.append('.').append(pathElementName); if (propertyNameAndValue != null) { s.append('[') .append(propertyNameAndValue.getName()) .append('=') .append(propertyNameAndValue.getValue()) .append(']'); } } /** * Returns a new RelationshpPath where the root entity class is a subtype of this RelationshipPath's root entity * class. * * @motivation RelationshipPaths that are defined on supertype in an entity class inheritance hierarchy cannot * be passed into GenericEntityDAO methods that take RelationshipPaths of a subtype. Rather than * redundantly defining the same static RelationshipPath on both the supertype and subtype, we define it * only on the supertype, and rely upon this method to create the subtype-specific version when needed. */ public <T extends E> RelationshipPath<T> castToSubtype(Class<T> subType) { if (subType.equals(_rootEntityClass)) { return (RelationshipPath<T>) this; } return new RelationshipPath<T>(subType, _entityClasses, _path, _inversePath, _restrictions, _cardinality); } /** * Returns a new RelationshpPath where the root entity class is a supertype of this RelationshipPath's root entity * class. * * @motivation needed for EntitySearchResults for an entity type that has subtypes, and some of the search result's * columns are specific to entity subtypes. */ public <T extends Entity> RelationshipPath<T> castToSupertype(Class<T> superType) { if (!superType.isAssignableFrom(_rootEntityClass)) { throw new IllegalArgumentException(superType + " is not a supertype of " + _rootEntityClass); } if (superType.equals(_rootEntityClass)) { return (RelationshipPath<T>) this; } return new RelationshipPath<T>(superType, _entityClasses, _path, _inversePath, _restrictions, _cardinality); } }