001    // This file is part of the Attempto Java Packages.
002    // Copyright 2008-2009, Attempto Group, University of Zurich (see http://attempto.ifi.uzh.ch).
003    //
004    // The Attempto Java Packages is free software: you can redistribute it and/or modify it under the
005    // terms of the GNU Lesser General Public License as published by the Free Software Foundation,
006    // either version 3 of the License, or (at your option) any later version.
007    //
008    // The Attempto Java Packages is distributed in the hope that it will be useful, but WITHOUT ANY
009    // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
010    // PURPOSE. See the GNU Lesser General Public License for more details.
011    //
012    // You should have received a copy of the GNU Lesser General Public License along with the Attempto
013    // Java Packages. If not, see http://www.gnu.org/licenses/.
014    
015    package ch.uzh.ifi.attempto.chartparser;
016    
017    import java.util.HashMap;
018    import java.util.Set;
019    
020    /**
021     * This class represents a grammatical category.
022     * 
023     * @author Tobias Kuhn
024     */
025    public abstract class Category {
026            
027            private static int skNumber = 0;
028            
029            private final String name;
030            private HashMap<String, StringRef> features = new HashMap<String, StringRef>();
031            
032            /**
033             * Creates a new category.
034             * 
035             * @param name The name of the category.
036             */
037            public Category(String name) {
038                    this.name = name;
039            }
040            
041            /**
042             * Returns the name of the category.
043             * 
044             * @return The name of the category.
045             */
046            public String getName() {
047                    return name;
048            }
049            
050            /**
051             * Sets a feature.
052             * 
053             * @param featureName The feature name
054             * @param featureValue
055             */
056            public void setFeature(String featureName, StringRef featureValue) {
057                    features.put(featureName, featureValue);
058            }
059            
060            /**
061             * Returns a feature value.
062             * 
063             * @param featureName The name of the feature.
064             * @return The value of the feature.
065             */
066            public StringRef getFeature(String featureName) {
067                    StringRef featureValue = features.get(featureName);
068                    if (featureValue == null) {
069                            featureValue = new StringRef();
070                            features.put(featureName, featureValue);
071                    }
072                    return featureValue;
073            }
074            
075            /**
076             * Unifies this category with another category. Two categories can unify if and only if they have
077             * the same names and they have no features with conflicting values. If the unification fails, a
078             * UnificationFailedException is thrown. In this case, the two categories remain partly unified,
079             * i.e. no backtracking is done. Thus, this operation should be perfomed only if it is certain that
080             * the unification succeeds, or if the operation is performed on copies of objects that are not
081             * used anymore afterwards.
082             * 
083             * @param category The category to be unified with this category.
084             * @throws UnificationFailedException If unification fails.
085             */
086            public void unify(Category category) throws UnificationFailedException {
087                    if (!name.equals(category.name)) {
088                            throw new UnificationFailedException();
089                    }
090                    for (String f : features.keySet()) {
091                            getFeature(f).unify(category.getFeature(f));
092                    }
093                    for (String f : category.features.keySet()) {
094                            getFeature(f).unify(category.getFeature(f));
095                    }
096            }
097            
098            /**
099             * Tries to unify this category with another category. If unification is not possible, an exception
100             * is thrown. In the case unification would be possible, the unification is not performed completely.
101             * In any case the two categories remain in an unconsistent state afterwards. Thus, this operation should
102             * be performed only on copies of objects that are not used anymore afterwards.
103             * 
104             * @param category The category to be unified with this category.
105             * @throws UnificationFailedException If unification fails.
106             */
107            public void tryToUnify(Category category) throws UnificationFailedException {
108                    if (!name.equals(category.name)) {
109                            throw new UnificationFailedException();
110                    }
111                    for (String f : features.keySet()) {
112                            features.get(f).unify(category.getFeature(f));
113                    }
114            }
115            
116            /**
117             * Skolemizes the feature values of this category.
118             */
119            public void skolemize() {
120                    for (String feature : features.keySet()) {
121                            StringRef s = features.get(feature);
122                            if (s.getString() == null) {
123                                    try {
124                                            s.unify(new StringRef("$SK" + skNumber++));
125                                    } catch (UnificationFailedException ex) {}
126                            }
127                    }
128            }
129            
130            /**
131             * Creates a deep copy of this category.
132             * 
133             * @return A deep copy.
134             */
135            public Category deepCopy() {
136                    return deepCopy(new HashMap<Integer, StringEntity>());
137            }
138            
139            /**
140             * Creates a deep copy of this category using the given string entities. This method is
141             * usually called form another deepCopy-method.
142             * 
143             * @param entities The string entities to be used.
144             * @return A deep copy.
145             */
146            public Category deepCopy(HashMap<Integer, StringEntity> entities) {
147                    Category c;
148                    if (this instanceof Terminal) {
149                            c = new Terminal(name);
150                    } else {
151                            c = new Nonterminal(name);
152                    }
153                    for (String feature : features.keySet()) {
154                            StringRef s = features.get(feature);
155                            StringEntity se = entities.get(s.getID());
156                            if (se != null) {
157                                    c.setFeature(feature, se.newStringRef());
158                            } else {
159                                    StringRef sr = new StringRef(s.getString());
160                                    c.setFeature(feature, sr);
161                                    entities.put(s.getID(), sr.getStringEntity());
162                            }
163                    }
164                    return c;
165            }
166            
167            public boolean subsumes(Category category) {
168                    if (!name.equals(category.name)) return false;
169                    
170                    Category category1C = deepCopy();
171                    Category category2C = category.deepCopy();
172                    category2C.skolemize();
173                    try {
174                            category1C.tryToUnify(category2C);
175                            return true;
176                    } catch (UnificationFailedException ex) {
177                            return false;
178                    }
179            }
180            
181            public boolean equals(Object obj) {
182                    if (!(obj instanceof Category)) return false;
183                    return toString().equals(obj.toString());
184            }
185            
186            public String toString() {
187                    String s = name;
188                    Set<String> featureKeys = features.keySet();
189                    if (featureKeys.size() > 0) s += "(";
190                    for (String feature : features.keySet()) {
191                            StringRef sr = features.get(feature);
192                            s += feature + ":";
193                            if (sr.getString() == null) {
194                                    s += sr.getID();
195                            } else {
196                                    s += sr.getString();
197                            }
198                            s += ",";
199                    }
200                    if (featureKeys.size() > 0) {
201                            s = s.substring(0, s.length()-1) + ")";
202                    }
203                    return s;
204            }
205    
206    }