/*
* Copyright (C) 2015 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.HashMap;
import java.util.Map;
/**
* A classifier that assigns an entity to the classification listed for the most specific group of which the entity is a member.
* Consider entities themselves to be singleton groups.
* Implemented more for obvious correctness than for performance on large data.
* @author m.t.b.carroll@dundee.ac.uk
* @since 5.2.1
* @param <G> the type of the groups of which entities may be members, and also of the entities themselves
* @param <C> the classes to which entities may be assigned
*/
class SpecificityClassifier<G, C> {
/**
* Tests group membership. The membership relation must be irreflexive, asymmetric, and transitive.
* @author m.t.b.carroll@dundee.ac.uk
* @since 5.2.1
* @param <G> the type of the groups of which entities may be members, and also of the entities themselves
*/
interface ContainmentTester<G> {
/**
* Report if one group is a proper superset of the other.
* @param parent a group that may contain another
* @param child the other group
* @return if the first group contains the other without being the same as it
*/
boolean isProperSupersetOf(G parent, G child);
}
private final ContainmentTester<G> tester;
private final Map<G, C> classOfGroup = new HashMap<G, C>();
/**
* Create a classifier that uses the given definition of group membership.
* @param tester the group membership tester
*/
SpecificityClassifier(ContainmentTester<G> tester) {
this.tester = tester;
}
/**
* Assert that specific groups are of the given classification.
* @param classification a classification
* @param groupsInClass the groups that are of this classification
* @throws IllegalArgumentException if any of the groups are already of a different classification
*/
void addClass(C classification, Iterable<G> groupsInClass) throws IllegalArgumentException {
for (final G group : groupsInClass) {
final C previousClass = classOfGroup.put(group, classification);
if (!(previousClass == null || previousClass.equals(classification))) {
throw new IllegalArgumentException("cannot classify " + group + " as " + classification +
" because it is also " + previousClass);
}
}
}
/**
* Classify the given group.
* @param group a group
* @return its classification, or {@code null} if it could not be classified
*/
C getClass(G group) {
final C directLookup = classOfGroup.get(group);
if (directLookup != null) {
return directLookup;
}
Map.Entry<G, C> bestGroupClassification = null;
for (final Map.Entry<G, C> currentGroupClassification : classOfGroup.entrySet()) {
if (tester.isProperSupersetOf(currentGroupClassification.getKey(), group) &&
(bestGroupClassification == null ||
tester.isProperSupersetOf(bestGroupClassification.getKey(), currentGroupClassification.getKey()))) {
bestGroupClassification = currentGroupClassification;
}
}
return bestGroupClassification == null ? null : bestGroupClassification.getValue();
}
}