// $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);
}
}