package plume; import java.lang.ref.WeakReference; import java.util.*; /** * Utilities for interning objects. Interning is also known as * canonicalization or hash-consing: it returns a single representative * object that <tt>.equals()</tt> the object, and the client discards the * argument and uses the result instead. Since only one object exists for * every set of equal objects, space usage is reduced. Time may also be * reduced, since it is possible to use <tt>==</tt> instead of * <tt>.equals()</tt> for comparisons. * <p> * * Java builds in interning for Strings, but not for other objects. The * methods in this class extend interning to all Java objects. **/ public final class Intern { private Intern() { throw new Error("do not instantiate"); } /////////////////////////////////////////////////////////////////////////// /// Strings /// /** * Replace each element of the array by its interned version. * Side-effects the array, but also returns it. * @see String#intern **/ @SuppressWarnings("interning") public static /*@Interned*/ String[] internStrings(String[] a) { for (int i=0; i<a.length; i++) if (a[i] != null) a[i] = a[i].intern(); return a; } /////////////////////////////////////////////////////////////////////////// /// Testing interning /// /** * Return true if the argument is interned (is canonical among all * objects equal to itself). **/ @SuppressWarnings("interning") public static boolean isInterned(/*@Nullable*/ Object value) { if (value == null) { // nothing to do return true; } else if (value instanceof String) { return (value == ((String) value).intern()); } else if (value instanceof String[]) { return (value == intern((String[]) value)); } else if (value instanceof Integer) { return (value == intern((Integer) value)); } else if (value instanceof Long) { return (value == intern((Long) value)); } else if (value instanceof int[]) { return (value == intern((int[]) value)); } else if (value instanceof long[]) { return (value == intern((long[]) value)); } else if (value instanceof Double) { return (value == intern((Double) value)); } else if (value instanceof double[]) { return (value == intern((double[]) value)); } else if (value instanceof Object[]) { return (value == intern((Object[]) value)); } else { // Nothing to do, because we don't intern other types. // System.out.println("What type? " + value.getClass().getName()); return true; } } /////////////////////////////////////////////////////////////////////////// /// Interning objects /// /** * Hasher object which hashes and compares Integers. * This is the obvious implementation that uses intValue() for the hashCode. * @see Hasher **/ private static final class IntegerHasher implements Hasher { public boolean equals(Object a1, Object a2) { return a1.equals(a2); } public int hashCode(Object o) { Integer i = (Integer) o; return i.intValue(); } } /** * Hasher object which hashes and compares Longs. * This is the obvious implementation that uses intValue() for the hashCode. * @see Hasher **/ private static final class LongHasher implements Hasher { public boolean equals(Object a1, Object a2) { return a1.equals(a2); } public int hashCode(Object o) { Long i = (Long) o; return i.intValue(); } } /** * Hasher object which hashes and compares int[] objects according * to their contents. * @see Hasher * @see java.util.Arrays#equals(int[], int[]) **/ private static final class IntArrayHasher implements Hasher { public boolean equals(Object a1, Object a2) { return java.util.Arrays.equals((int[])a1, (int[])a2); } public int hashCode(Object o) { int[] a = (int[])o; int result = 0; for (int i=0; i<a.length; i++) { result = result * FACTOR + a[i]; } return result; } } /** * Hasher object which hashes and compares long[] objects according * to their contents. * @see Hasher * @see java.util.Arrays#equals (long[], long[]) **/ private static final class LongArrayHasher implements Hasher { public boolean equals(Object a1, Object a2) { return java.util.Arrays.equals((long[])a1, (long[])a2); } public int hashCode(Object o) { long[] a = (long[])o; long result = 0; for (int i=0; i<a.length; i++) { result = result * FACTOR + a[i]; } return (int) (result % Integer.MAX_VALUE); } } private static final int FACTOR = 23; // private static final double DOUBLE_FACTOR = 65537; private static final double DOUBLE_FACTOR = 263; /** * Hasher object which hashes and compares Doubles. * @see Hasher **/ private static final class DoubleHasher implements Hasher { public boolean equals(Object a1, Object a2) { return a1.equals(a2); } public int hashCode(Object o) { Double d = (Double) o; // Could add "... % Integer.MAX_VALUE" here; is that good to do? long result = Math.round(d.doubleValue() * DOUBLE_FACTOR); return (int) (result % Integer.MAX_VALUE); } } /** * Hasher object which hashes and compares double[] objects according * to their contents. * @see Hasher * @see java.util.Arrays#equals(Object[],Object[]) **/ private static final class DoubleArrayHasher implements Hasher { public boolean equals(Object a1, Object a2) { // "java.util.Arrays.equals" considers +0.0 != -0.0. // Also, it gives inconsistent results (on different JVMs/classpaths?). // return java.util.Arrays.equals((double[])a1, (double[])a2); double[] da1 = (double[])a1; double[] da2 = (double[])a2; if (da1.length != da2.length) return false; for (int i=0; i<da1.length; i++) { if (! ((da1[i] == da2[i]) || (Double.isNaN(da1[i]) && Double.isNaN(da2[i])))) { return false; } } return true; } public int hashCode(Object o) { double[] a = (double[])o; double running = 0; for (int i=0; i<a.length; i++) { double elt = (Double.isNaN(a[i]) ? 0.0 : a[i]); running = running * FACTOR + elt * DOUBLE_FACTOR; } // Could add "... % Integer.MAX_VALUE" here; is that good to do? long result = Math.round(running); return (int) (result % Integer.MAX_VALUE); } } /** * Hasher object which hashes and compares String[] objects according * to their contents. * @see Hasher * java.util.Arrays.equals **/ private static final class StringArrayHasher implements Hasher { public boolean equals(Object a1, Object a2) { return java.util.Arrays.equals((String[])a1, (String[])a2); } public int hashCode(Object o) { String[] a = (String[])o; int result = 0; for (int i=0; i<a.length; i++) { int a_hashcode = (a[i] == null) ? 0 : a[i].hashCode(); result = result * FACTOR + a_hashcode; } return result; } } /** * Hasher object which hashes and compares Object[] objects according * to their contents. * @see Hasher * @see java.util.Arrays#equals(Object[], Object[]) **/ private static final class ObjectArrayHasher implements Hasher { public boolean equals(Object a1, Object a2) { return java.util.Arrays.equals((/*@Nullable*/ Object[])a1, (/*@Nullable*/ Object[])a2); } public int hashCode(Object o) { /*@Nullable*/ Object[] a = (/*@Nullable*/ Object[])o; int result = 0; for (int i=0; i<a.length; i++) { Object elt = a[i]; int elt_hashcode = (elt == null) ? 0 : elt.hashCode(); result = result * FACTOR + elt_hashcode; } return result; } } // Each of these maps has: // key = an interned object // value = a WeakReference for the object itself. // They can be looked up using a non-interned value; equality tests know // nothing of the interning types. private static WeakHasherMap</*@Interned*/ Integer,WeakReference</*@Interned*/ Integer>> internedIntegers; private static WeakHasherMap</*@Interned*/ Long,WeakReference</*@Interned*/ Long>> internedLongs; private static WeakHasherMap<int /*@Interned*/ [],WeakReference<int /*@Interned*/ []>> internedIntArrays; private static WeakHasherMap<long /*@Interned*/ [],WeakReference<long /*@Interned*/ []>> internedLongArrays; private static WeakHasherMap</*@Interned*/ Double,WeakReference</*@Interned*/ Double>> internedDoubles; private static /*@Interned*/ Double internedDoubleNaN; private static /*@Interned*/ Double internedDoubleZero; private static WeakHasherMap<double /*@Interned*/ [],WeakReference<double /*@Interned*/ []>> internedDoubleArrays; private static WeakHasherMap</*@Nullable*/ /*@Interned*/ String /*@Interned*/ [],WeakReference</*@Nullable*/ /*@Interned*/ String /*@Interned*/ []>> internedStringArrays; private static WeakHasherMap</*@Nullable*/ /*@Interned*/ Object /*@Interned*/ [],WeakReference</*@Nullable*/ /*@Interned*/ Object /*@Interned*/ []>> internedObjectArrays; private static WeakHasherMap<SequenceAndIndices<int /*@Interned*/ []>,WeakReference<int /*@Interned*/ []>> internedIntSequenceAndIndices; private static WeakHasherMap<SequenceAndIndices<long /*@Interned*/ []>,WeakReference<long /*@Interned*/ []>> internedLongSequenceAndIndices; private static WeakHasherMap<SequenceAndIndices<double /*@Interned*/ []>,WeakReference<double /*@Interned*/ []>> internedDoubleSequenceAndIndices; private static WeakHasherMap<SequenceAndIndices</*@Nullable*/ /*@Interned*/ Object /*@Interned*/ []>,WeakReference</*@Nullable*/ /*@Interned*/ Object /*@Interned*/ []>> internedObjectSequenceAndIndices; private static WeakHasherMap<SequenceAndIndices</*@Nullable*/ /*@Interned*/ String /*@Interned*/ []>,WeakReference</*@Nullable*/ /*@Interned*/ String /*@Interned*/ []>> internedStringSequenceAndIndices; static { internedIntegers = new WeakHasherMap</*@Interned*/ Integer,WeakReference</*@Interned*/ Integer>>(new IntegerHasher()); internedLongs = new WeakHasherMap</*@Interned*/ Long,WeakReference</*@Interned*/ Long>>(new LongHasher()); internedIntArrays = new WeakHasherMap<int /*@Interned*/ [],WeakReference<int /*@Interned*/ []>>(new IntArrayHasher()); internedLongArrays = new WeakHasherMap<long /*@Interned*/ [],WeakReference<long /*@Interned*/ []>>(new LongArrayHasher()); internedDoubles = new WeakHasherMap</*@Interned*/ Double,WeakReference</*@Interned*/ Double>>(new DoubleHasher()); internedDoubleNaN = new /*@Interned*/ Double(Double.NaN); internedDoubleZero = new /*@Interned*/ Double(0); internedDoubleArrays = new WeakHasherMap<double /*@Interned*/ [],WeakReference<double /*@Interned*/ []>>(new DoubleArrayHasher()); internedStringArrays = new WeakHasherMap</*@Nullable*/ /*@Interned*/ String /*@Interned*/ [],WeakReference</*@Nullable*/ /*@Interned*/ String /*@Interned*/ []>>(new StringArrayHasher()); internedObjectArrays = new WeakHasherMap</*@Nullable*/ /*@Interned*/ Object /*@Interned*/ [],WeakReference</*@Nullable*/ /*@Interned*/ Object /*@Interned*/ []>>(new ObjectArrayHasher()); internedIntSequenceAndIndices = new WeakHasherMap<SequenceAndIndices<int /*@Interned*/ []>,WeakReference<int /*@Interned*/ []>>(new SequenceAndIndicesHasher<int /*@Interned*/ []>()); internedLongSequenceAndIndices = new WeakHasherMap<SequenceAndIndices<long /*@Interned*/ []>,WeakReference<long /*@Interned*/ []>>(new SequenceAndIndicesHasher<long /*@Interned*/ []>()); internedDoubleSequenceAndIndices = new WeakHasherMap<SequenceAndIndices<double /*@Interned*/ []>,WeakReference<double /*@Interned*/ []>>(new SequenceAndIndicesHasher<double /*@Interned*/ []>()); internedObjectSequenceAndIndices = new WeakHasherMap<SequenceAndIndices</*@Nullable*/ /*@Interned*/ Object /*@Interned*/ []>,WeakReference</*@Nullable*/ /*@Interned*/ Object /*@Interned*/ []>>(new SequenceAndIndicesHasher</*@Nullable*/ /*@Interned*/ Object /*@Interned*/ []>()); internedStringSequenceAndIndices = new WeakHasherMap<SequenceAndIndices</*@Nullable*/ /*@Interned*/ String /*@Interned*/ []>,WeakReference</*@Nullable*/ /*@Interned*/ String /*@Interned*/ []>>(new SequenceAndIndicesHasher</*@Nullable*/ /*@Interned*/ String /*@Interned*/ []>()); } // For testing only public static int numIntegers() { return internedIntegers.size(); } public static int numLongs() { return internedLongs.size(); } public static int numIntArrays() { return internedIntArrays.size(); } public static int numLongArrays() { return internedLongArrays.size(); } public static int numDoubles() { return internedDoubles.size(); } public static int numDoubleArrays() { return internedDoubleArrays.size(); } public static int numStringArrays() { return internedStringArrays.size(); } public static int numObjectArrays() { return internedObjectArrays.size(); } public static Iterator</*@Interned*/ Integer> integers() { return internedIntegers.keySet().iterator(); } public static Iterator</*@Interned*/ Long> longs() { return internedLongs.keySet().iterator(); } public static Iterator<int /*@Interned*/ []> intArrays() { return internedIntArrays.keySet().iterator(); } public static Iterator<long /*@Interned*/ []> longArrays() { return internedLongArrays.keySet().iterator(); } public static Iterator</*@Interned*/ Double> doubles() { return internedDoubles.keySet().iterator(); } public static Iterator<double /*@Interned*/ []> doubleArrays() { return internedDoubleArrays.keySet().iterator(); } public static Iterator</*@Nullable*/ /*@Interned*/ String /*@Interned*/ []> stringArrays() { return internedStringArrays.keySet().iterator(); } public static Iterator</*@Nullable*/ /*@Interned*/ Object /*@Interned*/ []> objectArrays() { return internedObjectArrays.keySet().iterator(); } // Interns a String. // Delegates to the builtin String.intern() method. Provided for // completeness, so we can intern() any type used in OneOf.java.jpp. public static /*@Interned*/ /*@PolyNull*/ String intern(/*@PolyNull*/ String a) { return (a == null) ? null : a.intern(); } // Interns a long. // A no-op. Provided for completeness, so we can intern() any type // used in OneOf.java.jpp. public static long intern(long l) { return l; } // Interns a long. // A no-op. Provided for completeness, so we can intern() any type // used in OneOf.java.jpp. public static double intern(double l) { return l; } /** * Intern (canonicalize) an Integer. * Returns a canonical representation for the Integer. **/ // TODO: JLS 5.1.7 requires that the boxing conversion interns integer // values between -128 and 127 (and Intern.valueOf is intended to promise // the same). This does not currently take advantage of that. @SuppressWarnings("interning") public static /*@Interned*/ Integer intern(Integer a) { WeakReference</*@Interned*/ Integer> lookup = internedIntegers.get(a); if (lookup != null) { return lookup.get(); } else { @SuppressWarnings("cast") // cast is redundant (except in JSR 308) /*@Interned*/ Integer result = (/*@Interned*/ Integer) a; internedIntegers.put(result, new WeakReference</*@Interned*/ Integer>(result)); return result; } } // Not sure whether this convenience method is really worth it. /** Returns an interned Integer with value i. */ public static /*@Interned*/ Integer internedInteger(int i) { return intern(Integer.valueOf(i)); } // Not sure whether this convenience method is really worth it. /** Returns an interned Integer with value parsed from the string. */ public static /*@Interned*/ Integer internedInteger(String s) { return intern(Integer.decode(s)); } /** * Intern (canonicalize) a Long. * Returns a canonical representation for the Long. **/ // TODO: JLS 5.1.7 requires that the boxing conversion interns integer // values between -128 and 127 (and Long.valueOf is intended to promise // the same). This could take advantage of that. @SuppressWarnings("interning") public static /*@Interned*/ Long intern(Long a) { WeakReference</*@Interned*/ Long> lookup = internedLongs.get(a); if (lookup != null) { return lookup.get(); } else { @SuppressWarnings("cast") // cast is redundant (except in JSR 308) /*@Interned*/ Long result = (/*@Interned*/ Long) a; internedLongs.put(result, new WeakReference</*@Interned*/ Long>(result)); return result; } } // Not sure whether this convenience method is really worth it. /** Returns an interned Long with value i. */ public static /*@Interned*/ Long internedLong(long i) { return intern(Long.valueOf(i)); } // Not sure whether this convenience method is really worth it. /** Returns an interned Long with value parsed from the string. */ public static /*@Interned*/ Long internedLong(String s) { return intern(Long.decode(s)); } // I might prefer to have the intern methods first check using a straight // eq hashing, which would be more efficient if the array is already // interned. (How frequent do I expect that to be, and how much would // that really improve performance even in that case?) /** * Intern (canonicalize) an int[]. * Returns a canonical representation for the int[] array. * Arrays are compared according to their elements. **/ @SuppressWarnings("interning") public static int /*@Interned*/ [] intern(int[] a) { // Throwable stack = new Throwable("debug traceback"); // stack.fillInStackTrace(); // stack.printStackTrace(); WeakReference<int /*@Interned*/ []> lookup = internedIntArrays.get(a); if (lookup != null) { return lookup.get(); } else { @SuppressWarnings("cast") // cast is redundant (except in JSR 308) /*@Interned*/ int[] result = (int /*@Interned*/ []) a; internedIntArrays.put(result, new WeakReference<int /*@Interned*/ []>(result)); return result; } } /** * Intern (canonicalize) a long[]. * Returns a canonical representation for the long[] array. * Arrays are compared according to their elements. **/ @SuppressWarnings("interning") public static long /*@Interned*/ [] intern(long[] a) { //System.out.printf ("intern %s %s long[] %s\n", a.getClass(), // a, Arrays.toString (a)); WeakReference<long /*@Interned*/ []> lookup = internedLongArrays.get(a); if (lookup != null) { return lookup.get(); } else { @SuppressWarnings("cast") // cast is redundant (except in JSR 308) /*@Interned*/ long[] result = (long /*@Interned*/ []) a; internedLongArrays.put(result, new WeakReference<long /*@Interned*/ []>(result)); return result; } } /** * Intern (canonicalize) a Double. * Returns a canonical representation for the Double. **/ // TODO: JLS 5.1.7 requires that the boxing conversion interns integer // values between -128 and 127 (and Double.valueOf is intended to promise // the same). This could take advantage of that. @SuppressWarnings("interning") public static /*@Interned*/ Double intern(Double a) { // Double.NaN == Double.Nan always evaluates to false. if (a.isNaN()) return internedDoubleNaN; // Double.+0 == Double.-0, but they compare true via equals() if (a.doubleValue() == 0) // catches both positive and negative zero return internedDoubleZero; WeakReference</*@Interned*/ Double> lookup = internedDoubles.get(a); if (lookup != null) { return lookup.get(); } else { @SuppressWarnings("cast") // cast is redundant (except in JSR 308) /*@Interned*/ Double result = (/*@Interned*/ Double) a; internedDoubles.put(result, new WeakReference</*@Interned*/ Double>(result)); return result; } } // Not sure whether this convenience method is really worth it. /** Returns an interned Double with value i. */ public static /*@Interned*/ Double internedDouble(double d) { return intern(Double.valueOf(d)); } // Not sure whether this convenience method is really worth it. /** Returns an interned Double with value parsed from the string. */ public static /*@Interned*/ Double internedDouble(String s) { return internedDouble(Double.parseDouble(s)); } // I might prefer to have the intern methods first check using a straight // eq hashing, which would be more efficient if the array is already // interned. (How frequent do I expect that to be, and how much would // that really improve performance even in that case?) /** * Intern (canonicalize) a double[]. * Returns a canonical representation for the double[] array. * Arrays are compared according to their elements. **/ @SuppressWarnings("interning") public static double /*@Interned*/ [] intern(double[] a) { WeakReference<double /*@Interned*/ []> lookup = internedDoubleArrays.get(a); if (lookup != null) { return lookup.get(); } else { @SuppressWarnings("cast") // cast is redundant (except in JSR 308) /*@Interned*/ double[] result = (double /*@Interned*/ []) a; internedDoubleArrays.put(result, new WeakReference<double /*@Interned*/ []>(result)); return result; } } /** * Intern (canonicalize) an String[]. * Returns a canonical representation for the String[] array. * Arrays are compared according to their elements. * The elements should themselves already be interned; * they are compared using their equals() methods. **/ @SuppressWarnings("interning") public static /*@PolyNull*/ /*@Interned*/ String /*@Interned*/ [] intern(/*@PolyNull*/ /*@Interned*/ String[] a) { // Make sure each element is already interned for (int k = 0; k < a.length; k++) assert a[k] == Intern.intern (a[k]); @SuppressWarnings("nullness") // Polynull because value = parameter a, so same type & nullness as for parameter a WeakReference</*@PolyNull*/ /*@Interned*/ String /*@Interned*/ []> lookup = internedStringArrays.get(a); if (lookup != null) { return lookup.get(); } else { @SuppressWarnings("cast") // cast is redundant (except in JSR 308) /*@PolyNull*/ /*@Interned*/ String /*@Interned*/ [] result = (/*@PolyNull*/ /*@Interned*/ String /*@Interned*/ []) a; internedStringArrays.put(result, new WeakReference</*@Nullable*/ /*@Interned*/ String /*@Interned*/ []>(result)); return result; } } /** * Intern (canonicalize) an Object[]. * Returns a canonical representation for the Object[] array. * Arrays are compared according to their elements. * The elements should themselves already be interned; * they are compared using their equals() methods. **/ @SuppressWarnings({"interning"}) public static /*@PolyNull*/ /*@Interned*/ Object /*@Interned*/ [] intern(/*@PolyNull*/ /*@Interned*/ Object[] a) { @SuppressWarnings("nullness") // Polynull because value = parameter a, so same type & nullness as for parameter a WeakReference</*@PolyNull*/ /*@Interned*/ Object /*@Interned*/ []> lookup = internedObjectArrays.get(a); if (lookup != null) { return lookup.get(); } else { @SuppressWarnings("cast") // cast is redundant (except in JSR 308) /*@PolyNull*/ /*@Interned*/ Object /*@Interned*/ [] result = (/*@PolyNull*/ /*@Interned*/ Object /*@Interned*/ []) a; internedObjectArrays.put(result, new WeakReference</*@Nullable*/ /*@Interned*/ Object /*@Interned*/ []>(result)); return result; } } /** * Convenince method to intern an Object when we don't know its * runtime type. Its runtime type must be one of the types for * which we have an intern() method, else an exception is thrown. * If the argument is an array, its elements should themselves be * interned. **/ public static /*@Interned*/ /*@PolyNull*/ Object intern(/*@PolyNull*/ Object a) { if (a == null) { return null; } else if (a instanceof String) { return intern((String) a); } else if (a instanceof String[]) { @SuppressWarnings("interning") /*@Interned*/ String[] asArray = (/*@Interned*/ String[]) a; return intern(asArray); } else if (a instanceof Integer) { return intern((Integer) a); } else if (a instanceof Long) { return intern((Long) a); } else if (a instanceof int[]) { return intern((int[]) a); } else if (a instanceof long[]) { return intern((long[]) a); } else if (a instanceof Double) { return intern((Double) a); } else if (a instanceof double[]) { return intern((double[]) a); } else if (a instanceof Object[]) { @SuppressWarnings("interning") /*@Interned*/ Object[] asArray = (/*@Interned*/ Object[]) a; return intern(asArray); } else { throw new IllegalArgumentException ("Arguments of type " + a.getClass() + " cannot be interned"); } } /** * Return the subsequence of seq from start (inclusive) to end * (exclusive) that is interned. What's different about this method * from manually finding the subsequence and interning the * subsequence is that if the subsequence is already interned, we * can avoid having to compute the sequence. Since derived * variables in Daikon compute the subsequence many times, this * shortcut saves quite a bit of computation. It saves even more * when there may be many derived variables that are non-canonical, * since they are guaranteed to be ==. * <p> * Requires that seq is already interned. * @return a subsequence of seq from start to end that is interned. **/ public static int /*@Interned*/ [] internSubsequence (int /*@Interned*/ [] seq, int start, int end) { assert Intern.isInterned(seq); SequenceAndIndices<int /*@Interned*/ []> sai = new SequenceAndIndices<int /*@Interned*/ []> (seq, start, end); WeakReference<int /*@Interned*/ []> lookup = internedIntSequenceAndIndices.get(sai); if (lookup != null) { return lookup.get(); } else { int[] subseqUninterned = ArraysMDE.subarray(seq, start, end - start); int /*@Interned*/ [] subseq = Intern.intern (subseqUninterned); internedIntSequenceAndIndices.put (sai, new WeakReference<int /*@Interned*/ []>(subseq)); return subseq; } } /** * @see #internSubsequence(int[], int, int) **/ public static long /*@Interned*/ [] internSubsequence (long /*@Interned*/ [] seq, int start, int end) { assert Intern.isInterned(seq); SequenceAndIndices<long /*@Interned*/ []> sai = new SequenceAndIndices<long /*@Interned*/ []> (seq, start, end); WeakReference<long /*@Interned*/ []> lookup = internedLongSequenceAndIndices.get(sai); if (lookup != null) { return lookup.get(); } else { long[] subseq_uninterned = ArraysMDE.subarray(seq, start, end - start); long /*@Interned*/ [] subseq = Intern.intern (subseq_uninterned); internedLongSequenceAndIndices.put (sai, new WeakReference<long /*@Interned*/ []>(subseq)); return subseq; } } /** * @see #internSubsequence(int[], int, int) **/ public static double /*@Interned*/ [] internSubsequence (double /*@Interned*/ [] seq, int start, int end) { assert Intern.isInterned(seq); SequenceAndIndices<double /*@Interned*/ []> sai = new SequenceAndIndices<double /*@Interned*/ []> (seq, start, end); WeakReference<double /*@Interned*/ []> lookup = internedDoubleSequenceAndIndices.get(sai); if (lookup != null) { return lookup.get(); } else { double[] subseq_uninterned = ArraysMDE.subarray(seq, start, end - start); double /*@Interned*/ [] subseq = Intern.intern (subseq_uninterned); internedDoubleSequenceAndIndices.put (sai, new WeakReference<double /*@Interned*/ []>(subseq)); return subseq; } } /** * @see #internSubsequence(int[], int, int) **/ public static /*@PolyNull*/ /*@Interned*/ Object /*@Interned*/ [] internSubsequence (/*@PolyNull*/ /*@Interned*/ Object /*@Interned*/ [] seq, int start, int end) { assert Intern.isInterned(seq); SequenceAndIndices</*@PolyNull*/ /*@Interned*/ Object /*@Interned*/ []> sai = new SequenceAndIndices</*@PolyNull*/ /*@Interned*/ Object /*@Interned*/ []> (seq, start, end); @SuppressWarnings("nullness") // same nullness as key WeakReference</*@PolyNull*/ /*@Interned*/ Object /*@Interned*/ []> lookup = internedObjectSequenceAndIndices.get(sai); if (lookup != null) { return lookup.get(); } else { /*@PolyNull*/ /*@Interned*/ Object[] subseq_uninterned = ArraysMDE.subarray(seq, start, end - start); /*@PolyNull*/ /*@Interned*/ Object /*@Interned*/ [] subseq = Intern.intern (subseq_uninterned); @SuppressWarnings("nullness") // safe because map does no side effects Object ignore = // assignment just so there is a place to hang the @SuppressWarnings annotation internedObjectSequenceAndIndices.put (sai, new WeakReference</*@PolyNull*/ /*@Interned*/ Object /*@Interned*/ []>(subseq)); return subseq; } } /** * @see #internSubsequence(int[], int, int) **/ public static /*@PolyNull*/ /*@Interned*/ String /*@Interned*/ [] internSubsequence (/*@PolyNull*/ /*@Interned*/ String /*@Interned*/ [] seq, int start, int end) { assert Intern.isInterned(seq); SequenceAndIndices</*@PolyNull*/ /*@Interned*/ String /*@Interned*/ []> sai = new SequenceAndIndices</*@PolyNull*/ /*@Interned*/ String /*@Interned*/ []> (seq, start, end); @SuppressWarnings("nullness") // same nullness as key WeakReference</*@PolyNull*/ /*@Interned*/ String /*@Interned*/ []> lookup = internedStringSequenceAndIndices.get(sai); if (lookup != null) { return lookup.get(); } else { /*@PolyNull*/ /*@Interned*/ String[] subseq_uninterned = ArraysMDE.subarray(seq, start, end - start); /*@PolyNull*/ /*@Interned*/ String /*@Interned*/ [] subseq = Intern.intern (subseq_uninterned); @SuppressWarnings("nullness") // safe because map does no side effects Object ignore = // assignment just so there is a place to hang the @SuppressWarnings annotation internedStringSequenceAndIndices.put (sai, new WeakReference</*@PolyNull*/ /*@Interned*/ String /*@Interned*/ []>(subseq)); return subseq; } } /** * Data structure for storing triples of a sequence and start and * end indices, to represent a subsequence. Requires that the * sequence be interned. Used for interning the repeated finding * of subsequences on the same sequence. **/ private static final class SequenceAndIndices<T extends /*@Interned*/ Object> { public T seq; public int start; public int end; /** * @param seq An interned array **/ public SequenceAndIndices (T seq, int start, int end) { this.seq = seq; this.start = start; this.end = end; assert isInterned(seq); } @SuppressWarnings("unchecked") public boolean equals (/*@Nullable*/ Object other) { if (other instanceof SequenceAndIndices<?>) { @SuppressWarnings("unchecked") SequenceAndIndices<T> other_sai = (SequenceAndIndices<T>) other; return equals(other_sai); } else { return false; } } public boolean equals (SequenceAndIndices<T> other) { return ((this.seq == other.seq) && this.start == other.start && this.end == other.end); } public int hashCode() { return seq.hashCode() + start * 30 - end * 2; } // For debugging public String toString() { return "SAI(" + start + "," + end + ") from: " + ArraysMDE.toString(seq); } } /** * Hasher object which hashes and compares String[] objects according * to their contents. * @see Hasher **/ private static final class SequenceAndIndicesHasher<T extends /*@Interned*/ Object> implements Hasher { public boolean equals(Object a1, Object a2) { @SuppressWarnings("unchecked") SequenceAndIndices<T> sai1 = (SequenceAndIndices<T>) a1; @SuppressWarnings("unchecked") SequenceAndIndices<T> sai2 = (SequenceAndIndices<T>) a2; // The SAI objects are *not* interned, but the arrays inside them are. return sai1.equals(sai2); } public int hashCode(Object o) { return o.hashCode(); } } /////////////////////////////////////////////////////////////////////////// /// Interning arrays: old implementation #1 /// /// Interning arrays: old implmentation. /// The problem with this is that it doesn't release keys. // // I can also use java.util.Arrays.equals() to compare two arrays of base // // or Object type; but that doesn't do ordering. (It does properly deal // // with the possibility that the argument is null, which this doesn't // // right now. I may want to err in this implementation if the arguments // // are null or the lengths are not equal -- if I never mix arrays of // // different lengths.) // // Note: this comparator imposes orderings that are inconsistent with equals. // // That is, it may return 0 if the arrays are not equal (but do contain // // identical numbers). // static final class IntArrayComparator implements Comparator { // public int compare(Object o1, Object o2) { // if (o1 == o2) // return 0; // int[] a1 = (int[])o1; // int[] a2 = (int[])o2; // int tmp; // tmp = a1.length - a2.length; // if (tmp != 0) // return tmp; // for (int i=0; i<a1.length; i++) { // tmp = a1[i] - a2[i]; // if (tmp != 0) // return tmp; // } // return 0; // } // } // // Note: this comparator imposes orderings that are inconsistent with equals. // // That is, it may return 0 if the arrays are not equal (but do contain // // identical objects). // static final class ObjectArrayComparator implements Comparator { // public int compare(Object o1, Object o2) { // if (o1 == o2) // return 0; // Object[] a1 = (Object[])o1; // Object[] a2 = (Object[])o2; // int tmp; // tmp = a1.length - a2.length; // if (tmp != 0) // return tmp; // for (int i=0; i<a1.length; i++) { // tmp = a1[i].hashCode() - a2[i].hashCode(); // if (tmp != 0) // return tmp; // // I'm counting on the fact that hashCode returns a different // // number for each Object in the system. This checks that assumption. // assert a1[i].equals(a2[i]); // } // return 0; // } // } // private static TreeSet internedIntArrays; // private static TreeSet internedObjectArrays; // static { // internedIntArrays = new TreeSet(new IntArrayComparator()); // internedObjectArrays = new TreeSet(new ObjectArrayComparator()); // } // public static int[] internIntArray(int[] a) { // boolean added = internedIntArrays.add(a); // if (added) // return a; // else // return (int[])internedIntArrays.tailSet(a).first(); // } // // All the elements should already themselves be interned // public static Object[] internObjectArray(Object[] a) { // boolean added = internedObjectArrays.add(a); // if (added) // return a; // else // return (Object[])internedObjectArrays.tailSet(a).first(); // } /////////////////////////////////////////////////////////////////////////// /// Interning arrays: old implementation #2 /// /// This doesn't work because there are no references to the Wrappers, /// so all of the WeakHashMap elements are immediately removed. // // Create an ArrayWrapper which redefines equal (and hash) to act the // // way I want them to. // static final class IntArrayWrapper { // private int[] a; // IntArrayWrapper(int[] a) { // this.a = a; // } // boolean equals(IntArrayWrapper other) { // return java.util.Arrays.equals(a, other.a); // } // static final int FACTOR = 23; // public int hashCode() { // int result = 0; // for (int i=0; i<a.length; i++) { // result = result * FACTOR + a[i]; // } // return result; // } // } // static final class ObjectArrayWrapper { // private Object[] a; // ObjectArrayWrapper(Object[] a) { // this.a = a; // } // boolean equals(ObjectArrayWrapper other) { // return java.util.Arrays.equals(a, other.a); // } // static final int FACTOR = 23; // // Alternately, just xor all the element hash codes. // public int hashCode() { // int result = 0; // for (int i=0; i<a.length; i++) { // result = result * FACTOR + a[i].hashCode(); // } // return result; // } // } // // Map from an ArrayWrapper to the array (I don't need to map to a // // WeakReference because the array isn't the key of the WeakHashMap). // // non-private for debugging only // static WeakHashMap internedIntArrays; // static WeakHashMap internedObjectArrays; // // private static WeakHashMap internedIntArrays; // // private static WeakHashMap internedObjectArrays; // static { // internedIntArrays = new WeakHashMap(); // internedObjectArrays = new WeakHashMap(); // } // public static int[] internIntArray(int[] a) { // IntArrayWrapper w = new IntArrayWrapper(a); // Object result = internedIntArrays.get(w); // if (result != null) // return (int[])result; // else { // internedIntArrays.put(w, a); // return a; // } // } // // All the elements should already themselves be interned // public static Object[] internObjectArray(Object[] a) { // ObjectArrayWrapper w = new ObjectArrayWrapper(a); // Object result = internedObjectArrays.get(w); // if (result != null) // return (Object[])result; // else { // internedObjectArrays.put(w, a); // return a; // } // } }