package org.geotools.data.efeature;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
public abstract class EObjectFilter {
/**
* An ID indicating that no feature ID information is applicable.
*/
public final static int NO_FEATURE_ID = -1;
public final static EObjectFilter INCLUSIVE = new EObjectFilter() {
@Override
public boolean matches(Object object) {
return true;
}
};
public final static EObjectFilter EXCLUSIVE = new EObjectFilter() {
@Override
public boolean matches(Object object) {
return false;
}
};
public abstract boolean matches(Object object);
/**
* Creates a new filter combining me with another as a boolean conjunction. The "and" operation
* short-circuits; the <code>other</code> filter is not consulted when I (the first filter) do
* not match.
*
* @param other another filter (must not be <code>null</code>)
*
* @return a new "and" filter
*/
public final EObjectFilter and(final EObjectFilter other) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
return EObjectFilter.this.matches(object) && other.matches(object);
}
};
}
/**
* Creates a new filter combining me with another as a boolean disjunction. The "or" operation
* short-circuits; the <code>other</code> filter is not consulted when I (the first filter)
* match.
*
* @param other another filter (must not be <code>null</code>)
*
* @return a new "or" filter
*/
public final EObjectFilter or(final EObjectFilter other) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
return EObjectFilter.this.matches(object) || other.matches(object);
}
};
}
/**
* Creates a new filter that is the boolean negation of me.
*
* @return the opposite of me
*/
public final EObjectFilter negated() {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
return !EObjectFilter.this.matches(object);
}
};
}
/**
* Creates a filter matching any {@link EObject} with given id.
*
* @param id - a {@link EObject} instance id
*
* @return the filter
* @see {@link EcoreUtil#getID(EObject)}
*/
public static EObjectFilter createObjectIDFilter(final String id) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
return (object instanceof EObject ? id.equals(EcoreUtil.getID((EObject) object))
: false);
}
};
}
/**
* Creates a filter matching any specified object.
*
* @param object - a object instance
*
* @return the filter
*
* @see {@link EcoreUtil#equals(EObject, EObject)}
*/
public static EObjectFilter createObjectFilter(final EObject eObject) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
return (object instanceof EObject ? EcoreUtil.equals((EObject) object, eObject)
: false);
}
};
}
/**
* Creates a filter matching specified objects.
*
* @param all - if <code>true</code>, all objects must match
* @param negated - if <code>true</code>, concatenation is negated
* @param objects - object instances
*
* @return the filter
*/
public static EObjectFilter createObjectFilter(final boolean all, final boolean negated,
final EObject... eObjects) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
if (object instanceof EObject) {
int count = 0;
EObject match = (EObject) object;
for (EObject it : eObjects) {
if (EcoreUtil.equals(match, it)) {
count++;
if (!all)
break;
} else if (all) {
count = 0;
break;
}
}
return (all ? count > 0 : count == 1);
}
return false;
}
};
}
/**
* Creates a filter matching any specified feature.
*
* @param feature a structural feature meta-object
*
* @return the filter
*/
public static EObjectFilter createFeatureFilter(final EStructuralFeature feature) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
return object == feature;
}
};
}
/**
* Creates a filter matching specified features.
*
* @param all - if <code>true</code>, all features must match
* @param negated - if <code>true</code>, concatenation is negated
* @param features - structural features
*
* @return the filter
*/
public static EObjectFilter createFeatureFilter(final boolean all, final boolean negated,
final EStructuralFeature... features) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
if (object instanceof EStructuralFeature) {
int count = 0;
EStructuralFeature match = (EStructuralFeature) object;
for (EObject it : features) {
if (EcoreUtil.equals(match, it)) {
count++;
if (!all)
break;
} else if (all) {
count = 0;
break;
}
}
return (all ? count > 0 : count == 1);
}
return false;
}
};
}
/**
* Creates a filter matching the specified feature.
* <p>
* <strong>NOTE</strong>: This filter only work on {@link EObject}s that implement
* {@link EStructuralFeature}
* <p>
*
* @param ownerType the object type as a Java class or interface
* @param featureId the feature's numeric ID
*
* @return the filter
*/
public static EObjectFilter createFeatureFilter(final Class<?> ownerType, final int featureId) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
return (object instanceof EObject) && ownerType.isInstance(object)
&& (getFeatureID(ownerType, (EObject) object) == featureId);
}
};
}
public static int getFeatureID(Class<?> expectedClass, EObject eObject) {
if (eObject instanceof EStructuralFeature) {
EStructuralFeature feature = (EStructuralFeature) eObject;
return ((InternalEObject) eObject).eDerivedStructuralFeatureID(feature.getFeatureID(),
feature.getContainerClass());
}
return EObjectFilter.NO_FEATURE_ID;
}
/**
* Creates a filter matching any instance of the specified type. This variant is useful for
* notifiers that are not modeled via Ecore.
*
* @param type the object type as a Java class or interface
*
* @return the filter
*/
public static EObjectFilter createTypeFilter(final Class<?> type) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
return type.isInstance(object);
}
};
}
/**
* Creates a filter matching any instance of the specified type. This variant is useful for
* notifiers that are not modeled via Ecore.
*
* @param all - if <code>true</code>, all objects must match type
* @param negated - if <code>true</code>, concatenation is negated
* @param types - the object types as a Java class or interface
*
* @return the filter
*/
public static EObjectFilter createTypeFilter(final boolean all, final boolean negated,
final Class<?>... types) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
int count = 0;
for (Class<?> it : types) {
if (it.isInstance(object)) {
count++;
if (!all)
break;
} else if (all) {
count = 0;
break;
}
}
return (all ? count > 0 : count == 1);
}
};
}
/**
* Creates a filter matching any instance of the specified {@link EClassifier} type instance.
* This variant is useful for notifiers that are modelled via Ecore.
*
* @param type the {@link EClassifier} type
*
* @return the filter
*/
public static EObjectFilter createClassifierFilter(final EClassifier type) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
return type.isInstance(object);
}
};
}
/**
* Creates a filter matching any instance of the specified {@link EClassifier} type instance.
* This variant is useful for notifiers that are modelled via Ecore.
*
* @param all - if <code>true</code>, all objects must match given classifier
* @param negated - if <code>true</code>, concatenation is negated
* @param types the {@link EClassifier} types
*
* @return the filter
*/
public static EObjectFilter createClassifierFilter(final boolean all, final boolean negated,
final EClassifier... types) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
if (object instanceof EObject) {
int count = 0;
for (EClassifier it : types) {
if (it.isInstance(object)) {
count++;
if (!all)
break;
} else if (all) {
count = 0;
break;
}
}
return (all ? count > 0 : count == 1);
}
return false;
}
};
}
/**
* Creates a filter matching given instance of the specified {@link EClassifier} type instance
* having given data type instance This variant is useful for notifiers that are modelled via
* Ecore.
*
* @param type the {@link EClassifier} type
*
* @return the filter
*/
public static EObjectFilter createDataTypeFilter(final Class<? extends EDataType> cls,
final Class<? extends Serializable> type, final boolean equals) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
if (object instanceof EStructuralFeature) {
final EClassifier eType = ((EStructuralFeature) object).getEType();
if (cls.isInstance(eType)) {
Class<?> instClass = eType.getInstanceClass();
return (equals ? instClass == type : instClass.isAssignableFrom(type));
}
}
return false;
}
};
}
/**
* Creates a filter matching contents of given object.
* <p>
*
* @param eGeometryType the filter to use on containers
* @param all - if <code>true</code>, all contents must be matched by the filter
* @param tree - if <code>true</code>, {@link EObject#eAllContents()} is used instead of
* {@link EObject#eContents()}
*
* @return the filter
*
*/
public static EObjectFilter createContainerFilter(final EObjectFilter filter) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
if (object instanceof EObject) {
EObject eObj = (EObject) object;
return filter.matches(eObj.eContainer());
}
return false;
}
};
}
/**
* Creates a filter matching contents of given object.
* <p>
*
* @param filter - the ancestor filter
* @param all - if <code>true</code>, all contents must be matched by the filter
* @param tree - if <code>true</code>, {@link EObject#eAllContents()} is used instead of
* {@link EObject#eContents()}
*
* @return the filter
*
*/
public static EObjectFilter createAncestorFilter(final EObjectFilter filter) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
if (object instanceof EObject) {
EObject eObj = (EObject) object;
EObject eAncestor = null;
while (eObj != null) {
if (filter.matches(eObj)) {
return true;
}
eAncestor = eObj.eContainer();
// Reached break condition?
//
eObj = (eObj == eAncestor ? null : eAncestor);
}
}
return false;
}
};
}
/**
* Creates a filter matching contents of given object.
* <p>
*
* @param eGeometryType the filter to use on contents
* @param all - if <code>true</code>, all contents must be matched by the filter
* @param tree - if <code>true</code>, {@link EObject#eAllContents()} is used instead of
* {@link EObject#eContents()}
*
* @return the filter
*
*/
public static EObjectFilter createContentFilter(final EObjectFilter filter, final boolean all,
final boolean tree) {
return new EObjectFilter() {
@Override
public boolean matches(Object object) {
if (object instanceof EObject) {
int count = 0;
EObject eObj = (EObject) object;
if (all) {
TreeIterator<EObject> contents = eObj.eAllContents();
while (contents.hasNext()) {
if (filter.matches(contents.next())) {
count++;
if (!all)
break;
} else if (all) {
count = 0;
break;
}
}
} else {
EList<EObject> contents = eObj.eContents();
for (EObject it : contents) {
if (filter.matches(it)) {
count++;
if (!all)
break;
} else if (all) {
count = 0;
break;
}
}
}
return (all ? count > 0 : count == 1);
}
return false;
}
};
}
/**
* Creates a filter composite.
* <p>
*
* @param all - if <code>true</code>, all filters must match
* @param negated - if <code>true</code>, concatenation is negated
* @param filters - filters to concatenate
*
* @return the filter
*
*/
public static EObjectFilter createCompositeFilter(final boolean all, final boolean negated,
final EObjectFilter... filters) {
EObjectFilter filter = null;
for (EObjectFilter it : filters) {
if (filter == null) {
filter = it;
} else {
filter = (all ? filter.and(it) : filter.or(it));
}
}
return (negated ? filter.negated() : filter);
}
/**
* Utility method for conditional object selection
*
* @param <T> - object type
* @param items - items to select from
* @param filter - filter used to match items
* @return a list of selected items
*/
public static <T> Collection<T> select(Collection<T> items, EObjectFilter filter) {
List<T> selected = new ArrayList<T>(items.size());
for (T it : items) {
if (filter.matches(it)) {
selected.add(it);
}
}
return selected;
}
/**
* Utility method for conditional object selection
*
* @param <T> - object type
* @param items - items to select from
* @param filter - filter used to match items
* @return first match found, or <code>null</code> if not found
*/
public static <T> T selectHead(Collection<T> items, EObjectFilter filter) {
List<T> selected = new ArrayList<T>(items.size());
for (T it : items) {
if (filter.matches(it)) {
selected.add(it);
}
}
return null;
}
/**
* Utility method for conditional object selection
*
* @param <T> - object type
* @param items - items to select from
* @param filter - filter used to match items
* @return a list of selected items
*/
public static <T> Collection<T> select(TreeIterator<T> items, EObjectFilter filter) {
List<T> selected = new ArrayList<T>();
while (items.hasNext()) {
T it = items.next();
if (filter.matches(it)) {
selected.add(it);
}
}
return selected;
}
}