ch.uzh.ifi.attempto.chartparser.Category.java Source code

Java tutorial

Introduction

Here is the source code for ch.uzh.ifi.attempto.chartparser.Category.java

Source

// This file is part of AceWiki.
// Copyright 2008-2012, AceWiki developers.
// 
// AceWiki is free software: you can redistribute it and/or modify it under the terms of the GNU
// Lesser General Public License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// 
// AceWiki 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
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License along with AceWiki. If
// not, see http://www.gnu.org/licenses/.

package ch.uzh.ifi.attempto.chartparser;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang.ArrayUtils;

/**
 * This class represents a grammatical category.
 * 
 * @author Tobias Kuhn
 */
public abstract class Category {

    /**
     * This static array contains all category names that denote special categories. These special
     * category names are ">" and ">>" for forward references, "<" and "/<" for backward references,
     * "//" for scope openers, and "#" for position operators.
     */
    public static final String[] specialCategories = new String[] { ">", ">>", "<", "/<", "//", "#" };

    /**
     * The name of the category.
     */
    protected String name;

    /**
     * The feature map of the category. Backward references do not use this field, since they can
     * have multiple feature maps.
     */
    protected FeatureMap featureMap;

    /**
     * This method returns the type of the category. For example, "term" is returned for terminal
     * categories and "nonterm" for non-terminal ones.
     * 
     * @return The type of the category.
     */
    protected abstract String getType();

    /**
     * Returns the name of the category.
     * 
     * @return The name of the category.
     */
    public String getName() {
        return name;
    }

    /**
     * Returns true if this category is a special category. Special categories are references, scope
     * openers, and position operators.
     * 
     * @return true if this category is a special category.
     */
    public boolean isSpecialCategory() {
        return ArrayUtils.contains(specialCategories, name);
    }

    /**
     * Returns the map of features of this category. It returns null in the case of backward
     * references, where the methods for positive and negative feature maps have to be used.
     * 
     * @return The feature map.
     */
    public FeatureMap getFeatureMap() {
        return featureMap;
    }

    /**
     * This method returns the list of positive feature maps for backward references, or null
     * for all other categories.
     * 
     * @return The list of positive feature maps in the case of backward references.
     */
    public List<FeatureMap> getPosFeatureMaps() {
        return null;
    }

    /**
     * This method returns the list of negative feature maps for backward references, or null
     * for all other categories.
     * 
     * @return The list of negative feature maps in the case of backward references.
     */
    public List<FeatureMap> getNegFeatureMaps() {
        return null;
    }

    /**
     * Adds a positive feature map in the case of backward references, or does nothing for all
     * other categories.
     * 
     * @param fm The positive feature map to be added.
     */
    public void addPosFeatureMap(FeatureMap fm) {
    }

    /**
     * Adds a negative feature map in the case of backward references, or does nothing for all
     * other categories.
     * 
     * @param fm The negative feature map to be added.
     */
    public void addNegFeatureMap(FeatureMap fm) {
    }

    /**
     * Sets a feature. This method cannot be used for backward references, which can have more than
     * one feature map.
     * 
     * @param featureName The feature name
     * @param featureValue The string reference that points to the value of the feature.
     */
    public void setFeature(String featureName, StringRef featureValue) {
        featureMap.setFeature(featureName, featureValue);
    }

    /**
     * Sets a feature. This method cannot be used for backward references, which can have more than
     * one feature map.
     * 
     * @param featureName The feature name
     * @param featureValue The value of the feature.
     */
    public void setFeature(String featureName, String featureValue) {
        featureMap.setFeature(featureName, new StringRef(featureValue));
    }

    /**
     * Returns a feature value. This method cannot be used for backward references, which can have
     * more than one feature map.
     * 
     * @param featureName The name of the feature.
     * @return The value of the feature.
     */
    public StringRef getFeature(String featureName) {
        return featureMap.getFeature(featureName);
    }

    /**
     * Unifies this category with another category. Two categories can unify if and only if they have
     * the same names and the same types and they have no features with conflicting values. If the
     * unification fails, a UnificationFailedException is thrown. In this case, the two categories
     * remain partly unified, i.e. no backtracking is done. Thus, this operation should be perfomed
     * only if it is certain that the unification succeeds, or if the operation is performed on
     * copies of objects that are not used anymore afterwards.
     * 
     * @param c The category to be unified with this category.
     * @throws UnificationFailedException If unification fails.
     */
    public void unify(Category c) throws UnificationFailedException {
        if (!name.equals(c.name) || !getType().equals(c.getType())) {
            throw new UnificationFailedException();
        }
        featureMap.unify(c.featureMap);
    }

