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.Map;
021    import java.util.Set;
022    import java.util.TreeSet;
023    
024    /**
025     * This class represents a set of features consisting of name/value pairs.
026     * 
027     * @author Tobias Kuhn
028     */
029    public class FeatureMap {
030            
031            /**
032             * This counter is used for skolemization:
033             */
034            private static int skNumber = 0;
035            
036            private Map<String, StringRef> features = new HashMap<String, StringRef>();
037            
038            /**
039             * Creates an empty feature map.
040             */
041            public FeatureMap() {
042            }
043            
044            /**
045             * Sets a feature. If the feature is already present, the feature value is overridden and not
046             * unified.
047             * 
048             * @param featureName The name of the feature to set.
049             * @param featureValue The feature value.
050             */
051            public void setFeature(String featureName, StringRef featureValue) {
052                    features.put(featureName, featureValue);
053            }
054            
055            /**
056             * Returns a feature value.
057             * 
058             * @param featureName The name of the feature.
059             * @return The value of the feature.
060             */
061            public StringRef getFeature(String featureName) {
062                    StringRef featureValue = features.get(featureName);
063                    if (featureValue == null) {
064                            featureValue = new StringRef();
065                            features.put(featureName, featureValue);
066                    }
067                    return featureValue;
068            }
069            
070            /**
071             * Unifies this feature map with another feature map. Two feature maps can unify if and only if
072             * all values of common feature name unify. If the unification fails, a
073             * UnificationFailedException is thrown. In this case, the two feature maps remain partly unified,
074             * i.e. no backtracking is done. Thus, this operation should be perfomed only if it is certain that
075             * the unification succeeds, or if the operation is performed on copies of objects that are not
076             * used anymore afterwards.
077             * 
078             * @param featureMap The feature map to be unified with this feature map.
079             * @throws UnificationFailedException If unification fails.
080             */
081            public void unify(FeatureMap featureMap) throws UnificationFailedException {
082                    for (String f : features.keySet()) {
083                            getFeature(f).unify(featureMap.getFeature(f));
084                    }
085                    for (String f : featureMap.features.keySet()) {
086                            getFeature(f).unify(featureMap.getFeature(f));
087                    }
088            }
089            
090            /**
091             * Tries to unify this feature map with another feature map. If unification is not possible, an exception
092             * is thrown. In the case unification would be possible, the unification is not performed completely.
093             * In any case the two feature maps remain in an unconsistent state afterwards. Thus, this operation should
094             * be performed only on copies of objects that are not used anymore afterwards.
095             * 
096             * @param featureMap The feature map to be unified with this feature map.
097             * @throws UnificationFailedException If unification fails.
098             */
099            public void tryToUnify(FeatureMap featureMap) throws UnificationFailedException {
100                    if (featureMap == null) {
101                            throw new UnificationFailedException();
102                    }
103                    for (String f : features.keySet()) {
104                            features.get(f).unify(featureMap.getFeature(f));
105                    }
106            }
107            
108            /**
109             * This method detects whether this feature map can unify with the given feature map. Neither of the two
110             * feature maps are changed.
111             * 
112             * @param featureMap The feature map for the unification check.
113             * @return true if the two feature map can unify.
114             */
115            public boolean canUnify(FeatureMap featureMap) {
116                    if (!isSimilar(featureMap)) return false;
117                    FeatureMap thisC = deepCopy();
118                    FeatureMap otherC = featureMap.deepCopy();
119                    try {
120                            thisC.tryToUnify(otherC);
121                    } catch (UnificationFailedException ex) {
122                            return false;
123                    }
124                    return true;
125            }
126            
127            /**
128             * Skolemizes the feature values of this feature map.
129             */
130            public void skolemize() {
131                    for (String feature : features.keySet()) {
132                            StringRef s = features.get(feature);
133                            if (s.getString() == null) {
134                                    try {
135                                            s.unify(new StringRef("$SK" + skNumber++));
136                                    } catch (UnificationFailedException ex) {}
137                            }
138                    }
139            }
140            
141            /**
142             * This methods checks whether two feature maps are similar. Two feature maps are similar
143             * if and only if they do not share a feature with the same name but with values that do
144             * not unify locally (i.e. without considering the unifications entailed by other features).
145             * 
146             * @param fm The category for which similarity with this category should be checked.
147             * @return true if the two categories are similar.
148             */
149            public boolean isSimilar(FeatureMap fm) {
150                    if (fm == null) return false;
151                    for (String v : features.keySet()) {
152                            String s1 = features.get(v).getString();
153                            String s2 = null;
154                            StringRef sr2 = fm.features.get(v);
155                            if (sr2 != null) s2 = sr2.getString();
156                            if (s1 != null && s2 != null && !s1.equals(s2)) return false;
157                    }
158                    return true;
159            }
160            
161            /**
162             * Creates a deep copy of this feature map.
163             * 
164             * @return A deep copy.
165             */
166            public FeatureMap deepCopy() {
167                    return deepCopy(new HashMap<Integer, StringObject>());
168            }
169            
170            /**
171             * Creates a deep copy of this feature map using the given string objects. This method is
172             * usually called form another deepCopy-method.
173             * 
174             * @param stringObjs The string objects to be used.
175             * @return A deep copy.
176             */
177            FeatureMap deepCopy(HashMap<Integer, StringObject> stringObjs) {
178                    FeatureMap fm = new FeatureMap();
179                    for (String feature : features.keySet()) {
180                            StringRef s = features.get(feature);
181                            StringObject se = stringObjs.get(s.getID());
182                            if (se != null) {
183                                    fm.setFeature(feature, se.newStringRef());
184                            } else {
185                                    StringRef sr = new StringRef(s.getString());
186                                    fm.setFeature(feature, sr);
187                                    stringObjs.put(s.getID(), sr.getStringObject());
188                            }
189                    }
190                    return fm;
191            }
192            
193            /**
194             * Returns the used feature names.
195             * 
196             * @return The used feature names.
197             */
198            public Set<String> getFeatureNames() {
199                    return features.keySet();
200            }
201            
202            /**
203             * Returns the used feature values.
204             * 
205             * @return The used feature values.
206             */
207            public Collection<StringRef> getFeatureValues() {
208                    return features.values();
209            }
210            
211            String getIdentifier(List<Integer> mvars, String[] usedFeatureNames) {
212                    String s = "(";
213                    int i = 0;
214                    for (String n : usedFeatureNames) {
215                            i++;
216                            if (!getFeatureNames().contains(n)) continue;
217                            StringRef v = getFeature(n);
218                            if (v.getString() == null) {
219                                    if (mvars.contains(v.getID())) {
220                                            s += i + ":" + mvars.indexOf(v.getID()) + ",";
221                                    }
222                            } else {
223                                    s += i + ":" + v.getString() + ",";
224                            }
225                    }
226                    return s + ")";
227            }
228            
229            public String toString() {
230                    String s = "";
231                    Set<String> featureKeys = features.keySet();
232                    if (featureKeys.size() > 0) s += "(";
233                    for (String feature : new TreeSet<String>(features.keySet())) {
234                            // traverse the feature names in alphabetical order
235                            StringRef sr = features.get(feature);
236                            s += feature + ":";
237                            if (sr.getString() == null) {
238                                    s += sr.getID();
239                            } else {
240                                    s += sr.getString();
241                            }
242                            s += ",";
243                    }
244                    if (featureKeys.size() > 0) {
245                            s = s.substring(0, s.length()-1) + ")";
246                    }
247                    return s;
248            }
249    
250    }