/*
* #!
* Ontopia Engine
* #-
* Copyright (C) 2001 - 2013 The Ontopia Project
* #-
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* !#
*/
package net.ontopia.topicmaps.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import net.ontopia.infoset.core.LocatorIF;
import net.ontopia.topicmaps.core.NameIF;
import net.ontopia.topicmaps.core.TopicIF;
import net.ontopia.topicmaps.core.TopicMapIF;
import net.ontopia.topicmaps.core.TopicNameIF;
import net.ontopia.topicmaps.core.VariantNameIF;
import net.ontopia.utils.GrabberIF;
/**
* INTERNAL: Grabber that grabs the most suitable name from a topic,
* measured by whether it contains a particular theme in its scope.
* If the topic has a variant name with the given theme the belonging
* basename will be chosen. If not, the base name in the least
* constrained scope will be chosen.
*
* @since 1.1
*/
public class NameGrabber implements GrabberIF<TopicIF, NameIF> {
/**
* PROTECTED: The subject indicator of the theme used to decide
* suitability.
*/
protected LocatorIF themeIndicator;
protected boolean indicatorVariant;
/**
* INTERNAL: Alternatively to <code>themeIndicator</code> the
* basename scope (collection of TopicIF objects) to decide
* suitablity can be setup instantly.
* @since 1.1.2
*/
protected Collection<TopicIF> scope;
/**
* INTERNAL: A collection containing topic themes used for specifying
* the variant name scope.
* @since 1.2.1
*/
protected Collection<TopicIF> variantScope;
/**
* INTERNAL: Determine if grab should deliver only the most
* appropiate basename even if there is a more apropiate variant.
* Default value is true.
* @since 1.2.2
*/
protected boolean grabOnlyTopicName = true;
/**
* INTERNAL: Creates the grabber and sets the comparator to be a
* ScopedIFComparator using the least constrained scope. Grabs
* names that have the topic defined by the given PSI locator
* in their scope. By default the theme is used in the variant
* scope.
*/
public NameGrabber(LocatorIF themeIndicator) {
this(themeIndicator, true);
}
/**
* PUBLIC: Creates the grabber and sets the comparator to be a
* ScopedIFComparator using the least constrained scope. Grabs
* names that have the topic defined by the given PSI locator
* in their scope.
*
* @param variant Whether to use the indicator to set the variant
* scope (if false it is used for the base name scope)
*
* @since 2.0
*/
public NameGrabber(LocatorIF themeIndicator, boolean variant) {
this.themeIndicator = themeIndicator;
this.indicatorVariant = variant;
this.scope = Collections.emptySet();
this.variantScope = Collections.emptySet();
}
/**
* INTERNAL: Creates the grabber and sets the comparator to be a
* ScopedIFComparator using the specified scope. Grabs
* names that have most in common with specified scope.
*
* @since 1.1.2
*/
public NameGrabber(Collection<TopicIF> scope) {
this(scope, new HashSet<TopicIF>());
}
/**
* INTERNAL: Creates the grabber and sets the comparator to be a
* ScopedIFComparator using the specified scope. Grabs
* base names that have most in common with specified scope.
*
* @since 1.2.1
*/
public NameGrabber(Collection<TopicIF> basenameScope, Collection<TopicIF> variantScope) {
this(basenameScope, variantScope, true);
}
/**
* INTERNAL: Creates the grabber and sets the comparator to be a
* ScopedIFComparator using the specified scope. Grabs names
* (basename or variant, use <code>grabOnlyTopicName</code> to
* specify the policy) that have most in common with specified
* scope.
*
* @since 1.2.2
*/
public NameGrabber(Collection<TopicIF> basenameScope, Collection<TopicIF> variantScope,
boolean grabOnlyTopicName) {
this.themeIndicator = null;
this.scope = basenameScope;
this.variantScope = variantScope;
this.grabOnlyTopicName = grabOnlyTopicName;
}
/**
* INTERNAL: Sets the grab policy, if only instances of BasenameIF
* should be returned by grab, or also the more appropiate
* VariantIF.
*
* @since 1.2.2
*/
public void setGrabOnlyTopicName(boolean grabOnlyTopicName) {
this.grabOnlyTopicName = grabOnlyTopicName;
}
/**
* INTERNAL: Gets the grab policy.
* @see #setGrabOnlyTopicName(boolean)
* @since 1.2.2
*/
public boolean getGrabOnlyTopicName() {
return grabOnlyTopicName;
}
/**
* INTERNAL: Grabs the most appropiate base name (or if
* <code>grabOnlyTopicName</code> is false allow also to return the
* most appropiate VariantIF instance). The name returned is the
* first suitable base name belonging to a variant name found, when
* the basenames of the give topic have been sorted using the
* comparator. If there is no suitable base name belonging to a
* variant name, then the last base name found is returned,
* corresponding to the least constrained scope.
*
* @param object The topic whose name is being grabbed; formally an object.
* @return A name to display; an object implementing TopicNameIF
* (or VariantNameIF, see above) or null if the topic has no
* basenames.
* @exception throws OntopiaRuntimeException if object is not a topic.
*/
public NameIF grab(TopicIF topic) {
if (topic == null)
return null;
List<TopicNameIF> basenames = new ArrayList<TopicNameIF>(topic.getTopicNames());
if (basenames.isEmpty())
return null;
// If subject indicator of theme is set use this to setup scope
if (themeIndicator != null) {
// Get theme
TopicMapIF tm = topic.getTopicMap();
// Test if tm is null before getting theme. tm may be null if topic was
// created (for internal purposes) in situations where no topic map was
// available.
if (tm != null) {
TopicIF theme = tm.getTopicBySubjectIdentifier(themeIndicator);
if (theme != null) {
if (indicatorVariant)
variantScope = Collections.singleton(theme);
else
scope = Collections.singleton(theme);
}
}
}
// sort the base names
Collections.sort(basenames, new TopicNameComparator(scope));
// TODO: Do we really have to create this grabber over and over again?
VariantNameGrabber vngrabber = new VariantNameGrabber(variantScope);
NameIF name = null;
VariantNameIF vn = null;
for (TopicNameIF current : basenames) {
if (name == null)
name = current;
if (!variantScope.isEmpty()) {
vn = vngrabber.grab(current);
if (vn != null) {
// TODO: Should not use intersection to find appropriate
// variant, but rather exact matching, or perhaps ranking.
// if there exists some overlap between variant name themes
// and specified scope then we are delivering this variant
Collection<TopicIF> interSection = new HashSet<TopicIF>(vn.getScope());
interSection.retainAll(variantScope);
if (!interSection.isEmpty())
break;
else
vn = null;
}
}
} // while
// if valid variant name get base name which belongs to this
if (vn != null) {
if (grabOnlyTopicName)
name = vn.getTopicName();
else
name = vn;
}
return name;
}
}