/*
* Copyright (C) 2014 University of Dundee & Open Microscopy Environment.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package omero.cmd.graphs;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;
import ome.model.IObject;
import ome.services.graphs.GraphPathBean;
/**
* Child options adjust how child objects are treated according to their type and, if annotations, namespace,
* overriding the default graph traversal policy for orphans.
* @author m.t.b.carroll@dundee.ac.uk
* @since 5.1.0
*/
public class ChildOptionI extends ChildOption {
private final GraphPathBean graphPathBean;
private final ImmutableSet<String> defaultExcludeNs;
private Function<Class<? extends IObject>, Boolean> isIncludeType = null;
private Predicate<String> isTargetNamespace = null;
/**
* Construct a new child option instance.
* @param graphPathBean the graph path bean
* @param defaultExcludeNs annotation namespaces to exclude by default
*/
public ChildOptionI(GraphPathBean graphPathBean, ImmutableSet<String> defaultExcludeNs) {
this.graphPathBean = graphPathBean;
this.defaultExcludeNs = defaultExcludeNs;
}
/**
* Construct a new child option instance identical to that given. If the original was initialized, this one is too.
* @param original a child option instance
*/
public ChildOptionI(ChildOptionI original) {
graphPathBean = original.graphPathBean;
defaultExcludeNs = original.defaultExcludeNs;
includeType = original.includeType == null ? null : new ArrayList<String>(original.includeType);
excludeType = original.excludeType == null ? null : new ArrayList<String>(original.excludeType);
includeNs = original.includeNs == null ? null : new ArrayList<String>(original.includeNs);
excludeNs = original.excludeNs == null ? null : new ArrayList<String>(original.excludeNs);
isIncludeType = original.isIncludeType;
isTargetNamespace = original.isTargetNamespace;
}
/**
* Initialize this child option instance.
* An option takes effect according to the {@link ChildOption} field values set when this method was last called.
*/
public void init() {
/* convert the class names to actual classes */
final Function<String, Class<? extends IObject>> getClassFromName = new Function<String, Class<? extends IObject>>() {
@Override
public Class<? extends IObject> apply(String className) {
final int lastDot = className.lastIndexOf('.');
if (lastDot > 0) {
className = className.substring(lastDot + 1);
}
return graphPathBean.getClassForSimpleName(className);
}
};
/* construct the function corresponding to the type-based inclusion requirements */
final ImmutableSet<Class<? extends IObject>> typeInclusions;
final ImmutableSet<Class<? extends IObject>> typeExclusions;
if (CollectionUtils.isEmpty(includeType)) {
typeInclusions = ImmutableSet.of();
} else {
typeInclusions = ImmutableSet.copyOf(Collections2.transform(includeType, getClassFromName));
}
if (CollectionUtils.isEmpty(excludeType)) {
typeExclusions = ImmutableSet.of();
} else {
typeExclusions = ImmutableSet.copyOf(Collections2.transform(excludeType, getClassFromName));
}
if (typeInclusions.isEmpty() && typeExclusions.isEmpty()) {
throw new IllegalArgumentException("child option must include or exclude some type");
}
isIncludeType = new Function<Class<? extends IObject>, Boolean>() {
@Override
public Boolean apply(Class<? extends IObject> objectClass) {
for (final Class<? extends IObject> typeInclusion : typeInclusions) {
if (typeInclusion.isAssignableFrom(objectClass)) {
return Boolean.TRUE;
}
}
for (final Class<? extends IObject> typeExclusion : typeExclusions) {
if (typeExclusion.isAssignableFrom(objectClass)) {
return Boolean.FALSE;
}
}
return null;
}
};
/* if no annotation namespaces are set, then apply the defaults */
if (CollectionUtils.isEmpty(includeNs) && CollectionUtils.isEmpty(excludeNs)) {
excludeNs = new ArrayList<String>(defaultExcludeNs);
}
/* construct the predicate corresponding to the namespace restriction */
if (CollectionUtils.isEmpty(includeNs)) {
if (CollectionUtils.isEmpty(excludeNs)) {
/* there is no adjustment to make, not even for any default namespaces */
isTargetNamespace = Predicates.alwaysTrue();
} else {
final ImmutableSet<String> nsExclusions = ImmutableSet.copyOf(excludeNs);
isTargetNamespace = new Predicate<String>() {
@Override
public boolean apply(String namespace) {
return !nsExclusions.contains(namespace);
}
};
}
} else {
if (CollectionUtils.isEmpty(excludeNs)) {
final ImmutableSet<String> nsInclusions = ImmutableSet.copyOf(includeNs);
isTargetNamespace = new Predicate<String>() {
@Override
public boolean apply(String namespace) {
return nsInclusions.contains(namespace);
}
};
} else {
throw new IllegalArgumentException("child option may not both include and exclude namespace");
}
}
}
/**
* Test if this child option adjusts graph traversal policy for the given child object class.
* Requires {@link #init()} to have been called previously.
* @param objectClass a child object class
* @return {@code true} if such children should be included in the operation,
* {@code false} if such children should not be included in the operation, or
* {@code null} if this child option does not affect the treatment of such children
*/
public Boolean isIncludeType(Class<? extends IObject> objectClass) {
return isIncludeType.apply(objectClass);
}
/**
* Test if this child option adjusts graph traversal policy for child objects that are annotations in the given namespace.
* Requires {@link #init()} to have been called previously.
* @param namespace an annotation namespace
* @return if child objects that are annotations in this namespace are affected by this child option
*/
public boolean isTargetNamespace(String namespace) {
return isTargetNamespace.apply(namespace);
}
/**
* Cast {@code ChildOption[]} to {@code ChildOptionI[]}.
* @param childOptions an array of {@code ChildOption} which may all be casted to {@code ChildOptionI}, may be {@code null}
* @return an array of {@code ChildOptionI}, may be {@code null}
*/
public static List<ChildOptionI> castChildOptions(Collection<ChildOption> childOptions) {
if (childOptions == null) {
return null;
} else {
final List<ChildOptionI> childOptionsI = new ArrayList<ChildOptionI>(childOptions.size());
for (final ChildOption childOption : childOptions) {
childOptionsI.add((ChildOptionI) childOption);
}
return childOptionsI;
}
}
}