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    
019    /**
020     * This class represents a grammatical category.
021     * 
022     * @author Tobias Kuhn
023     */
024    public abstract class Category {
025            
026            private final String name;
027            private FeatureMap featureMap = new FeatureMap();
028            
029            /**
030             * Creates a new category.
031             * 
032             * @param name The name of the category.
033             */
034            public Category(String name) {
035                    this.name = name;
036            }
037            
038            /**
039             * Returns true if this category is a reference.
040             * 
041             * @return true if this is a reference.
042             */
043            public boolean isReference() {
044                    if (name.equals(">>>")) return true;
045                    if (name.equals("<<<")) return true;
046                    if (name.equals("</<")) return true;
047                    return false;
048            }
049            
050            /**
051             * Returns the name of the category.
052             * 
053             * @return The name of the category.
054             */
055            public String getName() {
056                    return name;
057            }
058            
059            /**
060             * Returns the map of features of this category.
061             * 
062             * @return The feature map.
063             */
064            public FeatureMap getFeatureMap() {
065                    return featureMap;
066            }
067            
068            /**
069             * Sets a feature.
070             * 
071             * @param featureName The feature name
072             * @param featureValue The string reference that points to the value of the feature.
073             */
074            public void setFeature(String featureName, StringRef featureValue) {
075                    featureMap.setFeature(featureName, featureValue);
076            }
077            
078            /**
079             * Sets a feature.
080             * 
081             * @param featureName The feature name
082             * @param featureValue The value of the feature.
083             */
084            public void setFeature(String featureName, String featureValue) {
085                    featureMap.setFeature(featureName, new StringRef(featureValue));
086            }
087            
088            /**
089             * Returns a feature value.
090             * 
091             * @param featureName The name of the feature.
092             * @return The value of the feature.
093             */
094            public StringRef getFeature(String featureName) {
095                    return featureMap.getFeature(featureName);
096            }
097            
098            /**
099             * Unifies this category with another category. Two categories can unify if and only if they have
100             * the same names and they have no features with conflicting values. If the unification fails, a
101             * UnificationFailedException is thrown. In this case, the two categories remain partly unified,
102             * i.e. no backtracking is done. Thus, this operation should be perfomed only if it is certain that
103             * the unification succeeds, or if the operation is performed on copies of objects that are not
104             * used anymore afterwards.
105             * 
106             * @param category The category to be unified with this category.
107             * @throws UnificationFailedException If unification fails.
108             */
109            public void unify(Category category) throws UnificationFailedException {
110                    if (!name.equals(category.name)) {
111                            throw new UnificationFailedException();
112                    }
113                    featureMap.unify(category.featureMap);
114            }
115            
116            /**
117             * Tries to unify this category with another category. If unification is not possible, an exception
118             * is thrown. In the case unification would be possible, the unification is not performed completely.
119             * In any case the two categories remain in an unconsistent state afterwards. Thus, this operation should
120             * be performed only on copies of objects that are not used anymore afterwards.
121             * 
122             * @param category The category to be unified with this category.
123             * @throws UnificationFailedException If unification fails.
124             */
125            public void tryToUnify(Category category) throws UnificationFailedException {
126                    if (!name.equals(category.name)) {
127                            throw new UnificationFailedException();
128                    }
129                    featureMap.tryToUnify(category.featureMap);
130            }
131            
132            /**
133             * This method detects whether this category can unify with the given category. Neither of the two
134             * categories are changed.
135             * 
136             * @param category The category for the unification check.
137             * @return true if the two categories can unify.
138             */
139            public boolean canUnify(Category category) {
140                    if (!isSimilar(category)) return false;
141                    Category thisC = deepCopy();
142                    Category otherC = category.deepCopy();
143                    try {
144                            thisC.tryToUnify(otherC);
145                    } catch (UnificationFailedException ex) {
146                            return false;
147                    }
148                    return true;
149            }
150            
151            /**
152             * Skolemizes the feature values of this category.
153             */
154            public void skolemize() {
155                    featureMap.skolemize();
156            }
157            
158            /**
159             * Creates a deep copy of this category.
160             * 
161             * @return A deep copy.
162             */
163            public Category deepCopy() {
164                    return deepCopy(new HashMap<Integer, StringEntity>());
165            }
166            
167            /**
168             * Creates a deep copy of this category using the given string entities. This method is
169             * usually called form another deepCopy-method.
170             * 
171             * @param entities The string entities to be used.
172             * @return A deep copy.
173             */
174            Category deepCopy(HashMap<Integer, StringEntity> entities) {
175                    Category c;
176                    if (this instanceof Terminal) {
177                            c = new Terminal(name);
178                    } else {
179                            c = new Nonterminal(name);
180                    }
181                    c.featureMap = featureMap.deepCopy(entities);
182                    return c;
183            }
184            
185            /**
186             * This methods checks whether two categories are similar. Two categories are similar if and
187             * only if the categories have the same name and no feature with the same name is present in
188             * both categories with values that do not unify locally.
189             * 
190             * @param c The category for which similarity with this category should be checked.
191             * @return true if the two categories are similar.
192             */
193            public boolean isSimilar(Category c) {
194                    if (!name.equals(c.name)) return false;
195                    return featureMap.isSimilar(c.featureMap);
196            }
197            
198            /**
199             * This method returns true if this category subsumes (i.e. is more general than) the given category,
200             * or false otherwise.
201             * 
202             * @param category The category for which it is checked whether this category subsumes it.
203             * @return true if this category subsumes the given category.
204             */
205            public boolean subsumes(Category category) {
206                    if (!isSimilar(category)) return false;
207                    
208                    // Both categories are copied to keep the existing categories untouched:
209                    Category category1C = deepCopy();
210                    Category category2C = category.deepCopy();
211                    
212                    // Category 1 subsumes category 2 iff 1 unifies with 2 after the skolemization of 2.
213                    category2C.skolemize();
214                    try {
215                            category1C.tryToUnify(category2C);
216                            return true;
217                    } catch (UnificationFailedException ex) {
218                            return false;
219                    }
220            }
221            
222            public boolean equals(Object obj) {
223                    if (!(obj instanceof Category)) return false;
224                    return toString().equals(obj.toString());
225            }
226            
227            public String toString() {
228                    return name + featureMap.toString();
229            }
230    
231    }