package org.opennms.core.criteria;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.opennms.core.criteria.restrictions.Restriction;
public class Criteria {
public static interface CriteriaVisitor {
public void visitClass(final Class<?> clazz);
public void visitOrder(final Order order);
public void visitOrdersFinished();
public void visitAlias(final Alias alias);
public void visitAliasesFinished();
public void visitFetch(final Fetch fetch);
public void visitFetchesFinished();
public void visitRestriction(final Restriction restriction);
public void visitRestrictionsFinished();
public void visitDistinct(final boolean distinct);
public void visitLimit(final Integer limit);
public void visitOffset(final Integer offset);
}
public void visit(final CriteriaVisitor visitor) {
visitor.visitClass(getCriteriaClass());
for (final Order order : getOrders()) {
visitor.visitOrder(order);
}
visitor.visitOrdersFinished();
for (final Alias alias : getAliases()) {
visitor.visitAlias(alias);
}
visitor.visitAliasesFinished();
for (final Fetch fetch : getFetchTypes()) {
visitor.visitFetch(fetch);
}
visitor.visitFetchesFinished();
for (final Restriction restriction : getRestrictions()) {
visitor.visitRestriction(restriction);
}
visitor.visitRestrictionsFinished();
visitor.visitDistinct(isDistinct());
visitor.visitLimit(getLimit());
visitor.visitOffset(getOffset());
}
private static final Pattern SPLIT_ON = Pattern.compile("\\.");
private Class<?> m_class;
private List<Order> m_orders = new ArrayList<Order>();
private List<Alias> m_aliases = new ArrayList<Alias>();
private Set<Fetch> m_fetchTypes = new LinkedHashSet<Fetch>();
private Set<Restriction> m_restrictions = new LinkedHashSet<Restriction>();
private boolean m_distinct = false;
private Integer m_limit = null;
private Integer m_offset = null;
public Criteria(final Class<?> clazz) {
m_class = clazz;
}
public Class<?> getCriteriaClass() {
return m_class;
}
public List<Order> getOrders() {
return Collections.unmodifiableList(m_orders);
}
public void setOrders(final Collection<? extends Order> orderCollection) {
m_orders.clear();
m_orders.addAll(orderCollection);
}
public List<Fetch> getFetchTypes() {
return Collections.unmodifiableList(new ArrayList<Fetch>(m_fetchTypes));
}
public void setFetchTypes(final Collection<? extends Fetch> fetchTypes) {
m_fetchTypes.clear();
m_fetchTypes.addAll(fetchTypes);
}
public List<Alias> getAliases() {
return Collections.unmodifiableList(m_aliases);
}
public void setAliases(final Collection<? extends Alias> aliases) {
m_aliases.clear();
m_aliases.addAll(aliases);
}
public List<Restriction> getRestrictions() {
return Collections.unmodifiableList(new ArrayList<Restriction>(m_restrictions));
}
public void setRestrictions(Collection<? extends Restriction> restrictions) {
m_restrictions.clear();
m_restrictions.addAll(restrictions);
}
public void addRestriction(final Restriction restriction) {
m_restrictions.add(restriction);
}
public boolean isDistinct() {
return m_distinct ;
}
public void setDistinct(final boolean distinct) {
m_distinct = distinct;
}
public Integer getLimit() {
return m_limit;
}
public void setLimit(final Integer limit) {
m_limit = limit;
}
public Integer getOffset() {
return m_offset;
}
public void setOffset(final Integer offset) {
m_offset = offset;
}
public Class<?> getType(final String path) throws IntrospectionException {
return getType(this.getCriteriaClass(), path);
}
private Class<?> getType(final Class<?> clazz, final String path) throws IntrospectionException {
final String[] split = SPLIT_ON.split(path);
final List<String> pathSections = Arrays.asList(split);
return getType(clazz, pathSections, new ArrayList<Alias>(getAliases()));
}
/**
* Given a class, a list of spring-resource-style path sections, and an array of aliases to process,
* return the type of class associated with the resource.
*
* @param clazz The class to process for properties.
* @param pathSections The path sections, eg: node.ipInterfaces
* @param aliases A list of aliases that have not yet been processed yet. We use this to detect whether an alias has already been resolved
* so it doesn't loop. See {@class ConcreteObjectTest#testAliases()} for an example of why this is necessary.
* @return The class type that matches.
* @throws IntrospectionException
*/
private Class<?> getType(final Class<?> clazz, final List<String> pathSections, final List<Alias> aliases) throws IntrospectionException {
if (pathSections.isEmpty()) {
return clazz;
}
final String pathElement = pathSections.get(0);
final List<String> remaining = pathSections.subList(1, pathSections.size());
final Iterator<Alias> aliasIterator = aliases.iterator();
while (aliasIterator.hasNext()) {
final Alias alias = aliasIterator.next();
if (alias.getAlias().equals(alias.getAssociationPath())) {
// in some cases, we will alias eg "node" -> "node", skip if they're identical
continue;
}
if (alias.getAlias().equals(pathElement)) {
aliasIterator.remove();
final String associationPath = alias.getAssociationPath();
// LogUtils.debugf(this, "match: class = %s, pathSections = %s, alias = %s", clazz.getName(), pathSections, alias);
// we have a match, retry with the "real" path
final List<String> paths = new ArrayList<String>();
paths.addAll(Arrays.asList(SPLIT_ON.split(associationPath)));
paths.addAll(remaining);
return getType(clazz, paths, aliases);
}
}
final BeanInfo bi = Introspector.getBeanInfo(clazz);
for (final PropertyDescriptor pd : bi.getPropertyDescriptors()) {
if (pathElement.equals(pd.getName())) {
final Class<?> propertyType = pd.getPropertyType();
if (Collection.class.isAssignableFrom(propertyType)) {
final Type[] t = getGenericReturnType(pd);
if (t != null && t.length == 1) {
return getType((Class<?>)t[0], remaining, aliases);
}
}
return getType(propertyType, remaining, aliases);
}
}
return null;
}
private Type[] getGenericReturnType(final PropertyDescriptor pd) {
final Method m = pd.getReadMethod();
if (m != null) {
final Type returnType = m.getGenericReturnType();
if (returnType != null && returnType instanceof ParameterizedType) {
final ParameterizedType pt = (ParameterizedType)returnType;
return pt.getActualTypeArguments();
}
}
return new Type[0];
}
@Override
public String toString() {
return "Criteria [class=" + m_class + ", orders=" + m_orders
+ ", aliases=" + m_aliases + ", fetchTypes=" + m_fetchTypes
+ ", restrictions=" + m_restrictions + ", distinct="
+ m_distinct + ", limit=" + m_limit + ", offset="
+ m_offset + "]";
}
}