All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.plumelib.util.Intern Maven / Gradle / Ivy

There is a newer version: 1.10.0
Show newest version
package org.plumelib.util;

import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Iterator;
import org.checkerframework.checker.index.qual.IndexFor;
import org.checkerframework.checker.index.qual.LTLengthOf;
import org.checkerframework.checker.index.qual.LessThan;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.index.qual.SameLen;
import org.checkerframework.checker.interning.qual.Interned;
import org.checkerframework.checker.lock.qual.GuardSatisfied;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.PolyNull;
import org.checkerframework.checker.signedness.qual.Signed;
import org.checkerframework.checker.signedness.qual.UnknownSignedness;
import org.checkerframework.common.value.qual.PolyValue;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.dataflow.qual.SideEffectFree;

/**
 * Utilities for interning objects. Interning is also known as canonicalization or hash-consing: it
 * returns a single representative object that {@link Object#equals} 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
 * {@code ==} instead of {@code .equals()} for comparisons.
 *
 * 

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 { /** This class is a collection of methods; it does not represent anything. */ private Intern() { throw new Error("do not instantiate"); } /** Whether assertions are enabled. */ private static boolean assertsEnabled = false; static { assert assertsEnabled = true; // Intentional side-effect!!! // Now assertsEnabled is set to the correct value } /////////////////////////////////////////////////////////////////////////// /// Strings /// /** * Replace each element of the array by its interned version. Side-effects the array, but also * returns it. * * @param a the array whose elements to intern in place * @return an interned version of a * @see String#intern */ @SuppressWarnings("interning") // side-effects the array in place (dangerous, but convenient) public static @Interned String @PolyValue @SameLen("#1") [] internStrings( String @PolyValue [] a) { for (int i = 0; i < a.length; i++) { if (a[i] != null) { a[i] = a[i].intern(); } } return a; } /////////////////////////////////////////////////////////////////////////// /// Testing interning /// /** * Returns true if the argument is interned (is canonical among all objects equal to itself). * * @param value the value to test for interning * @return true iff value is interned */ @SuppressWarnings({"interning"}) // interning implementation @Pure 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 { /** Create a new IntegerHasher. */ public IntegerHasher() {} @Override public boolean equals(Object a1, Object a2) { return a1.equals(a2); } @Override public int hashCode(@UnknownSignedness Object o) { @SuppressWarnings("signedness:cast.unsafe") // Signedness doesn't matter for hashCode(). Integer i = (@Signed 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 { /** Create a new LongHasher. */ public LongHasher() {} @Override public boolean equals(Object a1, Object a2) { return a1.equals(a2); } @Override public int hashCode(@UnknownSignedness Object o) { @SuppressWarnings("signedness:cast.unsafe") // Signedness doesn't matter for hashCode(). Long i = (@Signed Long) o; return i.intValue(); } } /** * Hasher object which hashes and compares int[] objects according to their contents. * * @see Hasher * @see Arrays#equals(int[], int[]) */ private static final class IntArrayHasher implements Hasher { /** Create a new IntArrayHasher. */ public IntArrayHasher() {} @Override public boolean equals(Object a1, Object a2) { return Arrays.equals((int[]) a1, (int[]) a2); } @Override public int hashCode(@UnknownSignedness Object o) { return Arrays.hashCode((int[]) o); } } /** * Hasher object which hashes and compares long[] objects according to their contents. * * @see Hasher * @see Arrays#equals (long[], long[]) */ private static final class LongArrayHasher implements Hasher { /** Create a new LongArrayHasher. */ public LongArrayHasher() {} @Override public boolean equals(Object a1, Object a2) { return Arrays.equals((long[]) a1, (long[]) a2); } @Override public int hashCode(@UnknownSignedness Object o) { return Arrays.hashCode((long[]) o); } } /** Multiplicative constant for use in hashing function. */ private static final int FACTOR = 23; /** Another multiplicative constant for use in hashing function. */ private static final double DOUBLE_FACTOR = 263; /** * Hasher object which hashes and compares Doubles. * * @see Hasher */ private static final class DoubleHasher implements Hasher { /** Create a new DoubleHasher. */ public DoubleHasher() {} @Override public boolean equals(Object a1, Object a2) { return a1.equals(a2); } @Override public int hashCode(@UnknownSignedness Object o) { Double d = (Double) o; return d.hashCode(); } } /** * Hasher object which hashes and compares double[] objects according to their contents. * * @see Hasher * @see Arrays#equals(Object[],Object[]) */ private static final class DoubleArrayHasher implements Hasher { /** Create a new DoubleArrayHasher. */ public DoubleArrayHasher() {} @Override public boolean equals(Object a1, Object a2) { // "Arrays.equals" considers +0.0 != -0.0. // Also, it gives inconsistent results (on different JVMs/classpaths?). // return 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; } @Override public int hashCode(@UnknownSignedness Object o) { double[] a = (double[]) o; // Not Arrays.hashCode(a), for consistency with equals method // immediately above. 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 * @see Arrays#equals */ private static final class StringArrayHasher implements Hasher { /** Create a new StringArrayHasher. */ public StringArrayHasher() {} @Override public boolean equals(Object a1, Object a2) { return Arrays.equals((String[]) a1, (String[]) a2); } @Override public int hashCode(@UnknownSignedness Object o) { return Arrays.hashCode((String[]) o); } } /** * Hasher object which hashes and compares Object[] objects according to their contents. * * @see Hasher * @see Arrays#equals(Object[], Object[]) */ private static final class ObjectArrayHasher implements Hasher { /** Create a new ObjectArrayHasher. */ public ObjectArrayHasher() {} @Override public boolean equals(Object a1, Object a2) { return Arrays.equals((@Nullable Object[]) a1, (@Nullable Object[]) a2); } @Override public int hashCode(@UnknownSignedness Object o) { return Arrays.hashCode((Object[]) o); } } // 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. /** All the interned Integers. */ private static WeakHasherMap<@Interned Integer, WeakReference<@Interned Integer>> internedIntegers; /** All the interned Longs. */ private static WeakHasherMap<@Interned Long, WeakReference<@Interned Long>> internedLongs; /** All the interned Int arrays. */ private static WeakHasherMap> internedIntArrays; /** All the interned Long arrays. */ private static WeakHasherMap> internedLongArrays; /** All the interned Doubles. */ private static WeakHasherMap<@Interned Double, WeakReference<@Interned Double>> internedDoubles; /** The interned NaN. */ private static @Interned Double internedDoubleNaN; /** The interned Double zero. */ private static @Interned Double internedDoubleZero; /** All the interned Double arrays. */ private static WeakHasherMap> internedDoubleArrays; /** All the interned String arrays. */ private static WeakHasherMap< @Nullable @Interned String @Interned [], WeakReference<@Nullable @Interned String @Interned []>> internedStringArrays; /** All the interned Object arrays. */ private static WeakHasherMap< @Nullable @Interned Object @Interned [], WeakReference<@Nullable @Interned Object @Interned []>> internedObjectArrays; /** All the interned Int subsequences. */ private static WeakHasherMap, WeakReference> internedIntSubsequence; /** All the interned Long subsequences. */ private static WeakHasherMap, WeakReference> internedLongSubsequence; /** All the interned Double subsequences. */ private static WeakHasherMap, WeakReference> internedDoubleSubsequence; /** All the interned Object subsequences. */ private static WeakHasherMap< Subsequence<@Nullable @Interned Object @Interned []>, WeakReference<@Nullable @Interned Object @Interned []>> internedObjectSubsequence; /** All the interned String subsequences. */ private static WeakHasherMap< Subsequence<@Nullable @Interned String @Interned []>, WeakReference<@Nullable @Interned String @Interned []>> internedStringSubsequence; static { internedIntegers = new WeakHasherMap<>(new IntegerHasher()); internedLongs = new WeakHasherMap<>(new LongHasher()); internedIntArrays = new WeakHasherMap<>(new IntArrayHasher()); internedLongArrays = new WeakHasherMap<>(new LongArrayHasher()); internedDoubles = new WeakHasherMap<>(new DoubleHasher()); internedDoubleNaN = Double.NaN; internedDoubleZero = 0.0; internedDoubleArrays = new WeakHasherMap<>(new DoubleArrayHasher()); internedStringArrays = new WeakHasherMap<>(new StringArrayHasher()); internedObjectArrays = new WeakHasherMap< @Nullable @Interned Object @Interned [], WeakReference<@Nullable @Interned Object @Interned []>>(new ObjectArrayHasher()); internedIntSubsequence = new WeakHasherMap<>(new SubsequenceHasher()); internedLongSubsequence = new WeakHasherMap<>(new SubsequenceHasher()); internedDoubleSubsequence = new WeakHasherMap<>(new SubsequenceHasher()); internedObjectSubsequence = new WeakHasherMap<>(new SubsequenceHasher<@Nullable @Interned Object @Interned []>()); internedStringSubsequence = new WeakHasherMap<>(new SubsequenceHasher<@Nullable @Interned String @Interned []>()); } /// For testing only /** * Returns the number of interned integers. For testing only. * * @return the number of interned integers */ static int numIntegers() { return internedIntegers.size(); } /** * Returns the number of interned longs. For testing only. * * @return the number of interned longs */ static int numLongs() { return internedLongs.size(); } /** * Returns the number of interned int arrays. For testing only. * * @return the number of interned int arrays */ static int numIntArrays() { return internedIntArrays.size(); } /** * Returns the number of interned long arrays. For testing only. * * @return the number of interned long arrays */ static int numLongArrays() { return internedLongArrays.size(); } /** * Returns the number of interned doubles. For testing only. * * @return the number of interned doubles */ static int numDoubles() { return internedDoubles.size(); } /** * Returns the number of interned double arrays. For testing only. * * @return the number of interned double arrays */ static int numDoubleArrays() { return internedDoubleArrays.size(); } /** * Returns the number of interned string arrays. For testing only. * * @return the number of interned string arrays */ static int numStringArrays() { return internedStringArrays.size(); } /** * Returns the number of interned object arrays. For testing only. * * @return the number of interned object arrays */ static int numObjectArrays() { return internedObjectArrays.size(); } /** * Returns all the interned integers. For testing only. * * @return all the interned integers */ static Iterator<@Interned Integer> integers() { return internedIntegers.keySet().iterator(); } /** * Returns all the interned longs. For testing only. * * @return all the interned longs */ static Iterator<@Interned Long> longs() { return internedLongs.keySet().iterator(); } /** * Returns all the interned int arrays. For testing only. * * @return all the interned int arrays */ static Iterator intArrays() { return internedIntArrays.keySet().iterator(); } /** * Returns all the interned long arrays. For testing only. * * @return all the interned long arrays */ static Iterator longArrays() { return internedLongArrays.keySet().iterator(); } /** * Returns all the interned doubles. For testing only. * * @return all the interned doubles */ static Iterator<@Interned Double> doubles() { return internedDoubles.keySet().iterator(); } /** * Returns all the interned double arrays. For testing only. * * @return all the interned double arrays */ static Iterator doubleArrays() { return internedDoubleArrays.keySet().iterator(); } /** * Returns all the interned string arrays. For testing only. * * @return all the interned string arrays */ static Iterator<@Nullable @Interned String @Interned []> stringArrays() { return internedStringArrays.keySet().iterator(); } /** * Returns all the interned object arrays. For testing only. * * @return all the interned object arrays */ static Iterator<@Nullable @Interned Object @Interned []> objectArrays() { return internedObjectArrays.keySet().iterator(); } /// End of testing methods /** * Interns a String. Delegates to the builtin String.intern() method, but handles {@code null}. * * @param a the string to intern; may be null * @return an interned version of the argument, or null if the argument was null */ @Pure public static @Interned @PolyNull @PolyValue @SameLen("#1") String intern( @PolyNull @PolyValue String a) { return (a == null) ? null : a.intern(); } /** * Interns a long. A no-op. Provided for completeness. * * @param l the long to intern * @return an interned version of the argument */ @Pure public static long intern(long l) { return l; } /** * Interns a double A no-op. Provided for completeness. * * @param d the double to intern * @return an interned version of the argument */ @Pure public static double intern(double d) { return d; } /** * Intern (canonicalize) an Integer. Return a canonical representation for the Integer. * * @param a an Integer to canonicalize * @return 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", "allcheckers:purity", "lock"}) // interning implementation @Pure public static @Interned Integer intern(Integer a) { WeakReference<@Interned Integer> lookup = internedIntegers.get(a); Integer result1 = (lookup != null) ? lookup.get() : null; if (result1 != null) { return result1; } else { @Interned Integer result = (@Interned Integer) a; internedIntegers.put(result, new WeakReference<>(result)); return result; } } // Not sure whether this convenience method is really worth it. /** * Returns an interned Integer with value i. * * @param i the value to intern * @return 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. * * @param s the string to parse * @return an interned Integer parsed from s */ public static @Interned Integer internedInteger(String s) { return intern(Integer.decode(s)); } /** * Intern (canonicalize) a Long. Return a canonical representation for the Long. * * @param a the value to intern * @return 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", "allcheckers:purity", "lock"}) @Pure public static @Interned Long intern(Long a) { WeakReference<@Interned Long> lookup = internedLongs.get(a); Long result1 = (lookup != null) ? lookup.get() : null; if (result1 != null) { return result1; } else { @Interned Long result = (@Interned Long) a; internedLongs.put(result, new WeakReference<>(result)); return result; } } // Not sure whether this convenience method is really worth it. /** * Returns an interned Long with value i. * * @param i the value to intern * @return an interned Integer 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. * * @param s the string to parse * @return an interned Long parsed from s */ 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[]. Return a canonical representation for the int[] array. Arrays * are compared according to their elements. * * @param a the array to canonicalize * @return a canonical representation for the int[] array */ @SuppressWarnings({"interning", "allcheckers:purity", "lock"}) @Pure public static int @Interned @PolyValue @SameLen("#1") [] intern(int @PolyValue [] a) { // Throwable stack = new Throwable("debug traceback"); // stack.fillInStackTrace(); // stack.printStackTrace(); WeakReference lookup = internedIntArrays.get(a); @SuppressWarnings({ "samelen:assignment", // for this map, get() can be annotated as // @SameLen("#1") "value" // for this map, get() can be annotated as @PolyAll (except not interning); also see // https://github.com/kelloggm/checker-framework/issues/177 }) int @PolyValue @SameLen("a") [] result1 = (lookup != null) ? lookup.get() : null; if (result1 != null) { return result1; } else { @Interned int[] result = (int @Interned @PolyValue []) a; internedIntArrays.put(result, new WeakReference<>(result)); return result; } } /** * Intern (canonicalize) a long[]. Return a canonical representation for the long[] array. Arrays * are compared according to their elements. * * @param a the array to canonicalize * @return a canonical representation for the long[] array */ @SuppressWarnings({"interning", "allcheckers:purity", "lock"}) @Pure public static long @Interned @PolyValue @SameLen("#1") [] intern(long @PolyValue [] a) { // System.out.printf("intern %s %s long[] %s%n", a.getClass(), // a, Arrays.toString (a)); WeakReference lookup = internedLongArrays.get(a); @SuppressWarnings({ "samelen:assignment", // for this map, get() can be annotated as // @SameLen("#1") "value" // for this map, get() can be annotated as @PolyAll (except not interning); also see // https://github.com/kelloggm/checker-framework/issues/177 }) long @PolyValue @SameLen("a") [] result1 = (lookup != null) ? lookup.get() : null; if (result1 != null) { return result1; } else { @Interned long[] result = (long @Interned @PolyValue []) a; internedLongArrays.put(result, new WeakReference<>(result)); return result; } } /** * Intern (canonicalize) a Double. Return a canonical representation for the Double. * * @param a the Double to canonicalize * @return 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", "allcheckers:purity", "lock"}) @Pure 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); Double result1 = (lookup != null) ? lookup.get() : null; if (result1 != null) { return result1; } else { @Interned Double result = (@Interned Double) a; internedDoubles.put(result, new WeakReference<>(result)); return result; } } // Not sure whether this convenience method is really worth it. /** * Returns an interned Double with value i. * * @param d the value to intern * @return an interned Double with value d */ 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. * * @param s the string to parse * @return an interned Double parsed from s */ 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[]. Return a canonical representation for the double[] array. * Arrays are compared according to their elements. * * @param a the array to canonicalize * @return a canonical representation for the double[] array */ @SuppressWarnings({"interning", "allcheckers:purity", "lock"}) @Pure public static double @Interned @PolyValue @SameLen("#1") [] intern(double @PolyValue [] a) { WeakReference lookup = internedDoubleArrays.get(a); @SuppressWarnings({ "samelen:assignment", // for this map, get() can be annotated as // @SameLen("#1") "value" // for this map, get() can be annotated as @PolyAll (except not interning); also see // https://github.com/kelloggm/checker-framework/issues/177 }) double @PolyValue @SameLen("a") [] result1 = (lookup != null) ? lookup.get() : null; if (result1 != null) { return result1; } else { @Interned double[] result = (double @Interned @PolyValue []) a; internedDoubleArrays.put(result, new WeakReference<>(result)); return result; } } /** * Intern (canonicalize) a String[]. Return a canonical representation for the String[] array. * Arrays are compared according to their elements' equals() methods. * * @param a the array to canonicalize. Its elements should already be interned. * @return a canonical representation for the String[] array */ @SuppressWarnings({ // Java warnings "cast", // Checker Framework warnings "interning", // interns its argument "allcheckers:purity", "lock", // Error Prone Warnings "ReferenceEquality" }) // cast is redundant (except in JSR 308) @Pure public static @PolyNull @Interned String @Interned @PolyValue @SameLen("#1") [] intern( @PolyNull @Interned String @PolyValue [] a) { // Make sure each element is already interned if (assertsEnabled) { for (int k = 0; k < a.length; k++) { if (a[k] != Intern.intern(a[k])) { throw new IllegalArgumentException(); } } } WeakReference<@Nullable @Interned String @Interned []> lookup = internedStringArrays.get(a); @Nullable @Interned String @Interned [] result = (lookup != null) ? lookup.get() : null; if (result == null) { result = (@Nullable @Interned String @Interned []) a; internedStringArrays.put(result, new WeakReference<>(result)); } @SuppressWarnings({ "nullness", // for this map, get() can be annotated as @PolyAll (except not interning); also // see https://github.com/kelloggm/checker-framework/issues/177 "samelen:assignment", // for this map, get() can be annotated as // @SameLen("#1") "value" // for this map, get() can be annotated as @PolyAll (except not interning); also see // https://github.com/kelloggm/checker-framework/issues/177 }) @PolyNull @Interned String @Interned @PolyValue @SameLen("a") [] polyresult = result; return polyresult; } /** * Intern (canonicalize) an Object[]. Return 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. * * @param a the array to canonicalize * @return a canonical representation for the Object[] array */ @SuppressWarnings({ "interning", // interns its argument "allcheckers:purity", "lock", "cast" }) // cast is redundant (except in JSR 308) @Pure public static @PolyNull @Interned Object @Interned @PolyValue @SameLen("#1") [] intern( @PolyNull @Interned @PolyValue Object[] a) { WeakReference<@Nullable @Interned Object @Interned []> lookup = internedObjectArrays.get(a); @Nullable @Interned Object @Interned [] result = (lookup != null) ? lookup.get() : null; if (result == null) { result = (@Nullable @Interned Object @Interned []) a; internedObjectArrays.put(result, new WeakReference<>(result)); } @SuppressWarnings({ "nullness", // for this map, get() can be annotated as @PolyAll (except not interning); also // see https://github.com/kelloggm/checker-framework/issues/177 "samelen:assignment", // for this map, get() can be annotated as // @SameLen("#1") "value" // for this map, get() can be annotated as @PolyAll (except not interning); also see // https://github.com/kelloggm/checker-framework/issues/177 }) // PolyNull/PolyValue: value = parameter a, so same type & nullness as for parameter a @PolyNull @Interned Object @Interned @PolyValue @SameLen("a") [] polyresult = result; return polyresult; } /** * Convenience method to intern an Object when we don't know its run-time type. Its run-time 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. * * @param a an Object to canonicalize * @return a canonical version of a */ @Pure 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[]) { @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[]) { @Interned Object[] asArray = (@Interned Object[]) a; return intern(asArray); } else { throw new IllegalArgumentException( "Arguments of type " + a.getClass() + " cannot be interned"); } } /** * Returns an interned subsequence of seq from start (inclusive) to end (exclusive). The argument * seq should already be interned. * *

The result is the same as computing the subsequence and then interning it, but this method * is more efficient: if the subsequence is already interned, it avoids computing the subsequence. * *

For example, 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 ==. * * @param seq the interned sequence whose subsequence should be computed and interned * @param start the index of the start of the subsequence to compute and intern * @param end the index of the end of the subsequence to compute and intern * @return a subsequence of seq from start to end that is interned */ public static int @Interned [] internSubsequence( int @Interned [] seq, @IndexFor("#1") @LessThan("#3") int start, @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int end) { if (assertsEnabled && !Intern.isInterned(seq)) { throw new IllegalArgumentException(); } Subsequence sai = new Subsequence<>(seq, start, end); WeakReference lookup = internedIntSubsequence.get(sai); int[] result1 = (lookup != null) ? lookup.get() : null; if (result1 != null) { return result1; } else { int[] subseqUninterned = ArraysPlume.subarray(seq, start, end - start); int @Interned [] subseq = Intern.intern(subseqUninterned); internedIntSubsequence.put(sai, new WeakReference<>(subseq)); return subseq; } } /** * Returns a subsequence of seq from start to end that is interned. * * @param seq the interned sequence whose subsequence should be computed and interned * @param start the index of the start of the subsequence to compute and intern * @param end the index of the end of the subsequence to compute and intern * @return a subsequence of seq from start to end that is interned * @see #internSubsequence(int[], int, int) */ @SuppressWarnings({"allcheckers:purity", "lock"}) // interning logic @Pure public static long @Interned [] internSubsequence( long @Interned [] seq, @IndexFor("#1") @LessThan("#3") int start, @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int end) { if (assertsEnabled && !Intern.isInterned(seq)) { throw new IllegalArgumentException(); } Subsequence sai = new Subsequence<>(seq, start, end); WeakReference lookup = internedLongSubsequence.get(sai); long[] result1 = (lookup != null) ? lookup.get() : null; if (result1 != null) { return result1; } else { long[] subseqUninterned = ArraysPlume.subarray(seq, start, end - start); long @Interned [] subseq = Intern.intern(subseqUninterned); internedLongSubsequence.put(sai, new WeakReference<>(subseq)); return subseq; } } /** * Returns a subsequence of seq from start to end that is interned. * * @param seq the interned sequence whose subsequence should be computed and interned * @param start the index of the start of the subsequence to compute and intern * @param end the index of the end of the subsequence to compute and intern * @return a subsequence of seq from start to end that is interned * @see #internSubsequence(int[], int, int) */ @SuppressWarnings({"allcheckers:purity", "lock"}) // interning logic @Pure public static double @Interned [] internSubsequence( double @Interned [] seq, @IndexFor("#1") @LessThan("#3") int start, @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int end) { if (assertsEnabled && !Intern.isInterned(seq)) { throw new IllegalArgumentException(); } Subsequence sai = new Subsequence<>(seq, start, end); WeakReference lookup = internedDoubleSubsequence.get(sai); double[] result1 = (lookup != null) ? lookup.get() : null; if (result1 != null) { return result1; } else { double[] subseqUninterned = ArraysPlume.subarray(seq, start, end - start); double @Interned [] subseq = Intern.intern(subseqUninterned); internedDoubleSubsequence.put(sai, new WeakReference<>(subseq)); return subseq; } } /** * Returns a subsequence of seq from start to end that is interned. * * @param seq the interned sequence whose subsequence should be computed and interned * @param start the index of the start of the subsequence to compute and intern * @param end the index of the end of the subsequence to compute and intern * @return a subsequence of seq from start to end that is interned * @see #internSubsequence(int[], int, int) */ @SuppressWarnings({"allcheckers:purity", "lock"}) // interning logic @Pure public static @PolyNull @Interned Object @Interned [] internSubsequence( @PolyNull @Interned Object @Interned [] seq, @IndexFor("#1") @LessThan("#3") int start, @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int end) { if (assertsEnabled && !Intern.isInterned(seq)) { throw new IllegalArgumentException(); } Subsequence<@PolyNull @Interned Object @Interned []> sai = new Subsequence<@PolyNull @Interned Object @Interned []>(seq, start, end); @SuppressWarnings("nullness") // same nullness as key WeakReference<@PolyNull @Interned Object @Interned []> lookup = internedObjectSubsequence.get(sai); @PolyNull @Interned Object[] result1 = (lookup != null) ? lookup.get() : null; if (result1 != null) { return result1; } else { @PolyNull @Interned Object[] subseqUninterned = ArraysPlume.subarray(seq, start, end - start); @PolyNull @Interned Object @Interned [] subseq = Intern.intern(subseqUninterned); @SuppressWarnings({"nullness", "UnusedVariable"}) // safe because map does no side effects Object ignore = // assignment just so there is a place to hang the @SuppressWarnings annotation internedObjectSubsequence.put(sai, new WeakReference<>(subseq)); return subseq; } } /** * Returns a subsequence of seq from start to end that is interned. * * @param seq the interned sequence whose subsequence should be computed and interned * @param start the index of the start of the subsequence to compute and intern * @param end the index of the end of the subsequence to compute and intern * @return a subsequence of seq from start to end that is interned * @see #internSubsequence(int[], int, int) */ @Pure @SuppressWarnings({"allcheckers:purity", "lock"}) // interning logic public static @PolyNull @Interned String @Interned [] internSubsequence( @PolyNull @Interned String @Interned [] seq, @IndexFor("#1") @LessThan("#3") int start, @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int end) { if (assertsEnabled && !Intern.isInterned(seq)) { throw new IllegalArgumentException(); } Subsequence<@PolyNull @Interned String @Interned []> sai = new Subsequence<@PolyNull @Interned String @Interned []>(seq, start, end); @SuppressWarnings("nullness") // same nullness as key WeakReference<@PolyNull @Interned String @Interned []> lookup = internedStringSubsequence.get(sai); @PolyNull @Interned String[] result1 = (lookup != null) ? lookup.get() : null; if (result1 != null) { return result1; } else { @PolyNull @Interned String[] subseqUninterned = ArraysPlume.subarray(seq, start, end - start); @PolyNull @Interned String @Interned [] subseq = Intern.intern(subseqUninterned); @SuppressWarnings({"nullness", "UnusedVariable"}) // safe because map does no side effects Object ignore = // assignment just so there is a place to hang the @SuppressWarnings annotation internedStringSubsequence.put(sai, new WeakReference<>(subseq)); return subseq; } } /** * A subsequence view on a sequence. Actually, this imposes no semantics. It just has 3 fields: an * interned sequence, a start index, and an end index. Requires that the sequence be interned. * Used for interning the repeated finding of subsequences on the same sequence. * * @param the type of elements of the sequence */ private static final class Subsequence { /** The full sequence. The Subsequence object represents part of this sequence. */ public T seq; /** The start index, inclusive. */ public @NonNegative int start; // TODO: inclusive or exclusive? /** The end index. */ public int end; /** * Creates a subsequence view. * * @param seq an interned array * @param start the start index * @param end the end index */ public Subsequence(T seq, @NonNegative int start, int end) { if (assertsEnabled && !Intern.isInterned(seq)) { throw new IllegalArgumentException(); } this.seq = seq; this.start = start; this.end = end; } @SuppressWarnings("unchecked") @Pure @Override public boolean equals( @GuardSatisfied Subsequence this, @GuardSatisfied @Nullable Object other) { if (other instanceof Subsequence) { @SuppressWarnings("unchecked") Subsequence otherSai = (Subsequence) other; return equalsSubsequence(otherSai); } else { return false; } } /** * Returns true if this object equals the given one. * * @param other the sequence to compare to * @return true if this object equals {@code other} */ @Pure public boolean equalsSubsequence( @GuardSatisfied Subsequence this, @GuardSatisfied Subsequence other) { return ((this.seq == other.seq) && this.start == other.start && this.end == other.end); } @Pure @Override public int hashCode(@GuardSatisfied Subsequence this) { return seq.hashCode() + start * 30 - end * 2; } // For debugging @SideEffectFree @Override public String toString(@GuardSatisfied Subsequence this) { return "SAI(" + start + "," + end + ") from: " + ArraysPlume.toString(seq); } } /** * Hasher object which hashes and compares sequences according to their contents. * * @param the type of the elements of the sequences * @see Hasher */ private static final class SubsequenceHasher implements Hasher { /** Create a new SubsequenceHasher. */ public SubsequenceHasher() {} @Override public boolean equals(Object a1, Object a2) { @SuppressWarnings("unchecked") Subsequence sai1 = (Subsequence) a1; @SuppressWarnings("unchecked") Subsequence sai2 = (Subsequence) a2; // The SAI objects are *not* interned, but the arrays inside them are. return sai1.equals(sai2); } @Override public int hashCode(@UnknownSignedness Object o) { return o.hashCode(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy