001 // This file is part of AceWiki. 002 // Copyright 2008-2012, AceWiki developers. 003 // 004 // AceWiki is free software: you can redistribute it and/or modify it under the terms of the GNU 005 // Lesser General Public License as published by the Free Software Foundation, either version 3 of 006 // the License, or (at your option) any later version. 007 // 008 // AceWiki is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 009 // even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 010 // Lesser General Public License for more details. 011 // 012 // You should have received a copy of the GNU Lesser General Public License along with AceWiki. If 013 // not, see http://www.gnu.org/licenses/. 014 015 package ch.uzh.ifi.attempto.chartparser; 016 017 import java.util.Collection; 018 import java.util.HashMap; 019 import java.util.List; 020 import java.util.Set; 021 022 import org.apache.commons.lang.ArrayUtils; 023 024 /** 025 * This class represents a grammatical category. 026 * 027 * @author Tobias Kuhn 028 */ 029 public abstract class Category { 030 031 /** 032 * This static array contains all category names that denote special categories. These special 033 * category names are ">" and ">>" for forward references, "<" and "/<" for backward references, 034 * "//" for scope openers, and "#" for position operators. 035 */ 036 public static final String[] specialCategories = new String[] {">", ">>", "<", "/<", "//", "#"}; 037 038 /** 039 * The name of the category. 040 */ 041 protected String name; 042 043 /** 044 * The feature map of the category. Backward references do not use this field, since they can 045 * have multiple feature maps. 046 */ 047 protected FeatureMap featureMap; 048 049 /** 050 * This method returns the type of the category. For example, "term" is returned for terminal 051 * categories and "nonterm" for non-terminal ones. 052 * 053 * @return The type of the category. 054 */ 055 protected abstract String getType(); 056 057 /** 058 * Returns the name of the category. 059 * 060 * @return The name of the category. 061 */ 062 public String getName() { 063 return name; 064 } 065 066 /** 067 * Returns true if this category is a special category. Special categories are references, scope 068 * openers, and position operators. 069 * 070 * @return true if this category is a special category. 071 */ 072 public boolean isSpecialCategory() { 073 return ArrayUtils.contains(specialCategories, name); 074 } 075 076 /** 077 * Returns the map of features of this category. It returns null in the case of backward 078 * references, where the methods for positive and negative feature maps have to be used. 079 * 080 * @return The feature map. 081 */ 082 public FeatureMap getFeatureMap() { 083 return featureMap; 084 } 085 086 /** 087 * This method returns the list of positive feature maps for backward references, or null 088 * for all other categories. 089 * 090 * @return The list of positive feature maps in the case of backward references. 091 */ 092 public List<FeatureMap> getPosFeatureMaps() { 093 return null; 094 } 095 096 /** 097 * This method returns the list of negative feature maps for backward references, or null 098 * for all other categories. 099 * 100 * @return The list of negative feature maps in the case of backward references. 101 */ 102 public List<FeatureMap> getNegFeatureMaps() { 103 return null; 104 } 105 106 /** 107 * Adds a positive feature map in the case of backward references, or does nothing for all 108 * other categories. 109 * 110 * @param fm The positive feature map to be added. 111 */ 112 public void addPosFeatureMap(FeatureMap fm) { 113 } 114 115 /** 116 * Adds a negative feature map in the case of backward references, or does nothing for all 117 * other categories. 118 * 119 * @param fm The negative feature map to be added. 120 */ 121 public void addNegFeatureMap(FeatureMap fm) { 122 } 123 124 /** 125 * Sets a feature. This method cannot be used for backward references, which can have more than 126 * one feature map. 127 * 128 * @param featureName The feature name 129 * @param featureValue The string reference that points to the value of the feature. 130 */ 131 public void setFeature(String featureName, StringRef featureValue) { 132 featureMap.setFeature(featureName, featureValue); 133 } 134 135 /** 136 * Sets a feature. This method cannot be used for backward references, which can have more than 137 * one feature map. 138 * 139 * @param featureName The feature name 140 * @param featureValue The value of the feature. 141 */ 142 public void setFeature(String featureName, String featureValue) { 143 featureMap.setFeature(featureName, new StringRef(featureValue)); 144 } 145 146 /** 147 * Returns a feature value. This method cannot be used for backward references, which can have 148 * more than one feature map. 149 * 150 * @param featureName The name of the feature. 151 * @return The value of the feature. 152 */ 153 public StringRef getFeature(String featureName) { 154 return featureMap.getFeature(featureName); 155 } 156 157 /** 158 * Unifies this category with another category. Two categories can unify if and only if they have 159 * the same names and the same types and they have no features with conflicting values. If the 160 * unification fails, a UnificationFailedException is thrown. In this case, the two categories 161 * remain partly unified, i.e. no backtracking is done. Thus, this operation should be perfomed 162 * only if it is certain that the unification succeeds, or if the operation is performed on 163 * copies of objects that are not used anymore afterwards. 164 * 165 * @param c The category to be unified with this category. 166 * @throws UnificationFailedException If unification fails. 167 */ 168 public void unify(Category c) throws UnificationFailedException { 169 if (!name.equals(c.name) || !getType().equals(c.getType())) { 170 throw new UnificationFailedException(); 171 } 172 featureMap.unify(c.featureMap); 173 } 174 175 /** 176 * Tries to unify this category with another category. If unification is not possible, an exception 177 * is thrown. In the case unification would be possible, the unification is not performed completely. 178 * In any case the two categories remain in an unconsistent state afterwards. Thus, this operation 179 * should be performed only on copies of objects that are not used anymore afterwards. 180 * 181 * @param c The category to be unified with this category. 182 * @throws UnificationFailedException If unification fails. 183 */ 184 public void tryToUnify(Category c) throws UnificationFailedException { 185 if (!name.equals(c.name) || !getType().equals(c.getType())) { 186 throw new UnificationFailedException(); 187 } 188 featureMap.tryToUnify(c.featureMap); 189 } 190 191 /** 192 * This method detects whether this category can unify with the given category. Neither of the two 193 * categories are changed. 194 * 195 * @param category The category for the unification check. 196 * @return true if the two categories can unify. 197 */ 198 public boolean canUnify(Category category) { 199 if (!isSimilar(category)) return false; 200 Category thisC = deepCopy(); 201 Category otherC = category.deepCopy(); 202 try { 203 thisC.tryToUnify(otherC); 204 } catch (UnificationFailedException ex) { 205 return false; 206 } 207 return true; 208 } 209 210 /** 211 * This methods checks whether two categories are similar. Two categories are similar if and 212 * only if the categories have the same name and the same type and no feature with the same 213 * name is present in both categories with values that do not unify locally. Two categories 214 * that are unifiable are always similar, but not necessarily vice versa. This check for 215 * similarity is computationally less expensive than the check for unification. 216 * 217 * @param c The category for which similarity with this category should be checked. 218 * @return true if the two categories are similar. 219 */ 220 public boolean isSimilar(Category c) { 221 if (!name.equals(c.name) || !getType().equals(c.getType())) return false; 222 if (c.featureMap == null) return false; 223 if (featureMap.getFeatureNames().isEmpty()) return true; 224 if (c.featureMap.getFeatureNames().isEmpty()) return true; 225 return featureMap.isSimilar(c.featureMap); 226 } 227 228 /** 229 * This method returns true if this category subsumes (in other words "is more general than") 230 * the given category, or false otherwise. 231 * 232 * @param c The category for which it is checked whether this category subsumes it. 233 * @return true if this category subsumes the given category. 234 */ 235 public boolean subsumes(Category c) { 236 if (!name.equals(c.name) || !getType().equals(c.getType())) return false; 237 238 // Some quick checks before doing the expensive copying of the categories: 239 if (c.featureMap == null) return false; 240 if (featureMap.getFeatureNames().isEmpty()) return true; 241 if (!featureMap.isSimilar(c.featureMap)) return false; 242 243 // Both categories are copied to keep the existing categories untouched: 244 Category category1C = deepCopy(); 245 Category category2C = c.deepCopy(); 246 247 // Category 1 subsumes category 2 iff 1 unifies with 2 after the skolemization of 2. 248 category2C.skolemize(); 249 try { 250 category1C.tryToUnify(category2C); 251 return true; 252 } catch (UnificationFailedException ex) { 253 return false; 254 } 255 } 256 257 /** 258 * Skolemizes the feature values of this category. 259 */ 260 public void skolemize() { 261 featureMap.skolemize(); 262 } 263 264 /** 265 * Returns the used feature names within the feature map. 266 * 267 * @return The used feature names. 268 */ 269 public Set<String> getFeatureNames() { 270 return featureMap.getFeatureNames(); 271 } 272 273 /** 274 * Returns the used feature values within the feature map. 275 * 276 * @return The used feature values. 277 */ 278 public Collection<StringRef> getFeatureValues() { 279 return featureMap.getFeatureValues(); 280 } 281 282 void collectVars(List<Integer> vars, List<Integer> mvars) { 283 if (this instanceof Terminal) return; 284 Collection<StringRef> fvalues = getFeatureValues(); 285 if (fvalues == null) return; 286 for (StringRef v : fvalues) { 287 if (v.getString() == null) { 288 int id = v.getID(); 289 if (vars.contains(id)) { 290 if (!mvars.contains(id)) { 291 mvars.add(id); 292 } 293 } else { 294 vars.add(id); 295 } 296 } 297 } 298 } 299 300 String getIdentifier(List<Integer> mvars, String[] usedFeatureNames) { 301 return getName() + featureMap.getIdentifier(mvars, usedFeatureNames); 302 } 303 304 /** 305 * Creates a deep copy of this category. 306 * 307 * @return A deep copy. 308 */ 309 public Category deepCopy() { 310 return deepCopy(new HashMap<Integer, StringObject>()); 311 } 312 313 /** 314 * Creates a deep copy of this category using the given string objects. This method is 315 * usually called form another deepCopy-method. 316 * 317 * @param stringObjs The string objects to be used. 318 * @return A deep copy. 319 */ 320 Category deepCopy(HashMap<Integer, StringObject> stringObjs) { 321 Category c; 322 if (this instanceof Terminal) { 323 c = new Terminal(name); 324 } else if (this instanceof Preterminal) { 325 c = new Preterminal(name); 326 } else if (this instanceof Nonterminal) { 327 c = new Nonterminal(name); 328 } else { 329 throw new RuntimeException("Unknown category type: " + this.getClass().toString()); 330 } 331 c.featureMap = featureMap.deepCopy(stringObjs); 332 return c; 333 } 334 335 public boolean equals(Object obj) { 336 if (!(obj instanceof Category)) return false; 337 if (!getType().equals(((Category) obj).getType())) return false; 338 return toString().equals(obj.toString()); 339 } 340 341 public abstract String toString(); 342 343 }