package edu.stanford.nlp.ling; import java.util.Comparator; import java.util.Map; import java.util.TreeMap; import edu.stanford.nlp.ling.CoreAnnotations.IndexAnnotation; import edu.stanford.nlp.ling.CoreAnnotations.TextAnnotation; import edu.stanford.nlp.ling.CoreAnnotations.ValueAnnotation; import edu.stanford.nlp.util.CoreMap; /** * Version of CoreLabel that allows for cycles in values/keys. * * Equals is defined as object equality, hashCode is defined on * object address, and toString will not print cycles. * * TODO: This class may be removable if it is the case that * TreeGraphNode (it's main user) doesn't actually need the * cyclic semantics (because we fixed a bug in its lack of * hashCode). * TODO: However, something now used in various places is that it has a tidy minimal toString output of value-index, which is not preserved in CoreLabel. Maybe it should be but changing that might also break stuff now. * * @author rafferty * */ public class CyclicCoreLabel extends CoreLabel { private static final long serialVersionUID = 1L; /** * Based on MapLabel printing structure */ private static String printOptions = "value-index"; /** Default constructor, calls super() */ public CyclicCoreLabel() { super(); } /** Copy constructor from any CoreMap. */ public CyclicCoreLabel(Label label) { super(label); } /** Copy constructor from any CoreMap. */ public CyclicCoreLabel(CoreMap label) { super(label); } /** Copy constructor from any CoreMap. */ public CyclicCoreLabel(CoreLabel label) { super(label); } /** Copy constructor from any CoreMap. */ public CyclicCoreLabel(CyclicCoreLabel label) { this((CoreMap) label); } /** * Two CoreMaps are equal iff all keys and values are equal (really, ==). * This equals method that is well defined even if there * are cycles in keys/values. Checks for object address * equality for key-value pairs. */ @SuppressWarnings({"unchecked"}) @Override public boolean equals(Object obj) { if (!(obj instanceof CoreLabel)) { return false; } CoreLabel other = (CoreLabel) obj; if (!this.keySet().equals(other.keySet())) { return false; } for (Class key : this.keySet()) { if (this.get(key) != other.get(key)) { return false; } } return true; } /** * Returns a composite hashcode over all the keys and values currently * stored in the map. Because they may change over time, this class * is not appropriate for use as map keys. * * This hashcode function works on object address * equality. Compatible with cyclicEquals. */ @Override @SuppressWarnings({"unchecked"}) public int hashCode() { int keyscode = 0; int valuescode = 0; for (Class key : keySet()) { keyscode += key.hashCode(); valuescode += (get(key) != null ? System.identityHashCode(get(key)) : 0); } return keyscode * 37 + valuescode; } private static final Comparator<Class<?>> asClassComparator = new Comparator<Class<?>>() { public int compare(Class<?> o1, Class<?> o2) { return o1.getName().compareTo(o2.getName()); } }; /** * Return a <code>String</code> containing the value (and index, * if any) of this label. This is equivalent to * toString("value-index"). */ @Override public String toString() { return toString(printOptions); } /** * Returns a formatted string representing this label. The * desired format is passed in as a <code>String</code>. * Currently supported formats include: * <ul> * <li>"value": just prints the value</li> * <li>"{map}": prints the complete map</li> * <li>"value{map}": prints the value followed by the contained * map (less the map entry containing key <code>CATEGORY_KEY</code>)</li> * <li>"value-index": extracts a value and an integer index from * the contained map using keys <code>INDEX_KEY</code>, * respectively, and prints them with a hyphen in between</li> * <li>"value-index{map}": a combination of the above; the index is * displayed first and then not shown in the map that is displayed</li> * <li>"word": Just the value of HEAD_WORD_KEY in the map</li> * </ul> * <p/> * Map is printed in alphabetical order of keys. */ @SuppressWarnings("unchecked") public String toString(String format) { StringBuilder buf = new StringBuilder(); if (format.equals("value")) { buf.append(value()); } else if (format.equals("{map}")) { Map map2 = new TreeMap(asClassComparator); for(Class<?> key : this.keySet()) { map2.put(key, get((Class<? extends CoreAnnotation>) key)); } buf.append(map2); } else if (format.equals("value{map}")) { buf.append(value()); Map map2 = new TreeMap(asClassComparator); for(Class<?> key : this.keySet()) { map2.put(key, get((Class<? extends CoreAnnotation>) key)); } map2.remove(ValueAnnotation.class); buf.append(map2); } else if (format.equals("value-index")) { buf.append(value()); Integer index = this.get(IndexAnnotation.class); if (index != null) { buf.append("-").append((index).intValue()); } } else if (format.equals("value-index{map}")) { buf.append(value()); Integer index = this.get(IndexAnnotation.class); if (index != null) { buf.append("-").append((index).intValue()); } Map<String,Object> map2 = new TreeMap<String,Object>(); for(Class<?> key : this.keySet()) { String cls = key.getName(); // special shortening of all the Annotation classes int idx = cls.indexOf('$'); if (idx >= 0) { cls = cls.substring(idx + 1); } map2.put(cls, this.get((Class<? extends CoreAnnotation>) key)); } map2.remove("IndexAnnotation"); map2.remove("ValueAnnotation"); if (!map2.isEmpty()) { buf.append(map2); } } else if (format.equals("word")) { buf.append(word()); } else if (format.equals("text-index")) { buf.append(this.get(TextAnnotation.class)); Integer index = this.get(IndexAnnotation.class); if (index != null) { buf.append("-").append((index).intValue()); } } return buf.toString(); } public static LabelFactory factory() { return new LabelFactory() { public Label newLabel(String labelStr) { CyclicCoreLabel label = new CyclicCoreLabel(); label.setValue(labelStr); return label; } public Label newLabel(String labelStr, int options) { return newLabel(labelStr); } public Label newLabel(Label oldLabel) { return new CyclicCoreLabel(oldLabel); } public Label newLabelFromString(String encodedLabelStr) { throw new UnsupportedOperationException("This code branch left blank" + " because we do not understand what this method should do."); } }; } /** * {@inheritDoc} */ @Override public LabelFactory labelFactory() { return CyclicCoreLabel.factory(); } }