    /**
     * Tries to unify this category with another category. If unification is not possible, an exception
     * is thrown. In the case unification would be possible, the unification is not performed completely.
     * In any case the two categories remain in an unconsistent state afterwards. Thus, this operation
     * should be performed only on copies of objects that are not used anymore afterwards.
     * 
     * @param c The category to be unified with this category.
     * @throws UnificationFailedException If unification fails.
     */
    public void tryToUnify(Category c) throws UnificationFailedException {
        if (!name.equals(c.name) || !getType().equals(c.getType())) {
            throw new UnificationFailedException();
        }
        featureMap.tryToUnify(c.featureMap);
    }

    /**
     * This method detects whether this category can unify with the given category. Neither of the two
     * categories are changed.
     * 
     * @param category The category for the unification check.
     * @return true if the two categories can unify.
     */
    public boolean canUnify(Category category) {
        if (!isSimilar(category))
            return false;
        Category thisC = deepCopy();
        Category otherC = category.deepCopy();
        try {
            thisC.tryToUnify(otherC);
        } catch (UnificationFailedException ex) {
            return false;
        }
        return true;
    }

    /**
     * This methods checks whether two categories are similar. Two categories are similar if and
     * only if the categories have the same name and the same type and no feature with the same
     * name is present in both categories with values that do not unify locally. Two categories
     * that are unifiable are always similar, but not necessarily vice versa. This check for
     * similarity is computationally less expensive than the check for unification.
     * 
     * @param c The category for which similarity with this category should be checked.
     * @return true if the two categories are similar.
     */
    public boolean isSimilar(Category c) {
        if (!name.equals(c.name) || !getType().equals(c.getType()))
            return false;
        if (c.featureMap == null)
            return false;
        if (featureMap.getFeatureNames().isEmpty())
            return true;
        if (c.featureMap.getFeatureNames().isEmpty())
            return true;
        return featureMap.isSimilar(c.featureMap);
    }

    /**
     * This method returns true if this category subsumes (in other words "is more general than")
     * the given category, or false otherwise.
     * 
     * @param c The category for which it is checked whether this category subsumes it.
     * @return true if this category subsumes the given category.
     */
    public boolean subsumes(Category c) {
        if (!name.equals(c.name) || !getType().equals(c.getType()))
            return false;

        // Some quick checks before doing the expensive copying of the categories:
        if (c.featureMap == null)
            return false;
        if (featureMap.getFeatureNames().isEmpty())
            return true;
        if (!featureMap.isSimilar(c.featureMap))
            return false;

        // Both categories are copied to keep the existing categories untouched:
        Category category1C = deepCopy();
        Category category2C = c.deepCopy();

        // Category 1 subsumes category 2 iff 1 unifies with 2 after the skolemization of 2.
        category2C.skolemize();
        try {
            category1C.tryToUnify(category2C);
            return true;
        } catch (UnificationFailedException ex) {
            return false;
        }
    }

    /**
     * Skolemizes the feature values of this category.
     */
    public void skolemize() {
        featureMap.skolemize();
    }

    /**
     * Returns the used feature names within the feature map.
     * 
     * @return The used feature names.
     */
    public Set<String> getFeatureNames() {
        return featureMap.getFeatureNames();
    }

    /**
     * Returns the used feature values within the feature map.
     * 
     * @return The used feature values.
     */
    public Collection<StringRef> getFeatureValues() {
        return featureMap.getFeatureValues();
    }

    void collectVars(List<Integer> vars, List<Integer> mvars) {
        if (this instanceof Terminal)
            return;
        Collection<StringRef> fvalues = getFeatureValues();
        if (fvalues == null)
            return;
        for (StringRef v : fvalues) {
            if (v.getString() == null) {
                int id = v.getID();
                if (vars.contains(id)) {
                    if (!mvars.contains(id)) {
                        mvars.add(id);
                    }
                } else {
                    vars.add(id);
                }
            }
        }
    }

    String getIdentifier(List<Integer> mvars, String[] usedFeatureNames) {
        return getName() + featureMap.getIdentifier(mvars, usedFeatureNames);
    }

    /**
     * Creates a deep copy of this category.
     * 
     * @return A deep copy.
     */
    public Category deepCopy() {
        return deepCopy(new HashMap<Integer, StringObject>());
    }

    /**
     * Creates a deep copy of this category using the given string objects. This method is
     * usually called form another deepCopy-method.
     * 
     * @param stringObjs The string objects to be used.
     * @return A deep copy.
     */
    Category deepCopy(HashMap<Integer, StringObject> stringObjs) {
        Category c;
        if (this instanceof Terminal) {
            c = new Terminal(name);
        } else if (this instanceof Preterminal) {
            c = new Preterminal(name);
        } else if (this instanceof Nonterminal) {
            c = new Nonterminal(name);
        } else {
            throw new RuntimeException("Unknown category type: " + this.getClass().toString());
        }
        c.featureMap = featureMap.deepCopy(stringObjs);
        return c;
    }

    public boolean equals(Object obj) {
        if (!(obj instanceof Category))
            return false;
        if (!getType().equals(((Category) obj).getType()))
            return false;
        return toString().equals(obj.toString());
    }

    public abstract String toString();

}