| author | szarnekow | 2009-04-01 09:50:52 (EDT) |
|---|---|---|
| committer | sefftinge | 2009-04-01 09:50:52 (EDT) |
| commit | 91b8c1febce4eb39febd02563837e406e38fc7fd (patch) (side-by-side diff) | |
| tree | 4eabfdd7edf67dd314a76c0379b4a9cd3039b825 | |
| parent | bcc5ee7c8916a5e9f10ab2ebaed945e20a75b4a2 (diff) | |
| download | org.eclipse.xtext-91b8c1febce4eb39febd02563837e406e38fc7fd.zip org.eclipse.xtext-91b8c1febce4eb39febd02563837e406e38fc7fd.tar.gz org.eclipse.xtext-91b8c1febce4eb39febd02563837e406e38fc7fd.tar.bz2 | |
google collections - initial commit
99 files changed, 25659 insertions, 0 deletions
diff --git a/plugins/com.google.collect/.classpath b/plugins/com.google.collect/.classpath new file mode 100644 index 0000000..304e861 --- a/dev/null +++ b/plugins/com.google.collect/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/plugins/com.google.collect/.project b/plugins/com.google.collect/.project new file mode 100644 index 0000000..9ddfd46 --- a/dev/null +++ b/plugins/com.google.collect/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>com.google.collect</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/plugins/com.google.collect/META-INF/MANIFEST.MF b/plugins/com.google.collect/META-INF/MANIFEST.MF new file mode 100644 index 0000000..e437507 --- a/dev/null +++ b/plugins/com.google.collect/META-INF/MANIFEST.MF @@ -0,0 +1,9 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %bundleName +Bundle-SymbolicName: com.google.collect +Bundle-Version: 0.8.0.qualifier +Bundle-Vendor: %bundleVendor +Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Export-Package: com.google.common.base, + com.google.common.collect diff --git a/plugins/com.google.collect/build.properties b/plugins/com.google.collect/build.properties new file mode 100644 index 0000000..303135f --- a/dev/null +++ b/plugins/com.google.collect/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.properties
\ No newline at end of file diff --git a/plugins/com.google.collect/plugin.properties b/plugins/com.google.collect/plugin.properties new file mode 100755 index 0000000..9d226fb --- a/dev/null +++ b/plugins/com.google.collect/plugin.properties @@ -0,0 +1,3 @@ +#Properties file for com.google.collection +bundleVendor = google.com +bundleName = Google Collections
\ No newline at end of file diff --git a/plugins/com.google.collect/src/com/google/common/base/FinalizablePhantomReference.java b/plugins/com.google.collect/src/com/google/common/base/FinalizablePhantomReference.java new file mode 100644 index 0000000..bed130a --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/FinalizablePhantomReference.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import java.lang.ref.PhantomReference; + +/** + * Phantom reference with a {@code finalizeReferent()} method which a + * background thread invokes after the garbage collector reclaims the + * referent. This is a simpler alternative to using a {@link + * java.lang.ref.ReferenceQueue}. + * + * @author Bob Lee + */ +public abstract class FinalizablePhantomReference<T> + extends PhantomReference<T> implements FinalizableReference { + + protected FinalizablePhantomReference(T referent) { + super(referent, FinalizableReferenceQueue.getInstance()); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/base/FinalizableReference.java b/plugins/com.google.collect/src/com/google/common/base/FinalizableReference.java new file mode 100644 index 0000000..7fc443e --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/FinalizableReference.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +/** + * Package-private interface implemented by references that have code to run + * after garbage collection of their referents. + * + * @author Bob Lee + */ +interface FinalizableReference { + + /** + * Invoked on a background thread after the referent has been garbage + * collected. + */ + void finalizeReferent(); +} diff --git a/plugins/com.google.collect/src/com/google/common/base/FinalizableReferenceQueue.java b/plugins/com.google.collect/src/com/google/common/base/FinalizableReferenceQueue.java new file mode 100644 index 0000000..d34990d --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/FinalizableReferenceQueue.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Starts a background thread that cleans up after reclaimed referents. + * + * @author Bob Lee + */ +class FinalizableReferenceQueue extends ReferenceQueue<Object> { + + private static final Logger logger = + Logger.getLogger(FinalizableReferenceQueue.class.getName()); + + /** + * Returns the singleton instance. + */ + public static ReferenceQueue<Object> getInstance() { + return LazyInstanceHolder.queue; + } + + private static class LazyInstanceHolder { + static final ReferenceQueue<Object> queue = new FinalizableReferenceQueue(); + } + + private FinalizableReferenceQueue() { + start(); + } + + void start() { + Thread thread = new Thread(getClass().getSimpleName()) { + @Override public void run() { + while (true) { + try { + cleanUp(remove()); + } catch (InterruptedException e) { /* ignore */ } + } + } + }; + thread.setDaemon(true); + thread.start(); + } + + void cleanUp(Reference<?> reference) { + try { + ((FinalizableReference) reference).finalizeReferent(); + } catch (Throwable t) { + logger.log(Level.SEVERE, "Error cleaning up after reference.", t); + } + } +} diff --git a/plugins/com.google.collect/src/com/google/common/base/FinalizableSoftReference.java b/plugins/com.google.collect/src/com/google/common/base/FinalizableSoftReference.java new file mode 100644 index 0000000..1d95523 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/FinalizableSoftReference.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import java.lang.ref.SoftReference; + +/** + * Soft reference with a {@code finalizeReferent()} method which a background + * thread invokes after the garbage collector reclaims the referent. This is a + * simpler alternative to using a {@link java.lang.ref.ReferenceQueue}. + * + * @author Bob Lee + */ +public abstract class FinalizableSoftReference<T> extends SoftReference<T> + implements FinalizableReference { + + protected FinalizableSoftReference(T referent) { + super(referent, FinalizableReferenceQueue.getInstance()); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/base/FinalizableWeakReference.java b/plugins/com.google.collect/src/com/google/common/base/FinalizableWeakReference.java new file mode 100644 index 0000000..413a865 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/FinalizableWeakReference.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import java.lang.ref.WeakReference; + +/** + * Weak reference with a {@code finalizeReferent()} method which a background + * thread invokes after the garbage collector reclaims the referent. This is a + * simpler alternative to using a {@link java.lang.ref.ReferenceQueue}. + * + * @author Bob Lee + */ +public abstract class FinalizableWeakReference<T> extends WeakReference<T> + implements FinalizableReference { + + protected FinalizableWeakReference(T referent) { + super(referent, FinalizableReferenceQueue.getInstance()); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/base/Function.java b/plugins/com.google.collect/src/com/google/common/base/Function.java new file mode 100644 index 0000000..5c95c4f --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/Function.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +/** + * A transformation from one object to another. For example, a + * {@code StringToIntegerFunction} may implement + * <code>Function<String,Integer></code> and transform integers in + * {@code String} format to {@code Integer} format. + * + * <p>The transformation on the source object does not necessarily result in + * an object of a different type. For example, a + * {@code FarenheitToCelsiusFunction} may implement + * <code>Function<Float,Float></code>. + * + * <p>Implementations which may cause side effects upon evaluation are strongly + * encouraged to state this fact clearly in their API documentation. + * + * @param <F> the type of the function input + * @param <T> the type of the function output + * @author Kevin Bourrillion + * @author Scott Bonneau + */ +public interface Function<F, T> { + + /** + * Applies the function to an object of type {@code F}, resulting in an object + * of type {@code T}. Note that types {@code F} and {@code T} may or may not + * be the same. + * + * @param from the source object + * @return the resulting object + */ + T apply(@Nullable F from); + + /** + * Indicates whether some other object is equal to this {@code Function}. + * This method can return {@code true} <i>only</i> if the specified object is + * also a {@code Function} and, for every input object {@code o}, it returns + * exactly the same value. Thus, {@code function1.equals(function2)} implies + * that either {@code function1.apply(o)} and {@code function2.apply(o)} are + * both null, or {@code function1.apply(o).equals(function2.apply(o))}. + * + * <p>Note that it is always safe <em>not</em> to override + * {@link Object#equals}. + */ + boolean equals(@Nullable Object obj); +} diff --git a/plugins/com.google.collect/src/com/google/common/base/Functions.java b/plugins/com.google.collect/src/com/google/common/base/Functions.java new file mode 100644 index 0000000..724b492 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/Functions.java @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.Serializable; +import java.util.Map; + +/** + * Useful functions. + * + * @author Mike Bostock + * @author Vlad Patryshev + * @author Jared Levy + */ +public final class Functions { + private Functions() { } + + /** + * A function that calls {@code toString()} on its argument. This function + * does not accept nulls; it will throw a {@link NullPointerException} when + * applied to {@code null}. + * + * <p>TODO: Consider deprecating this in favor of {@link #toStringFunction()}. + */ + public static final Function<Object, String> TO_STRING = + ToStringFunction.INSTANCE; + + /** + * Returns a function that calls {@code toString()} on its argument. The + * function does not accept nulls; it will throw a + * {@link NullPointerException} when applied to {@code null}. + */ + @SuppressWarnings("unchecked") // see comment below + public static <F> Function<F, String> toStringFunction() { + /* + * Function<F, T> is contravariant on F, so this is essentially a widening + * cast. Note: IntelliJ incorrectly colors the following line red. + */ + return (Function<F, String>) ToStringFunction.INSTANCE; + } + + // enum singleton pattern + private enum ToStringFunction implements Function<Object, String> { + INSTANCE; + + public String apply(Object o) { + return o.toString(); + } + + @Override public String toString() { + return "toString"; + } + } + + /** + * Returns a function that determines the hash code of its argument. For null + * arguments, the function returns 0, for consistency with the hash code + * calculations in the Java Collections classes. + */ + public static Function<Object, Integer> toHashCode() { + return HashCodeFunction.INSTANCE; + } + + // enum singleton pattern + private enum HashCodeFunction implements Function<Object, Integer> { + INSTANCE; + + public Integer apply(Object o) { + return (o == null) ? 0 : o.hashCode(); + } + + @Override public String toString() { + return "hashCode"; + } + } + + // enum singleton pattern + private enum TrimStringFunction implements Function<String, String> { + INSTANCE; + + public String apply(String string) { + return string.trim(); + } + + @Override public String toString() { + return "String.trim"; + } + } + + /** + * Returns the identity function. + */ + @SuppressWarnings("unchecked") + public static <E> Function<E, E> identity() { + return (Function<E, E>) IdentityFunction.INSTANCE; + } + + // enum singleton pattern + private enum IdentityFunction implements Function<Object, Object> { + INSTANCE; + + public Object apply(Object o) { + return o; + } + + @Override public String toString() { + return "identity"; + } + } + + /** + * Returns a function which performs a map lookup. + * + * <p>The difference between a map and a function is that a map is defined on + * a set of keys, while a function is defined on all inputs of the correct + * type. The function created by this method returns {@code null} for all + * inputs that do not belong to the map's key set. + * + * @param map source map that determines the function behavior + * @return function that returns {@code map.get(a)} for each {@code a} + */ + public static <A, B> Function<A, B> forMap( + final Map<? super A, ? extends B> map) { + return new FunctionForMapNoDefault<A, B>(map); + } + + private static class FunctionForMapNoDefault<A, B> + implements Function<A, B>, Serializable { + private final Map<? super A, ? extends B> map; + + public FunctionForMapNoDefault( + Map<? super A, ? extends B> map) { + this.map = checkNotNull(map); + } + public B apply(A a) { + return map.get(a); + } + @Override public boolean equals(Object o) { + if (o instanceof FunctionForMapNoDefault) { + FunctionForMapNoDefault<?, ?> that = (FunctionForMapNoDefault<?, ?>) o; + return map.equals(that.map); + } + return false; + } + @Override public int hashCode() { + return map.hashCode(); + } + @Override public String toString() { + return "forMap(" + map + ")"; + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a function which performs a map lookup with a default value. The + * function created by this method returns {@code defaultValue} for all + * inputs that do not belong to the map's key set. + * + * @param map source map that determines the function behavior + * @param defaultValue the value to return for inputs that aren't map keys + * @return function that returns {@code map.get(a)} when {@code a} is a key, + * or {@code defaultValue} otherwise + */ + public static <A, B> Function<A, B> forMap( + Map<? super A, ? extends B> map, @Nullable final B defaultValue) { + if (defaultValue == null) { + return forMap(map); + } + return new ForMapWithDefault<A, B>(map, defaultValue); + } + + private static class ForMapWithDefault<A, B> + implements Function<A, B>, Serializable { + private final Map<? super A, ? extends B> map; + private final B defaultValue; + + public ForMapWithDefault(Map<? super A, ? extends B> map, B defaultValue) { + this.map = checkNotNull(map); + this.defaultValue = defaultValue; + } + public B apply(A a) { + return map.containsKey(a) ? map.get(a) : defaultValue; + } + @Override public boolean equals(Object o) { + if (o instanceof ForMapWithDefault) { + ForMapWithDefault<?, ?> that = (ForMapWithDefault<?, ?>) o; + return map.equals(that.map) && defaultValue.equals(that.defaultValue); + } + return false; + } + @Override public int hashCode() { + return map.hashCode() + defaultValue.hashCode(); + } + @Override public String toString() { + return "forMap(" + map + ", defaultValue=" + defaultValue + ")"; + } + private static final long serialVersionUID = 0; + } + + /** + * Returns the composition of two functions. For {@code f: A->B} and + * {@code g: B->C}, composition is defined as the function h such that + * {@code h(a) == g(f(a))} for each {@code a}. + * + * @see <a href="//en.wikipedia.org/wiki/Function_composition"> + * function composition</a> + * + * @param g the second function to apply + * @param f the first function to apply + * @return the composition of {@code f} and {@code g} + */ + public static <A, B, C> Function<A, C> compose( + final Function<? super B, ? extends C> g, + final Function<? super A, ? extends B> f) { + return new FunctionComposition<A, B, C>(g, f); + } + + private static class FunctionComposition<A, B, C> + implements Function<A, C>, Serializable { + private final Function<? super B, ? extends C> g; + private final Function<? super A, ? extends B> f; + + public FunctionComposition(Function<? super B, ? extends C> g, + Function<? super A, ? extends B> f) { + this.g = checkNotNull(g); + this.f = checkNotNull(f); + } + public C apply(A a) { + return g.apply(f.apply(a)); + } + @Override public boolean equals(Object obj) { + if (obj instanceof FunctionComposition) { + FunctionComposition<?, ?, ?> that = (FunctionComposition<?, ?, ?>) obj; + return f.equals(that.f) && g.equals(that.g); + } + return false; + } + + @Override public int hashCode() { + /* + * TODO: To leave the door open for future enhancement, this + * calculation should be coordinated with the hashCode() method of the + * corresponding composition method in Predicates. To construct the + * composition: + * predicate(function2(function1(x))) + * + * There are two different ways of composing it: + * compose(predicate, compose(function2, function1)) + * compose(compose(predicate, function2), function1) + * + * It would be nice if these could be equal. + */ + return f.hashCode() ^ g.hashCode(); + } + @Override public String toString() { + return g.toString() + "(" + f.toString() + ")"; + } + private static final long serialVersionUID = 0; + } + + /** + * Creates a function that returns the same boolean output as the given + * predicate for all inputs. + */ + public static <T> Function<T, Boolean> forPredicate( + Predicate<? super T> predicate) { + return new PredicateFunction<T>(predicate); + } + + /** @see Functions#forPredicate */ + private static class PredicateFunction<T> + implements Function<T, Boolean>, Serializable { + private final Predicate<? super T> predicate; + + private PredicateFunction(Predicate<? super T> predicate) { + this.predicate = checkNotNull(predicate); + } + + public Boolean apply(T t) { + return predicate.apply(t); + } + @Override public boolean equals(Object obj) { + if (obj instanceof PredicateFunction) { + PredicateFunction<?> that = (PredicateFunction<?>) obj; + return predicate.equals(that.predicate); + } + return false; + } + @Override public int hashCode() { + return predicate.hashCode(); + } + @Override public String toString() { + return "forPredicate(" + predicate + ")"; + } + private static final long serialVersionUID = 0; + } + + /** + * Creates a function that returns {@code value} for any input. + * + * @param value the constant value for the function to return + * @return a function that always returns {@code value} + */ + public static <E> Function<Object, E> constant(@Nullable E value) { + return new ConstantFunction<E>(value); + } + + private static class ConstantFunction<E> + implements Function<Object, E>, Serializable { + private final E value; + + public ConstantFunction(@Nullable E value) { + this.value = value; + } + public E apply(Object from) { + return value; + } + @Override public boolean equals(Object obj) { + if (obj instanceof ConstantFunction) { + ConstantFunction<?> that = (ConstantFunction<?>) obj; + return Objects.equal(value, that.value); + } + return false; + } + @Override public int hashCode() { + return (value == null) ? 0 : value.hashCode(); + } + @Override public String toString() { + return "constant(" + value + ")"; + } + private static final long serialVersionUID = 0; + } +} diff --git a/plugins/com.google.collect/src/com/google/common/base/Join.java b/plugins/com.google.collect/src/com/google/common/base/Join.java new file mode 100644 index 0000000..2c448d9 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/Join.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.IOException; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Utility for joining pieces of text separated by a delimiter. It can handle + * iterators, collections, arrays, and varargs, and can append to any + * {@link Appendable} or just return a {@link String}. For example, + * {@code join(":", "a", "b", "c")} returns {@code "a:b:c"}. + * + * <p>All methods of this class throw {@link NullPointerException} when a value + * of {@code null} is supplied for any parameter. The elements within the + * collection, iterator, array, or varargs parameter list <i>may</i> be null -- + * these will be represented in the output by the string {@code "null"}. + * + * @author Kevin Bourrillion + */ +public final class Join { + private Join() {} + + /** + * Returns a string containing the {@code tokens}, converted to strings if + * necessary, separated by {@code delimiter}. If {@code tokens} is empty, it + * returns an empty string. + * + * <p>Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param tokens objects to append + * @return a string consisting of the joined elements + */ + public static String join(String delimiter, Iterable<?> tokens) { + return join(delimiter, tokens.iterator()); + } + + /** + * Returns a string containing the {@code tokens}, converted to strings if + * necessary, separated by {@code delimiter}. If {@code tokens} is empty, it + * returns an empty string. + * + * <p>Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param tokens objects to append + * @return a string consisting of the joined elements + */ + public static String join(String delimiter, Object[] tokens) { + return join(delimiter, Arrays.asList(tokens)); + } + + /** + * Returns a string containing the {@code tokens}, converted to strings if + * necessary, separated by {@code delimiter}. + * + * <p>Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param firstToken the first object to append + * @param otherTokens subsequent objects to append + * @return a string consisting of the joined elements + */ + public static String join( + String delimiter, @Nullable Object firstToken, Object... otherTokens) { + checkNotNull(otherTokens); + return join(delimiter, asList(firstToken, otherTokens)); + } + + /** + * Returns a string containing the {@code tokens}, converted to strings if + * necessary, separated by {@code delimiter}. If {@code tokens} is empty, it + * returns an empty string. + * + * <p>Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param tokens objects to append + * @return a string consisting of the joined elements + */ + public static String join(String delimiter, Iterator<?> tokens) { + StringBuilder sb = new StringBuilder(); + join(sb, delimiter, tokens); + return sb.toString(); + } + + /** + * Returns a string containing the contents of {@code map}, with entries + * separated by {@code entryDelimiter}, and keys and values separated with + * {@code keyValueSeparator}. + * + * <p>Each key and value will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param keyValueSeparator a string to append between every key and its + * associated value + * @param entryDelimiter a string to append between every entry, but not at + * the beginning or end + * @param map the map containing the data to join + * @return a string consisting of the joined entries of the map; empty if the + * map is empty + */ + public static String join( + String keyValueSeparator, String entryDelimiter, Map<?, ?> map) { + return join(new StringBuilder(), keyValueSeparator, entryDelimiter, map) + .toString(); + } + + /** + * Appends each of the {@code tokens} to {@code appendable}, separated by + * {@code delimiter}. + * + * <p>Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param appendable the object to append the results to + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param tokens objects to append + * @return the same {@code Appendable} instance that was passed in + * @throws JoinException if an {@link IOException} occurs + */ + public static <T extends Appendable> T join( + T appendable, String delimiter, Iterable<?> tokens) { + return join(appendable, delimiter, tokens.iterator()); + } + + /** + * Appends each of the {@code tokens} to {@code appendable}, separated by + * {@code delimiter}. + * + * <p>Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param appendable the object to append the results to + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param tokens objects to append + * @return the same {@code Appendable} instance that was passed in + * @throws JoinException if an {@link IOException} occurs + */ + public static <T extends Appendable> T join( + T appendable, String delimiter, Object[] tokens) { + return join(appendable, delimiter, Arrays.asList(tokens)); + } + + /** + * Appends each of the {@code tokens} to {@code appendable}, separated by + * {@code delimiter}. + * + * <p>Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param appendable the object to append the results to + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param firstToken the first object to append + * @param otherTokens subsequent objects to append + * @return the same {@code Appendable} instance that was passed in + * @throws JoinException if an {@link IOException} occurs + */ + public static <T extends Appendable> T join(T appendable, String delimiter, + @Nullable Object firstToken, Object... otherTokens) { + checkNotNull(otherTokens); + return join(appendable, delimiter, asList(firstToken, otherTokens)); + } + + /** + * Appends each of the {@code tokens} to {@code appendable}, separated by + * {@code delimiter}. + * + * <p>Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param appendable the object to append the results to + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param tokens objects to append + * @return the same {@code Appendable} instance that was passed in + * @throws JoinException if an {@link IOException} occurs + */ + public static <T extends Appendable> T join( + T appendable, String delimiter, Iterator<?> tokens) { + + /* This method is the workhorse of the class */ + + checkNotNull(appendable); + checkNotNull(delimiter); + if (tokens.hasNext()) { + try { + appendOneToken(appendable, tokens.next()); + while (tokens.hasNext()) { + appendable.append(delimiter); + appendOneToken(appendable, tokens.next()); + } + } catch (IOException e) { + throw new JoinException(e); + } + } + return appendable; + } + + /** + * Appends the contents of {@code map} to {@code appendable}, with entries + * separated by {@code entryDelimiter}, and keys and values separated with + * {@code keyValueSeparator}. + * + * <p>Each key and value will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param appendable the object to append the results to + * @param keyValueSeparator a string to append between every key and its + * associated value + * @param entryDelimiter a string to append between every entry, but not at + * the beginning or end + * @param map the map containing the data to join + * @return the same {@code Appendable} instance that was passed in + */ + public static <T extends Appendable> T join(T appendable, + String keyValueSeparator, String entryDelimiter, Map<?, ?> map) { + checkNotNull(appendable); + checkNotNull(keyValueSeparator); + checkNotNull(entryDelimiter); + Iterator<? extends Map.Entry<?, ?>> entries = map.entrySet().iterator(); + if (entries.hasNext()) { + try { + appendOneEntry(appendable, keyValueSeparator, entries.next()); + while (entries.hasNext()) { + appendable.append(entryDelimiter); + appendOneEntry(appendable, keyValueSeparator, entries.next()); + } + } catch (IOException e) { + throw new JoinException(e); + } + } + return appendable; + } + + private static void appendOneEntry( + Appendable appendable, String keyValueSeparator, Map.Entry<?, ?> entry) + throws IOException { + appendOneToken(appendable, entry.getKey()); + appendable.append(keyValueSeparator); + appendOneToken(appendable, entry.getValue()); + } + + private static void appendOneToken(Appendable appendable, Object token) + throws IOException { + appendable.append(toCharSequence(token)); + } + + private static CharSequence toCharSequence(Object token) { + return (token instanceof CharSequence) + ? (CharSequence) token + : String.valueOf(token); + } + + /** + * Exception thrown in response to an {@link IOException} from the supplied + * {@link Appendable}. This is used because most callers won't want to + * worry about catching an IOException. + */ + public static class JoinException extends RuntimeException { + private JoinException(IOException cause) { + super(cause); + } + + private static final long serialVersionUID = 1L; + } + + /** + * Duplicate of + * {@link com.google.common.collect.Lists#asList(Object, Object[])}, copied + * here to remove dependencies. + */ + private static List<Object> asList(final Object first, final Object[] rest) { + return new AbstractList<Object>() { + @Override public int size() { + return rest.length + 1; + } + @Override public Object get(int index) { + return (index == 0) ? first : rest[index - 1]; + } + }; + } +} diff --git a/plugins/com.google.collect/src/com/google/common/base/Nullable.java b/plugins/com.google.collect/src/com/google/common/base/Nullable.java new file mode 100644 index 0000000..c878815 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/Nullable.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The presence of this annotation on a method parameter indicates that + * {@code null} is an acceptable value for that parameter. It should not be + * used for parameters of primitive types. + * + * @author Kevin Bourrillion + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Nullable { } diff --git a/plugins/com.google.collect/src/com/google/common/base/Objects.java b/plugins/com.google.collect/src/com/google/common/base/Objects.java new file mode 100644 index 0000000..4adaba1 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/Objects.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import java.util.Arrays; + +/** + * Helper functions that can operate on any {@code Object}. + * + * @author Laurence Gonsalves + */ +public final class Objects { + private Objects() {} + + /** + * Determines whether two possibly-null objects are equal. Returns: + * + * <ul> + * <li>{@code true} if {@code a} and {@code b} are both null. + * <li>{@code true} if {@code a} and {@code b} are both non-null and they are + * equal according to {@link Object#equals(Object)}. + * <li>{@code false} in all other situations. + * </ul> + * + * <p>This assumes that any non-null objects passed to this function conform + * to the {@code equals()} contract. + */ + public static boolean equal(@Nullable Object a, @Nullable Object b) { + return a == b || (a != null && a.equals(b)); + } + + /** + * Generates a hash code for multiple values. The hash code is generated by + * calling {@link Arrays#hashCode(Object[])}. + * + * <p>This is useful for implementing {@link Object#hashCode()}. For example, + * in an object that has three properties, {@code x}, {@code y}, and + * {@code z}, one could write: + * <pre> + * public int hashCode() { + * return Objects.hashCode(getX(), getY(), getZ()); + * }</pre> + * + * <b>Warning</b>: When a single object is supplied, the returned hash code + * does not equal the hash code of that object. + */ + public static int hashCode(Object... objects) { + return Arrays.hashCode(objects); + } + + /** + * Returns the first of two given parameters that is not {@code null}, if + * either is, or otherwise throws a {@link NullPointerException}. + * + * @return {@code first} if {@code first} is not {@code null}, or + * {@code second} if {@code first} is {@code null} and {@code second} is + * not {@code null} + * @throws NullPointerException if both {@code first} and {@code second} were + * {@code null} + */ + public static <T> T firstNonNull(@Nullable T first, @Nullable T second) { + return first != null ? first : Preconditions.checkNotNull(second); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/base/Preconditions.java b/plugins/com.google.collect/src/com/google/common/base/Preconditions.java new file mode 100644 index 0000000..93e4afd --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/Preconditions.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import java.util.Collection; +import java.util.NoSuchElementException; + +/** + * Simple static methods to be called at the start of your own methods to verify + * correct arguments and state. This allows constructs such as + * <pre> + * if (count <= 0) { + * throw new IllegalArgumentException("must be positive: " + count); + * }</pre> + * + * to be replaced with the more compact + * <pre> + * checkArgument(count > 0, "must be positive: %s", count);</pre> + * + * Note that the sense of the expression is inverted; with {@code Preconditions} + * you declare what you expect to be <i>true</i>, just as you do with an + * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/language/assert.html"> + * {@code assert}</a> or a JUnit {@code assertTrue()} call. + * + * <p>Take care not to confuse precondition checking with other similar types + * of checks! Precondition exceptions -- including those provided here, but also + * {@link IndexOutOfBoundsException}, {@link NoSuchElementException}, {@link + * UnsupportedOperationException} and others -- are used to signal that the + * <i>calling method</i> has made an error. This tells the caller that it should + * not have invoked the method when it did, with the arguments it did, or + * perhaps <i>ever</i>. Postcondition or other invariant failures should not + * throw these types of exceptions. + * + * <p><b>Note:</b> The methods of the {@code Preconditions} class are highly + * unusual in one way: they are <i>supposed to</i> throw exceptions, and promise + * in their specifications to do so even when given perfectly valid input. That + * is, {@code null} is a valid parameter to the method {@link + * #checkNotNull(Object)} -- and technically this parameter could be even marked + * as {@link Nullable} -- yet the method will still throw an exception anyway, + * because that's what its contract says to do. + * + * @author Kevin Bourrillion + */ +public final class Preconditions { + private Preconditions() {} + + /** + * Ensures the truth of an expression involving one or more parameters to the + * calling method. + * + * @param expression a boolean expression + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the + * calling method. + * + * @param expression a boolean expression + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the + * calling method. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @throws IllegalArgumentException if {@code expression} is false + * @throws NullPointerException if the check fails and either {@code + * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let + * this happen) + */ + public static void checkArgument(boolean expression, + String errorMessageTemplate, Object... errorMessageArgs) { + if (!expression) { + throw new IllegalArgumentException( + format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(boolean expression) { + if (!expression) { + throw new IllegalStateException(); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalStateException(String.valueOf(errorMessage)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @throws IllegalStateException if {@code expression} is false + * @throws NullPointerException if the check fails and either {@code + * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let + * this happen) + */ + public static void checkState(boolean expression, + String errorMessageTemplate, Object... errorMessageArgs) { + if (!expression) { + throw new IllegalStateException( + format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static <T> T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; + } + + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static <T> T checkNotNull(T reference, Object errorMessage) { + if (reference == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + return reference; + } + + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @param errorMessageTemplate a template for the exception message should the + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static <T> T checkNotNull(T reference, String errorMessageTemplate, + Object... errorMessageArgs) { + if (reference == null) { + // If either of these parameters is null, the right thing happens anyway + throw new NullPointerException( + format(errorMessageTemplate, errorMessageArgs)); + } + return reference; + } + + /** + * Ensures that an {@code Iterable} object passed as a parameter to the + * calling method is not null and contains no null elements. + * + * @param iterable the iterable to check the contents of + * @return the non-null {@code iterable} reference just validated + * @throws NullPointerException if {@code iterable} is null or contains at + * least one null element + */ + public static <T extends Iterable<?>> T checkContentsNotNull(T iterable) { + if (containsOrIsNull(iterable)) { + throw new NullPointerException(); + } + return iterable; + } + + /** + * Ensures that an {@code Iterable} object passed as a parameter to the + * calling method is not null and contains no null elements. + * + * @param iterable the iterable to check the contents of + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @return the non-null {@code iterable} reference just validated + * @throws NullPointerException if {@code iterable} is null or contains at + * least one null element + */ + public static <T extends Iterable<?>> T checkContentsNotNull( + T iterable, Object errorMessage) { + if (containsOrIsNull(iterable)) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + return iterable; + } + + /** + * Ensures that an {@code Iterable} object passed as a parameter to the + * calling method is not null and contains no null elements. + * + * @param iterable the iterable to check the contents of + * @param errorMessageTemplate a template for the exception message should the + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @return the non-null {@code iterable} reference just validated + * @throws NullPointerException if {@code iterable} is null or contains at + * least one null element + */ + public static <T extends Iterable<?>> T checkContentsNotNull(T iterable, + String errorMessageTemplate, Object... errorMessageArgs) { + if (containsOrIsNull(iterable)) { + throw new NullPointerException( + format(errorMessageTemplate, errorMessageArgs)); + } + return iterable; + } + + private static boolean containsOrIsNull(Iterable<?> iterable) { + if (iterable == null) { + return true; + } + + if (iterable instanceof Collection) { + Collection<?> collection = (Collection<?>) iterable; + try { + return collection.contains(null); + } catch (NullPointerException e) { + // A NPE implies that the collection doesn't contain null. + return false; + } + } else { + for (Object element : iterable) { + if (element == null) { + return true; + } + } + return false; + } + } + + /** + * Substitutes each {@code %s} in {@code template} with an argument. These + * are matched by position - the first {@code %s} gets {@code args[0]}, etc. + * If there are more arguments than placeholders, the unmatched arguments will + * be appended to the end of the formatted message in square braces. + * + * @param template a non-null string containing 0 or more {@code %s} + * placeholders. + * @param args the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. Arguments can be null. + */ + // VisibleForTesting + static String format(String template, Object... args) { + // start substituting the arguments into the '%s' placeholders + StringBuilder builder = new StringBuilder( + template.length() + 16 * args.length); + int templateStart = 0; + int i = 0; + while (i < args.length) { + int placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + builder.append(template.substring(templateStart, placeholderStart)); + builder.append(args[i++]); + templateStart = placeholderStart + 2; + } + builder.append(template.substring(templateStart)); + + // if we run out of placeholders, append the extra args in square braces + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + builder.append("]"); + } + + return builder.toString(); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/base/Predicate.java b/plugins/com.google.collect/src/com/google/common/base/Predicate.java new file mode 100644 index 0000000..db1c995 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/Predicate.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +/** + * Determines a true or false value for a given input. For example, a + * {@code RegexPredicate} might implement {@code Predicate<String>}, and return + * {@code true} for any string that matches its given regular expression. + * + * <p>Implementations which may cause side effects upon evaluation are strongly + * encouraged to state this fact clearly in their API documentation. + * + * @author Kevin Bourrillion + */ +public interface Predicate<T> { + + /* + * This interface does not extend Function<T, Boolean> because doing so would + * let predicates return null. + */ + + /** + * Applies this predicate to the given object. + * + * @param input the input that the predicate should act on + * @return the value of this predicate when applied to the input {@code t} + */ + boolean apply(@Nullable T input); + + + /** + * Indicates whether some other object is equal to this {@code Predicate}. + * This method can return {@code true} <i>only</i> if the specified object is + * also a {@code Predicate} and, for every input object {@code input}, it + * returns exactly the same value. Thus, {@code predicate1.equals(predicate2)} + * implies that either {@code predicate1.apply(input)} and + * {@code predicate2.apply(input)} are both {@code true} or both + * {@code false}. + * + * <p>Note that it is always safe <i>not</i> to override + * {@link Object#equals}. + */ + boolean equals(@Nullable Object obj); +} diff --git a/plugins/com.google.collect/src/com/google/common/base/Predicates.java b/plugins/com.google.collect/src/com/google/common/base/Predicates.java new file mode 100644 index 0000000..fb9cac2 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/Predicates.java @@ -0,0 +1,517 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkContentsNotNull; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; + +/** + * Contains static factory methods for creating {@code Predicate} instances. + * + * @author Kevin Bourrillion + */ +public final class Predicates { + private Predicates() {} + + // TODO: considering having these implement a VisitablePredicate interface + // which specifies an accept(PredicateVisitor) method. + + /** + * Returns a predicate that always evaluates to {@code true}. + */ + @SuppressWarnings("unchecked") + public static <T> Predicate<T> alwaysTrue() { + return (Predicate<T>) AlwaysTruePredicate.INSTANCE; + } + + /** + * Returns a predicate that always evaluates to {@code false}. + */ + @SuppressWarnings("unchecked") + public static <T> Predicate<T> alwaysFalse() { + return (Predicate<T>) AlwaysFalsePredicate.INSTANCE; + } + + /** + * Returns a predicate that evaluates to {@code true} if the object reference + * being tested is null. + */ + @SuppressWarnings("unchecked") + public static <T> Predicate<T> isNull() { + return (Predicate<T>) IsNullPredicate.INSTANCE; + } + + /** + * Returns a predicate that evaluates to {@code true} if the object reference + * being tested is not null. + */ + @SuppressWarnings("unchecked") + public static <T> Predicate<T> notNull() { + return (Predicate<T>) NotNullPredicate.INSTANCE; + } + + /** + * Returns a predicate that evaluates to {@code true} if the given predicate + * evaluates to {@code false}. + */ + public static <T> Predicate<T> not(Predicate<? super T> predicate) { + return new NotPredicate<T>(predicate); + } + + /** + * Returns a predicate that evaluates to {@code true} if each of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as a false + * predicate is found. It does not defensively copy the iterable passed in, so + * future changes to it will alter the behavior of this predicate. If + * {@code components} is empty, the returned predicate will always evaluate to + * {@code true}. + */ + public static <T> Predicate<T> and( + Iterable<? extends Predicate<? super T>> components) { + return new AndPredicate<T>(components); + } + + /** + * Returns a predicate that evaluates to {@code true} if each of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as a false + * predicate is found. It does not defensively copy the array passed in, so + * future changes to it will alter the behavior of this predicate. If + * {@code components} is empty, the returned predicate will always evaluate to + * {@code true}. + */ + public static <T> Predicate<T> and(Predicate<? super T>... components) { + return and(Arrays.asList(components)); + } + + /** + * Returns a predicate that evaluates to {@code true} if both of its + * components evaluate to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as a false + * predicate is found. + */ + @SuppressWarnings("unchecked") + public static <T> Predicate<T> and(Predicate<? super T> first, + Predicate<? super T> second) { + return and(Arrays.<Predicate<? super T>> asList(first, second)); + } + + /** + * Returns a predicate that evaluates to {@code true} if any one of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as as soon as a + * true predicate is found. It does not defensively copy the iterable passed + * in, so future changes to it will alter the behavior of this predicate. If + * {@code components} is empty, the returned predicate will always evaluate to + * {@code false}. + */ + public static <T> Predicate<T> or( + Iterable<? extends Predicate<? super T>> components) { + return new OrPredicate<T>(components); + } + + /** + * Returns a predicate that evaluates to {@code true} if any one of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as as soon as a + * true predicate is found. It does not defensively copy the array passed in, + * so future changes to it will alter the behavior of this predicate. If + * {@code components} is empty, the returned predicate will always evaluate to + * {@code false}. + */ + public static <T> Predicate<T> or(Predicate<? super T>... components) { + return or(Arrays.asList(components)); + } + + /** + * Returns a predicate that evaluates to {@code true} if either of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as as soon as a + * true predicate is found. + */ + @SuppressWarnings("unchecked") + public static <T> Predicate<T> or(Predicate<? super T> first, + Predicate<? super T> second) { + return or(Arrays.<Predicate<? super T>> asList(first, second)); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object being + * tested {@code equals()} the given target or both are null. + */ + public static <T> Predicate<T> isEqualTo(@Nullable T target) { + // TODO: Change signature to return Predicate<Object>. + return (target == null) + ? Predicates.<T>isNull() + : new IsEqualToPredicate<T>(target); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object being + * tested refers to the same object as the given target or both are null. + */ + public static Predicate<Object> isSameAs(@Nullable Object target) { + return (target == null) + ? Predicates.isNull() + : new IsSameAsPredicate(target); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object reference + * being tested is a member of the given collection. It does not defensively + * copy the collection passed in, so future changes to it will alter the + * behavior of the predicate. + * + * @param target the collection that may contain the function input + */ + public static <T> Predicate<T> in(Collection<?> target) { + return new InPredicate<T>(target); + } + + /** + * Returns the composition of a function and a predicate. For every {@code x}, + * the generated predicate returns {@code predicate(function(x))}. + * + * @return the composition of the provided function and predicate + */ + public static <A, B> Predicate<A> compose( + Predicate<? super B> predicate, + Function<? super A, ? extends B> function) { + return new CompositionPredicate<A, B>(predicate, function); + } + + /** @see Predicates#alwaysTrue() */ + // enum singleton pattern + private enum AlwaysTruePredicate implements Predicate<Object> { + INSTANCE; + + public boolean apply(Object o) { + return true; + } + @Override public String toString() { + return "AlwaysTrue"; + } + } + + /** @see Predicates#alwaysFalse() */ + // enum singleton pattern + private enum AlwaysFalsePredicate implements Predicate<Object> { + INSTANCE; + + public boolean apply(Object o) { + return false; + } + @Override public String toString() { + return "AlwaysFalse"; + } + } + + /** @see Predicates#not(Predicate) */ + private static class NotPredicate<T> + implements Predicate<T>, Serializable { + private final Predicate<? super T> predicate; + + private NotPredicate(Predicate<? super T> predicate) { + this.predicate = checkNotNull(predicate); + } + public boolean apply(T t) { + return !predicate.apply(t); + } + @Override public int hashCode() { + return ~predicate.hashCode(); /* Invert all bits. */ + } + @Override public boolean equals(Object obj) { + if (obj instanceof NotPredicate<?>) { + NotPredicate<?> that = (NotPredicate<?>) obj; + return predicate.equals(that.predicate); + } + return false; + } + @Override public String toString() { + return "Not(" + predicate.toString() + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#and(Iterable) */ + private static class AndPredicate<T> + implements Predicate<T>, Serializable { + private final Iterable<? extends Predicate<? super T>> components; + + private AndPredicate(Iterable<? extends Predicate<? super T>> components) { + this.components = checkContentsNotNull(components); + } + public boolean apply(T t) { + for (Predicate<? super T> predicate : components) { + if (!predicate.apply(t)) { + return false; + } + } + return true; + } + @Override public int hashCode() { + int result = -1; /* Start with all bits on. */ + for (Predicate<? super T> predicate : components) { + result &= predicate.hashCode(); + } + return result; + } + @Override public boolean equals(Object obj) { + if (obj instanceof AndPredicate<?>) { + AndPredicate<?> that = (AndPredicate<?>) obj; + return iterableElementsEqual(components, that.components); + } + return false; + } + @Override public String toString() { + return "And(" + Join.join(",", components) + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#or(Iterable) */ + private static class OrPredicate<T> + implements Predicate<T>, Serializable { + private final Iterable<? extends Predicate<? super T>> components; + + private OrPredicate(Iterable<? extends Predicate<? super T>> components) { + this.components = checkContentsNotNull(components); + } + public boolean apply(T t) { + for (Predicate<? super T> predicate : components) { + if (predicate.apply(t)) { + return true; + } + } + return false; + } + @Override public int hashCode() { + int result = 0; /* Start with all bits off. */ + for (Predicate<? super T> predicate : components) { + result |= predicate.hashCode(); + } + return result; + } + @Override public boolean equals(Object obj) { + if (obj instanceof OrPredicate<?>) { + OrPredicate<?> that = (OrPredicate<?>) obj; + return iterableElementsEqual(components, that.components); + } + return false; + } + @Override public String toString() { + return "Or(" + Join.join(",", components) + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#isEqualTo(Object) */ + private static class IsEqualToPredicate<T> + implements Predicate<T>, Serializable { + private final T target; + + private IsEqualToPredicate(T target) { + this.target = target; + } + public boolean apply(T t) { + return target.equals(t); + } + @Override public int hashCode() { + return target.hashCode(); + } + @Override public boolean equals(Object obj) { + if (obj instanceof IsEqualToPredicate) { + IsEqualToPredicate<?> that = (IsEqualToPredicate<?>) obj; + return target.equals(that.target); + } + return false; + } + @Override public String toString() { + return "IsEqualTo(" + target + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#isSameAs(Object) */ + private static class IsSameAsPredicate + implements Predicate<Object>, Serializable { + private final Object target; + + private IsSameAsPredicate(Object target) { + this.target = target; + } + public boolean apply(Object o) { + return target == o; + } + @Override public int hashCode() { + return target.hashCode(); + } + @Override public boolean equals(Object obj) { + if (obj instanceof IsSameAsPredicate) { + IsSameAsPredicate that = (IsSameAsPredicate) obj; + return target == that.target; + } + return false; + } + @Override public String toString() { + return "IsSameAs(" + target + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#isNull() */ + // enum singleton pattern + private enum IsNullPredicate implements Predicate<Object> { + INSTANCE; + + public boolean apply(Object o) { + return o == null; + } + @Override public String toString() { + return "IsNull"; + } + } + + /** @see Predicates#notNull() */ + // enum singleton pattern + private enum NotNullPredicate implements Predicate<Object> { + INSTANCE; + + public boolean apply(Object o) { + return o != null; + } + @Override public String toString() { + return "NotNull"; + } + } + + /** @see Predicates#in(Collection) */ + private static class InPredicate<T> + implements Predicate<T>, Serializable { + private final Collection<?> target; + + private InPredicate(Collection<?> target) { + this.target = checkNotNull(target); + } + + public boolean apply(T t) { + try { + return target.contains(t); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + + @Override public boolean equals(Object obj) { + if (obj instanceof InPredicate<?>) { + InPredicate<?> that = (InPredicate<?>) obj; + return target.equals(that.target); + } + return false; + } + + @Override public int hashCode() { + return target.hashCode(); + } + + @Override public String toString() { + return "In(" + target + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#compose(Predicate, Function) */ + private static class CompositionPredicate<A, B> + implements Predicate<A>, Serializable { + final Predicate<? super B> p; + final Function<? super A, ? extends B> f; + + private CompositionPredicate( + Predicate<? super B> p, Function<? super A, ? extends B> f) { + this.p = checkNotNull(p); + this.f = checkNotNull(f); + } + + public boolean apply(A a) { + return p.apply(f.apply(a)); + } + + @Override public boolean equals(Object obj) { + if (obj instanceof CompositionPredicate<?, ?>) { + CompositionPredicate<?, ?> that = (CompositionPredicate<?, ?>) obj; + return f.equals(that.f) && p.equals(that.p); + } + return false; + } + + @Override public int hashCode() { + /* + * TODO: To leave the door open for future enhancement, this + * calculation should be coordinated with the hashCode() method of the + * corresponding composition method in Functions. To construct the + * composition: + * predicate(function2(function1(x))) + * + * There are two different ways of composing it: + * compose(predicate, compose(function2, function1)) + * compose(compose(predicate, function2), function1) + * + * It would be nice if these could be equal. + */ + return f.hashCode() ^ p.hashCode(); + } + + @Override public String toString() { + return p.toString() + "(" + f.toString() + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Determines whether the two Iterables contain equal elements. More + * specifically, this method returns {@code true} if {@code iterable1} and + * {@code iterable2} contain the same number of elements and every element of + * {@code iterable1} is equal to the corresponding element of {@code + * iterable2}. + * + * <p>This is not a general-purpose method; it assumes that the iterations + * contain no {@code null} elements. + */ + private static boolean iterableElementsEqual( + Iterable<?> iterable1, Iterable<?> iterable2) { + Iterator<?> iterator1 = iterable1.iterator(); + Iterator<?> iterator2 = iterable2.iterator(); + while (iterator1.hasNext()) { + if (!iterator2.hasNext()) { + return false; + } + if (!iterator1.next().equals(iterator2.next())) { + return false; + } + } + return !iterator2.hasNext(); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/base/ReferenceType.java b/plugins/com.google.collect/src/com/google/common/base/ReferenceType.java new file mode 100644 index 0000000..8e26459 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/ReferenceType.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +/** + * Reference type. Used to specify what type of reference to keep to a + * referent. + * + * @see java.lang.ref.Reference + * @author Bob Lee + */ +public enum ReferenceType { + + /** + * Prevents referent from being reclaimed by the garbage collector. + */ + STRONG, + + /** + * Referent reclaimed in an LRU fashion when the VM runs low on memory and + * no strong references exist. + * + * @see java.lang.ref.SoftReference + */ + SOFT, + + /** + * Referent reclaimed when no strong or soft references exist. + * + * @see java.lang.ref.WeakReference + */ + WEAK, + + /** + * Similar to weak references except the garbage collector doesn't actually + * reclaim the referent. More flexible alternative to finalization. + * + * @see java.lang.ref.PhantomReference + */ + PHANTOM; +} diff --git a/plugins/com.google.collect/src/com/google/common/base/Supplier.java b/plugins/com.google.collect/src/com/google/common/base/Supplier.java new file mode 100644 index 0000000..bd82236 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/Supplier.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +/** + * A class that can supply objects of a single type. Semantically, this could + * be a factory, generator, builder, closure, or something else entirely. No + * guarantees are implied by this interface. + * + * @author Harry Heymann + */ +public interface Supplier<T> { + /** + * Retrieves an instance of the appropriate type. The returned object may or + * may not be a new instance, depending on the implementation. + * + * @return an instance of the appropriate type + */ + public T get(); +} diff --git a/plugins/com.google.collect/src/com/google/common/base/Suppliers.java b/plugins/com.google.collect/src/com/google/common/base/Suppliers.java new file mode 100644 index 0000000..6bf7ef5 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/Suppliers.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import java.io.Serializable; + +/** + * Useful suppliers. + * + * @author Laurence Gonsalves + * @author Harry Heymann + */ +public final class Suppliers { + private Suppliers() {} + + /** + * Returns a new supplier which is the composition of the provided function + * and supplier. In other words, the new supplier's value will be computed by + * retrieving the value from {@code first}, and then applying + * {@code function} to that value. Note that the resulting supplier will not + * call {@code first} or invoke {@code function} until it is called. + */ + public static <F, T> Supplier<T> compose( + Function<? super F, ? extends T> function, Supplier<? extends F> first) { + return new SupplierComposition<F, T>(function, first); + } + + private static class SupplierComposition<F, T> + implements Supplier<T>, Serializable { + private final Function<? super F, ? extends T> function; + private final Supplier<? extends F> first; + + public SupplierComposition(Function<? super F, ? extends T> function, + Supplier<? extends F> first) { + this.function = function; + this.first = first; + } + public T get() { + return function.apply(first.get()); + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a supplier which caches the instance retrieved during the first + * call to {@code get()} and returns that value on subsequent calls to + * {@code get()}. See: + * <a href="http://en.wikipedia.org/wiki/Memoization">memoization</a> + * + * <p>The returned supplier will throw {@code CyclicDependencyException} if + * the call to {@link Supplier#get} tries to get its own value. The returned + * supplier is <i>not</i> thread-safe. + */ + public static <T> Supplier<T> memoize(Supplier<T> delegate) { + return new MemoizingSupplier<T>(delegate); + } + + private static class MemoizingSupplier<T> + implements Supplier<T>, Serializable { + private final Supplier<T> delegate; + private MemoizationState state = MemoizationState.NOT_YET; + private T value; + + public MemoizingSupplier(Supplier<T> delegate) { + this.delegate = delegate; + } + public T get() { + switch (state) { + case NOT_YET: + state = MemoizationState.COMPUTING; + try { + value = delegate.get(); + } finally { + state = MemoizationState.NOT_YET; + } + state = MemoizationState.DONE; + break; + case COMPUTING: + throw new CyclicDependencyException(); + } + return value; + } + private static final long serialVersionUID = 0; + } + + private enum MemoizationState { NOT_YET, COMPUTING, DONE } + + /** + * Exception thrown when a memoizing supplier tries to get its own value. + */ + public static class CyclicDependencyException extends RuntimeException { + CyclicDependencyException() { + super("Cycle detected when invoking a memoizing supplier."); + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a supplier that always supplies {@code instance}. + */ + public static <T> Supplier<T> ofInstance(@Nullable T instance) { + return new SupplierOfInstance<T>(instance); + } + + private static class SupplierOfInstance<T> + implements Supplier<T>, Serializable { + private final T instance; + + public SupplierOfInstance(T instance) { + this.instance = instance; + } + public T get() { + return instance; + } + private static final long serialVersionUID = 0; + } +} diff --git a/plugins/com.google.collect/src/com/google/common/base/package-info.java b/plugins/com.google.collect/src/com/google/common/base/package-info.java new file mode 100644 index 0000000..1fb94a5 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/base/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Miscellaneous common util classes and annotations. + */ +package com.google.common.base; diff --git a/plugins/com.google.collect/src/com/google/common/collect/AbstractIterable.java b/plugins/com.google.collect/src/com/google/common/collect/AbstractIterable.java new file mode 100644 index 0000000..3ca5223 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/AbstractIterable.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +/** + * Provides an implementation of {@link Object#toString} for {@link Iterable} + * instances. + * + * @author Mike Bostock + */ +public abstract class AbstractIterable<E> implements Iterable<E> { + /** + * Returns a string representation of this iterable. The string representation + * consists of a list of the iterable's elements in the order they are + * returned by its iterator, enclosed in square brackets ("[]"). Adjacent + * elements are separated by the characters ", " (comma and space). Elements + * are converted to strings as by {@link String#valueOf(Object)}. + */ + @Override public String toString() { + return Iterables.toString(this); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/AbstractIterator.java b/plugins/com.google.collect/src/com/google/common/collect/AbstractIterator.java new file mode 100644 index 0000000..9684b50 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/AbstractIterator.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkState; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * This class provides a skeletal implementation of the {@code Iterator} + * interface, to make this interface easier to implement for certain types of + * data sources. + * + * <p>{@code Iterator} requires its implementations to support querying the + * end-of-data status without changing the iterator's state, using the {@link + * #hasNext} method. But many data sources, such as {@link + * java.io.Reader#read()}), do not expose this information; the only way to + * discover whether there is any data left is by trying to retrieve it. These + * types of data sources are ordinarily difficult to write iterators for. But + * using this class, one must implement only the {@link #computeNext} method, + * and invoke the {@link #endOfData} method when appropriate. + * + * <p>Another example is an iterator that skips over null elements in a backing + * iterator. This could be implemented as: <pre> {@code + * + * public static Iterator<String> skipNulls(final Iterator<String> in) { + * return new AbstractIterator<String>() { + * protected String computeNext() { + * while (in.hasNext()) { + * String s = in.next(); + * if (s != null) { + * return s; + * } + * } + * return endOfData(); + * } + * }; + * }}</pre> + * + * This class supports iterators that include null elements. The {@link + * #remove()} method throws an {@link UnsupportedOperationException}, but the + * similar class {@link AbstractRemovableIterator} does support {@code remove}. + * + * @author Kevin Bourrillion + */ +public abstract class AbstractIterator<T> implements Iterator<T> { + private State state = State.NOT_READY; + + private enum State { + /** We have computed the next element and haven't returned it yet. */ + READY, + + /** We haven't yet computed or have already returned the element. */ + NOT_READY, + + /** We have reached the end of the data and are finished. */ + DONE, + + /** We've suffered an exception and are kaput. */ + FAILED, + } + + private T next; + + /** + * Returns the next element. <b>Note:</b> the implementation must call {@link + * #endOfData} when there are no elements left in the iteration. Failure to do + * so could result in an infinite loop. + * + * <p>The initial invocation of {@link #hasNext()} or {@link #next()} calls + * this method, as does the first invocation of {@code hasNext} or + * {@code next} following each successful call to {@code next}. Once the + * implementation either invokes {@code endOfData} or throws an exception, + * {@code computeNext} is guaranteed to never be called again. + * + * <p>If this method throws an exception, it will propagate outward to the + * {@code hasNext()} or {@code next()} invocation that invoked this method. + * Any further attempts to use the iterator will result in an {@link + * IllegalStateException}. + * + * @return the next element if there was one. If {@code endOfData} was called + * during execution, the return value will be ignored. + * @throws RuntimeException if any unrecoverable error happens. This exception + * will propagate outward to the {@code hasNext()}, {@code next()}, or + * {@code peek()} invocation that invoked this method. Any further + * attempts to use the iterator will result in an + * {@link IllegalStateException}. + */ + protected abstract T computeNext(); + + /** + * Implementations of {@code computeNext} <b>must</b> invoke this method when + * there are no elements left in the iteration. + * + * @return {@code null}; a convenience so your {@link #computeNext} + * implementation can use the simple statement {@code return endOfData();} + */ + protected final T endOfData() { + state = State.DONE; + return null; + } + + public boolean hasNext() { + checkState(state != State.FAILED); + switch (state) { + case DONE: + return false; + case READY: + return true; + default: + } + return tryToComputeNext(); + } + + private boolean tryToComputeNext() { + state = State.FAILED; // temporary pessimism + next = computeNext(); + if (state != State.DONE) { + state = State.READY; + return true; + } + return false; + } + + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + state = State.NOT_READY; + return next; + } + + /** + * Returns the next element in the iteration without advancing the iteration, + * according to the contract of {@link PeekingIterator#peek()}. + * + * <p>Implementations of {@code AbstractIterator} that wish to expose this + * functionality should implement {@code PeekingIterator}. + */ + public T peek() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return next; + } + + /** + * This method is not supported. + */ + public void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/AbstractMapBasedMultiset.java b/plugins/com.google.collect/src/com/google/common/collect/AbstractMapBasedMultiset.java new file mode 100644 index 0000000..170a814 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/AbstractMapBasedMultiset.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.base.Nullable; + +import java.io.InvalidObjectException; +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Basic implementation of {@code Multiset<E>} backed by an instance of {@code + * Map<E, AtomicInteger>}. + * + * <p>For serialization to work, the subclass must specify explicit {@code + * readObject} and {@code writeObject} methods. + * + * @author Kevin Bourrillion + */ +abstract class AbstractMapBasedMultiset<E> extends AbstractMultiset<E> + implements Serializable { + + // TODO: Replace AtomicInteger with a to-be-written IntegerHolder class for + // better performance. + private transient Map<E, AtomicInteger> backingMap; + + /* + * Cache the size for efficiency. Using a long lets us avoid the need for + * overflow checking and ensures that size() will function correctly even if + * the multiset had once been larger than Integer.MAX_VALUE. + */ + private transient long size; + + /** Standard constructor. */ + protected AbstractMapBasedMultiset(Map<E, AtomicInteger> backingMap) { + this.backingMap = checkNotNull(backingMap); + this.size = super.size(); + } + + protected Map<E, AtomicInteger> backingMap() { + return backingMap; + } + + /** Used during deserialization only. The backing map must be empty. */ + protected void setBackingMap(Map<E, AtomicInteger> backingMap) { + this.backingMap = backingMap; + } + + // Required Implementations + + private transient volatile EntrySet entrySet; + + /** + * {@inheritDoc} + * + * <p>Invoking {@link Multiset.Entry#getCount} on an entry in the returned + * set always returns the current count of that element in the multiset, as + * opposed to the count at the time the entry was retrieved. + */ + @Override public Set<Multiset.Entry<E>> entrySet() { + EntrySet result = entrySet; + if (result == null) { + entrySet = result = new EntrySet(); + } + return result; + } + + private class EntrySet extends AbstractSet<Multiset.Entry<E>> { + @Override public Iterator<Multiset.Entry<E>> iterator() { + final Iterator<Map.Entry<E, AtomicInteger>> backingEntries + = backingMap.entrySet().iterator(); + return new Iterator<Multiset.Entry<E>>() { + Map.Entry<E, AtomicInteger> toRemove; + + public boolean hasNext() { + return backingEntries.hasNext(); + } + + public Multiset.Entry<E> next() { + final Map.Entry<E, AtomicInteger> mapEntry = backingEntries.next(); + toRemove = mapEntry; + return new AbstractMultisetEntry<E>() { + public E getElement() { + return mapEntry.getKey(); + } + public int getCount() { + int count = mapEntry.getValue().get(); + if (count == 0) { + AtomicInteger frequency = backingMap.get(getElement()); + if (frequency != null) { + count = frequency.get(); + } + } + return count; + } + }; + } + + public void remove() { + checkState(toRemove != null, + "no calls to next() since the last call to remove()"); + size -= toRemove.getValue().getAndSet(0); + backingEntries.remove(); + toRemove = null; + } + }; + } + + @Override public int size() { + return backingMap.size(); + } + + @Override public boolean retainAll(Collection<?> c) { + return super.retainAll(checkNotNull(c)); + } + + // The following overrides are for better performance. + + @Override public void clear() { + for (AtomicInteger frequency : backingMap.values()) { + frequency.set(0); + } + backingMap.clear(); + size = 0L; + } + + @Override public boolean contains(Object o) { + if (o instanceof Entry) { + Entry<?> entry = (Entry<?>) o; + int count = count(entry.getElement()); + return (count == entry.getCount()) && (count > 0); + } + return false; + } + + @Override public boolean remove(Object o) { + if (contains(o)) { + Entry<?> entry = (Entry<?>) o; + AtomicInteger frequency = backingMap.remove(entry.getElement()); + int numberRemoved = frequency.getAndSet(0); + size -= numberRemoved; + return true; + } + return false; + } + } + + // Optimizations - Query Operations + + @Override public int size() { + return (int) Math.min(this.size, Integer.MAX_VALUE); + } + + @Override public Iterator<E> iterator() { + return new MapBasedMultisetIterator(); + } + + /* + * Not subclassing AbstractMultiset$MultisetIterator because next() needs to + * retrieve the Map.Entry<E, AtomicInteger> entry, which can then be used for + * a more efficient remove() call. + */ + private class MapBasedMultisetIterator implements Iterator<E> { + final Iterator<Map.Entry<E, AtomicInteger>> entryIterator; + Map.Entry<E, AtomicInteger> currentEntry; + int occurrencesLeft; + boolean canRemove; + + MapBasedMultisetIterator() { + this.entryIterator = backingMap.entrySet().iterator(); + } + + public boolean hasNext() { + return occurrencesLeft > 0 || entryIterator.hasNext(); + } + + public E next() { + if (occurrencesLeft == 0) { + currentEntry = entryIterator.next(); + occurrencesLeft = currentEntry.getValue().get(); + } + occurrencesLeft--; + canRemove = true; + return currentEntry.getKey(); + } + + public void remove() { + checkState(canRemove, + "no calls to next() since the last call to remove()"); + int frequency = currentEntry.getValue().get(); + if (frequency <= 0) { + throw new ConcurrentModificationException(); + } + if (currentEntry.getValue().addAndGet(-1) == 0) { + entryIterator.remove(); + } + size--; + canRemove = false; + } + } + + @Override public int count(@Nullable Object element) { + AtomicInteger frequency = backingMap.get(element); + return (frequency == null) ? 0 : frequency.get(); + } + + // Optional Operations - Modification Operations + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the call would result in more than + * {@link Integer#MAX_VALUE} occurrences of {@code element} in this + * multiset. + */ + @Override public boolean add(@Nullable E element, int occurrences) { + if (occurrences == 0) { + return false; + } + checkArgument( + occurrences > 0, "occurrences cannot be negative: %s", occurrences); + AtomicInteger frequency = backingMap.get(element); + if (frequency == null) { + backingMap.put(element, new AtomicInteger(occurrences)); + } else { + long newCount = (long) frequency.get() + (long) occurrences; + checkArgument(newCount <= Integer.MAX_VALUE, + "too many occurrences: %s", newCount); + frequency.getAndAdd(occurrences); + } + size += occurrences; + return true; + } + + @Override public int remove(@Nullable Object element, int occurrences) { + if (occurrences == 0) { + return 0; + } + checkArgument( + occurrences > 0, "occurrences cannot be negative: %s", occurrences); + AtomicInteger frequency = backingMap.get(element); + if (frequency == null) { + return 0; + } + + int numberRemoved; + if (frequency.get() > occurrences) { + numberRemoved = occurrences; + } else { + numberRemoved = frequency.get(); + backingMap.remove(element); + } + + frequency.addAndGet(-numberRemoved); + size -= numberRemoved; + return numberRemoved; + } + + @Override public int removeAllOccurrences(@Nullable Object element) { + return removeAllOccurrences(element, backingMap); + } + + private int removeAllOccurrences(@Nullable Object element, + Map<E, AtomicInteger> map) { + AtomicInteger frequency = map.remove(element); + if (frequency == null) { + return 0; + } + int numberRemoved = frequency.getAndSet(0); + size -= numberRemoved; + return numberRemoved; + } + + // Views + + @Override protected Set<E> createElementSet() { + return new MapBasedElementSet(backingMap); + } + + class MapBasedElementSet extends ForwardingSet<E> { + /** + * This mapping is the usually the same as {@code backingMap}, but can + * be a submap in some implementations. + */ + private final Map<E, AtomicInteger> map; + private final Set<E> delegate; + + MapBasedElementSet(Map<E, AtomicInteger> map) { + this.map = map; + delegate = map.keySet(); + } + + @Override protected Set<E> delegate() { + return delegate; + } + + // TODO: a way to not have to write this much code? + + @Override public Iterator<E> iterator() { + final Iterator<Map.Entry<E, AtomicInteger>> entries + = map.entrySet().iterator(); + return new Iterator<E>() { + Map.Entry<E, AtomicInteger> toRemove; + + public boolean hasNext() { + return entries.hasNext(); + } + + public E next() { + toRemove = entries.next(); + return toRemove.getKey(); + } + + public void remove() { + checkState(toRemove != null, + "no calls to next() since the last call to remove()"); + size -= toRemove.getValue().getAndSet(0); + entries.remove(); + toRemove = null; + } + }; + } + + @Override public boolean remove(Object element) { + return removeAllOccurrences(element, map) != 0; + } + + @Override public boolean removeAll(Collection<?> elementsToRemove) { + return Iterators.removeAll(iterator(), elementsToRemove); + } + + @Override public boolean retainAll(Collection<?> elementsToRetain) { + return Iterators.retainAll(iterator(), elementsToRetain); + } + + @Override public void clear() { + if (map == backingMap) { + AbstractMapBasedMultiset.this.clear(); + } else { + Iterator<E> i = iterator(); + while (i.hasNext()) { + i.next(); + i.remove(); + } + } + } + + public Map<E, AtomicInteger> getMap() { + return map; + } + } + + /** Don't allow default serialization. */ + protected void readObjectNoData() throws InvalidObjectException { + throw new InvalidObjectException("Stream data required"); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/AbstractMapEntry.java b/plugins/com.google.collect/src/com/google/common/collect/AbstractMapEntry.java new file mode 100644 index 0000000..903bb49 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/AbstractMapEntry.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Objects; + +import java.util.Map.Entry; + +/** + * Implementation of the {@code equals}, {@code hashCode}, and {@code toString} + * methods of {@code Entry}. + * + * @author Jared Levy + */ +public abstract class AbstractMapEntry<K, V> implements Entry<K, V> { + + public abstract K getKey(); + + public abstract V getValue(); + + /** + * {@inheritDoc} + * + * <p>This implementation throws an {@link UnsupportedOperationException}. + * Override this method to support mutable map entries. + */ + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + /** + * Indicates whether an object equals this entry, following the behavior + * specified in {@link Entry#equals}. + */ + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Entry)) { + return false; + } + Entry<?, ?> e = (Entry<?, ?>) o; + return Objects.equal(e.getKey(), getKey()) + && Objects.equal(e.getValue(), getValue()); + } + + /** + * Return this entry's hash code, following the behavior specified in + * {@link Entry#hashCode}. + */ + @Override public int hashCode() { + K k = getKey(); + V v = getValue(); + return ((k == null) ? 0 : k.hashCode()) ^ ((v == null) ? 0 : v.hashCode()); + } + + /** + * Returns a string representation of the form <code>{key}={value}</code>. + */ + @Override public String toString() { + return getKey() + "=" + getValue(); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/AbstractMultiset.java b/plugins/com.google.collect/src/com/google/common/collect/AbstractMultiset.java new file mode 100644 index 0000000..ad6fb3b --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/AbstractMultiset.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Nullable; +import com.google.common.base.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * This class provides a skeletal implementation of the {@link Multiset} + * interface. A new multiset implementation can be created easily by extending + * this class and implementing the {@link Multiset#entrySet()} method, plus + * optionally overriding {@link #add(Object, int)} and + * {@link #remove(Object, int)} to enable modifications to the multiset. + * + * <p>The {@link #contains}, {@link #containsAll}, {@link #count}, and + * {@link #size} implementations all iterate across the set returned by + * {@link Multiset#entrySet()}, as do many methods acting on the set returned by + * {@link #elementSet}. Override those methods for better performance. + * + * @author Kevin Bourrillion + */ +public abstract class AbstractMultiset<E> extends AbstractCollection<E> + implements Multiset<E> { + public abstract Set<Entry<E>> entrySet(); + + // Query Operations + + /** + * {@inheritDoc} + * + * <p>This implementation iterates across {@link Multiset#entrySet()} and + * sums the counts of the entries. + */ + @Override public int size() { + long sum = 0L; + for (Entry<E> entry : entrySet()) { + sum += entry.getCount(); + } + return (int) Math.min(sum, Integer.MAX_VALUE); + } + + @Override public boolean isEmpty() { + return entrySet().isEmpty(); + } + + /** + * Returns {@code true} if this collection contains the specified element. + * + * <p>This implementation checks whether {@link #elementSet} contains the + * element. + */ + @Override public boolean contains(@Nullable Object element) { + return elementSet().contains(element); + } + + /** + * {@inheritDoc} + * + * <p>This implementation usually invokes methods of the + * {@link Multiset#entrySet()} iterator. However, the iterator's + * {@code remove} method sometimes calls the multiset's {@code remove}. + */ + @Override public Iterator<E> iterator() { + return new MultisetIterator(); + } + + private class MultisetIterator implements Iterator<E> { + private final Iterator<Entry<E>> entryIterator; + private Entry<E> currentEntry; + /** Count of subsequent elements equal to current element */ + private int laterCount; + /** Count of all elements equal to current element */ + private int totalCount; + private boolean canRemove; + + MultisetIterator() { + this.entryIterator = entrySet().iterator(); + } + + public boolean hasNext() { + return laterCount > 0 || entryIterator.hasNext(); + } + + public E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + if (laterCount == 0) { + currentEntry = entryIterator.next(); + totalCount = laterCount = currentEntry.getCount(); + } + laterCount--; + canRemove = true; + return currentEntry.getElement(); + } + + public void remove() { + checkState(canRemove, + "no calls to next() since the last call to remove()"); + if (totalCount == 1) { + entryIterator.remove(); + } else { + AbstractMultiset.this.remove(currentEntry.getElement()); + } + totalCount--; + canRemove = false; + } + } + + /** + * {@inheritDoc} + * + * <p>This implementation iterates across {@link Multiset#entrySet()} and + * sums the count of all entries. + */ + public int count(Object element) { + for (Entry<E> entry : entrySet()) { + if (Objects.equal(entry.getElement(), element)) { + return entry.getCount(); + } + } + return 0; + } + + // Modification Operations + + /** + * Ensures that this collection contains the specified element. + * + * <p>This implementation calls {@link #add(Object, int)} with one occurrence. + * + * @return {@code true} always + */ + @Override public boolean add(@Nullable E element) { + add(element, 1); + return true; + } + + /** + * {@inheritDoc} + * + * <p>This implementation always throws an + * {@link UnsupportedOperationException}. To support adding elements, override + * it. + */ + public boolean add(E element, int occurrences) { + throw new UnsupportedOperationException(); + } + + /** + * Removes a single instance of the specified element from this collection, if + * it is present. + * + * <p>This implementation calls {@link #remove(Object, int)} with 1 + * occurrence. + */ + @Override public boolean remove(Object element) { + return remove(element, 1) == 1; + } + + /** + * {@inheritDoc} + * + * <p>This implementation always throws an + * {@link UnsupportedOperationException}. To support removing elements, + * override it. + */ + public int remove(Object element, int occurrences) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * <p>This implementation calls {@link #remove(Object, int)} with + * {@code Integer.MAX_VALUE} occurrences. + */ + public int removeAllOccurrences(Object element) { + return remove(element, Integer.MAX_VALUE); + } + + // Bulk Operations + + /** + * Returns {@code true} if this multiset contains all of the elements in the + * specified collection. + * + * <p><b>Note:</b> this method does not take into account the occurrence + * count of an element in the two collections; it may still return {@code + * true} even if {@code elements} contains several occurrences of an element + * and this multiset contains only one. This is no different than any other + * collection type like {@link List}, but it may be unexpected to the user of + * a multiset. + * + * <p>This implementation checks whether {@link #elementSet} contains the + * elements. + */ + @Override public boolean containsAll(Collection<?> elements) { + return elementSet().containsAll(elements); + } + + /** + * Adds all of the elements in the specified collection to this multiset. + * + * <p>If the collection being added is a multiset, this implementation + * iterates over that multiset's entry set to add the appropriate number of + * occurrences of each of its elements to this multiset. Otherwise, it calls + * {@link AbstractCollection#addAll}. + */ + @Override public boolean addAll(Collection<? extends E> elementsToAdd) { + if (elementsToAdd.isEmpty()) { + return false; + } + if (elementsToAdd instanceof Multiset) { + @SuppressWarnings("unchecked") + Multiset<? extends E> that = (Multiset<? extends E>) elementsToAdd; + for (Entry<? extends E> entry : that.entrySet()) { + add(entry.getElement(), entry.getCount()); + } + } else { + super.addAll(elementsToAdd); + } + return true; + } + + /** + * Removes all of this multiset's elements that are also contained in the + * specified collection. + * + * <p>This implementation iterates over the elements in the collection or, if + * {@code elementsToRemove} is a multiset, the elements in its element set, + * and calls {@link #removeAllOccurrences} on each element. In some cases, + * this approach has better performance than + * {@link AbstractCollection#removeAll}. + */ + @Override public boolean removeAll(Collection<?> elementsToRemove) { + Iterable<?> iterable = (elementsToRemove instanceof Multiset) + ? ((Multiset<?>) elementsToRemove).elementSet() : elementsToRemove; + + boolean modified = false; + for (Object element : iterable) { + if (removeAllOccurrences(element) != 0) { + modified = true; + } + } + return modified; + } + + /** + * Retains only the elements in this multiset that are contained in the + * specified collection. + * + * <p>This implementation iterates over {@link #entrySet()}, checking each + * entry's element to see if it's contained in the provided collection. + * If it's not found, the {@code remove} method of the entry set's + * iterator is invoked. In some cases, this approach has better performance + * than {@link AbstractCollection#removeAll}. + */ + @Override public boolean retainAll(Collection<?> elementsToRetain) { + checkNotNull(elementsToRetain); + Iterator<Entry<E>> entries = entrySet().iterator(); + boolean modified = false; + while (entries.hasNext()) { + Entry<E> entry = entries.next(); + if (!elementsToRetain.contains(entry.getElement())) { + entries.remove(); + modified = true; + } + } + return modified; + } + + /** + * Removes all of the elements from this multiset. + * + * <p>This implementation calls {@code clear} on {@link Multiset#entrySet()}. + */ + @Override public void clear() { + entrySet().clear(); + } + + // Views + + private transient volatile Set<E> elementSet; + + /** + * {@inheritDoc} + * + * <p>The returned set's methods are implemented by calling + * {@link Multiset#entrySet()} methods. + */ + public Set<E> elementSet() { + Set<E> result = elementSet; + if (result == null) { + elementSet = result = createElementSet(); + } + return result; + } + + /** + * Creates a new instance of this multiset's element set, which will be + * returned by {@link #elementSet}. + */ + protected Set<E> createElementSet() { + return new ElementSet(); + } + + private class ElementSet extends AbstractSet<E> { + @Override public Iterator<E> iterator() { + final Iterator<Entry<E>> entryIterator = entrySet().iterator(); + return new Iterator<E>() { + public boolean hasNext() { + return entryIterator.hasNext(); + } + public E next() { + return entryIterator.next().getElement(); + } + public void remove() { + entryIterator.remove(); + } + }; + } + @Override public int size() { + return entrySet().size(); + } + } + + // Object methods + + /** + * {@inheritDoc} + * + * <p>This implementation returns {@code true} if {@code other} is a multiset + * of the same size and if, for each element, the two multisets have the same + * count. + */ + @Override public boolean equals(@Nullable Object other) { + if (other instanceof Multiset) { + Multiset<?> that = (Multiset<?>) other; + /* + * We can't simply check whether the entry sets are equal, since that + * approach fails when a TreeMultiset has a comparator that returns 0 + * when passed unequal elements. + */ + + if (this.size() != that.size()) { + return false; + } + + for (Entry<?> entry : that.entrySet()) { + if (count(entry.getElement()) != entry.getCount()) { + return false; + } + } + + return true; + } + + return false; + } + + /** + * {@inheritDoc} + * + * <p>This implementation returns the hash code of {@link + * Multiset#entrySet()}. + */ + @Override public int hashCode() { + return entrySet().hashCode(); + } + + /** + * {@inheritDoc} + * + * <p>This implementation returns the result of invoking {@code toString} on + * {@link Multiset#entrySet()}. + */ + @Override public String toString() { + return entrySet().toString(); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/AbstractMultisetEntry.java b/plugins/com.google.collect/src/com/google/common/collect/AbstractMultisetEntry.java new file mode 100644 index 0000000..595f83b --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/AbstractMultisetEntry.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Objects; +import com.google.common.collect.Multiset.Entry; + +/** + * Implementation of the {@code equals}, {@code hashCode}, and {@code toString} + * methods of {@link Entry}. + * + * @author Mike Bostock + */ +public abstract class AbstractMultisetEntry<E> implements Entry<E> { + /** + * Indicates whether an object equals this entry, following the behavior + * specified in {@link Entry#equals}. + */ + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Entry)) { + return false; + } + Entry<?> e = (Entry<?>) o; + return Objects.equal(e.getElement(), getElement()) + && (e.getCount() == getCount()); + } + + /** + * Return this entry's hash code, following the behavior specified in + * {@link Entry#hashCode}. + */ + @Override public int hashCode() { + E e = getElement(); + return ((e == null) ? 0 : e.hashCode()) ^ getCount(); + } + + /** + * Returns a string representation of this multiset entry. The string + * representation consists of the associated element if the associated count + * is one, and otherwise the associated element followed by the characters " x + * " (space, x and space) followed by the count. Elements and counts are + * converted to strings as by {@code String.valueOf}. + */ + @Override public String toString() { + String text = String.valueOf(getElement()); + int n = getCount(); + return (n == 1) ? text : (text + " x " + n); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/AbstractRemovableIterator.java b/plugins/com.google.collect/src/com/google/common/collect/AbstractRemovableIterator.java new file mode 100644 index 0000000..9221506 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/AbstractRemovableIterator.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkState; + +/** + * A specialization of {@code AbstractIterator} for data sources which can + * handle arbitrary removal by element, concurrently with iteration. Subclasses + * must implement both the {@link AbstractIterator#computeNext} and {@link + * #remove(Object)} methods. + * + * @author Kevin Bourrillion + */ +public abstract class AbstractRemovableIterator<T> extends AbstractIterator<T> { + private T elementToRemove; + private boolean canRemove; + + /** Removes {@code element} from the backing data source. */ + protected abstract void remove(T element); + + @Override public T next() { + T element = super.next(); + canRemove = true; + elementToRemove = element; + return element; + } + + /** + * Removes from the underlying collection the last element returned by the + * iterator. + */ + @Override public void remove() { + checkState(canRemove, "no calls to next() since the last call to remove()"); + try { + remove(elementToRemove); + } finally { + elementToRemove = null; + canRemove = false; + } + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ArrayListMultimap.java b/plugins/com.google.collect/src/com/google/common/collect/ArrayListMultimap.java new file mode 100644 index 0000000..5d78567 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ArrayListMultimap.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; + +/** + * Implementation of {@code Multimap} that uses an {@code ArrayList} to store + * the values for a given key. A {@link HashMap} associates each key with an + * {@link ArrayList} of values. + * + * <p>When iterating through the collections supplied by this class, the + * ordering of values for a given key agrees with the order in which the values + * were added. + * + * <p>This multimap allows duplicate key-value pairs. After adding a new + * key-value pair equal to an existing key-value pair, the {@code + * ArrayListMultimap} will contain entries for both the new value and the old + * value. + * + * <p>Keys and values may be null. All optional multimap methods are supported, + * and all returned views are modifiable. + * + * <p>This class is not threadsafe when any concurrent operations update the + * multimap. Concurrent read operations will work correctly. To allow concurrent + * update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedListMultimap}. + * + * @author Jared Levy + */ +public final class ArrayListMultimap<K, V> extends StandardListMultimap<K, V> { + // Default from ArrayList + /*@VisibleForTesting*/ static final int DEFAULT_CAPACITY = 10; + + /*@VisibleForTesting*/ transient int initialListCapacity; + + /** Constructs an empty {@code ArrayListMultimap}. */ + public ArrayListMultimap() { + super(new HashMap<K, Collection<V>>()); + initialListCapacity = DEFAULT_CAPACITY; + } + + /** + * Constructs an empty {@code ArrayListMultimap} with the expected number of + * distinct keys and the expected number of values per distinct key. + * + * @param distinctKeys the expected number of distinct keys + * @param valuesPerKey the expected number of values per distinct key + * @throws IllegalArgumentException if either argument is negative + */ + public ArrayListMultimap(int distinctKeys, int valuesPerKey) { + super(new HashMap<K, Collection<V>>(Maps.capacity(distinctKeys))); + checkArgument(valuesPerKey >= 0); + initialListCapacity = valuesPerKey; + } + + /** + * Constructs an {@code ArrayListMultimap} with the same mappings as the + * specified {@code Multimap}. + */ + public ArrayListMultimap(Multimap<? extends K, ? extends V> multimap) { + this(multimap.keySet().size(), + (multimap instanceof ArrayListMultimap) ? + ((ArrayListMultimap<?, ?>) multimap).initialListCapacity : + DEFAULT_CAPACITY); + putAll(multimap); + } + + /** + * Creates a new empty {@code ArrayList} to hold the collection of values for + * an arbitrary key. + */ + @Override List<V> createCollection() { + return new ArrayList<V>(initialListCapacity); + } + + /** + * Reduces the memory used by this {@code ArrayListMultimap}, if feasible. + */ + public void trimToSize() { + for (Collection<V> collection : backingMap().values()) { + ArrayList<V> arrayList = (ArrayList<V>) collection; + arrayList.trimToSize(); + } + } + + /** + * @serialData initial list capacity, number of distinct keys, and then for + * each distinct key: the key, number of values for that key, and the + * key's values + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeInt(initialListCapacity); + Serialization.writeMultimap(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + setMap(new HashMap<K, Collection<V>>()); + initialListCapacity = stream.readInt(); + Serialization.populateMultimap(this, stream); + } + + private static final long serialVersionUID = 0; +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/BiMap.java b/plugins/com.google.collect/src/com/google/common/collect/BiMap.java new file mode 100644 index 0000000..421cc00 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/BiMap.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.util.Map; +import java.util.Set; + +/** + * A bimap (or "bidirectional map") is a map that preserves the uniqueness of + * its values as well as that of its keys. This constraint enables bimaps to + * support an "inverse view", which is another bimap containing the same entries + * as this bimap but with reversed keys and values. + * + * @author Kevin Bourrillion + */ +public interface BiMap<K, V> extends Map<K, V> { + // Modification Operations + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the given value is already bound to a + * different key in this bimap. The bimap will remain unmodified in this + * event. To avoid this exception, call {@link #forcePut} instead. + */ + V put(K key, V value); + + /** + * An alternate form of {@code put} that silently removes any existing entry + * with the value {@code value} before proceeding with the {@link #put} + * operation. If the bimap previously contained the provided key-value + * mapping, this method has no effect. + * + * <p>Note that a successful call to this method could cause the size of the + * bimap to increase by one, stay the same, or even decrease by one. + * + * <p><b>Warning</b>: If an existing entry with this value is removed, the key + * for that entry is discarded and not returned. + * + * @param key the key with which the specified value is to be associated + * @param value the value to be associated with the specified key + * @return the value which was previously associated with the key, which may + * be {@code null}, or {@code null} if there was no previous entry + */ + V forcePut(K key, V value); + + // Bulk Operations + + /** + * {@inheritDoc} + * + * <p><b>Warning:</b> the results of calling this method may vary depending on + * the iteration order of {@code map}. + * + * @throws IllegalArgumentException if an attempt to {@code put} any + * entry fails. Note that some map entries may have been added to the + * bimap before the exception was thrown. + */ + void putAll(Map<? extends K, ? extends V> map); + + // Views + + /** + * {@inheritDoc} + * + * <p>Because a bimap has unique values, this method returns a {@link Set}, + * instead of the {@link java.util.Collection} specified in the {@link Map} + * interface. + */ + Set<V> values(); + + /** + * Returns the inverse view of this bimap, which maps each of this bimap's + * values to its associated key. The two bimaps are backed by the same data; + * any changes to one will appear in the other. + * + * <p><b>Note:</b>There is no guaranteed correspondence between the iteration + * order of a bimap and that of its inverse. + * + * @return the inverse view of this bimap + */ + BiMap<V, K> inverse(); +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ClassToInstanceMap.java b/plugins/com.google.collect/src/com/google/common/collect/ClassToInstanceMap.java new file mode 100644 index 0000000..40524a9 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ClassToInstanceMap.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Nullable; + +import java.util.Map; + +/** + * A map, each entry of which maps a Java + * <a href="http://tinyurl.com/2cmwkz">raw type</a> to an instance of that type. + * In addition to implementing {@code Map}, the additional type-safe operations + * {@link #putInstance} and {@link #getInstance} are available. + * + * <p>Like any other {@code Map<Class, Object>}, this map may contain entries + * for primitive types, and a primitive type and its corresponding wrapper type + * may map to different values. + * + * @param <B> the common supertype that all entries must share; often this is + * simply {@link Object} + * + * @author Kevin Bourrillion + */ +public interface ClassToInstanceMap<B> extends Map<Class<? extends B>, B> { + /** + * Returns the value the specified class is mapped to, or {@code null} if no + * entry for this class is present. This will only return a value that was + * bound to this specific class, not a value that may have been bound to a + * subtype. + */ + <T extends B> T getInstance(Class<T> type); + + /** + * Maps the specified class to the specified value. Does <i>not</i> associate + * this value with any of the class's supertypes. + * + * @return the value previously associated with this class (possibly {@code + * null}), or {@code null} if there was no previous entry. + */ + <T extends B> T putInstance(Class<T> type, @Nullable T value); +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/Collections2.java b/plugins/com.google.collect/src/com/google/common/collect/Collections2.java new file mode 100644 index 0000000..ad9455f --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/Collections2.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Predicate; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; + +/** + * Provides static methods for working with {@code Collection} instances. + * + * @author Chris Povirk + * @author Mike Bostock + * @author Jared Levy + */ +public final class Collections2 { + private Collections2() {} + + /** + * Returns a limited {@link Collection} view of the given {@link Iterable}, or + * the {@code Iterable} itself if it is already a {@code Collection} (in which + * case the rest of this documentation does not apply). The returned + * collection is not appropriate for general use for a number of reasons. + * Instead, it exists to provide frequently desired methods for dealing with + * {@code Iterable} objects -- such as + * {@link Collection#removeAll(Collection)} -- through the familiar + * {@code Collection} interface. To treat the contents of an {@code Iterable} + * as a full-fledged, performant {@code Collection}, it is recommended that + * clients call a method like {@link ImmutableSet#copyOf(Iterable)} or + * {@link Iterables#addAll(Collection, Iterable)} to dump the contents of the + * {@code Iterable} into a standard {@code Collection}. For cases in which a + * view of the {@code Iterable} is required, {@code forIterable()} is + * available. + * + * <p>A number of limitations result from building on the {@code Iterable} + * interface. Notably, {@code size()}, {@code contains()}, and many other + * methods of the returned collection are O(n). The returned collection does + * not support the insertion of items. Removal of elements is supported if the + * underlying {@code Iterable} supports it, and all non-mutative operations + * are supported. Additionally, each method call on the returned collection + * calls {@link Iterable#iterator()} on the source {@code Iterable}. Thus, if + * you wish to call more than one method on the collection or to otherwise + * access the contents of the {@code Iterable} after calling a method, the + * {@code Iterable} must support the creation of multiple iterators. + * + * <p>{@link #equals(Object)} and {@link #hashCode()} are inherited from + * {@link Object}, as the returned {@code Collection} is not an implementation + * of any additional interface, such as {@link java.util.List} or + * {@link java.util.Set}. + * + * <p>The behavior of the returned collection's iterator in the face of + * concurrent structural modification of the returned collection or of the + * underlying {@code Iterable} is undefined, and no guarantee is made that the + * objects are fail-fast. + * + * <p><b>Usage Example</b> + * + * <pre> + * // Remove all instances of "foo" from an Iterable: + * Collections2.forIterable(iterable).removeAll(ImmutableSet.of("foo")); + * </pre> + */ + public static <T> Collection<T> forIterable(final Iterable<T> iterable) { + checkNotNull(iterable); + + if (iterable instanceof Collection) { + return (Collection<T>) iterable; + } + + return new AbstractCollection<T>() { + @Override public Iterator<T> iterator() { + return iterable.iterator(); + } + + @Override public int size() { + return Iterables.size(iterable); + } + + @Override public boolean isEmpty() { + return Iterables.isEmpty(iterable); + } + + @Override public boolean removeAll(Collection<?> c) { + return Iterators.removeAll(iterator(), c); + } + + @Override public boolean retainAll(Collection<?> c) { + return Iterators.retainAll(iterator(), c); + } + }; + } + + /** + * Returns {@code true} if the collection {@code self} contains all of the + * elements in the collection {@code c}. + * + * <p>This method iterates over the specified collection {@code c}, checking + * each element returned by the iterator in turn to see if it is contained in + * the specified collection {@code self}. If all elements are so contained, + * {@code true} is returned, otherwise {@code false}. + * + * @param self a collection which might contain all elements in {@code c} + * @param c a collection whose elements might be contained by {@code self} + */ + // TODO: Make public? + static boolean containsAll(Collection<?> self, Collection<?> c) { + checkNotNull(self); + for (Object o : c) { + if (!self.contains(o)) { + return false; + } + } + return true; + } + + /** + * Converts an iterable into a collection. If the iterable is already a + * collection, it is returned. Otherwise, an {@link java.util.ArrayList} is + * created with the contents of the iterable in same iteration order. + */ + static <E> Collection<E> toCollection(Iterable<E> iterable) { + return (iterable instanceof Collection) + ? (Collection<E>) iterable : Lists.newArrayList(iterable); + } + + /** + * Returns the elements of {@code unfiltered} that satisfy a predicate. The + * returned collection is a live view of {@code unfiltered}; changes to one + * affect the other. + * + * <p>The resulting collection's iterator does not support {@code remove()}, + * but all other collection methods are supported. The collection's + * {@code add()} and {@code addAll()} methods throw an + * {@link IllegalArgumentException} if an element that doesn't satisfy the + * predicate is provided. When methods such as {@code removeAll()} and + * {@code clear()} are called on the filtered collection, only elements that + * satisfy the filter will be removed from the underlying collection. + * + * <p>The returned collection isn't threadsafe or serializable, even if + * {@code unfiltered} is. + * + * <p>Many of the filtered collection's methods, such as {@code size()}, + * iterate across every element in the underlying collection and determine + * which elements satisfy the filter. When a live view is <i>not</i> needed, + * it may be faster to copy the filtered collection and use the copy. + * + * <p>The {@code clear()}, {@code removeAll()}, and {@code retainAll()} + * methods all call {@link Iterator#remove()} on the underlying collection's + * iterator. Consequently, methods like the following throw an + * {@link UnsupportedOperationException}. + * <pre> Collections2.filter(Collections2.filter(collection, predicate1), + * predicate2)).clear();</pre> + * Instead, call + * {@link com.google.common.base.Predicates#and(Predicate, Predicate)} to + * combine the predicates and pass the combined predicate to this method. + * + */ + public static <T> Collection<T> filter( + Collection<T> unfiltered, Predicate<? super T> predicate) { + return new FilteredCollection<T>(unfiltered, predicate); + } + + static class FilteredCollection<T> implements Collection<T> { + private final Collection<T> unfiltered; + private final Predicate<? super T> predicate; + + FilteredCollection(Collection<T> unfiltered, + Predicate<? super T> predicate) { + this.unfiltered = checkNotNull(unfiltered); + this.predicate = checkNotNull(predicate); + } + + public boolean add(T element) { + checkArgument(predicate.apply(element)); + return unfiltered.add(element); + } + + public boolean addAll(Collection<? extends T> collection) { + for (T element : collection) { + checkArgument(predicate.apply(element)); + } + return unfiltered.addAll(collection); + } + + public void clear() { + Iterator<T> iterator = unfiltered.iterator(); + while (iterator.hasNext()) { + T element = iterator.next(); + if (predicate.apply(element)) { + iterator.remove(); + } + } + } + + // if a ClassCastException occurs, contains() returns false + @SuppressWarnings("unchecked") + public boolean contains(Object element) { + try { + return predicate.apply((T) element) && unfiltered.contains(element); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + + public boolean containsAll(Collection<?> collection) { + for (Object element : collection) { + if (!contains(element)) { + return false; + } + } + return true; + } + + public boolean isEmpty() { + return !Iterators.any(unfiltered.iterator(), predicate); + } + + public Iterator<T> iterator() { + return Iterators.filter(unfiltered.iterator(), predicate); + } + + // if a ClassCastException occurs, remove() returns false + @SuppressWarnings("unchecked") + public boolean remove(Object element) { + try { + return predicate.apply((T) element) && unfiltered.remove(element); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + + public boolean removeAll(Collection<?> collection) { + checkNotNull(collection); + boolean changed = false; + Iterator<T> iterator = unfiltered.iterator(); + while (iterator.hasNext()) { + T element = iterator.next(); + if (predicate.apply(element) && collection.contains(element)) { + iterator.remove(); + changed = true; + } + } + return changed; + } + + public boolean retainAll(Collection<?> collection) { + checkNotNull(collection); + boolean changed = false; + Iterator<T> iterator = unfiltered.iterator(); + while (iterator.hasNext()) { + T element = iterator.next(); + if (predicate.apply(element) && !collection.contains(element)) { + iterator.remove(); + changed = true; + } + } + return changed; + } + + public int size() { + return Iterators.size(iterator()); + } + + public Object[] toArray() { + // creating an ArrayList so filtering happens once + return Lists.newArrayList(iterator()).toArray(); + } + + public <T> T[] toArray(T[] array) { + return Lists.newArrayList(iterator()).toArray(array); + } + + @Override public String toString() { + return Iterators.toString(iterator()); + } + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/Comparators.java b/plugins/com.google.collect/src/com/google/common/collect/Comparators.java new file mode 100644 index 0000000..9c99498 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/Comparators.java @@ -0,0 +1,896 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkContentsNotNull; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Function; +import com.google.common.base.Nullable; +import com.google.common.base.Objects; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +/** + * Standard comparators and utilities for creating and working with comparators. + * + * <p>Some of these methods return an {@link Ordering}, a serializable class + * that implements {@link Comparator} and includes many additional methods. + * + * <p>Several method signatures include {@code <C extends Comparable>} with a + * raw {@link Comparable}, instead of + * {@code <C extends Comparable<? super C>}. That's necessary to support classes + * defined without generics. + * + * @author Jared Levy + * @author Kevin Bourrillion + * @author Mike Bostock + */ +public final class Comparators { + private Comparators() {} + + /** + * Returns a comparator that uses the natural ordering of the values. The + * comparator throws a {@link NullPointerException} when passed a null + * parameter. + */ + @SuppressWarnings("unchecked") // see explanation in class Javadoc + public static <T extends Comparable> Ordering<T> naturalOrder() { + return (Ordering<T>) NATURAL_ORDER; + } + + /** @see #naturalOrder */ + @SuppressWarnings("unchecked") // see explanation in class Javadoc + private static final Ordering<Comparable> NATURAL_ORDER + = new NaturalOrdering(); + + @SuppressWarnings("unchecked") // see explanation in class Javadoc + private static class NaturalOrdering extends Ordering<Comparable> { + public int compare(Comparable left, Comparable right) { + checkNotNull(right); // later code throws NPE when only left is null + if (left == right) { + return 0; + } + + /* + * compareTo() may throw a ClassCastException if the elements are not + * mutually comparable. + */ + int result = left.compareTo(right); + return result; + } + + // preserve singleton-ness, so equals() and hashCode() work correctly + private Object readResolve() { + return NATURAL_ORDER; + } + + @Override public String toString() { + return "naturalOrder()"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a comparator that treats {@code null} as less than all other + * values and uses {@code comparator} to compare non-null values. + */ + public static <T> Ordering<T> nullLeastOrder(Comparator<T> comparator) { + checkNotNull(comparator); + return new NullLeastOrdering<T>(comparator); + } + + private static class NullLeastOrdering<T> extends NullHandlingOrdering<T> { + NullLeastOrdering(Comparator<T> comparator) { + super(comparator); + } + + @Override int compareNullAndNonNull() { + return -1; + } + + @Override public String toString() { + return "nullLeastOrder(" + comparator + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a comparator that uses the natural ordering of the values, but also + * handles null values, treating them as less than all other values. + */ + @SuppressWarnings("unchecked") // see explanation in class Javadoc + public static <T extends Comparable> Ordering<T> nullLeastOrder() { + return (Ordering<T>) NULL_LEAST_ORDER; + } + + @SuppressWarnings("unchecked") // see explanation in class Javadoc + private static final Ordering<Comparable> NULL_LEAST_ORDER + = new NaturalNullLeastOrder(); + + @SuppressWarnings("unchecked") // see explanation in class Javadoc + private static class NaturalNullLeastOrder + extends NullLeastOrdering<Comparable> { + private NaturalNullLeastOrder() { + super(NATURAL_ORDER); + } + + // preserve singleton-ness + private Object readResolve() { + return NULL_LEAST_ORDER; + } + + @Override public String toString() { + return "nullLeastOrder()"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a comparator that treats {@code null} as greater than all other + * values and uses the given comparator to compare non-null values. + */ + public static <T> Ordering<T> nullGreatestOrder(Comparator<T> comparator) { + checkNotNull(comparator); + return new NullGreatestOrdering<T>(comparator); + } + + private static class NullGreatestOrdering<T> extends NullHandlingOrdering<T> { + NullGreatestOrdering(Comparator<T> comparator) { + super(comparator); + } + + @Override int compareNullAndNonNull() { + return 1; + } + + @Override public String toString() { + return "nullGreatestOrder(" + comparator + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a comparator that uses the natural ordering of the values, but also + * handles null values, treating them as greater than all other values. + */ + @SuppressWarnings("unchecked") // see explanation in class Javadoc + public static <T extends Comparable> Ordering<T> nullGreatestOrder() { + return (Ordering<T>) NULL_GREATEST_ORDER; + } + + @SuppressWarnings("unchecked") // see explanation in class Javadoc + private static final Ordering<Comparable> NULL_GREATEST_ORDER + = new NaturalNullGreatestOrder(); + + @SuppressWarnings("unchecked") // see explanation in class Javadoc + private static class NaturalNullGreatestOrder + extends NullGreatestOrdering<Comparable> { + private NaturalNullGreatestOrder() { + super(NATURAL_ORDER); + } + + // preserve singleton-ness + private Object readResolve() { + return NULL_GREATEST_ORDER; + } + + @Override public String toString() { + return "nullGreatestOrder()"; + } + + private static final long serialVersionUID = 0; + } + + private static abstract class NullHandlingOrdering<T> extends Ordering<T> { + final Comparator<T> comparator; + + public NullHandlingOrdering(Comparator<T> comparator) { + this.comparator = comparator; + } + + public int compare(T left, T right) { + if (left == right) { + return 0; + } + if (left == null) { + return compareNullAndNonNull(); + } + if (right == null) { + return -compareNullAndNonNull(); + } + return comparator.compare(left, right); + } + + /** + * Returns the value this comparator should produce when comparing {@code + * null} to any non-null value (in that order). + */ + abstract int compareNullAndNonNull(); + + @Override public boolean equals(Object object) { + if (object == null) { + return false; + } + if (object instanceof NullHandlingOrdering) { + NullHandlingOrdering<?> that = (NullHandlingOrdering<?>) object; + return (this.compareNullAndNonNull() == that.compareNullAndNonNull()) + && this.comparator.equals(that.comparator); + } + return false; + } + + @Override public int hashCode() { + return comparator.hashCode(); + } + } + + /** + * Returns a comparator which tries two comparators in order until a non-zero + * result is found, returning that result, and returning zero only if both + * comparators return zero. + * + * @param first the first comparator to invoke + * @param second the second comparator to invoke + * @see #compound(Iterable) + */ + @SuppressWarnings("unchecked") + public static <T> Ordering<T> compound(Comparator<? super T> first, + Comparator<? super T> second) { + Comparator<T> firstT = (Comparator<T>) first; + Comparator<T> secondT = (Comparator<T>) second; + return compound(Arrays.asList(firstT, secondT)); + } + + /** + * Returns a comparator which tries three comparators in order until a + * non-zero result is found, returning that result, and returning zero only if + * all comparators return zero. + * + * @param first the first comparator to invoke + * @param second the second comparator to invoke + * @param third the third comparator to invoke + * @see #compound(Iterable) + */ + @SuppressWarnings("unchecked") + public static <T> Ordering<T> compound(Comparator<? super T> first, + Comparator<? super T> second, Comparator<? super T> third) { + Comparator<T> firstT = (Comparator<T>) first; + Comparator<T> secondT = (Comparator<T>) second; + Comparator<T> thirdT = (Comparator<T>) third; + return compound(Arrays.asList(firstT, secondT, thirdT)); + } + + /** + * Returns a comparator which tries four comparators in order until a non-zero + * result is found, returning that result, and returning zero only if all + * comparators return zero. + * + * @param first the first comparator to invoke + * @param second the second comparator to invoke + * @param third the third comparator to invoke + * @param forth the fourth comparator to invoke + * @see #compound(Iterable) + */ + @SuppressWarnings("unchecked") + public static <T> Ordering<T> compound(Comparator<? super T> first, + Comparator<? super T> second, Comparator<? super T> third, + Comparator<? super T> forth) { + Comparator<T> firstT = (Comparator<T>) first; + Comparator<T> secondT = (Comparator<T>) second; + Comparator<T> thirdT = (Comparator<T>) third; + Comparator<T> forthT = (Comparator<T>) forth; + return compound(Arrays.asList(firstT, secondT, thirdT, forthT)); + } + + /** + * Returns a comparator which tries each given comparator in order until a + * non-zero result is found, returning that result, and returning zero only if + * all comparators return zero. + * + * <p>Subsequent changes to the {@code rest} array do not affect the behavior + * of the returned comparator. + * + * @param first the first comparator to invoke + * @param second the second comparator to invoke + * @param third the third comparator to invoke + * @param forth the fourth comparator to invoke + * @param rest additional comparators to invoke as necessary + * @see #compound(Iterable) + */ + @SuppressWarnings("unchecked") // TODO: check that this is right + public static <T> Ordering<T> compound(Comparator<? super T> first, + Comparator<? super T> second, Comparator<? super T> third, + Comparator<? super T> forth, Comparator<? super T>... rest) { + // TODO: is this really the best way? if so, explain why. + Comparator<T> firstT = (Comparator<T>) first; + Comparator<T> secondT = (Comparator<T>) second; + Comparator<T> thirdT = (Comparator<T>) third; + Comparator<T> forthT = (Comparator<T>) forth; + List<Comparator<T>> list = Lists.newArrayList( + firstT, secondT, thirdT, forthT); + list.addAll(Arrays.asList((Comparator<T>[]) rest)); + return compound(list); + } + + /** + * Returns a comparator which tries each given comparator in order until a + * non-zero result is found, returning that result, and returning zero only if + * all comparators return zero. + * + * <p>The returned comparator is a "view" of the specified {@code Iterable} + * instance; changes to the iterable will be reflected in the behavior of the + * returned comparator. + * + * <p><b>Warning:</b> Supplying an argument with undefined iteration order, + * such as a {@link java.util.HashSet}, will produce non-deterministic + * results. + * + * @param comparators the comparators to try in order + */ + public static <T> Ordering<T> compound( + Iterable<? extends Comparator<? super T>> comparators) { + return new CompoundOrder<T>(comparators); + } + + /** @see Comparators#compound(Iterable) */ + static class CompoundOrder<T> extends Ordering<T> { + private final Iterable<? extends Comparator<? super T>> comparators; + + CompoundOrder(Iterable<? extends Comparator<? super T>> comparators) { + this.comparators = checkContentsNotNull(comparators); + } + + public int compare(T left, T right) { + if (left == right) { + return 0; + } + for (Comparator<? super T> comparator : comparators) { + int result = comparator.compare(left, right); + if (result != 0) { + return result; + } + } + return 0; + } + + @Override public boolean equals(Object object) { + if (object instanceof CompoundOrder) { + CompoundOrder<?> that = (CompoundOrder<?>) object; + return (this.comparators).equals(that.comparators); + } + return false; + } + + @Override public int hashCode() { + return comparators.hashCode(); + } + + @Override public String toString() { + return "compound(" + comparators + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a comparator that compares any two items by applying a function to + * each of them and using the natural ordering of the results. + * + * @param function the function returning the value to compare. The function + * should never return {@code null}. + * @return the generated comparator + */ + @SuppressWarnings("unchecked") // see explanation in class Javadoc + public static <F, T extends Comparable> Ordering<F> + fromFunction(Function<F, T> function) { + return new TransformingNaturalOrder<F, T>(function); + } + + /** @see Comparators#fromFunction(Function) */ + @SuppressWarnings("unchecked") // see explanation in class Javadoc + private static class TransformingNaturalOrder<F, T extends Comparable> + extends Ordering<F> { + private final Function<F, T> function; + + TransformingNaturalOrder(Function<F, T> function) { + this.function = checkNotNull(function); + } + + public int compare(F left, F right) { + T leftTransformed = function.apply(left); + T rightTransformed = function.apply(right); + + /* + * Let this throw a ClassCastException if T is a bizarre Comparable that + * can't be compared to itself. + */ + return leftTransformed.compareTo(rightTransformed); + } + + @Override public boolean equals(Object object) { + if (object instanceof TransformingNaturalOrder) { + TransformingNaturalOrder<?, ?> that + = (TransformingNaturalOrder<?, ?>) object; + return (this.function).equals(that.function); + } + return false; + } + + @Override public int hashCode() { + return function.hashCode(); + } + + @Override public String toString() { + return "fromFunction(" + function + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a comparator that compares any two items by applying a function to + * each of them and using the supplied comparator to compare the results. + * + * @param function the function returning the value to compare + * @param comparator the comparator that receives the function output + * @return the generated comparator + */ + public static <F, T> Ordering<F> fromFunction( + Function<F, T> function, Comparator<? super T> comparator) { + return new TransformingOrder<F, T>(function, comparator); + } + + /** @see Comparators#fromFunction(Function,Comparator) */ + private static class TransformingOrder<F, T> extends Ordering<F> { + private final Function<F, T> function; + private final Comparator<? super T> comparator; + + TransformingOrder( + Function<F, T> function, Comparator<? super T> comparator) { + this.function = checkNotNull(function); + this.comparator = checkNotNull(comparator); + } + + public int compare(F left, F right) { + return comparator.compare(function.apply(left), function.apply(right)); + } + + @Override public boolean equals(Object object) { + if (object instanceof TransformingOrder) { + TransformingOrder<?, ?> that = (TransformingOrder<?, ?>) object; + return (this.function).equals(that.function) + && (this.comparator).equals(that.comparator); + } + return false; + } + + @Override public int hashCode() { + return Objects.hashCode(function, comparator); + } + + @Override public String toString() { + return "fromFunction(" + function + ", " + comparator + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * A comparator that compares objects by the natural ordering of their string + * representations as returned by {@code toString()}. It does not support null + * values. + * + * <p>TODO: Deprecate this in favor of {@link #toStringOrder}. + */ + public static final Ordering<Object> STRING_FORM_ORDER = new ToStringOrder(); + + private static class ToStringOrder extends Ordering<Object> { + public int compare(Object o1, Object o2) { + return o1.toString().compareTo(o2.toString()); + } + + // preserve singleton-ness, so equals() and hashCode() work correctly + private Object readResolve() { + return STRING_FORM_ORDER; + } + + @Override public String toString() { + return "toStringOrder()"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a comparator that compares objects by the natural ordering of their + * string representations as returned by {@code toString()}. It does not + * support null values. + */ + @SuppressWarnings("unchecked") // casting STRING_FORM_ORDER + public static final <T> Ordering<T> toStringOrder() { + return (Ordering<T>) STRING_FORM_ORDER; + } + + /** + * Returns the smaller of the two values, according to their natural ordering. + * If the values are equal, the first is returned. + * + * <p>To handle more than two values, call + * {@link Ordering#min(Object, Object, Object, Object...)} or + * {@link Ordering#min(Iterable)} on the {@link Ordering} returned by + * {@link Ordering#natural()}. + * + * @param a non-null value to compare, returned if less than or equal to b. + * @param b non-null value to compare. + * @throws ClassCastException if the parameters are not mutually comparable + * (for example, a string and an integer). + */ + @SuppressWarnings("unchecked") // see explanation in class Javadoc + public static <T extends Comparable> T min(T a, T b) { + /* + * Let this throw a ClassCastException if T is a bizarre Comparable that + * can't be compared to itself, as documented. + */ + int result = a.compareTo(b); + return result <= 0 ? a : b; + } + + /** + * Returns the larger of the two values, according to their natural ordering. + * If the values are equal, the first is returned. + * + * <p>To handle more than two values, call + * {@link Ordering#max(Object, Object, Object, Object...)} or + * {@link Ordering#max(Iterable)} on the {@link Ordering} returned by + * {@link Ordering#natural()}. + * + * @param a non-null value to compare, returned if greater than or equal to b. + * @param b non-null value to compare. + * @throws ClassCastException if the parameters are not mutually comparable + * (for example, a string and an integer). + */ + @SuppressWarnings("unchecked") // see explanation in class Javadoc + public static <T extends Comparable> T max(T a, T b) { + /* + * Let this throw a ClassCastException if T is a bizarre Comparable that + * can't be compared to itself, as documented. + */ + int result = a.compareTo(b); + return result >= 0 ? a : b; + } + + /** + * Returns the smaller of the two values according to the specified + * comparator. If the values are equal, the first is returned. Null values + * are allowed if the comparator supports them. + * + * <p>To handle more than two values, call + * {@link Ordering#min(Object, Object, Object, Object...)} or + * {@link Ordering#min(Iterable)} on the {@link Ordering} returned by + * {@link Ordering#forComparator(Comparator)}. + * + * @param comparator comparator that compares the two values + * @param a value to compare, returned if less than or equal to b + * @param b value to compare + */ + public static <T> T min(Comparator<? super T> comparator, @Nullable T a, + @Nullable T b) { + return comparator.compare(a, b) <= 0 ? a : b; + } + + /** + * Returns the larger of the two values according to the specified comparator. + * If the values are equal, the first is returned. Null values are allowed if + * the comparator supports them. + * + * <p>To handle more than two values, call + * {@link Ordering#max(Object, Object, Object, Object...)} or + * {@link Ordering#max(Iterable)} on the {@link Ordering} returned by + * {@link Ordering#forComparator(Comparator)}. + * + * @param comparator comparator that compares the two values + * @param a value to compare, returned if greater than or equal to b + * @param b value to compare + */ + public static <T> T max(Comparator<? super T> comparator, @Nullable T a, + @Nullable T b) { + return comparator.compare(a, b) >= 0 ? a : b; + } + + /** + * Returns a comparator that compares objects according to the order in + * which they appear in the given list. Only objects present in the list + * (according to {@link Object#equals}) may be compared. This comparator + * imposes a "partial ordering" over the type {@code T}. Subsequent changes + * to the {@code valuesInOrder} list will have no effect on the returned + * comparator. Null values in the list are supported. + * + * <p>The returned comparator throws an {@link ClassCastException} when it + * receives an input parameter that isn't in {@code valuesInOrder}. + * + * @param valuesInOrder the values that the returned comparator will be able + * to compare, in the order the comparator should follow + * @return the comparator described above + * @throws IllegalArgumentException if {@code valuesInOrder} contains any + * non-consecutive duplicate values (according to {@link Object#equals}) + */ + public static <T> Ordering<T> givenOrder(List<? extends T> valuesInOrder) { + return new GivenOrder<T>(valuesInOrder); + } + + /** + * Returns the comparator that compares objects according to the order in + * which they are given to this method. Only objects present in the argument + * list (according to {@link Object#equals}) may be compared. This comparator + * imposes a "partial ordering" over the type {@code T}. Null values in the + * argument list are supported. + * + * <p>The returned comparator throws a {@link ClassCastException} when it + * receives an input parameter that isn't equal to {@code leastValue} + * or in {@code remainingValuesInOrder}. + * + * @param leastValue the value which the returned comparator should consider + * the "least" of all values + * @param remainingValuesInOrder the rest of the values that the returned + * comparator will be able to compare, in the order the comparator should + * follow + * @return the comparator described above + * @throws IllegalArgumentException if any non-consecutive duplicate values + * (according to {@link Object#equals}) are present among the method + * arguments + */ + public static <T> Ordering<T> givenOrder( + @Nullable T leastValue, T... remainingValuesInOrder) { + return givenOrder(Lists.asList(leastValue, remainingValuesInOrder)); + } + + /** @see Comparators#givenOrder(List) */ + private static class GivenOrder<T> extends Ordering<T> { + final Map<T, Integer> rankMap; + + GivenOrder(List<? extends T> valuesInOrder) { + rankMap = buildRankMap(valuesInOrder); + } + + public int compare(T left, T right) { + return rank(left) - rank(right); // safe because both are nonnegative + } + + int rank(T value) { + Integer rank = rankMap.get(value); + if (rank == null) { + throw new IncomparableValueException(value); + } + return rank; + } + + static <T> Map<T, Integer> buildRankMap( + Collection<? extends T> valuesInOrder) { + Map<T, Integer> ranks + = Maps.newHashMapWithExpectedSize(valuesInOrder.size()); + T previousValue = null; + int rank = 0; + for (T value : valuesInOrder) { + if ((rank == 0) || !Objects.equal(value, previousValue)) { + Integer priorRank = ranks.put(value, rank); + if (priorRank != null) { + throw new DuplicateValueException(value, priorRank, rank); + } + } + rank++; + previousValue = value; + } + return ranks; + } + + @Override public boolean equals(Object object) { + if (object instanceof GivenOrder) { + GivenOrder<?> that = (GivenOrder<?>) object; + return (this.rankMap).equals(that.rankMap); + } + return false; + } + + @Override public int hashCode() { + return rankMap.hashCode(); + } + + @Override public String toString() { + return "givenOrder(" + rankMap + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Exception thrown by a {@link #givenOrder(List)} or + * {@link #givenOrder(Object, Object...)} comparator when comparing a value + * outside the set of values it can compare. Extending + * {@link ClassCastException} may seem odd, but it fits the spirit of the + * {@link Comparator#compare} specification, if you consider that we are + * handling what is conceptually a "subtype" of {@code T}. + */ + static class IncomparableValueException extends ClassCastException { + final Object value; + + IncomparableValueException(Object value) { + super("Cannot compare value: " + value); + this.value = value; + } + + private static final long serialVersionUID = 0; + } + + /** + * Exception thrown when a duplicate value is found in a list or array which + * is not expected to contain any. + */ + static class DuplicateValueException extends IllegalArgumentException { + private static final long serialVersionUID = 0; + final Object value; + final int firstIndex; + final int secondIndex; + + DuplicateValueException(Object value, int firstIndex, int secondIndex) { + super(String.format("Duplicate value at indices %s and %s: %s", + firstIndex, secondIndex, value)); + this.value = value; + this.firstIndex = firstIndex; + this.secondIndex = secondIndex; + } + } + + /** + * Compares the two specified {@code byte} values. The sign of the value + * returned is the same as that of the value that would be returned by the + * call: + * + * <pre> Byte.valueOf(a).compareTo(Byte.valueOf(b))</pre> + * + * @param a the first {@code byte} to compare + * @param b the second {@code byte} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive + * value if {@code a} is greater than {@code b}; otherwise zero. + */ + public static int compare(byte a, byte b) { + return (a < b) ? -1 : ((a > b) ? 1 : 0); + } + + /** + * Compares the two specified {@code char} values. The sign of the value + * returned is the same as that of the value that would be returned by the + * call: + * + * <pre> Character.valueOf(a).compareTo(Character.valueOf(b))</pre> + * + * @param a the first {@code char} to compare + * @param b the second {@code char} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive + * value if {@code a} is greater than {@code b}; otherwise zero. + */ + public static int compare(char a, char b) { + return (a < b) ? -1 : ((a > b) ? 1 : 0); + } + + /** + * Compares the two specified {@code short} values. The sign of the value + * returned is the same as that of the value that would be returned by the + * call: + * + * <pre> Short.valueOf(a).compareTo(Short.valueOf(b))</pre> + * + * @param a the first {@code short} to compare + * @param b the second {@code short} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive + * value if {@code a} is greater than {@code b}; otherwise zero. + */ + public static int compare(short a, short b) { + return (a < b) ? -1 : ((a > b) ? 1 : 0); + } + + /** + * Compares the two specified {@code int} values. The sign of the value + * returned is the same as that of the value that would be returned by the + * call: + * + * <pre> Integer.valueOf(a).compareTo(Integer.valueOf(b))</pre> + * + * @param a the first {@code int} to compare + * @param b the second {@code int} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive + * value if {@code a} is greater than {@code b}; otherwise zero. + */ + public static int compare(int a, int b) { + return (a < b) ? -1 : ((a > b) ? 1 : 0); + } + + /** + * Compares the two specified {@code long} values. The sign of the value + * returned is the same as that of the value that would be returned by the + * call: + * + * <pre> Long.valueOf(a).compareTo(Long.valueOf(b))</pre> + * + * @param a the first {@code long} to compare + * @param b the second {@code long} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive + * value if {@code a} is greater than {@code b}; otherwise zero. + */ + public static int compare(long a, long b) { + return (a < b) ? -1 : ((a > b) ? 1 : 0); + } + + /** + * Compares the two specified {@code double} values. The sign of the value + * returned is the same as that of the value that would be returned by the + * call: + * + * <pre> Double.valueOf(a).compareTo(Double.valueOf(b))</pre> + * + * @param a the first {@code double} to compare + * @param b the second {@code double} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive + * value if {@code a} is greater than {@code b}; otherwise zero. + * @see Double#compare + */ + public static int compare(double a, double b) { + return Double.compare(a, b); // takes care of Double.NaN + } + + /** + * Compares the two specified {@code float} values. The sign of the value + * returned is the same as that of the value that would be returned by the + * call: + * + * <pre> Float.valueOf(a).compareTo(Float.valueOf(b))</pre> + * + * @param a the first {@code float} to compare + * @param b the second {@code float} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive + * value if {@code a} is greater than {@code b}; otherwise zero. + * @see Float#compare + */ + public static int compare(float a, float b) { + return Float.compare(a, b); // takes care of Float.NaN + } + + /** + * Compares the two specified {@code boolean} values. The sign of the value + * returned is the same as that of the value that would be returned by the + * call: + * + * <pre> Boolean.valueOf(a).compareTo(Boolean.valueOf(b))</pre> + * + * @param a the first {@code boolean} to compare + * @param b the second {@code boolean} to compare + * @return a negative value if {@code a} is false and {@code b} is true; a + * positive value if {@code a} is true and {@code b} is false; otherwise + * zero. + */ + public static int compare(boolean a, boolean b) { + return (a == b) ? 0 : (a ? 1 : -1); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ConcurrentMultiset.java b/plugins/com.google.collect/src/com/google/common/collect/ConcurrentMultiset.java new file mode 100644 index 0000000..6bd1766 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ConcurrentMultiset.java @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Nullable; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A multiset that supports concurrent modifications and that provides atomic + * versions of most {@code Multiset} operations (exceptions where noted). Null + * elements are not supported. + * + * @author Cliff L. Biffle + */ +public final class ConcurrentMultiset<E> extends AbstractMultiset<E> + implements Serializable { + /* + * The ConcurrentMultiset's atomic operations are implemented in terms of + * ConcurrentMap's atomic operations. Many of them, such as add(E, int), are + * read-modify-write sequences, and so are implemented as loops that wrap + * ConcurrentMap's compare-and-set operations (like putIfAbsent). + */ + + /** The number of occurrences of each element. */ + private transient final ConcurrentMap<E, Integer> countMap; + + /** + * Creates an empty instance. + */ + public ConcurrentMultiset() { + this(new ConcurrentHashMap<E, Integer>()); + } + + /** + * Creates an instance that contains the elements in a given collection. + */ + public ConcurrentMultiset(Collection<? extends E> collection) { + this(new ConcurrentHashMap<E, Integer>(Maps.capacity(collection.size()))); + addAll(collection); + } + + /** + * Creates an instance using {@code countMap} to store elements and their + * counts. + * + * <p>This instance will assume ownership of {@code countMap}, and other code + * should not maintain references to the map or modify it in any way. + * + * @param countMap backing map for storing the elements in the multiset and + * their counts. It must be empty. + * @throws IllegalArgumentException if {@code countMap} is not empty + */ + ConcurrentMultiset(ConcurrentMap<E, Integer> countMap) { + checkArgument(countMap.isEmpty()); + this.countMap = countMap; + } + + // Query Operations + + /** + * Returns the number of occurrences of {@code element} in this multiset. + * + * @param element the element to look for + * @return the nonnegative number of occurrences of the element + */ + @Override public int count(@Nullable Object element) { + try { + return unbox(countMap.get(element)); + } catch (NullPointerException e) { + return 0; + } catch (ClassCastException e) { + return 0; + } + } + + /** + * {@inheritDoc} + * + * <p>If the data in the multiset is modified by any other threads during this + * method, it is undefined which (if any) of these modifications will be + * reflected in the result. + */ + @Override public int size() { + long sum = 0L; + for (Integer value : countMap.values()) { + sum += value; + } + return (int) Math.min(sum, Integer.MAX_VALUE); + } + + /* + * Note: the superclass toArray() methods assume that size() gives a correct + * answer, which ours does not. + */ + + @Override public Object[] toArray() { + return snapshot().toArray(); + } + + @Override public <T> T[] toArray(T[] array) { + return snapshot().toArray(array); + } + + /* + * We'd love to use 'new ArrayList(this)' or 'list.addAll(this)', but + * either of these would recurse back to us again! + */ + private List<E> snapshot() { + List<E> list = Lists.newArrayListWithExpectedSize(size()); + for (Multiset.Entry<E> entry : entrySet()) { + E element = entry.getElement(); + for (int i = entry.getCount(); i > 0; i--) { + list.add(element); + } + } + return list; + } + + // Modification Operations + + /** + * Adds a number of occurrences of the specified element to this multiset. + * + * @param element the element to add + * @param occurrences the number of occurrences to add + * @return {@code true} if the collection changed as a result (this should + * always be the case unless {@code occurrences} is zero) + * @throws IllegalArgumentException if {@code occurrences} is negative, or if + * the resulting amount would exceed {@link Integer#MAX_VALUE} + */ + @Override public boolean add(E element, int occurrences) { + if (occurrences == 0) { + return false; + } + checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences); + + while (true) { + int current = count(element); + if (current == 0) { + if (countMap.putIfAbsent(element, occurrences) == null) { + return true; + } + } else { + checkArgument(occurrences <= Integer.MAX_VALUE - current, + "Overflow adding %s occurrences to a count of %s", + occurrences, current); + int next = current + occurrences; + if (countMap.replace(element, current, next)) { + return true; + } + } + // If we're still here, there was a race, so just try again. + } + } + + /** + * Removes a number of occurrences of the specified element from this + * multiset. If the multiset contains fewer than this number of occurrences to + * begin with, all occurrences will be removed. + * + * @param element the element whose occurrences should be removed + * @param occurrences the number of occurrences of this element to remove + * @return the number of occurrences that were successfully removed (zero if + * the element was not present) + * @throws IllegalArgumentException if {@code occurrences} is negative + */ + @Override public int remove(@Nullable Object element, int occurrences) { + if (occurrences == 0) { + return 0; + } + checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences); + + while (true) { + int current = count(element); + if (current == 0) { + return 0; + } + if (occurrences >= current) { + if (countMap.remove(element, current)) { + return current; + } + } else { + // We know it's an "E" because it already exists in the map. + @SuppressWarnings("unchecked") + E casted = (E) element; + + if (countMap.replace(casted, current, current - occurrences)) { + return occurrences; + } + } + // If we're still here, there was a race, so just try again. + } + } + + /** + * Removes <b>all</b> occurrences of the specified element from this multiset. + * This method complements {@link Multiset#remove(Object)}, which removes only + * one occurrence at a time. + * + * @param element the element whose occurrences should all be removed + * @return the number of occurrences successfully removed, possibly zero + */ + @Override public int removeAllOccurrences(@Nullable Object element) { + try { + return unbox(countMap.remove(element)); + } catch (NullPointerException e) { + return 0; + } catch (ClassCastException e) { + return 0; + } + } + + /** + * Removes exactly the specified number of occurrences of {@code element}, or + * makes no change if this is not possible. + * + * <p>This method, in contrast to {@link #remove(Object, int)}, has no effect + * when the element count is smaller than {@code occurrences}. + * + * @param element the element to remove + * @param occurrences the number of occurrences of {@code element} to remove + * @return {@code true} if the removal was possible (including if {@code + * occurrences} is zero) + */ + public boolean removeExactly(@Nullable Object element, int occurrences) { + if (occurrences == 0) { + return true; + } + checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences); + + while (true) { + int current = count(element); + if (occurrences > current) { + return false; + } + if (occurrences == current) { + if (countMap.remove(element, occurrences)) { + return true; + } + } else { + @SuppressWarnings("unchecked") // it's in the map, must be an "E" + E casted = (E) element; + if (countMap.replace(casted, current, current - occurrences)) { + return true; + } + } + // If we're still here, there was a race, so just try again. + } + } + /** + * Adds or removes occurrences of {@code element} such that the {@link #count} + * of the element becomes {@code count}. + * + * @return the count of {@code element} in the multiset before this call + * @throws IllegalArgumentException if {@code count} is negative + */ + public int setCount(E element, int count) { + checkArgument(count >= 0, "Invalid count: %s", count); + return (count == 0) + ? removeAllOccurrences(element) + : unbox(countMap.put(element, count)); + } + + /** + * Sets the number of occurrences of {@code element} to {@code newCount}, but + * only if the count is currently {@code oldCount}. If {@code element} does + * not appear in the multiset exactly {@code oldCount} times, no changes will + * be made. + * + * @return {@code true} if the change was successful. This usually indicates + * that the multiset has been modified, but not always: in the case that + * {@code oldCount == newCount}, the method will return {@code true} if + * the condition was met. + * @throws IllegalArgumentException if {@code oldCount} or {@code newCount} is + * negative + */ + public boolean setCount(E element, int oldCount, int newCount) { + checkArgument(oldCount >= 0, "Invalid oldCount: %s", oldCount); + checkArgument(newCount >= 0, "Invalid newCount: %s", newCount); + if (newCount == 0) { + if (oldCount == 0) { + // No change to make, but must return true if the element is not present + return !countMap.containsKey(element); + } else { + return countMap.remove(element, oldCount); + } + } + if (oldCount == 0) { + return countMap.putIfAbsent(element, newCount) == null; + } + return countMap.replace(element, oldCount, newCount); + } + + // Views + + @Override public Set<E> elementSet() { + return countMap.keySet(); + } + + private volatile transient EntrySet entrySet; + + @Override public Set<Multiset.Entry<E>> entrySet() { + EntrySet result = entrySet; + if (result == null) { + entrySet = result = new EntrySet(); + } + return result; + } + + private class EntrySet extends AbstractSet<Multiset.Entry<E>> { + @Override public int size() { + return countMap.size(); + } + + @Override public boolean isEmpty() { + return countMap.isEmpty(); + } + + @Override public boolean contains(Object object) { + if (object instanceof Multiset.Entry) { + Multiset.Entry<?> entry = (Multiset.Entry<?>) object; + Object element = entry.getElement(); + int entryCount = entry.getCount(); + return entryCount > 0 && count(element) == entryCount; + } + return false; + } + + @Override public Iterator<Multiset.Entry<E>> iterator() { + final Iterator<Map.Entry<E, Integer>> backingIterator + = countMap.entrySet().iterator(); + return new Iterator<Multiset.Entry<E>>() { + public boolean hasNext() { + return backingIterator.hasNext(); + } + + public Multiset.Entry<E> next() { + Map.Entry<E, Integer> backingEntry = backingIterator.next(); + return Multisets.immutableEntry( + backingEntry.getKey(), backingEntry.getValue()); + } + + public void remove() { + backingIterator.remove(); + } + }; + } + + /* + * Note: the superclass toArray() methods assume that size() gives a correct + * answer, which ours does not. + */ + + @Override public Object[] toArray() { + return snapshot().toArray(); + } + + @Override public <T> T[] toArray(T[] array) { + return snapshot().toArray(array); + } + + /* + * We'd love to use 'new ArrayList(this)' or 'list.addAll(this)', but + * either of these would recurse back to us again! + */ + private List<Multiset.Entry<E>> snapshot() { + List<Multiset.Entry<E>> list = Lists.newArrayListWithExpectedSize(size()); + for (Multiset.Entry<E> entry : this) { + list.add(entry); + } + return list; + } + + @Override public boolean remove(Object object) { + if (object instanceof Multiset.Entry) { + Multiset.Entry<?> entry = (Multiset.Entry<?>) object; + Object element = entry.getElement(); + int entryCount = entry.getCount(); + return countMap.remove(element, entryCount); + } + return false; + } + + @Override public boolean retainAll(Collection<?> c) { + return super.retainAll(checkNotNull(c)); + } + + @Override public void clear() { + countMap.clear(); + } + + /** + * The hash code is the same as countMap's, though the objects aren't equal. + */ + @Override public int hashCode() { + return countMap.hashCode(); + } + } + + /** + * We use a special form of unboxing that treats null as zero. + */ + private static int unbox(Integer i) { + return (i == null) ? 0 : i; + } + + /** + * @serialData the number of distinct elements, the first element, its count, + * the second element, its count, and so on + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + // creating HashMultiset to handle concurrent changes + Serialization.writeMultiset(HashMultiset.create(this), stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException, NoSuchFieldException { + stream.defaultReadObject(); + Serialization.setFinalField(ConcurrentMultiset.class, this, "countMap", + Maps.newConcurrentHashMap()); + Serialization.populateMultiset(this, stream); + } + + private static final long serialVersionUID = 0L; +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/Constraint.java b/plugins/com.google.collect/src/com/google/common/collect/Constraint.java new file mode 100644 index 0000000..4badb91 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/Constraint.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +/** + * A constraint that an element must satisfy in order to be added to a + * collection. For example, {@link Constraints#notNull()}, which prevents a + * collection from including any null elements, could be implemented like this: + * <pre> {@code + * + * public Object checkElement(Object element) { + * if (element == null) { + * throw new NullPointerException(); + * } + * return element; + * }}</pre> + * + * In order to be effective, constraints should be deterministic; that is, + * they should not depend on state that can change (such as external state, + * random variables, and time) and should only depend on the value of the + * passed-in element. A non-deterministic constraint cannot reliably enforce + * that all the collection's elements meet the constraint, since the constraint + * is only enforced when elements are added. + * + * @see Constraints + * @see MapConstraint + * @author Mike Bostock + */ +public interface Constraint<E> { + /** + * Throws a suitable {@code RuntimeException} if the specified element is + * illegal. Typically this is either a {@link NullPointerException}, an + * {@link IllegalArgumentException}, or a {@link ClassCastException}, though + * an application-specific exception class may be used if appropriate. + * + * @param element the element to check + * @return the provided element + */ + E checkElement(E element); + + /** + * Returns a brief human readable description of this constraint, such as + * "Not null" or "Positive number". + */ + String toString(); +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/Constraints.java b/plugins/com.google.collect/src/com/google/common/collect/Constraints.java new file mode 100644 index 0000000..b981922 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/Constraints.java @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; +import java.util.RandomAccess; +import java.util.Set; +import java.util.SortedSet; + +/** + * Factories and utilities pertaining to the {@link Constraint} interface. + * + * @see MapConstraints + * @author Mike Bostock + * @author Jared Levy + */ +public final class Constraints { + private Constraints() {} + + // enum singleton pattern + private enum NotNullConstraint implements Constraint<Object> { + INSTANCE; + + public Object checkElement(Object element) { + return checkNotNull(element); + } + + @Override public String toString() { + return "Not null"; + } + } + + /** + * Returns a constraint that verifies that the element is not null. If the + * element is null, a {@link NullPointerException} is thrown. + */ + @SuppressWarnings("unchecked") // the cast is safe + public static final <E> Constraint<E> notNull() { + return (Constraint<E>) NotNullConstraint.INSTANCE; + } + + /** + * Returns a constrained view of the specified collection, using the specified + * constraint. Any operations that add new elements to the collection will + * call the provided constraint. However, this method does not verify that + * existing elements satisfy the constraint. + * + * <p>The returned collection is not serializable. + * + * @param collection the collection to constrain + * @param constraint the constraint that validates added elements + * @return a constrained view of the collection + */ + public static <E> Collection<E> constrainedCollection( + Collection<E> collection, Constraint<? super E> constraint) { + return new ConstrainedCollection<E>(collection, constraint); + } + + /** @see Constraints#constrainedCollection */ + static class ConstrainedCollection<E> extends ForwardingCollection<E> { + private final Collection<E> delegate; + private final Constraint<? super E> constraint; + + public ConstrainedCollection( + Collection<E> delegate, Constraint<? super E> constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + @Override protected Collection<E> delegate() { + return delegate; + } + @Override public boolean add(E element) { + constraint.checkElement(element); + return delegate.add(element); + } + @Override public boolean addAll(Collection<? extends E> elements) { + return delegate.addAll(checkElements(elements, constraint)); + } + } + + /** + * Returns a constrained view of the specified set, using the specified + * constraint. Any operations that add new elements to the set will call the + * provided constraint. However, this method does not verify that existing + * elements satisfy the constraint. + * + * <p>The returned set is not serializable. + * + * @param set the set to constrain + * @param constraint the constraint that validates added elements + * @return a constrained view of the set + */ + public static <E> Set<E> constrainedSet( + Set<E> set, Constraint<? super E> constraint) { + return new ConstrainedSet<E>(set, constraint); + } + + /** @see Constraints#constrainedSet */ + static class ConstrainedSet<E> extends ForwardingSet<E> { + private final Set<E> delegate; + private final Constraint<? super E> constraint; + + public ConstrainedSet(Set<E> delegate, Constraint<? super E> constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + @Override protected Set<E> delegate() { + return delegate; + } + @Override public boolean add(E element) { + constraint.checkElement(element); + return delegate.add(element); + } + @Override public boolean addAll(Collection<? extends E> elements) { + return delegate.addAll(checkElements(elements, constraint)); + } + } + + /** + * Returns a constrained view of the specified sorted set, using the specified + * constraint. Any operations that add new elements to the sorted set will + * call the provided constraint. However, this method does not verify that + * existing elements satisfy the constraint. + * + * <p>The returned set is not serializable. + * + * @param sortedSet the sorted set to constrain + * @param constraint the constraint that validates added elements + * @return a constrained view of the sorted set + */ + public static <E> SortedSet<E> constrainedSortedSet( + SortedSet<E> sortedSet, Constraint<? super E> constraint) { + return new ConstrainedSortedSet<E>(sortedSet, constraint); + } + + /** @see Constraints#constrainedSortedSet */ + private static class ConstrainedSortedSet<E> extends ForwardingSortedSet<E> { + final SortedSet<E> delegate; + final Constraint<? super E> constraint; + + ConstrainedSortedSet( + SortedSet<E> delegate, Constraint<? super E> constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + @Override protected SortedSet<E> delegate() { + return delegate; + } + @Override public SortedSet<E> headSet(E toElement) { + return constrainedSortedSet(delegate.headSet(toElement), constraint); + } + @Override public SortedSet<E> subSet(E fromElement, E toElement) { + return constrainedSortedSet( + delegate.subSet(fromElement, toElement), constraint); + } + @Override public SortedSet<E> tailSet(E fromElement) { + return constrainedSortedSet(delegate.tailSet(fromElement), constraint); + } + @Override public boolean add(E element) { + constraint.checkElement(element); + return delegate.add(element); + } + @Override public boolean addAll(Collection<? extends E> elements) { + return delegate.addAll(checkElements(elements, constraint)); + } + } + + /** + * Returns a constrained view of the specified list, using the specified + * constraint. Any operations that add new elements to the list will call the + * provided constraint. However, this method does not verify that existing + * elements satisfy the constraint. + * + * <p>If {@code list} implements {@link RandomAccess}, so will the returned + * list. The returned list is not serializable. + * + * @param list the list to constrain + * @param constraint the constraint that validates added elements + * @return a constrained view of the list + */ + public static <E> List<E> constrainedList( + List<E> list, Constraint<? super E> constraint) { + return (list instanceof RandomAccess) + ? new ConstrainedRandomAccessList<E>(list, constraint) + : new ConstrainedList<E>(list, constraint); + } + + /** @see Constraints#constrainedList */ + private static class ConstrainedList<E> extends ForwardingList<E> { + final List<E> delegate; + final Constraint<? super E> constraint; + + ConstrainedList(List<E> delegate, Constraint<? super E> constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + @Override protected List<E> delegate() { + return delegate; + } + + @Override public boolean add(E element) { + constraint.checkElement(element); + return delegate.add(element); + } + @Override public void add(int index, E element) { + constraint.checkElement(element); + delegate.add(index, element); + } + @Override public boolean addAll(Collection<? extends E> elements) { + return delegate.addAll(checkElements(elements, constraint)); + } + @Override public boolean addAll(int index, Collection<? extends E> elements) + { + return delegate.addAll(index, checkElements(elements, constraint)); + } + @Override public ListIterator<E> listIterator() { + return constrainedListIterator(delegate.listIterator(), constraint); + } + @Override public ListIterator<E> listIterator(int index) { + return constrainedListIterator(delegate.listIterator(index), constraint); + } + @Override public E set(int index, E element) { + constraint.checkElement(element); + return delegate.set(index, element); + } + @Override public List<E> subList(int fromIndex, int toIndex) { + return constrainedList(delegate.subList(fromIndex, toIndex), constraint); + } + } + + /** @see Constraints#constrainedList */ + static class ConstrainedRandomAccessList<E> extends ConstrainedList<E> + implements RandomAccess { + ConstrainedRandomAccessList( + List<E> delegate, Constraint<? super E> constraint) { + super(delegate, constraint); + } + } + + /** + * Returns a constrained view of the specified list iterator, using the + * specified constraint. Any operations that would add new elements to the + * underlying list will be verified by the constraint. + * + * @param listIterator the iterator for which to return a constrained view + * @param constraint the constraint for elements in the list + * @return a constrained view of the specified iterator + */ + // TODO: Make public? + private static <E> ListIterator<E> constrainedListIterator( + ListIterator<E> listIterator, Constraint<? super E> constraint) { + return new ConstrainedListIterator<E>(listIterator, constraint); + } + + /** @see Constraints#constrainedListIterator */ + static class ConstrainedListIterator<E> extends ForwardingListIterator<E> { + private final ListIterator<E> delegate; + private final Constraint<? super E> constraint; + + public ConstrainedListIterator( + ListIterator<E> delegate, Constraint<? super E> constraint) { + this.delegate = delegate; + this.constraint = constraint; + } + @Override protected ListIterator<E> delegate() { + return delegate; + } + + @Override public void add(E element) { + constraint.checkElement(element); + delegate.add(element); + } + @Override public void set(E element) { + constraint.checkElement(element); + delegate.set(element); + } + } + + @SuppressWarnings("unchecked") + static <E> Collection<E> constrainedTypePreservingCollection( + Collection<E> collection, Constraint<E> constraint) { + if (collection instanceof SortedSet) { + return constrainedSortedSet((SortedSet<E>) collection, constraint); + } else if (collection instanceof Set) { + return constrainedSet((Set<E>) collection, constraint); + } else if (collection instanceof List) { + return constrainedList((List<E>) collection, constraint); + } else { + return constrainedCollection(collection, constraint); + } + } + + /** + * Returns a constrained view of the specified multiset, using the specified + * constraint. Any operations that add new elements to the multiset will call + * the provided constraint. However, this method does not verify that + * existing elements satisfy the constraint. + * + * <p>The returned multiset is not serializable. + * + * @param multiset the multiset to constrain + * @param constraint the constraint that validates added elements + * @return a constrained view of the multiset + */ + public static <E> Multiset<E> constrainedMultiset( + Multiset<E> multiset, Constraint<? super E> constraint) { + return new ConstrainedMultiset<E>(multiset, constraint); + } + + /** @see Constraints#constrainedMultiset */ + static class ConstrainedMultiset<E> extends ForwardingMultiset<E> { + private Multiset<E> delegate; + private final Constraint<? super E> constraint; + + public ConstrainedMultiset( + Multiset<E> delegate, Constraint<? super E> constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + @Override protected Multiset<E> delegate() { + return delegate; + } + + @Override public boolean add(E element) { + constraint.checkElement(element); + return delegate.add(element); + } + @Override public boolean addAll(Collection<? extends E> elements) { + return delegate.addAll(checkElements(elements, constraint)); + } + @Override public boolean add(E element, int occurrences) { + constraint.checkElement(element); + return delegate.add(element, occurrences); + } + } + + // TODO: For better performance, avoid making a copy of the elements by having + // addAll() call add() repeatedly instead. + + private static <E> Collection<E> checkElements( + Collection<E> elements, Constraint<? super E> constraint) { + Collection<E> copy = Lists.newArrayList(elements); + for (E element : copy) { + constraint.checkElement(element); + } + return copy; + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/EnumBiMap.java b/plugins/com.google.collect/src/com/google/common/collect/EnumBiMap.java new file mode 100644 index 0000000..4859077 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/EnumBiMap.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.EnumMap; +import java.util.Map; + +/** + * A {@code BiMap} backed by two {@code EnumMap} instances. Null keys and values + * are not permitted. An {@code EnumBiMap} and its inverse are both + * serializable. + * + * @author Mike Bostock + */ +public final class EnumBiMap<K extends Enum<K>, V extends Enum<V>> + extends StandardBiMap<K, V> { + private transient Class<K> keyType; + private transient Class<V> valueType; + + /** + * Constructs a new empty bimap using the specified key type and value type. + * + * @param keyType the key type + * @param valueType the value type + */ + public EnumBiMap(Class<K> keyType, Class<V> valueType) { + super(new EnumMap<K, V>(keyType), new EnumMap<V, K>(valueType)); + this.keyType = keyType; + this.valueType = valueType; + } + + /** + * Constructs a new bimap with the same mappings as the specified map. If the + * specified map is an {@code EnumBiMap}, the new bimap has the same types as + * the provided map. Otherwise, the specified map must contain at least one + * mapping, in order to determine the key and value types. + * + * @param map the map whose mappings are to be placed in this map + * @throws IllegalArgumentException if map is not an {@code EnumBiMap} + * instance and contains no mappings + */ + public EnumBiMap(Map<K, V> map) { + this(inferKeyType(map), inferValueType(map)); + putAll(map); // careful if we make this class non-final + } + + static <K extends Enum<K>> Class<K> inferKeyType(Map<K, ?> map) { + if (map instanceof EnumBiMap) { + return ((EnumBiMap<K, ?>) map).keyType(); + } + if (map instanceof EnumHashBiMap) { + return ((EnumHashBiMap<K, ?>) map).keyType(); + } + checkArgument(!map.isEmpty()); + return map.keySet().iterator().next().getDeclaringClass(); + } + + private static <V extends Enum<V>> Class<V> inferValueType(Map<?, V> map) { + if (map instanceof EnumBiMap) { + return ((EnumBiMap<?, V>) map).valueType; + } + checkArgument(!map.isEmpty()); + return map.values().iterator().next().getDeclaringClass(); + } + + /** Returns the associated key type. */ + public Class<K> keyType() { + return keyType; + } + + /** Returns the associated value type. */ + public Class<V> valueType() { + return valueType; + } + + /** + * @serialData the key class, value class, number of entries, first key, first + * value, second key, second value, and so on. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(keyType); + stream.writeObject(valueType); + Serialization.writeMap(this, stream); + } + + @SuppressWarnings("unchecked") // reading fields populated by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyType = (Class<K>) stream.readObject(); + valueType = (Class<V>) stream.readObject(); + setDelegates(new EnumMap<K, V>(keyType), new EnumMap<V, K>(valueType)); + Serialization.populateMap(this, stream); + } + + private static final long serialVersionUID = 0; +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/EnumHashBiMap.java b/plugins/com.google.collect/src/com/google/common/collect/EnumHashBiMap.java new file mode 100644 index 0000000..742f535 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/EnumHashBiMap.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Nullable; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; + +/** + * A {@code BiMap} backed by an {@code EnumMap} instance for keys-to-values, and + * a {@code HashMap} instance for values-to-keys. Null keys are not permitted, + * but null values are. An {@code EnumHashBiMap} and its inverse are both + * serializable. + * + * @author Mike Bostock + */ +public final class EnumHashBiMap<K extends Enum<K>, V> + extends StandardBiMap<K, V> { + private transient Class<K> keyType; + + /** + * Constructs a new empty bimap using the specified key type, sized to contain + * an entry for every possible key. + * + * @param keyType the key type + */ + public EnumHashBiMap(Class<K> keyType) { + super(new EnumMap<K, V>(keyType), + new HashMap<V, K>(keyType.getEnumConstants().length * 3 / 2)); + this.keyType = keyType; + } + + /** + * Constructs a new bimap with the same mappings as the specified map. If the + * specified map is an {@code EnumHashBiMap} or an {@link EnumBiMap}, the new + * bimap has the same key type as the input bimap. Otherwise, the specified + * map must contain at least one mapping, in order to determine the key type. + * + * @param map the map whose mappings are to be placed in this map + * @throws IllegalArgumentException if map is not an {@code EnumBiMap} or an + * {@code EnumHashBiMap} instance and contains no mappings + */ + public EnumHashBiMap(Map<K, ? extends V> map) { + this(EnumBiMap.inferKeyType(map)); + putAll(map); // careful if we make this class non-final + } + + // Overriding these two methods to show that values may be null (but not keys) + + @Override public V put(K key, @Nullable V value) { + return super.put(key, value); + } + + @Override public V forcePut(K key, @Nullable V value) { + return super.forcePut(key, value); + } + + /** Returns the associated key type. */ + public Class<K> keyType() { + return keyType; + } + + /** + * @serialData the key class, number of entries, first key, first value, + * second key, second value, and so on. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(keyType); + Serialization.writeMap(this, stream); + } + + @SuppressWarnings("unchecked") // reading field populated by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyType = (Class<K>) stream.readObject(); + setDelegates(new EnumMap<K, V>(keyType), + new HashMap<V, K>(keyType.getEnumConstants().length * 3 / 2)); + Serialization.populateMap(this, stream); + } + + private static final long serialVersionUID = 0; +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/EnumMultiset.java b/plugins/com.google.collect/src/com/google/common/collect/EnumMultiset.java new file mode 100644 index 0000000..6a25fb2 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/EnumMultiset.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Multiset implementation backed by an {@link EnumMap}. + * + * @author Jared Levy + */ +public final class EnumMultiset<E extends Enum<E>> + extends AbstractMapBasedMultiset<E> { + private transient Class<E> type; + + /** Creates an empty {@code EnumMultiset}. */ + public EnumMultiset(Class<E> type) { + super(new EnumMap<E, AtomicInteger>(type)); + this.type = type; + } + + /** + * Creates a new {@code EnumMultiset} containing the specified elements. + * + * @param elements the elements that the multiset should contain + * @throws IllegalArgumentException if {@code elements} is empty + */ + public EnumMultiset(Iterable<E> elements) { + this(findClass(elements)); + Iterables.addAll(this, elements); + } + + /** + * Determine the class of the first element in an {@link Iterable}. + * + * @param elements the elements to examine + * @return the {@link Class} of the first element + * @throws IllegalArgumentException if {@code elements} is empty + */ + private static <E extends Enum<E>> Class<E> findClass(Iterable<E> elements) { + Iterator<E> iterator = elements.iterator(); + checkArgument(iterator.hasNext(), + "EnumMultiset constructor passed empty Iterable"); + return iterator.next().getDeclaringClass(); + } + + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(type); + Serialization.writeMultiset(this, stream); + } + + /** + * @serialData the {@code Class<E>} for the enum type, the number of distinct + * elements, the first element, its count, the second element, its count, + * and so on + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + @SuppressWarnings("unchecked") // reading data stored by writeObject + Class<E> localType = (Class<E>) stream.readObject(); + type = localType; + setBackingMap(new EnumMap<E, AtomicInteger>(type)); + Serialization.populateMultiset(this, stream); + } + + private static final long serialVersionUID = 0; +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ForwardingCollection.java b/plugins/com.google.collect/src/com/google/common/collect/ForwardingCollection.java new file mode 100644 index 0000000..6e1b9c3 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ForwardingCollection.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Collection; +import java.util.Iterator; + +/** + * A collection which forwards all its method calls to another collection. + * Subclasses should override one or more methods to modify the behavior of + * the backing collection as desired per the <a + * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>. + * + * @see ForwardingObject + * @author Kevin Bourrillion + */ +public abstract class ForwardingCollection<E> extends ForwardingObject + implements Collection<E> { + + @Override protected abstract Collection<E> delegate(); + + public Iterator<E> iterator() { + return delegate().iterator(); + } + + public int size() { + return delegate().size(); + } + + /** + * {@inheritDoc} + * + * <p>This method always throws a {@link NullPointerException} when + * {@code collection} is null. + */ + public boolean removeAll(Collection<?> collection) { + return delegate().removeAll(checkNotNull(collection)); + } + + public boolean isEmpty() { + return delegate().isEmpty(); + } + + public boolean contains(Object object) { + return delegate().contains(object); + } + + public Object[] toArray() { + return delegate().toArray(); + } + + public <T> T[] toArray(T[] array) { + return delegate().toArray(array); + } + + public boolean add(E element) { + return delegate().add(element); + } + + public boolean remove(Object object) { + return delegate().remove(object); + } + + public boolean containsAll(Collection<?> collection) { + return delegate().containsAll(collection); + } + + public boolean addAll(Collection<? extends E> collection) { + return delegate().addAll(collection); + } + + /** + * {@inheritDoc} + * + * <p>This method always throws a {@link NullPointerException} when + * {@code collection} is null. + */ + public boolean retainAll(Collection<?> collection) { + return delegate().retainAll(checkNotNull(collection)); + } + + public void clear() { + delegate().clear(); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ForwardingConcurrentMap.java b/plugins/com.google.collect/src/com/google/common/collect/ForwardingConcurrentMap.java new file mode 100644 index 0000000..ec920c1 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ForwardingConcurrentMap.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.util.concurrent.ConcurrentMap; + +/** + * A concurrent map which forwards all its method calls to another concurrent + * map. Subclasses should override one or more methods to modify the behavior of + * the backing map as desired per the <a + * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>. + * + * @see ForwardingObject + * @author Charles Fry + */ +public abstract class ForwardingConcurrentMap<K, V> extends ForwardingMap<K, V> + implements ConcurrentMap<K, V> { + + @Override protected abstract ConcurrentMap<K, V> delegate(); + + public V putIfAbsent(K key, V value) { + return delegate().putIfAbsent(key, value); + } + + public boolean remove(Object key, Object value) { + return delegate().remove(key, value); + } + + public V replace(K key, V value) { + return delegate().replace(key, value); + } + + public boolean replace(K key, V oldValue, V newValue) { + return delegate().replace(key, oldValue, newValue); + } + +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ForwardingIterator.java b/plugins/com.google.collect/src/com/google/common/collect/ForwardingIterator.java new file mode 100644 index 0000000..1474f4b --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ForwardingIterator.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.util.Iterator; + +/** + * An iterator which forwards all its method calls to another iterator. + * Subclasses should override one or more methods to modify the behavior of the + * backing iterator as desired per the <a + * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>. + * + * @see ForwardingObject + * @author Kevin Bourrillion + */ +public abstract class ForwardingIterator<T> + extends ForwardingObject implements Iterator<T> { + + @Override protected abstract Iterator<T> delegate(); + + public boolean hasNext() { + return delegate().hasNext(); + } + + public T next() { + return delegate().next(); + } + + public void remove() { + delegate().remove(); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ForwardingList.java b/plugins/com.google.collect/src/com/google/common/collect/ForwardingList.java new file mode 100644 index 0000000..61da7f7 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ForwardingList.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; + +/** + * A list which forwards all its method calls to another list. Subclasses should + * override one or more methods to modify the behavior of the backing list as + * desired per the <a + * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>. + * + * <p>This class does not implement {@link java.util.RandomAccess}. If the + * delegate supports random access, the {@code ForwadingList} subclass should + * implement the {@code RandomAccess} interface. + * + * @author Mike Bostock + */ +public abstract class ForwardingList<E> extends ForwardingCollection<E> + implements List<E> { + + @Override protected abstract List<E> delegate(); + + public void add(int index, E element) { + delegate().add(index, element); + } + + public boolean addAll(int index, Collection<? extends E> elements) { + return delegate().addAll(index, elements); + } + + public E get(int index) { + return delegate().get(index); + } + + public int indexOf(Object element) { + return delegate().indexOf(element); + } + + public int lastIndexOf(Object element) { + return delegate().lastIndexOf(element); + } + + public ListIterator<E> listIterator() { + return delegate().listIterator(); + } + + public ListIterator<E> listIterator(int index) { + return delegate().listIterator(index); + } + + public E remove(int index) { + return delegate().remove(index); + } + + public E set(int index, E element) { + return delegate().set(index, element); + } + + public List<E> subList(int fromIndex, int toIndex) { + return delegate().subList(fromIndex, toIndex); + } + + @Override public boolean equals(Object obj) { + return (this == obj) || delegate().equals(obj); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ForwardingListIterator.java b/plugins/com.google.collect/src/com/google/common/collect/ForwardingListIterator.java new file mode 100644 index 0000000..f704680 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ForwardingListIterator.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.util.ListIterator; + +/** + * A list iterator which forwards all its method calls to another list + * iterator. Subclasses should override one or more methods to modify the + * behavior of the backing iterator as desired per the <a + * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>. + * + * @see ForwardingObject + * @author Mike Bostock + */ +public abstract class ForwardingListIterator<E> extends ForwardingIterator<E> + implements ListIterator<E> { + + @Override protected abstract ListIterator<E> delegate(); + + public void add(E element) { + delegate().add(element); + } + + public boolean hasPrevious() { + return delegate().hasPrevious(); + } + + public int nextIndex() { + return delegate().nextIndex(); + } + + public E previous() { + return delegate().previous(); + } + + public int previousIndex() { + return delegate().previousIndex(); + } + + public void set(E element) { + delegate().set(element); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ForwardingMap.java b/plugins/com.google.collect/src/com/google/common/collect/ForwardingMap.java new file mode 100644 index 0000000..fcf7349 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ForwardingMap.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * A map which forwards all its method calls to another map. Subclasses should + * override one or more methods to modify the behavior of the backing map as + * desired per the <a + * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>. + * + * @see ForwardingObject + * @author Kevin Bourrillion + * @author Jared Levy + */ +public abstract class ForwardingMap<K, V> extends ForwardingObject + implements Map<K, V> { + + @Override protected abstract Map<K, V> delegate(); + + public int size() { + return delegate().size(); + } + + public boolean isEmpty() { + return delegate().isEmpty(); + } + + public V remove(Object object) { + return delegate().remove(object); + } + + public void clear() { + delegate().clear(); + } + + public boolean containsKey(Object key) { + return delegate().containsKey(key); + } + + public boolean containsValue(Object value) { + return delegate().containsValue(value); + } + + public V get(Object key) { + return delegate().get(key); + } + + public V put(K key, V value) { + return delegate().put(key, value); + } + + public void putAll(Map<? extends K, ? extends V> map) { + delegate().putAll(map); + } + + private transient Set<K> keySet; + + /** + * {@inheritDoc} + * + * <p>The returned set's {@code removeAll} and {@code retainAll} methods + * always throw a {@link NullPointerException} when given a null collection. + */ + public Set<K> keySet() { + return (keySet == null) ? keySet = createKeySet() : keySet; + } + + private Set<K> createKeySet() { + final Set<K> delegate = delegate().keySet(); + return new ForwardingSet<K>() { + @Override protected Set<K> delegate() { + return delegate; + } + }; + } + + private transient Collection<V> values; + + /** + * {@inheritDoc} + * + * <p>The returned collection's {@code removeAll} and {@code retainAll} + * methods always throw a {@link NullPointerException} when given a null + * collection. + */ + public Collection<V> values() { + return (values == null) ? values = createValues() : values; + } + + private Collection<V> createValues() { + final Collection<V> delegate = delegate().values(); + return new ForwardingCollection<V>() { + @Override protected Collection<V> delegate() { + return delegate; + } + }; + } + + private transient Set<Entry<K, V>> entrySet; + + /** + * {@inheritDoc} + * + * <p>The returned set's {@code removeAll} and {@code retainAll} methods + * always throw a {@link NullPointerException} when given a null collection. + */ + public Set<Entry<K, V>> entrySet() { + return (entrySet == null) ? entrySet = createEntrySet() : entrySet; + } + + private Set<Entry<K, V>> createEntrySet() { + final Set<Entry<K, V>> delegate = delegate().entrySet(); + return new ForwardingSet<Entry<K, V>>() { + @Override protected Set<Entry<K, V>> delegate() { + return delegate; + } + }; + } + + @Override public boolean equals(Object obj) { + return (this == obj) || delegate().equals(obj); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ForwardingMapEntry.java b/plugins/com.google.collect/src/com/google/common/collect/ForwardingMapEntry.java new file mode 100644 index 0000000..acb9ce2 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ForwardingMapEntry.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.util.Map; + +/** + * A map entry which forwards all its method calls to another map entry. + * Subclasses should override one or more methods to modify the behavior of the + * backing map entry as desired per the <a + * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>. + * + * @see ForwardingObject + * @author Mike Bostock + */ +public abstract class ForwardingMapEntry<K, V> + extends ForwardingObject implements Map.Entry<K, V> { + + @Override protected abstract Map.Entry<K, V> delegate(); + + public K getKey() { + return delegate().getKey(); + } + + public V getValue() { + return delegate().getValue(); + } + + public V setValue(V value) { + return delegate().setValue(value); + } + + @Override public boolean equals(Object obj) { + return delegate().equals(obj); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ForwardingMultimap.java b/plugins/com.google.collect/src/com/google/common/collect/ForwardingMultimap.java new file mode 100644 index 0000000..85219a7 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ForwardingMultimap.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Nullable; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * A multimap which forwards all its method calls to another multimap. + * Subclasses should override one or more methods to modify the behavior of + * the backing multimap as desired per the <a + * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>. + * + * @see ForwardingObject + * @author Robert Konigsberg + */ +public abstract class ForwardingMultimap<K, V> extends ForwardingObject + implements Multimap<K, V> { + + @Override protected abstract Multimap<K, V> delegate(); + + public Map<K, Collection<V>> asMap() { + return delegate().asMap(); + } + + public void clear() { + delegate().clear(); + } + + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { + return delegate().containsEntry(key, value); + } + + public boolean containsKey(@Nullable Object key) { + return delegate().containsKey(key); + } + + public boolean containsValue(@Nullable Object value) { + return delegate().containsValue(value); + } + + public Collection<Entry<K, V>> entries() { + return delegate().entries(); + } + + public Collection<V> get(@Nullable K key) { + return delegate().get(key); + } + + public boolean isEmpty() { + return delegate().isEmpty(); + } + + public Multiset<K> keys() { + return delegate().keys(); + } + + public Set<K> keySet() { + return delegate().keySet(); + } + + public boolean put(K key, V value) { + return delegate().put(key, value); + } + + public boolean putAll(K key, Iterable<? extends V> values) { + return delegate().putAll(key, values); + } + + public boolean putAll(Multimap<? extends K, ? extends V> multimap) { + return delegate().putAll(multimap); + } + + public boolean remove(@Nullable Object key, @Nullable Object value) { + return delegate().remove(key, value); + } + + public Collection<V> removeAll(@Nullable Object key) { + return delegate().removeAll(key); + } + + public Collection<V> replaceValues(K key, Iterable<? extends V> values) { + return delegate().replaceValues(key, values); + } + + public int size() { + return delegate().size(); + } + + public Collection<V> values() { + return delegate().values(); + } + + @Override public boolean equals(@Nullable Object obj) { + return (this == obj) || delegate().equals(obj); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ForwardingMultiset.java b/plugins/com.google.collect/src/com/google/common/collect/ForwardingMultiset.java new file mode 100644 index 0000000..dd522ee --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ForwardingMultiset.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.util.Set; + +/** + * A multiset which forwards all its method calls to another multiset. + * Subclasses should override one or more methods to modify the behavior of the + * backing multiset as desired per the <a + * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>. + * + * @see ForwardingObject + * @author Kevin Bourrillion + */ +public abstract class ForwardingMultiset<E> extends ForwardingCollection<E> + implements Multiset<E> { + + @Override protected abstract Multiset<E> delegate(); + + public int count(Object element) { + return delegate().count(element); + } + + public boolean add(E element, int occurrences) { + return delegate().add(element, occurrences); + } + + public int remove(Object element, int occurrences) { + return delegate().remove(element, occurrences); + } + + public int removeAllOccurrences(Object element) { + return delegate().removeAllOccurrences(element); + } + + public Set<E> elementSet() { + return delegate().elementSet(); + } + + public Set<Entry<E>> entrySet() { + return delegate().entrySet(); + } + + @Override public boolean equals(Object obj) { + return (this == obj) || delegate().equals(obj); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ForwardingObject.java b/plugins/com.google.collect/src/com/google/common/collect/ForwardingObject.java new file mode 100644 index 0000000..ce6784d --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ForwardingObject.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.io.Serializable; + +/** + * An abstract base class for implementing the <a + * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>. + * The {@link #delegate()} method must be overridden to return the instance + * being decorated. + * + * This class does <i>not</i> forward the {@code hashCode} and {@code equals} + * methods through to the backing object, but relies on {@code Object}'s + * implementation. This is necessary to preserve the symmetry of {@code equals}. + * Custom definitions of equality are usually based on an interface, such as + * {@code Set} or {@code List}, so that the implementation of {@code equals} can + * cast the object being tested for equality to the custom interface. {@code + * ForwardingObject} implements no such custom interfaces directly; they + * are implemented only in subclasses. Therefore, forwarding {@code equals} + * would break symmetry, as the forwarding object might consider itself equal to + * the object being tested, but the reverse could not be true. This behavior is + * consistent with the JDK's collection wrappers, such as + * {@link java.util.Collections#unmodifiableCollection}. Use an + * interface-specific subclass of {@code ForwardingObject}, such as {@link + * ForwardingList}, to preserve equality behavior, or override {@code equals} + * directly. + * + * <p>The {@code toString} method is forwarded to the delegate. Although this + * class does not implement {@link Serializable}, a serializable subclass may be + * created since this class has a parameter-less constructor. + * + * @author Mike Bostock + */ +public abstract class ForwardingObject { + + /** Sole constructor. */ + protected ForwardingObject() {} + + /** + * Returns the backing delegate instance that methods are forwarded to. + * Abstract subclasses generally override the {@link ForwardingObject} method + * with an abstract method that has a more specific return type, such as + * {@link ForwardingSet#delegate}. Concrete subclasses override this method to + * supply the instance being decorated. + */ + protected abstract Object delegate(); + + /** + * Returns the string representation generated by the delegate's + * {@code toString} method. + */ + @Override public String toString() { + return delegate().toString(); + } + + /* No equals or hashCode. See class comments for details. */ +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ForwardingQueue.java b/plugins/com.google.collect/src/com/google/common/collect/ForwardingQueue.java new file mode 100644 index 0000000..c6b0e17 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ForwardingQueue.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.util.Queue; + +/** + * A queue which forwards all its method calls to another queue. Subclasses + * should override one or more methods to modify the behavior of the backing + * queue as desired per the <a + * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>. + * + * @see ForwardingObject + * @author Mike Bostock + */ +public abstract class ForwardingQueue<E> extends ForwardingCollection<E> + implements Queue<E> { + + @Override protected abstract Queue<E> delegate(); + + public boolean offer(E o) { + return delegate().offer(o); + } + + public E poll() { + return delegate().poll(); + } + + public E remove() { + return delegate().remove(); + } + + public E peek() { + return delegate().peek(); + } + + public E element() { + return delegate().element(); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ForwardingSet.java b/plugins/com.google.collect/src/com/google/common/collect/ForwardingSet.java new file mode 100644 index 0000000..77201fc --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ForwardingSet.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.util.Set; + +/** + * A set which forwards all its method calls to another set. Subclasses should + * override one or more methods to modify the behavior of the backing set as + * desired per the <a + * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>. + * + * @see ForwardingObject + * @author Kevin Bourrillion + */ +public abstract class ForwardingSet<E> extends ForwardingCollection<E> + implements Set<E> { + + @Override protected abstract Set<E> delegate(); + + @Override public boolean equals(Object obj) { + return (this == obj) || delegate().equals(obj); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ForwardingSortedMap.java b/plugins/com.google.collect/src/com/google/common/collect/ForwardingSortedMap.java new file mode 100644 index 0000000..94ebe25 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ForwardingSortedMap.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.util.Comparator; +import java.util.SortedMap; + +/** + * A sorted map which forwards all its method calls to another sorted map. + * Subclasses should override one or more methods to modify the behavior of + * the backing sorted map as desired per the <a + * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>. + * + * @see ForwardingObject + * @author Mike Bostock + */ +public abstract class ForwardingSortedMap<K, V> extends ForwardingMap<K, V> + implements SortedMap<K, V> { + + @Override protected abstract SortedMap<K, V> delegate(); + + public Comparator<? super K> comparator() { + return delegate().comparator(); + } + + public K firstKey() { + return delegate().firstKey(); + } + + public SortedMap<K, V> headMap(K toKey) { + return delegate().headMap(toKey); + } + + public K lastKey() { + return delegate().lastKey(); + } + + public SortedMap<K, V> subMap(K fromKey, K toKey) { + return delegate().subMap(fromKey, toKey); + } + + public SortedMap<K, V> tailMap(K fromKey) { + return delegate().tailMap(fromKey); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ForwardingSortedSet.java b/plugins/com.google.collect/src/com/google/common/collect/ForwardingSortedSet.java new file mode 100644 index 0000000..15993b2 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ForwardingSortedSet.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.util.Comparator; +import java.util.SortedSet; + +/** + * A sorted set which forwards all its method calls to another sorted set. + * Subclasses should override one or more methods to modify the behavior of the + * backing sorted set as desired per the <a + * href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>. + * + * @see ForwardingObject + * @author Mike Bostock + */ +public abstract class ForwardingSortedSet<E> extends ForwardingSet<E> + implements SortedSet<E> { + + @Override protected abstract SortedSet<E> delegate(); + + public Comparator<? super E> comparator() { + return delegate().comparator(); + } + + public E first() { + return delegate().first(); + } + + public SortedSet<E> headSet(E toElement) { + return delegate().headSet(toElement); + } + + public E last() { + return delegate().last(); + } + + public SortedSet<E> subSet(E fromElement, E toElement) { + return delegate().subSet(fromElement, toElement); + } + + public SortedSet<E> tailSet(E fromElement) { + return delegate().tailSet(fromElement); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/HashBiMap.java b/plugins/com.google.collect/src/com/google/common/collect/HashBiMap.java new file mode 100644 index 0000000..318dc6e --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/HashBiMap.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Nullable; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * A {@link BiMap} backed by two {@link HashMap} instances. This implementation + * allows null keys and values. A {@code HashBiMap} and its inverse are both + * serializable. + * + * @author Mike Bostock + */ +public final class HashBiMap<K, V> extends StandardBiMap<K, V> { + /** + * Constructs a new empty bimap with the default initial capacity (16). + */ + public HashBiMap() { + super(new HashMap<K, V>(), new HashMap<V, K>()); + } + + /** + * Constructs a new empty bimap with the specified expected size. + * + * @param expectedSize the expected number of entries + * @throws IllegalArgumentException if the specified expected size is + * negative + */ + public HashBiMap(int expectedSize) { + super(new HashMap<K, V>(Maps.capacity(expectedSize)), + new HashMap<V, K>(Maps.capacity(expectedSize))); + } + + /** + * Constructs a new bimap containing initial values from {@code map}. The + * bimap is created with an initial capacity sufficient to hold the mappings + * in the specified map. + */ + public HashBiMap(Map<? extends K, ? extends V> map) { + this(map.size()); + putAll(map); // careful if we make this class non-final + } + + // Override these two methods to show that keys and values may be null + + @Override public V put(@Nullable K key, @Nullable V value) { + return super.put(key, value); + } + + @Override public V forcePut(@Nullable K key, @Nullable V value) { + return super.forcePut(key, value); + } + + /** + * @serialData the number of entries, first key, first value, second key, + * second value, and so on. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMap(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + setDelegates(new HashMap<K, V>(), new HashMap<V, K>()); + Serialization.populateMap(this, stream); + } + + private static final long serialVersionUID = 0; +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/HashMultimap.java b/plugins/com.google.collect/src/com/google/common/collect/HashMultimap.java new file mode 100644 index 0000000..aed5c61 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/HashMultimap.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * Implementation of {@link Multimap} using hash tables. + * + * <p>The multimap does not store duplicate key-value pairs. Adding a new + * key-value pair equal to an existing key-value pair has no effect. + * + * <p>Keys and values may be null. All optional multimap methods are supported, + * and all returned views are modifiable. + * + * <p>This class is not threadsafe when any concurrent operations update the + * multimap. Concurrent read operations will work correctly. To allow concurrent + * update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedSetMultimap}. + * + * @author Jared Levy + */ +public final class HashMultimap<K, V> extends StandardSetMultimap<K, V> { + /** Constructs an empty {@code HashMultimap}. */ + public HashMultimap() { + super(new HashMap<K, Collection<V>>()); + } + + /** + * Constructs a {@code HashMultimap} with the same mappings as the specified + * {@code Multimap}. If a key-value mapping appears multiple times in the + * input multimap, it only appears once in the constructed multimap. + * + * @param multimap the multimap whose contents are copied to this multimap. + * @see #putAll(Multimap) + */ + public HashMultimap(Multimap<? extends K, ? extends V> multimap) { + super(new HashMap<K, Collection<V>>( + Maps.capacity(multimap.keySet().size()))); + putAll(multimap); + } + + /** + * {@inheritDoc} + * + * <p>Creates an empty {@code HashSet} for a collection of values for one key. + * + * @return a new {@code HashSet} containing a collection of values for one key + */ + @Override Set<V> createCollection() { + return new HashSet<V>(); + } + + /** + * @serialData number of distinct keys, and then for each distinct key: the + * key, the number of values for that key, and the key's values + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultimap(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + setMap(new HashMap<K, Collection<V>>()); + Serialization.populateMultimap(this, stream); + } + + private static final long serialVersionUID = 0; +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/HashMultiset.java b/plugins/com.google.collect/src/com/google/common/collect/HashMultiset.java new file mode 100644 index 0000000..5c398a7 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/HashMultiset.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Multiset implementation backed by a {@link HashMap}. + * + * @author Kevin Bourrillion + * @author Jared Levy + */ +public final class HashMultiset<E> extends AbstractMapBasedMultiset<E> { + + /** + * Creates a new empty {@code HashMultiset} using the default initial + * capacity. + */ + public static <E> HashMultiset<E> create() { + return new HashMultiset<E>(); + } + + /** + * Creates a new empty {@code HashMultiset} with the specified expected number + * of distinct elements. + * + * @param distinctElements the expected number of distinct elements + * @throws IllegalArgumentException if {@code distinctElements} is negative + */ + public static <E> HashMultiset<E> create(int distinctElements) { + return new HashMultiset<E>(distinctElements); + } + + /** + * Creates a new {@code HashMultiset} containing the specified elements. + * + * @param elements the elements that the multiset should contain + */ + public static <E> HashMultiset<E> create(Iterable<? extends E> elements) { + return new HashMultiset<E>(elements); + } + + /** + * Constructs a new empty {@code HashMultiset} using the default initial + * capacity. + */ + public HashMultiset() { + super(new HashMap<E, AtomicInteger>()); + } + + /** + * Constructs a new empty {@code HashMultiset} with the specified expected + * number of distinct elements. + * + * @param distinctElements the expected number of distinct elements + * @throws IllegalArgumentException if {@code distinctElements} is negative + */ + private HashMultiset(int distinctElements) { + super(new HashMap<E, AtomicInteger>(Maps.capacity(distinctElements))); + } + + /** + * Constructs a new {@code HashMultiset} containing the specified elements. + * + * @param elements the elements that the multiset should contain + */ + private HashMultiset(Iterable<? extends E> elements) { + this(Multisets.inferDistinctElements(elements)); + Iterables.addAll(this, elements); // careful if we make this class non-final + } + + /** + * @serialData the number of distinct elements, the first element, its count, + * the second element, its count, and so on + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultiset(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + setBackingMap(new HashMap<E, AtomicInteger>()); + Serialization.populateMultiset(this, stream); + } + + private static final long serialVersionUID = 0; +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/Hashing.java b/plugins/com.google.collect/src/com/google/common/collect/Hashing.java new file mode 100644 index 0000000..9f2d0cf --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/Hashing.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Static methods for implementing hash-based collections. + * + * @author Kevin Bourrillion + * @author Jesse Wilson + */ +final class Hashing { + private Hashing() {} + + /* + * This method was written by Doug Lea with assistance from members of JCP + * JSR-166 Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain + */ + static int smear(int hashCode) { + hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12); + return hashCode ^ (hashCode >>> 7) ^ (hashCode >>> 4); + } + + // We use power-of-2 tables, and this is the highest int that's a power of 2 + private static final int MAX_TABLE_SIZE = 1 << 30; + + // If the set has this many elements, it will "max out" the table size + private static final int CUTOFF = 1 << 29; + + // Size the table to be at most 50% full, if possible + static int chooseTableSize(int setSize) { + if (setSize < CUTOFF) { + return Integer.highestOneBit(setSize) << 2; + } + + // The table can't be completely full or we'll get infinite reprobes + checkArgument(setSize < MAX_TABLE_SIZE, "collection too large"); + return MAX_TABLE_SIZE; + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ImmutableBiMap.java b/plugins/com.google.collect/src/com/google/common/collect/ImmutableBiMap.java new file mode 100644 index 0000000..7f31e04 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ImmutableBiMap.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Nullable; + +import java.util.Map; + +/** + * An immutable {@link BiMap} with reliable user-specified iteration order. Does + * not permit null keys or values. An {@code ImmutableBiMap} and its inverse + * have the same iteration ordering. + * + * <p>An instance of {@code ImmutableBiMap} contains its own data and will + * <i>never</i> change. {@code ImmutableBiMap} is convenient for + * {@code public static final} maps ("constant maps") and also lets you easily + * make a "defensive copy" of a bimap provided to your class by a caller. + * + * <p><b>Note</b>: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class are + * guaranteed to be immutable. + * + * @author Jared Levy + */ +public abstract class ImmutableBiMap<K, V> extends ImmutableMap<K,V> + implements BiMap<K, V> { + + private static final ImmutableBiMap<Object, Object> EMPTY_IMMUTABLE_BIMAP + = new EmptyBiMap(); + + /** + * Returns the empty bimap. + */ + // Casting to any type is safe because the set will never hold any elements. + @SuppressWarnings("unchecked") + public static <K, V> ImmutableBiMap<K, V> of() { + return (ImmutableBiMap<K, V>) EMPTY_IMMUTABLE_BIMAP; + } + + /** + * Returns an immutable bimap containing a single entry. + */ + public static <K, V> ImmutableBiMap<K, V> of(K k1, V v1) { + return new RegularImmutableBiMap<K, V>(ImmutableMap.of(k1, v1)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static <K, V> ImmutableBiMap<K, V> of(K k1, V v1, K k2, V v2) { + return new RegularImmutableBiMap<K, V>(ImmutableMap.of(k1, v1, k2, v2)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static <K, V> ImmutableBiMap<K, V> of( + K k1, V v1, K k2, V v2, K k3, V v3) { + return new RegularImmutableBiMap<K, V>(ImmutableMap.of( + k1, v1, k2, v2, k3, v3)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static <K, V> ImmutableBiMap<K, V> of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return new RegularImmutableBiMap<K, V>(ImmutableMap.of( + k1, v1, k2, v2, k3, v3, k4, v4)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static <K, V> ImmutableBiMap<K, V> of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return new RegularImmutableBiMap<K, V>(ImmutableMap.of( + k1, v1, k2, v2, k3, v3, k4, v4, k5, v5)); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static <K, V> Builder<K, V> builder() { + return new Builder<K, V>(); + } + + /** + * A builder for creating immutable bimap instances, especially {@code public + * static final} bimaps ("constant bimaps"). Example: <pre> {@code + * + * static final ImmutableBiMap<String, Integer> WORD_TO_INT = + * new ImmutableBiMap.Builder<String, Integer>() + * .put("one", 1) + * .put("two", 2) + * .put("three", 3) + * .build();}</pre> + * + * For <i>small</i> immutable bimaps, the {@code ImmutableBiMap.of()} methods + * are even more convenient. + * + * <p>Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple bimaps in series. Each bimap is a superset + * of the bimaps created before it. + */ + public static final class Builder<K, V> extends ImmutableMap.Builder<K, V> { + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableBiMap#builder}. + */ + public Builder() {} + + /** + * Associates {@code key} with {@code value} in the built bimap. Duplicate + * keys or values are not allowed, and will cause {@link #build} to fail. + */ + @Override public Builder<K, V> put(K key, V value) { + super.put(key, value); + return this; + } + + /** + * Associates all of {@code map's} keys and values in the built bimap. + * Duplicate keys or values are not allowed, and will cause {@link #build} + * to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + @Override public Builder<K, V> putAll(Map<? extends K, ? extends V> map) { + super.putAll(map); + return this; + } + + /** + * Returns a newly-created immutable bimap. + * + * @throws IllegalArgumentException if duplicate keys or values were added + */ + @Override public ImmutableBiMap<K, V> build() { + ImmutableMap<K, V> map = super.build(); + if (map.isEmpty()) { + return of(); + } + return new RegularImmutableBiMap<K, V>(super.build()); + } + } + + /** + * Returns an immutable bimap containing the same entries as {@code map}. If + * {@code map} somehow contains entries with duplicate keys (for example, if + * it is a {@code SortedMap} whose comparator is not <i>consistent with + * equals</i>), the results of this method are undefined. + * + * <p><b>Note:</b> If {@code map} is an {@code ImmutableBiMap}, the given map + * itself will be returned. + * + * @throws IllegalArgumentException if two keys have the same value + * @throws NullPointerException if any key or value in {@code map} is null + */ + public static <K, V> ImmutableBiMap<K, V> copyOf( + Map<? extends K, ? extends V> map) { + if (map instanceof ImmutableBiMap) { + @SuppressWarnings("unchecked") // safe since map is not writable + ImmutableBiMap<K, V> bimap = (ImmutableBiMap<K, V>) map; + return bimap; + } + + if (map.isEmpty()) { + return of(); + } + + ImmutableMap<K, V> immutableMap = ImmutableMap.copyOf(map); + return new RegularImmutableBiMap<K, V>(immutableMap); + } + + abstract ImmutableMap<K, V> delegate(); + + /** + * {@inheritDoc} + * + * <p>The inverse of an {@code ImmutableBiMap} is another + * {@code ImmutableBiMap}. + */ + public abstract ImmutableBiMap<V, K> inverse(); + + @Override public boolean containsKey(@Nullable Object key) { + return delegate().containsKey(key); + } + + @Override public boolean containsValue(@Nullable Object value) { + return inverse().containsKey(value); + } + + @Override public ImmutableSet<Entry<K, V>> entrySet() { + return delegate().entrySet(); + } + + @Override public V get(@Nullable Object key) { + return delegate().get(key); + } + + @Override public ImmutableSet<K> keySet() { + return delegate().keySet(); + } + + /** + * Returns an immutable set of the values in this map. The values are in the + * same order as the parameters used to build this map. + */ + @Override public ImmutableSet<V> values() { + return inverse().keySet(); + } + + /** + * Guaranteed to throw an exception and leave the bimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public V forcePut(K key, V value) { + throw new UnsupportedOperationException(); + } + + public boolean isEmpty() { + return delegate().isEmpty(); + } + + public int size() { + return delegate().size(); + } + + @Override public boolean equals(@Nullable Object o) { + return (o == this) || delegate().equals(o); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } + + @Override public String toString() { + return delegate().toString(); + } + + @Override Object writeReplace() { + return this; // don't use the ImmutableMap serialized form + } + + /** Bimap with no mappings. */ + private static class EmptyBiMap extends ImmutableBiMap<Object, Object> { + @Override ImmutableMap<Object, Object> delegate() { + return ImmutableMap.of(); + } + @Override public ImmutableBiMap<Object, Object> inverse() { + return this; + } + Object readResolve() { + return EMPTY_IMMUTABLE_BIMAP; // preserve singleton property + } + private static final long serialVersionUID = 0; + } + + /** Bimap with one or more mappings. */ + private static class RegularImmutableBiMap<K, V> + extends ImmutableBiMap<K, V> { + final ImmutableMap<K, V> delegate; + final ImmutableBiMap<V, K> inverse; + + RegularImmutableBiMap(ImmutableMap<K, V> delegate) { + this.delegate = delegate; + + ImmutableMap.Builder<V, K> builder = ImmutableMap.builder(); + for (Entry<K, V> entry : delegate.entrySet()) { + builder.put(entry.getValue(), entry.getKey()); + } + ImmutableMap<V, K> backwardMap = builder.build(); + this.inverse = new RegularImmutableBiMap<V, K>(backwardMap, this); + } + + RegularImmutableBiMap(ImmutableMap<K, V> delegate, + ImmutableBiMap<V, K> inverse) { + this.delegate = delegate; + this.inverse = inverse; + } + + @Override ImmutableMap<K, V> delegate() { + return delegate; + } + + @Override public ImmutableBiMap<V, K> inverse() { + return inverse; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ImmutableCollection.java b/plugins/com.google.collect/src/com/google/common/collect/ImmutableCollection.java new file mode 100644 index 0000000..719889e --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ImmutableCollection.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Nullable; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; + +/** + * An immutable collection. Does not permit null elements. + * + * <p><b>Note</b>: Although this class is not final, it cannot be subclassed + * outside of this package as it has no public or protected constructors. Thus, + * instances of this type are guaranteed to be immutable. + * + * @author Jesse Wilson + */ +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableCollection<E> + implements Collection<E>, Serializable { + static final ImmutableCollection<Object> EMPTY_IMMUTABLE_COLLECTION + = new EmptyImmutableCollection(); + + ImmutableCollection() {} + + public Object[] toArray() { + Object[] newArray = new Object[size()]; + return toArray(newArray); + } + + public <T> T[] toArray(T[] other) { + int size = size(); + if (other.length < size) { + other = ObjectArrays.newArray(other, size); + } else if (other.length > size) { + other[size] = null; + } + int index = 0; + for (E element : this) { + /* + * Sleazy fake cast. However, if element is not a T, then the very next + * line must fail with an ArrayStoreException, so we should be safe. + */ + @SuppressWarnings("unchecked") + T elementAsT = (T) element; + + other[index++] = elementAsT; + } + return other; + } + + public boolean contains(@Nullable Object object) { + if (object == null) { + return false; + } + for (E element : this) { + if (element.equals(object)) { + return true; + } + } + return false; + } + + public boolean containsAll(Collection<?> targets) { + for (Object target : targets) { + if (!contains(target)) { + return false; + } + } + return true; + } + + public boolean isEmpty() { + return size() == 0; + } + + @Override public String toString() { + StringBuilder sb = new StringBuilder(size() * 16); + sb.append('['); + Iterator<E> i = iterator(); + if (i.hasNext()) { + sb.append(i.next()); + } + while (i.hasNext()) { + sb.append(", "); + sb.append(i.next()); + } + return sb.append(']').toString(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public final boolean add(E e) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public final boolean remove(Object object) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public final boolean addAll(Collection<? extends E> newElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public final boolean removeAll(Collection<?> oldElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public final boolean retainAll(Collection<?> elementsToKeep) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public final void clear() { + throw new UnsupportedOperationException(); + } + + private static class EmptyImmutableCollection + extends ImmutableCollection<Object> { + public int size() { + return 0; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public boolean contains(@Nullable Object object) { + return false; + } + + public Iterator<Object> iterator() { + return Iterators.emptyIterator(); + } + + @Override public Object[] toArray() { + return ObjectArrays.EMPTY_ARRAY; + } + + @Override public <T> T[] toArray(T[] array) { + if (array.length > 0) { + array[0] = null; + } + return array; + } + } + + private static class ArrayImmutableCollection<E> + extends ImmutableCollection<E> { + private final E[] elements; + + ArrayImmutableCollection(E[] elements) { + this.elements = elements; + } + + public int size() { + return elements.length; + } + + @Override public boolean isEmpty() { + return false; + } + + public Iterator<E> iterator() { + return Iterators.forArray(elements); + } + } + + /* + * Serializes ImmutableCollections as their logical contents. This ensures + * that implementation types do not leak into the serialized representation. + */ + private static class SerializedForm implements Serializable { + final Object[] elements; + SerializedForm(Object[] elements) { + this.elements = elements; + } + Object readResolve() { + return elements.length == 0 + ? EMPTY_IMMUTABLE_COLLECTION + : new ArrayImmutableCollection<Object>(elements.clone()); + } + private static final long serialVersionUID = 0; + } + + Object writeReplace() { + return new SerializedForm(toArray()); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ImmutableList.java b/plugins/com.google.collect/src/com/google/common/collect/ImmutableList.java new file mode 100644 index 0000000..ca97ab7 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ImmutableList.java @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Nullable; + +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.RandomAccess; + +/** + * A high-performance, immutable, random-access {@code List} implementation. + * Does not permit null elements. + * + * <p>Unlike {@link Collections#unmodifiableList}, which is a <i>view</i> of a + * separate collection that can still change, an instance of {@code + * ImmutableList} contains its own private data and will <i>never</i> change. + * {@code ImmutableList} is convenient for {@code public static final} lists + * ("constant lists") and also lets you easily make a "defensive copy" of a list + * provided to your class by a caller. + * + * <p><b>Note</b>: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this type are + * guaranteed to be immutable. + * + * @see ImmutableMap + * @see ImmutableSet + * @author Kevin Bourrillion + */ +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableList<E> extends ImmutableCollection<E> + implements List<E>, RandomAccess { + private static final ImmutableList<?> EMPTY_IMMUTABLE_LIST + = new EmptyImmutableList(); + + /** + * Returns the empty immutable list. This set behaves and performs comparably + * to {@link Collections#emptyList}, and is preferable mainly for consistency + * and maintainability of your code. + */ + // Casting to any type is safe because the list will never hold any elements. + @SuppressWarnings("unchecked") + public static <E> ImmutableList<E> of() { + return (ImmutableList<E>) EMPTY_IMMUTABLE_LIST; + } + + /** + * Returns an immutable list containing a single element. This list behaves + * and performs comparably to {@link Collections#singleton}, but will not + * accept a null element. It is preferable mainly for consistency and + * maintainability of your code. + * + * @throws NullPointerException if {@code element} is null + */ + public static <E> ImmutableList<E> of(E element) { + // TODO: evaluate a specialized SingletonImmutableList + return new RegularImmutableList<E>(copyIntoArray(element)); + } + + // TODO: Add similar overloadings to ImmutableSet and ImmutableSortedSet + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static <E> ImmutableList<E> of(E e1, E e2) { + return new RegularImmutableList<E>(copyIntoArray(e1, e2)); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static <E> ImmutableList<E> of(E e1, E e2, E e3) { + return new RegularImmutableList<E>(copyIntoArray(e1, e2, e3)); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4) { + return new RegularImmutableList<E>(copyIntoArray(e1, e2, e3, e4)); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5) { + return new RegularImmutableList<E>(copyIntoArray(e1, e2, e3, e4, e5)); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static <E> ImmutableList<E> of(E... elements) { + return (elements.length == 0) + ? ImmutableList.<E>of() + : new RegularImmutableList<E>(copyIntoArray(elements)); + } + + /** + * Returns an immutable list containing the given elements, in order. Note + * that if {@code list} is a {@code List<String>}, then {@code + * ImmutableList.copyOf(list)} returns an {@code ImmutableList<String>} + * containing each of the strings in {@code list}, while + * ImmutableList.of(list)} returns an {@code ImmutableList<List<String>>} + * containing one element (the given list itself). + * + * <p><b>Note:</b> Despite what the method name suggests, if {@code elements} + * is an {@code ImmutableList}, no copy will actually be performed, and the + * given list itself will be returned. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static <E> ImmutableList<E> copyOf(Iterable<? extends E> elements) { + if (elements instanceof ImmutableList) { + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableList<E> list = (ImmutableList<E>) elements; + return list; + } + return copyOfInternal(Collections2.toCollection(elements)); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static <E> ImmutableList<E> copyOf(Iterator<? extends E> elements) { + return copyOfInternal(Lists.newArrayList(elements)); + } + + private static <E> ImmutableList<E> copyOfInternal(Collection<?> collection) { + // TODO: Support concurrent collections that change while this method is + // running. + int size = collection.size(); + return (size == 0) + ? ImmutableList.<E>of() + : new RegularImmutableList<E>(copyIntoArray(collection, size)); + } + + private ImmutableList() {} + + // Mark these two methods with @Nullable + + public abstract int indexOf(@Nullable Object object); + + public abstract int lastIndexOf(@Nullable Object object); + + // constrain the return type to ImmutableList<E> + + /** + * Returns an immutable list of the elements between the specified {@code + * fromIndex}, inclusive, and {@code toIndex}, exclusive. (If {@code + * fromIndex} and {@code toIndex} are equal, the empty immutable list is + * returned.) + */ + public abstract ImmutableList<E> subList(int fromIndex, int toIndex); + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + public final boolean addAll(int index, Collection<? extends E> newElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + public final E set(int index, E element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + public final void add(int index, E element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + public final E remove(int index) { + throw new UnsupportedOperationException(); + } + + private static final class EmptyImmutableList extends ImmutableList<Object> { + public int size() { + return 0; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public boolean contains(Object target) { + return false; + } + + public Iterator<Object> iterator() { + return Iterators.emptyIterator(); + } + + @Override public Object[] toArray() { + return ObjectArrays.EMPTY_ARRAY; + } + + @Override public <T> T[] toArray(T[] a) { + if (a.length > 0) { + a[0] = null; + } + return a; + } + + public Object get(int index) { + throw new IndexOutOfBoundsException( + "Invalid index: " + index + ", list size is 0"); + } + + @Override public int indexOf(Object target) { + return -1; + } + + @Override public int lastIndexOf(Object target) { + return -1; + } + + @Override public ImmutableList<Object> subList(int fromIndex, int toIndex) { + if (fromIndex != 0 || toIndex != 0) { + throw new IndexOutOfBoundsException("Invalid range: " + fromIndex + + ".." + toIndex + ", list size is 0"); + } + return this; + } + + public ListIterator<Object> listIterator() { + return Iterators.emptyListIterator(); + } + + public ListIterator<Object> listIterator(int start) { + if (start != 0) { + throw new IndexOutOfBoundsException( + "Invalid index: " + start + ", list size is 0"); + } + return Iterators.emptyListIterator(); + } + + @Override public boolean containsAll(Collection<?> targets) { + return targets.isEmpty(); + } + + @Override public boolean equals(Object object) { + return object == this + || (object instanceof List && ((List<?>) object).isEmpty()); + } + + @Override public int hashCode() { + return 1; + } + + @Override public String toString() { + return "[]"; + } + + private Object readResolve() { + return EMPTY_IMMUTABLE_LIST; + } + } + + private static final class RegularImmutableList<E> extends ImmutableList<E> { + private final int offset; + private final int size; + private final Object[] array; + + private RegularImmutableList(Object[] array, int offset, int size) { + this.offset = offset; + this.size = size; + this.array = array; + } + + private RegularImmutableList(Object[] array) { + this(array, 0, array.length); + } + + public int size() { + return size; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public boolean contains(Object target) { + return indexOf(target) != -1; + } + + // The fake cast to E is safe because the creation methods only allow E's + @SuppressWarnings("unchecked") + public Iterator<E> iterator() { + return (Iterator<E>) Iterators.forArray(array, offset, size); + } + + @Override public Object[] toArray() { + Object[] newArray = new Object[size()]; + System.arraycopy(array, offset, newArray, 0, size); + return newArray; + } + + @Override public <T> T[] toArray(T[] other) { + if (other.length < size) { + other = ObjectArrays.newArray(other, size); + } else if (other.length > size) { + other[size] = null; + } + System.arraycopy(array, offset, other, 0, size); + return other; + } + + // The fake cast to E is safe because the creation methods only allow E's + @SuppressWarnings("unchecked") + public E get(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException( + "Invalid index: " + index + ", list size is " + size); + } + return (E) array[index + offset]; + } + + @Override public int indexOf(Object target) { + if (target != null) { + for (int i = offset; i < offset + size; i++) { + if (array[i].equals(target)) { + return i - offset; + } + } + } + return -1; + } + + @Override public int lastIndexOf(Object target) { + if (target != null) { + for (int i = offset + size - 1; i >= offset; i--) { + if (array[i].equals(target)) { + return i - offset; + } + } + } + return -1; + } + + @Override public ImmutableList<E> subList(int fromIndex, int toIndex) { + if (fromIndex < 0 || toIndex > size || fromIndex > toIndex) { + throw new IndexOutOfBoundsException("Invalid range: " + fromIndex + + ".." + toIndex + ", list size is " + size); + } + + return (fromIndex == toIndex) + ? ImmutableList.<E>of() + : new RegularImmutableList<E>( + array, offset + fromIndex, toIndex - fromIndex); + } + + public ListIterator<E> listIterator() { + return listIterator(0); + } + + public ListIterator<E> listIterator(final int start) { + if ((start < 0) || (start > size)) { + throw new IndexOutOfBoundsException( + "Invalid index: " + start + ", list size is " + size); + } + + return new ListIterator<E>() { + int index = start; + + public boolean hasNext() { + return index < size; + } + public boolean hasPrevious() { + return index > 0; + } + + public int nextIndex() { + return index; + } + public int previousIndex() { + return index - 1; + } + + public E next() { + E result; + try { + result = get(index); + } catch (IndexOutOfBoundsException rethrown) { + throw new NoSuchElementException(); + } + index++; + return result; + } + public E previous() { + E result; + try { + result = get(index - 1); + } catch (IndexOutOfBoundsException rethrown) { + throw new NoSuchElementException(); + } + index--; + return result; + } + + public void set(E o) { + throw new UnsupportedOperationException(); + } + public void add(E o) { + throw new UnsupportedOperationException(); + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof List)) { + return false; + } + + List<?> that = (List<?>) object; + if (that.size() != size()) { + return false; + } + + int index = offset; + if (object instanceof RegularImmutableList) { + RegularImmutableList<?> other = (RegularImmutableList<?>) object; + for (int i = other.offset; i < other.offset + other.size; i++) { + if (!array[index++].equals(other.array[i])) { + return false; + } + } + } else { + for (Object element : that) { + if (!array[index++].equals(element)) { + return false; + } + } + } + return true; + } + + @Override public int hashCode() { + // not caching hash code since it could change if the elements are mutable + // in a way that modifies their hash codes + int hashCode = 1; + for (int i = offset; i < offset + size; i++) { + hashCode = 31 * hashCode + array[i].hashCode(); + } + return hashCode; + } + + @Override public String toString() { + StringBuilder sb = new StringBuilder(size() * 16); + sb.append('[').append(array[offset]); + for (int i = offset + 1; i < offset + size; i++) { + sb.append(", ").append(array[i]); + } + return sb.append(']').toString(); + } + } + + private static Object[] copyIntoArray(Object... source) { + Object[] array = new Object[source.length]; + int index = 0; + for (Object element : source) { + if (element == null) { + throw new NullPointerException("at index " + index); + } + array[index++] = element; + } + return array; + } + + private static Object[] copyIntoArray(Iterable<?> source, int size) { + Object[] array = new Object[size]; + int index = 0; + for (Object element : source) { + if (element == null) { + throw new NullPointerException("at index " + index); + } + array[index++] = element; + } + return array; + } + + /* + * Serializes ImmutableLists as their logical contents. This ensures that + * implementation types do not leak into the serialized representation. + */ + private static class SerializedForm implements Serializable { + final Object[] elements; + SerializedForm(Object[] elements) { + this.elements = elements; + } + Object readResolve() { + return of(elements); + } + private static final long serialVersionUID = 0; + } + + private void readObject(ObjectInputStream stream) + throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + @Override Object writeReplace() { + return new SerializedForm(toArray()); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ImmutableMap.java b/plugins/com.google.collect/src/com/google/common/collect/ImmutableMap.java new file mode 100644 index 0000000..0c260d4 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ImmutableMap.java @@ -0,0 +1,772 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.getOnlyElement; + +import com.google.common.base.Join; +import com.google.common.base.Nullable; +import com.google.common.collect.ImmutableSet.ArrayImmutableSet; +import com.google.common.collect.ImmutableSet.TransformedImmutableSet; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; + +/** + * An immutable, hash-based {@link Map} with reliable user-specified iteration + * order. Does not permit null keys or values. + * + * <p>Unlike {@link Collections#unmodifiableMap}, which is a <i>view</i> of a + * separate map which can still change, an instance of {@code ImmutableMap} + * contains its own data and will <i>never</i> change. {@code ImmutableMap} is + * convenient for {@code public static final} maps ("constant maps") and also + * lets you easily make a "defensive copy" of a map provided to your class by a + * caller. + * + * <p><b>Note</b>: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class are + * guaranteed to be immutable. + * + * @see ImmutableList + * @see ImmutableSet + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableMap<K, V> + implements ConcurrentMap<K, V>, Serializable { + private static final ImmutableMap<?, ?> EMPTY_IMMUTABLE_MAP + = new EmptyImmutableMap(); + + // TODO: restore prebuilder API? optimize, compare performance to HashMap + + /** + * Returns the empty map. This map behaves and performs comparably to + * {@link Collections#emptyMap}, and is preferable mainly for consistency + * and maintainability of your code. + */ + // Casting to any type is safe because the set will never hold any elements. + @SuppressWarnings("unchecked") + public static <K, V> ImmutableMap<K, V> of() { + return (ImmutableMap<K, V>) EMPTY_IMMUTABLE_MAP; + } + + /** + * Returns an immutable map containing a single entry. This map behaves and + * performs comparably to {@link Collections#singletonMap} but will not accept + * a null key or value. It is preferable mainly for consistency and + * maintainability of your code. + */ + public static <K, V> ImmutableMap<K, V> of(K k1, V v1) { + return new SingletonImmutableMap<K, V>( + checkNotNull(k1), checkNotNull(v1)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are added + */ + public static <K, V> ImmutableMap<K, V> of(K k1, V v1, K k2, V v2) { + return new RegularImmutableMap<K, V>(entryOf(k1, v1), entryOf(k2, v2)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are added + */ + public static <K, V> ImmutableMap<K, V> of( + K k1, V v1, K k2, V v2, K k3, V v3) { + return new RegularImmutableMap<K, V>( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are added + */ + public static <K, V> ImmutableMap<K, V> of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return new RegularImmutableMap<K, V>( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are added + */ + public static <K, V> ImmutableMap<K, V> of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return new RegularImmutableMap<K, V>(entryOf(k1, v1), + entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4), entryOf(k5, v5)); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static <K, V> Builder<K, V> builder() { + return new Builder<K, V>(); + } + + /** + * Verifies that {@code key} and {@code value} are non-null, and returns a new + * entry with those values. + */ + private static <K, V> Entry<K, V> entryOf(K key, V value) { + return Maps.immutableEntry(checkNotNull(key), checkNotNull(value)); + } + + /** + * A builder for creating immutable map instances, especially {@code public + * static final} maps ("constant maps"). Example: <pre> {@code + * + * static final ImmutableMap<String, Integer> WORD_TO_INT = + * new ImmutableMap.Builder<String, Integer>() + * .put("one", 1) + * .put("two", 2) + * .put("three", 3) + * .build();}</pre> + * + * For <i>small</i> immutable maps, the {@code ImmutableMap.of()} methods are + * even more convenient. + * + * <p>Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple maps in series. Each map is a superset of + * the maps created before it. + */ + public static class Builder<K, V> { + final List<Entry<K, V>> entries = Lists.newArrayList(); + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableMap#builder}. + */ + public Builder() {} + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate + * keys are not allowed, and will cause {@link #build} to fail. + */ + public Builder<K, V> put(K key, V value) { + entries.add(entryOf(key, value)); + return this; + } + + /** + * Associates all of {@code map's} keys and values in the built map. + * Duplicate keys are not allowed, and will cause {@link #build} to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + public Builder<K, V> putAll(Map<? extends K, ? extends V> map) { + for (Entry<? extends K, ? extends V> entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + return this; + } + + // TODO: Should build() and the ImmutableBiMap version throw an + // IllegalStateException instead? + + /** + * Returns a newly-created immutable map. + * + * @throws IllegalArgumentException if duplicate keys were added + */ + public ImmutableMap<K, V> build() { + return fromEntryList(entries); + } + + private static <K, V> ImmutableMap<K, V> fromEntryList( + List<Entry<K, V>> entries) { + int size = entries.size(); + switch (size) { + case 0: + return of(); + case 1: + return new SingletonImmutableMap<K, V>(getOnlyElement(entries)); + default: + Entry<?, ?>[] entryArray + = entries.toArray(new Entry<?, ?>[entries.size()]); + return new RegularImmutableMap<K, V>(entryArray); + } + } + } + + /** + * Returns an immutable map containing the same entries as {@code map}. If + * {@code map} somehow contains entries with duplicate keys (for example, if + * it is a {@code SortedMap} whose comparator is not <i>consistent with + * equals</i>), the results of this method are undefined. + * + * <p><b>Note:</b> Despite what the method name suggests, if {@code map} is an + * {@code ImmutableMap}, no copy will actually be performed, and the given map + * itself will be returned. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + public static <K, V> ImmutableMap<K, V> copyOf( + Map<? extends K, ? extends V> map) { + if (map instanceof ImmutableMap) { + @SuppressWarnings("unchecked") // safe since map is not writable + ImmutableMap<K, V> kvMap = (ImmutableMap<K, V>) map; + return kvMap; + } + + int size = map.size(); + switch (size) { + case 0: + return of(); + case 1: + Map.Entry<? extends K, ? extends V> loneEntry + = getOnlyElement(map.entrySet()); + /* + * Must cast next line to (K) and (V) to avoid returning an + * ImmutableMap<? extends K, ? extends V>, which is incompatible + * with the return type ImmutableMap<K, V>. (Eclipse will complain + * mightily about this line if there's no cast.) + */ + return of((K) loneEntry.getKey(), (V) loneEntry.getValue()); + default: + Entry<?, ?>[] array = new Entry<?, ?>[size]; + int i = 0; + for (Entry<? extends K, ? extends V> entry : map.entrySet()) { + /* + * See comment above re: <? extends K, ? extends V> to <K, V>. + */ + array[i++] = entryOf((K) entry.getKey(), (V) entry.getValue()); + } + return new RegularImmutableMap<K, V>(array); + } + } + + ImmutableMap() {} + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + public final V put(K k, V v) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + public final V remove(Object o) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + public final V putIfAbsent(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + public final boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + public final boolean replace(K key, V oldValue, V newValue) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + public final V replace(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + public final void putAll(Map<? extends K, ? extends V> map) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + public final void clear() { + throw new UnsupportedOperationException(); + } + + // Overriding to mark it Nullable + public abstract boolean containsKey(@Nullable Object key); + + // Overriding to mark it Nullable + public abstract boolean containsValue(@Nullable Object value); + + // Overriding to mark it Nullable + public abstract V get(@Nullable Object key); + + /** + * Returns an immutable set of the mappings in this map. The entries are in + * the same order as the parameters used to build this map. + */ + public abstract ImmutableSet<Entry<K, V>> entrySet(); + + /** + * Returns an immutable set of the keys in this map. These keys are in + * the same order as the parameters used to build this map. + */ + public abstract ImmutableSet<K> keySet(); + + /** + * Returns an immutable collection of the values in this map. The values are + * in the same order as the parameters used to build this map. + */ + public abstract ImmutableCollection<V> values(); + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Map) { + Map<?, ?> map = (Map<?, ?>) object; + return entrySet().equals(map.entrySet()); + } + return false; + } + + @Override public int hashCode() { + // not caching hash code since it could change if map values are mutable + // in a way that modifies their hash codes + return entrySet().hashCode(); + } + + @Override public String toString() { + StringBuilder result = new StringBuilder(size() * 16).append('{'); + Join.join(result, ", ", entrySet()); + return result.append('}').toString(); + } + + private static final class EmptyImmutableMap + extends ImmutableMap<Object, Object> { + + @Override public Object get(Object key) { + return null; + } + + public int size() { + return 0; + } + + public boolean isEmpty() { + return true; + } + + @Override public boolean containsKey(Object key) { + return false; + } + + @Override public boolean containsValue(Object value) { + return false; + } + + @Override public ImmutableSet<Entry<Object, Object>> entrySet() { + return ImmutableSet.of(); + } + + @Override public ImmutableSet<Object> keySet() { + return ImmutableSet.of(); + } + + @Override public ImmutableCollection<Object> values() { + return ImmutableCollection.EMPTY_IMMUTABLE_COLLECTION; + } + + @Override public boolean equals(Object object) { + return object == this + || (object instanceof Map && ((Map<?, ?>) object).isEmpty()); + } + + @Override public int hashCode() { + return 0; + } + + @Override public String toString() { + return "{}"; + } + } + + private static final class SingletonImmutableMap<K, V> + extends ImmutableMap<K, V> { + private transient final K singleKey; + private transient final V singleValue; + private transient Entry<K, V> entry; + + private SingletonImmutableMap(K singleKey, V singleValue) { + this.singleKey = singleKey; + this.singleValue = singleValue; + } + + private SingletonImmutableMap(Entry<K, V> entry) { + this.entry = entry; + this.singleKey = entry.getKey(); + this.singleValue = entry.getValue(); + } + + private Entry<K, V> entry() { + Entry<K, V> e = entry; + return (e == null) + ? (entry = Maps.immutableEntry(singleKey, singleValue)) : e; + } + + @Override public V get(Object key) { + return singleKey.equals(key) ? singleValue : null; + } + + public int size() { + return 1; + } + + public boolean isEmpty() { + return false; + } + + @Override public boolean containsKey(Object key) { + return singleKey.equals(key); + } + + @Override public boolean containsValue(Object value) { + return singleValue.equals(value); + } + + private transient ImmutableSet<Entry<K, V>> entrySet; + + @Override public ImmutableSet<Entry<K, V>> entrySet() { + ImmutableSet<Entry<K, V>> es = entrySet; + return (es == null) ? (entrySet = ImmutableSet.of(entry())) : es; + } + + private transient ImmutableSet<K> keySet; + + @Override public ImmutableSet<K> keySet() { + ImmutableSet<K> ks = keySet; + return (ks == null) ? (keySet = ImmutableSet.of(singleKey)) : ks; + } + + private transient ImmutableCollection<V> values; + + @Override public ImmutableCollection<V> values() { + ImmutableCollection<V> v = values; + return (v == null) ? (values = new Values<V>(singleValue)) : v; + } + + private static class Values<V> extends ImmutableCollection<V> { + final V singleValue; + + Values(V singleValue) { + this.singleValue = singleValue; + } + + @Override public boolean contains(Object object) { + return singleValue.equals(object); + } + + @Override public boolean isEmpty() { + return false; + } + + public int size() { + return 1; + } + + public Iterator<V> iterator() { + return Iterators.singletonIterator(singleValue); + } + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof Map) { + Map<?, ?> map = (Map<?, ?>) object; + return map.size() == 1 && map.entrySet().contains(entry()); + } + return false; + } + + @Override public int hashCode() { + return singleKey.hashCode() ^ singleValue.hashCode(); + } + + @Override public String toString() { + return new StringBuilder() + .append('{') + .append(singleKey.toString()) + .append('=') + .append(singleValue.toString()) + .append('}') + .toString(); + } + } + + private static final class RegularImmutableMap<K, V> + extends ImmutableMap<K, V> { + private transient final Entry<K, V>[] entries; // entries in insertion order + private transient final Object[] table; // alternating keys and values + // 'and' with an int then shift to get a table index + private transient final int mask; + private transient final int keySetHashCode; + + private RegularImmutableMap(Entry<?, ?>... entries) { + // each of our 6 callers carefully put only Entry<K, V>s into the array! + @SuppressWarnings("unchecked") + Entry<K, V>[] tmp = (Entry<K, V>[]) entries; + this.entries = tmp; + + int tableSize = Hashing.chooseTableSize(entries.length); + table = new Object[tableSize * 2]; + mask = tableSize - 1; + + int keySetHashCodeMutable = 0; + for (Entry<K, V> entry : this.entries) { + K key = entry.getKey(); + int keyHashCode = key.hashCode(); + for (int i = Hashing.smear(keyHashCode); true; i++) { + int index = (i & mask) * 2; + Object existing = table[index]; + if (existing == null) { + V value = entry.getValue(); + table[index] = key; + table[index + 1] = value; + keySetHashCodeMutable += keyHashCode; + break; + } else if (existing.equals(key)) { + throw new IllegalArgumentException("duplicate key: " + key); + } + } + } + keySetHashCode = keySetHashCodeMutable; + } + + @Override public V get(Object key) { + if (key == null) { + return null; + } + for (int i = Hashing.smear(key.hashCode()); true; i++) { + int index = (i & mask) * 2; + Object candidate = table[index]; + if (candidate == null) { + return null; + } + if (candidate.equals(key)) { + // we're careful to store only V's at odd indices + @SuppressWarnings("unchecked") + V value = (V) table[index + 1]; + return value; + } + } + } + + public int size() { + return entries.length; + } + + public boolean isEmpty() { + return false; + } + + @Override public boolean containsKey(Object key) { + return get(key) != null; + } + + @Override public boolean containsValue(Object value) { + if (value == null) { + return false; + } + for (Entry<K, V> entry : entries) { + if (entry.getValue().equals(value)) { + return true; + } + } + return false; + } + + // TODO: Serialization of the map views should serialize the map, and + // deserialization should call entrySet(), keySet(), or values() on the + // deserialized map. The views are serializable since the Immutable* classes + // are. + + private transient ImmutableSet<Entry<K, V>> entrySet; + + @Override public ImmutableSet<Entry<K, V>> entrySet() { + ImmutableSet<Entry<K, V>> es = entrySet; + return (es == null) ? (entrySet = new EntrySet<K, V>(this)) : es; + } + + private static class EntrySet<K, V> extends ArrayImmutableSet<Entry<K, V>> { + final RegularImmutableMap<K, V> map; + + EntrySet(RegularImmutableMap<K, V> map) { + super(map.entries); + this.map = map; + } + + @Override public boolean contains(Object target) { + if (target instanceof Entry) { + Entry<?, ?> entry = (Entry<?, ?>) target; + V mappedValue = map.get(entry.getKey()); + return mappedValue != null && mappedValue.equals(entry.getValue()); + } + return false; + } + } + + private transient ImmutableSet<K> keySet; + + @Override public ImmutableSet<K> keySet() { + ImmutableSet<K> ks = keySet; + return (ks == null) ? (keySet = new KeySet<K, V>(this)) : ks; + } + + private static class KeySet<K, V> + extends TransformedImmutableSet<Map.Entry<K, V>, K> { + final RegularImmutableMap<K, V> map; + + KeySet(RegularImmutableMap<K, V> map) { + super(map.entries, map.keySetHashCode); + this.map = map; + } + + @Override K transform(Entry<K, V> element) { + return element.getKey(); + } + + @Override public boolean contains(Object target) { + return map.containsKey(target); + } + } + + private transient ImmutableCollection<V> values; + + @Override public ImmutableCollection<V> values() { + ImmutableCollection<V> v = values; + return (v == null) ? (values = new Values<V>(this)) : v; + } + + private static class Values<V> extends ImmutableCollection<V> { + final RegularImmutableMap<?, V> map; + + Values(RegularImmutableMap<?, V> map) { + this.map = map; + } + + public int size() { + return map.entries.length; + } + + @Override public boolean isEmpty() { + return false; + } + + public Iterator<V> iterator() { + return new AbstractIterator<V>() { + int index = 0; + @Override protected V computeNext() { + return (index < map.entries.length) + ? map.entries[index++].getValue() + : endOfData(); + } + }; + } + + @Override public boolean contains(Object target) { + return map.containsValue(target); + } + } + + @Override public String toString() { + StringBuilder result = new StringBuilder(size() * 16) + .append('{') + .append(entries[0]); + for (int e = 1; e < entries.length; e++) { + result.append(", ").append(entries[e].toString()); + } + return result.append('}').toString(); + } + } + + /* + * Serialized type for all ImmutableMap instances. It captures the logical + * contents and they are reconstructed using public factory methods. This + * ensures that the implementation types remain as implementation details. + */ + private static class SerializedForm implements Serializable { + final Object[] keys; + final Object[] values; + SerializedForm(ImmutableMap<?, ?> map) { + keys = new Object[map.size()]; + values = new Object[map.size()]; + int i = 0; + for (Entry<?, ?> entry : map.entrySet()) { + keys[i] = entry.getKey(); + values[i] = entry.getValue(); + i++; + } + } + Object readResolve() { + Builder<Object, Object> builder = new Builder<Object, Object>(); + for (int i = 0; i < keys.length; i++) { + builder.put(keys[i], values[i]); + } + return builder.build(); + } + private static final long serialVersionUID = 0; + } + + Object writeReplace() { + return new SerializedForm(this); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ImmutableMultimap.java b/plugins/com.google.collect/src/com/google/common/collect/ImmutableMultimap.java new file mode 100644 index 0000000..f47d628 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ImmutableMultimap.java @@ -0,0 +1,591 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Nullable; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * An immutable {@link ListMultimap} with reliable user-specified key and value + * iteration order. Does not permit null keys or values. + * + * <p>Unlike {@link Multimaps#unmodifiableListMultimap(ListMultimap)}, which is + * a <i>view</i> of a separate map which can still change, an instance of + * {@code ImmutableMultimap} contains its own data and will <i>never</i> change. + * {@code ImmutableMultimap} is convenient for {@code public static final} + * multimaps ("constant multimaps") and also lets you easily make a "defensive + * copy" of a multimap provided to your class by a caller. + * + * <p><b>Note</b>: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class are + * guaranteed to be immutable. + * + * @author Jared Levy + */ +public class ImmutableMultimap<K, V> + implements ListMultimap<K, V>, Serializable { + + private static ImmutableMultimap<Object, Object> EMPTY_MULTIMAP + = new EmptyMultimap(); + + private static class EmptyMultimap extends ImmutableMultimap<Object, Object> { + EmptyMultimap() { + super(ImmutableMap.<Object, ImmutableList<Object>>of(), 0); + } + @Override public boolean isEmpty() { + return true; + } + Object readResolve() { + return EMPTY_MULTIMAP; // preserve singleton property + } + private static final long serialVersionUID = 0; + } + + /** Returns the empty multimap. */ + // Casting is safe because the multimap will never hold any elements. + @SuppressWarnings("unchecked") + public static <K, V> ImmutableMultimap<K, V> empty() { + return (ImmutableMultimap<K, V>) EMPTY_MULTIMAP; + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static <K, V> Builder<K, V> builder() { + return new Builder<K, V>(); + } + + /** + * Multimap for {@link Builder} that maintains key and value orderings, allows + * duplicate values, and performs better than {@link LinkedListMultimap}. + */ + private static class BuilderMultimap<K, V> extends StandardMultimap<K, V> { + BuilderMultimap() { + super(new LinkedHashMap<K, Collection<V>>()); + } + @Override Collection<V> createCollection() { + return Lists.newArrayList(); + } + private static final long serialVersionUID = 0; + } + + /** + * A builder for creating immutable multimap instances, especially + * {@code public static final} multimaps ("constant multimaps"). Example: + * <pre> {@code + * + * static final Multimap<String,Integer> STRING_TO_INTEGER_MULTIMAP = + * new ImmutableMultimap.Builder<String, Integer>() + * .put("one", 1) + * .putAll("several", 1, 2, 3) + * .putAll("many", 1, 2, 3, 4, 5) + * .build();}</pre> + * + * <p>Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple multimaps in series. Each multimap + * contains the key-value mappings in the previously created multimaps. + */ + public static class Builder<K, V> { + private final Multimap<K, V> builderMultimap = new BuilderMultimap<K, V>(); + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableMultimap#builder}. + */ + public Builder() {} + + /** + * Adds a key-value mapping to the built multimap. + */ + public Builder<K, V> put(K key, V value) { + builderMultimap.put(checkNotNull(key), checkNotNull(value)); + return this; + } + + /** + * Stores a collection of values with the same key in the built multimap. + * + * @throws NullPointerException if {@code key}, {@code values}, or any + * element in {@code values} is null. The builder is left in an invalid + * state. + */ + public Builder<K, V> putAll(K key, Iterable<? extends V> values) { + Collection<V> valueList = builderMultimap.get(checkNotNull(key)); + for (V value : values) { + valueList.add(checkNotNull(value)); + } + return this; + } + + /** + * Stores an array of values with the same key in the built multimap. + * + * @throws NullPointerException if the key or any value is null. If a later + * value is null, earlier values may be added to the builder. + */ + public Builder<K, V> putAll(K key, V... values) { + Collection<V> valueList = builderMultimap.get(checkNotNull(key)); + for (V value : values) { + valueList.add(checkNotNull(value)); + } + return this; + } + + public ImmutableMultimap<K, V> build() { + return copyOf(builderMultimap); + } + } + + /** + * Returns an immutable multimap containing the same mappings as + * {@code multimap}. The generated multimap's key and value orderings + * correspond to the iteration ordering of the {@code multimap.asMap()} view. + * + * <p><b>Note:</b> Despite what the method name suggests, if + * {@code multimap} is an {@code ImmutableMultimap}, no copy will actually be + * performed, and the given map itself will be returned. + * + * @throws NullPointerException if any key or value in {@code multimap} is + * null + */ + public static <K, V> ImmutableMultimap<K, V> copyOf( + Multimap<? extends K, ? extends V> multimap) { + if (multimap.isEmpty()) { + return empty(); + } + + if (multimap instanceof ImmutableMultimap) { + @SuppressWarnings("unchecked") // safe since multimap is not writable + ImmutableMultimap<K, V> kvMultimap = (ImmutableMultimap<K, V>) multimap; + return kvMultimap; + } + + ImmutableMap.Builder<K, ImmutableList<V>> builder = ImmutableMap.builder(); + int size = 0; + + for (Map.Entry<? extends K, ? extends Collection<? extends V>> entry + : multimap.asMap().entrySet()) { + ImmutableList<V> list = ImmutableList.copyOf(entry.getValue()); + if (!list.isEmpty()) { + builder.put(entry.getKey(), list); + size += list.size(); + } + } + + return new ImmutableMultimap<K, V>(builder.build(), size); + } + + private final transient ImmutableMap<K, ImmutableList<V>> map; + private final transient int size; + + private ImmutableMultimap(ImmutableMap<K, ImmutableList<V>> map, int size) { + this.map = map; + this.size = size; + } + + // mutators (not supported) + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public List<V> removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public List<V> replaceValues(K key, Iterable<? extends V> values) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public boolean put(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public boolean putAll(K key, Iterable<? extends V> values) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public boolean putAll(Multimap<? extends K, ? extends V> multimap) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + // accessors + + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { + Collection<V> valueList = map.get(key); + return (valueList != null) && valueList.contains(value); + } + + public boolean containsKey(@Nullable Object key) { + return map.containsKey(key); + } + + public boolean containsValue(@Nullable Object value) { + for (Collection<V> valueList : map.values()) { + if (valueList.contains(value)) { + return true; + } + } + return false; + } + + public boolean isEmpty() { + return false; + } + + public int size() { + return size; + } + + @Override public boolean equals(@Nullable Object obj) { + if (obj instanceof Multimap) { + Multimap<?, ?> that = (Multimap<?, ?>) obj; + return map.equals(that.asMap()); + } + return false; + } + + @Override public int hashCode() { + return map.hashCode(); + } + + @Override public String toString() { + return map.toString(); + } + + // views + + /** + * Returns an immutable list of the values for the given key. If no mappings + * in the multimap have the provided key, an empty immutable list is returned. + * The values are in the same order as the parameters used to build this + * multimap. + */ + public ImmutableList<V> get(@Nullable K key) { + ImmutableList<V> list = map.get(key); + return (list == null) ? ImmutableList.<V>of() : list; + } + + /** + * Returns an immutable set of the distinct keys in this multimap. These keys + * are ordered according to when they first appeared during the construction + * of this multimap. + */ + public ImmutableSet<K> keySet() { + return map.keySet(); + } + + /** + * Returns an immutable map that associates each key with its corresponding + * values in the multimap. Though the method signature doesn't say so + * explicitly, the returned map has {@link ImmutableList} values. + */ + @SuppressWarnings("unchecked") // a widening cast + public ImmutableMap<K, Collection<V>> asMap() { + return (ImmutableMap) map; + } + + private transient ImmutableCollection<Map.Entry<K, V>> entries; + + /** + * Returns an immutable collection of all key-value pairs in the multimap. Its + * iterator traverses the values for the first key, the values for the second + * key, and so on. + */ + public ImmutableCollection<Map.Entry<K, V>> entries() { + ImmutableCollection<Map.Entry<K, V>> result = entries; + return (result == null) ? (entries = new Entries<K, V>(this)) : result; + } + + private static class Entries<K, V> + extends ImmutableCollection<Map.Entry<K, V>> { + final ImmutableMultimap<K, V> multimap; + + Entries(ImmutableMultimap<K, V> multimap) { + this.multimap = multimap; + } + + public Iterator<Map.Entry<K, V>> iterator() { + final Iterator<Map.Entry<K, ImmutableList<V>>> mapIterator + = multimap.map.entrySet().iterator(); + + return new UnmodifiableIterator<Map.Entry<K, V>>() { + Map.Entry<K, ImmutableList<V>> mapEntry; + int index; + + public boolean hasNext() { + return ((mapEntry != null) && (index < mapEntry.getValue().size())) + || mapIterator.hasNext(); + } + + public Map.Entry<K, V> next() { + if ((mapEntry == null) || (index >= mapEntry.getValue().size())) { + mapEntry = mapIterator.next(); + index = 0; + } + V value = mapEntry.getValue().get(index); + index++; + return Maps.immutableEntry(mapEntry.getKey(), value); + } + }; + } + + public int size() { + return multimap.size(); + } + + @Override public boolean contains(Object object) { + if (object instanceof Map.Entry) { + Map.Entry<?, ?> entry = (Map.Entry<?, ?>) object; + return multimap.containsEntry(entry.getKey(), entry.getValue()); + } + return false; + } + + private static final long serialVersionUID = 0; + } + + private transient ImmutableMultiset<K> keys; + + /** + * Returns a collection, which may contain duplicates, of all keys. The number + * of times of key appears in the returned multiset equals the number of + * mappings the key has in the multimap. Duplicate keys appear consecutively + * in the multiset's iteration order. + */ + public ImmutableMultiset<K> keys() { + ImmutableMultiset<K> result = keys; + return (result == null) + ? (keys = new ImmutableMultiset<K>(new CountMap<K, V>(this), size)) + : result; + } + + /** + * Map from key to value count, used to create the {@link #keys} multiset. + * Methods that {@link ImmutableMultiset} doesn't require are unsupported. + */ + private static class CountMap<K, V> extends ImmutableMap<K, Integer> { + final ImmutableMultimap<K, V> multimap; + + CountMap(ImmutableMultimap<K, V> multimap) { + this.multimap = multimap; + } + + @Override public boolean containsKey(Object key) { + return multimap.containsKey(key); + } + + @Override public boolean containsValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override public Integer get(Object key) { + Collection<?> valueList = multimap.map.get(key); + return (valueList == null) ? 0 : valueList.size(); + } + + @Override public ImmutableSet<K> keySet() { + return multimap.keySet(); + } + + @Override public ImmutableCollection<Integer> values() { + throw new UnsupportedOperationException(); + } + + public boolean isEmpty() { + return multimap.isEmpty(); + } + + public int size() { + return multimap.map.size(); + } + + transient ImmutableSet<Entry<K, Integer>> entrySet; + + @Override public ImmutableSet<Entry<K, Integer>> entrySet() { + ImmutableSet<Entry<K, Integer>> result = entrySet; + return (result == null) + ? entrySet = new EntrySet<K, V>(multimap) : result; + } + + private static class EntrySet<K, V> + extends ImmutableSet<Entry<K, Integer>> { + final ImmutableMultimap<K, V> multimap; + + EntrySet(ImmutableMultimap<K, V> multimap) { + this.multimap = multimap; + } + + public Iterator<Entry<K, Integer>> iterator() { + final Iterator<Entry<K, ImmutableList<V>>> mapIterator + = multimap.map.entrySet().iterator(); + return new UnmodifiableIterator<Entry<K, Integer>>() { + public boolean hasNext() { + return mapIterator.hasNext(); + } + public Entry<K, Integer> next() { + Entry<K, ImmutableList<V>> entry = mapIterator.next(); + return Maps.immutableEntry(entry.getKey(), entry.getValue().size()); + } + }; + } + + public int size() { + return multimap.map.size(); + } + + private static final long serialVersionUID = 0; + } + + private static final long serialVersionUID = 0; + } + + private transient ImmutableCollection<V> values; + + /** + * Returns an immutable collection of the values in this multimap. Its + * iterator traverses the values for the first key, the values for the second + * key, and so on. + */ + public ImmutableCollection<V> values() { + ImmutableCollection<V> v = values; + return (v == null) ? (values = new Values<V>(this)) : v; + } + + private static class Values<V> extends ImmutableCollection<V> { + final Multimap<?, V> multimap; + + Values(Multimap<?, V> multimap) { + this.multimap = multimap; + } + + public Iterator<V> iterator() { + final Iterator<? extends Map.Entry<?, V>> entryIterator + = multimap.entries().iterator(); + return new UnmodifiableIterator<V>() { + public boolean hasNext() { + return entryIterator.hasNext(); + } + public V next() { + return entryIterator.next().getValue(); + } + }; + } + + public int size() { + return multimap.size(); + } + + private static final long serialVersionUID = 0; + } + + /** + * @serialData number of distinct keys, and then for each distinct key: the + * key, the number of values for that key, and the key's values + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultimap(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException, NoSuchFieldException { + stream.defaultReadObject(); + int keyCount = stream.readInt(); + if (keyCount < 0) { + throw new InvalidObjectException("Invalid key count " + keyCount); + } + ImmutableMap.Builder<K, ImmutableList<V>> builder = ImmutableMap.builder(); + int tmpSize = 0; + + for (int i = 0; i < keyCount; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultimap + K key = (K) stream.readObject(); + int valueCount = stream.readInt(); + if (valueCount <= 0) { + throw new InvalidObjectException("Invalid value count " + valueCount); + } + @SuppressWarnings("unchecked") // casting a newly allocated array + V[] values = (V[]) new Object[valueCount]; + for (int j = 0; j < valueCount; j++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultimap + V value = (V) stream.readObject(); + values[j] = value; + tmpSize += valueCount; + } + builder.put(key, ImmutableList.of(values)); + } + + Serialization.setFinalField( + ImmutableMultimap.class, this, "map", builder.build()); + Serialization.setFinalField(ImmutableMultimap.class, this, "size", tmpSize); + } + + private static final long serialVersionUID = 0; +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ImmutableMultiset.java b/plugins/com.google.collect/src/com/google/common/collect/ImmutableMultiset.java new file mode 100644 index 0000000..82b7bd4 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ImmutableMultiset.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Nullable; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * An immutable hash-based multiset. Does not permit null elements. + * + * <p>Its iterator orders elements according to the first appearance of the + * element among the items passed to the factory method. When the multiset + * contains multiple instances of an element, those instances are consecutive in + * the iteration order. + * + * @author Jared Levy + */ +public class ImmutableMultiset<E> extends ImmutableCollection<E> + implements Multiset<E> { + + private static final ImmutableMultiset<Object> EMPTY_MULTISET + = new EmptyMultiset(); + + private static class EmptyMultiset extends ImmutableMultiset<Object> { + EmptyMultiset() { + super(ImmutableMap.<Object, Integer>of(), 0); + } + Object readResolve() { + return EMPTY_MULTISET; // preserve singleton property + } + private static final long serialVersionUID = 0; + } + + /** + * Returns the empty immutable multiset. + */ + @SuppressWarnings("unchecked") // all supported methods are covariant + public static <E> ImmutableMultiset<E> of() { + return (ImmutableMultiset<E>) EMPTY_MULTISET; + } + + /** + * Returns an immutable multiset containing the given elements. + * + * <p>The multiset is ordered by the first occurrence of each element. For + * example, {@code ImmutableMultiset.of(2, 3, 1, 3)} yields a multiset with + * elements in the order {@code 2, 3, 3, 1}. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static <E> ImmutableMultiset<E> of(E... elements) { + return copyOf(Arrays.asList(elements)); + } + + /** + * Returns an immutable multiset containing the given elements. + * + * <p>The multiset is ordered by the first occurrence of each element. For + * example, {@code ImmutableMultiset.copyOf(Arrays.asList(2, 3, 1, 3))} yields + * a multiset with elements in the order {@code 2, 3, 3, 1}. + * + * <p>Note that if {@code c} is a {@code Collection<String>}, then {@code + * ImmutableMultiset.copyOf(c)} returns an {@code ImmutableMultiset<String>} + * containing each of the strings in {@code c}, while + * {@code ImmutableMultiset.of(c)} returns an + * {@code ImmutableMultiset<Collection<String>>} containing one element (the + * given collection itself). + * + * <p><b>Note:</b> Despite what the method name suggests, if {@code elements} + * is an {@code ImmutableMultiset}, no copy will actually be performed, and + * the given multiset itself will be returned. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static <E> ImmutableMultiset<E> copyOf( + Iterable<? extends E> elements) { + if (elements instanceof ImmutableMultiset) { + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableMultiset<E> result = (ImmutableMultiset<E>) elements; + return result; + } + + @SuppressWarnings("unchecked") // the cast causes a warning + Multiset<? extends E> multiset = (elements instanceof Multiset) + ? (Multiset<? extends E>) elements + : new LinkedHashMultiset<E>(elements); + + return copyOfInternal(multiset); + } + + + private static <E> ImmutableMultiset<E> copyOfInternal( + Multiset<? extends E> multiset) { + long size = 0; + ImmutableMap.Builder<E, Integer> builder = ImmutableMap.builder(); + + for (Entry<? extends E> entry : multiset.entrySet()) { + int count = entry.getCount(); + if (count > 0) { + // Since ImmutableMap.Builder throws an NPE if an element is null, no + // other null checks are needed. + builder.put(entry.getElement(), count); + size += count; + } + } + + return new ImmutableMultiset<E>( + builder.build(), (int) Math.min(size, Integer.MAX_VALUE)); + } + + /** + * Returns an immutable multiset containing the given elements. + * + * <p>The multiset is ordered by the first occurrence of each element. For + * example, + * {@code ImmutableMultiset.copyOf(Arrays.asList(2, 3, 1, 3).iterator())} + * yields a multiset with elements in the order {@code 2, 3, 3, 1}. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static <E> ImmutableMultiset<E> copyOf( + Iterator<? extends E> elements) { + Multiset<E> multiset = new LinkedHashMultiset<E>(); + Iterators.addAll(multiset, elements); + return copyOfInternal(multiset); + } + + private final transient ImmutableMap<E, Integer> map; + private final transient int size; + + ImmutableMultiset(ImmutableMap<E, Integer> map, int size) { + this.map = map; + this.size = size; + } + + public int count(@Nullable Object element) { + Integer value = map.get(element); + return (value == null) ? 0 : value; + } + + public Iterator<E> iterator() { + final Iterator<Map.Entry<E, Integer>> mapIterator + = map.entrySet().iterator(); + + return new Iterator<E>() { + int remaining; + E element; + + public boolean hasNext() { + return (remaining > 0) || mapIterator.hasNext(); + } + + public E next() { + if (remaining <= 0) { + Map.Entry<E, Integer> entry = mapIterator.next(); + element = entry.getKey(); + remaining = entry.getValue(); + } + remaining--; + return element; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + public int size() { + return size; + } + + @Override public boolean contains(@Nullable Object element) { + return map.containsKey(element); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public boolean add(E element, int occurrences) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public int remove(Object element, int occurrences) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public int removeAllOccurrences(Object element) { + throw new UnsupportedOperationException(); + } + + @Override public boolean equals(@Nullable Object other) { + if (other == this) { + return true; + } + + if (other instanceof Multiset) { + Multiset<?> that = (Multiset<?>) other; + if (this.size() != that.size()) { + return false; + } + for (Entry<?> entry : that.entrySet()) { + if (count(entry.getElement()) != entry.getCount()) { + return false; + } + } + return true; + } + + return false; + } + + @Override public int hashCode() { + // could cache this, but not considered worthwhile to do so + return map.hashCode(); + } + + @Override public String toString() { + return entrySet().toString(); + } + + // TODO: Serialization of the element set should serialize the multiset, and + // deserialization should call multiset.elementSet(). Then + // reserialized(multiset).elementSet() == reserialized(multiset.elementSet()) + // Currently, those object references differ. + public Set<E> elementSet() { + return map.keySet(); + } + + private transient ImmutableSet<Entry<E>> entrySet; + + public Set<Entry<E>> entrySet() { + ImmutableSet<Entry<E>> es = entrySet; + return (es == null) ? (entrySet = new EntrySet<E>(this)) : es; + } + + private static class EntrySet<E> extends ImmutableSet<Entry<E>> { + final ImmutableMultiset<E> multiset; + + public EntrySet(ImmutableMultiset<E> multiset) { + this.multiset = multiset; + } + + public Iterator<Entry<E>> iterator() { + final Iterator<Map.Entry<E, Integer>> mapIterator + = multiset.map.entrySet().iterator(); + return new Iterator<Entry<E>>() { + public boolean hasNext() { + return mapIterator.hasNext(); + } + public Entry<E> next() { + Map.Entry<E, Integer> mapEntry = mapIterator.next(); + return + Multisets.immutableEntry(mapEntry.getKey(), mapEntry.getValue()); + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + public int size() { + return multiset.map.size(); + } + + @Override public boolean contains(Object o) { + if (o instanceof Entry) { + Entry<?> entry = (Entry<?>) o; + if (entry.getCount() <= 0) { + return false; + } + int count = multiset.count(entry.getElement()); + return count == entry.getCount(); + } + return false; + } + + @Override public int hashCode() { + return multiset.map.hashCode(); + } + + @Override Object writeReplace() { + return this; + } + + private static final long serialVersionUID = 0; + } + + /** + * @serialData the number of distinct elements, the first element, its count, + * the second element, its count, and so on + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultiset(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException, NoSuchFieldException { + stream.defaultReadObject(); + int entryCount = stream.readInt(); + ImmutableMap.Builder<E, Integer> builder = ImmutableMap.builder(); + long tmpSize = 0; + for (int i = 0; i < entryCount; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultiset + E element = (E) stream.readObject(); + int count = stream.readInt(); + if (count <= 0) { + throw new InvalidObjectException("Invalid count " + count); + } + builder.put(element, count); + tmpSize += count; + } + + Serialization.setFinalField(ImmutableMultiset.class, this, "map", + builder.build()); + Serialization.setFinalField(ImmutableMultiset.class, this, "size", + (int) Math.min(tmpSize, Integer.MAX_VALUE)); + } + + @Override Object writeReplace() { + return this; + } + + private static final long serialVersionUID = 0; +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ImmutableSet.java b/plugins/com.google.collect/src/com/google/common/collect/ImmutableSet.java new file mode 100644 index 0000000..e4b62f1 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ImmutableSet.java @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * A high-performance, immutable {@code Set} with reliable, user-specified + * iteration order. Does not permit null elements. + * + * <p>Unlike {@link Collections#unmodifiableSet}, which is a <i>view</i> of a + * separate collection that can still change, an instance of this class contains + * its own private data and will <i>never</i> change. This class is convenient + * for {@code public static final} sets ("constant sets") and also lets you + * easily make a "defensive copy" of a set provided to your class by a caller. + * + * <p><b>Warning:</b> Like most sets, an {@code ImmutableSet} will not function + * correctly if an element is modified after being placed in the set. For this + * reason, and to avoid general confusion, it is strongly recommended to place + * only immutable objects into this collection. + * + * <p>This class has been observed to perform significantly better than {@link + * HashSet} for objects with very fast {@link Object#hashCode} implementations + * (as a well-behaved immutable object should). While this class's factory + * methods create hash-based instances, the {@link ImmutableSortedSet} subclass + * performs binary searches instead. + * + * <p><b>Note</b>: Although this class is not final, it cannot be subclassed + * outside its package as it has no public or protected constructors. Thus, + * instances of this type are guaranteed to be immutable. + * + * @see ImmutableList + * @see ImmutableMap + * @author Kevin Bourrillion + */ +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableSet<E> extends ImmutableCollection<E> + implements Set<E> { + private static final ImmutableSet<?> EMPTY_IMMUTABLE_SET + = new EmptyImmutableSet(); + + /** + * Returns the empty immutable set. This set behaves and performs comparably + * to {@link Collections#emptySet}, and is preferable mainly for consistency + * and maintainability of your code. + */ + // Casting to any type is safe because the set will never hold any elements. + @SuppressWarnings({"unchecked"}) + public static <E> ImmutableSet<E> of() { + return (ImmutableSet<E>) EMPTY_IMMUTABLE_SET; + } + + /** + * Returns an immutable set containing a single element. This set behaves and + * performs comparably to {@link Collections#singleton}, but will not accept + * a null element. It is preferable mainly for consistency and + * maintainability of your code. + */ + public static <E> ImmutableSet<E> of(E element) { + return new SingletonImmutableSet<E>(element, element.hashCode()); + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored (but too many of these may result in the set being + * sized inappropriately). + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static <E> ImmutableSet<E> of(E... elements) { + switch (elements.length) { + case 0: + return of(); + case 1: + return of(elements[0]); + default: + return create(Arrays.asList(elements), elements.length); + } + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored (but too many of these may result in the set being + * sized inappropriately). + * + * <p>Note that if {@code s} is a {@code Set<String>}, then {@code + * ImmutableSet.copyOf(s)} returns an {@code ImmutableSet<String>} containing + * each of the strings in {@code s}, while {@code ImmutableSet.of(s)} returns + * a {@code ImmutableSet<Set<String>>} containing one element (the given set + * itself). + * + * <p><b>Note:</b> Despite what the method name suggests, if {@code elements} + * is an {@code ImmutableSet}, no copy will actually be performed, and the + * given set itself will be returned. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static <E> ImmutableSet<E> copyOf(Iterable<? extends E> elements) { + if (elements instanceof ImmutableSet) { + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableSet<E> set = (ImmutableSet<E>) elements; + return set; + } + return copyOfInternal(Collections2.toCollection(elements)); + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static <E> ImmutableSet<E> copyOf(Iterator<? extends E> elements) { + Collection<E> list = Lists.newArrayList(elements); + return copyOfInternal(list); + } + + private static <E> ImmutableSet<E> copyOfInternal( + Collection<? extends E> collection) { + // TODO: Support concurrent collections that change while this method is + // running. + switch (collection.size()) { + case 0: + return of(); + case 1: + // TODO: Remove "ImmutableSet.<E>" when eclipse bug is fixed. + return ImmutableSet.<E>of(collection.iterator().next()); + default: + return create(collection, collection.size()); + } + } + + ImmutableSet() {} + + /** Returns {@code true} if the {@code hashCode()} method runs quickly. */ + boolean isHashCodeFast() { + return false; + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ImmutableSet + && isHashCodeFast() + && ((ImmutableSet<?>) object).isHashCodeFast() + && hashCode() != object.hashCode()) { + return false; + } + if (object instanceof Set) { + Set<?> that = (Set<?>) object; + return size() == that.size() && containsAll(that); + } + return false; + } + + @Override public int hashCode() { + return Sets.hashCodeImpl(this); + } + + @Override public String toString() { + if (isEmpty()) { + return "[]"; + } + Iterator<E> iterator = iterator(); + StringBuilder result = new StringBuilder(size() * 16); + result.append('[').append(iterator.next().toString()); + for (int i = 1; i < size(); i++) { + result.append(", ").append(iterator.next().toString()); + } + return result.append(']').toString(); + } + + private static final class EmptyImmutableSet extends ImmutableSet<Object> { + public int size() { + return 0; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public boolean contains(Object target) { + return false; + } + + public Iterator<Object> iterator() { + return Iterators.emptyIterator(); + } + + @Override public Object[] toArray() { + return ObjectArrays.EMPTY_ARRAY; + } + + @Override public <T> T[] toArray(T[] a) { + if (a.length > 0) { + a[0] = null; + } + return a; + } + + @Override public boolean containsAll(Collection<?> targets) { + return targets.isEmpty(); + } + + @Override public boolean equals(Object object) { + return object == this + || (object instanceof Set && ((Set<?>) object).isEmpty()); + } + + @Override public final int hashCode() { + return 0; + } + + @Override boolean isHashCodeFast() { + return true; + } + + @Override public String toString() { + return "[]"; + } + } + + private static final class SingletonImmutableSet<E> extends ImmutableSet<E> { + final E element; + final int hashCode; + + SingletonImmutableSet(E element, int hashCode) { + this.element = element; + this.hashCode = hashCode; + } + + public int size() { + return 1; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public boolean contains(Object target) { + return element.equals(target); + } + + public Iterator<E> iterator() { + return Iterators.singletonIterator(element); + } + + @Override public Object[] toArray() { + return new Object[] { element }; + } + + @SuppressWarnings({"unchecked"}) + @Override public <T> T[] toArray(T[] array) { + if (array.length == 0) { + array = ObjectArrays.newArray(array, 1); + } else if (array.length > 1) { + array[1] = null; + } + array[0] = (T) element; + return array; + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof Set) { + Set<?> set = (Set<?>) object; + return set.size() == 1 && contains(set.iterator().next()); + } + return false; + } + + @Override public final int hashCode() { + return hashCode; + } + + @Override boolean isHashCodeFast() { + return true; + } + + @Override public String toString() { + String elementToString = element.toString(); + return new StringBuilder(elementToString.length() + 2) + .append('[') + .append(elementToString) + .append(']') + .toString(); + } + } + + private static <E> ImmutableSet<E> create( + Iterable<? extends E> iterable, int count) { + // count is always the (nonzero) number of elements in the iterable + int tableSize = Hashing.chooseTableSize(count); + Object[] table = new Object[tableSize]; + int mask = tableSize - 1; + + List<E> elements = new ArrayList<E>(count); + int hashCode = 0; + + for (E element : iterable) { + int hash = element.hashCode(); + for (int i = Hashing.smear(hash); true; i++) { + int index = i & mask; + Object value = table[index]; + if (value == null) { + // Came to an empty bucket. Put the element here. + table[index] = element; + elements.add(element); + hashCode += hash; + break; + } else if (value.equals(element)) { + break; // Found a duplicate. Nothing to do. + } + } + } + + // The iterable might have contained only duplicates of the same element. + return (elements.size() == 1) + ? new SingletonImmutableSet<E>(elements.get(0), hashCode) + : new RegularImmutableSet<E>(elements.toArray(), hashCode, table, mask); + } + + abstract static class ArrayImmutableSet<E> extends ImmutableSet<E> { + final Object[] elements; // the elements (two or more) in the desired order + + ArrayImmutableSet(Object[] elements) { + this.elements = elements; + } + + public int size() { + return elements.length; + } + + @Override public boolean isEmpty() { + return false; + } + + /* + * The cast is safe because the only way to create an instance is via the + * create() method above, which only permits elements of type E. + */ + @SuppressWarnings("unchecked") + public Iterator<E> iterator() { + return (Iterator<E>) Iterators.forArray(elements); + } + + @Override public Object[] toArray() { + Object[] array = new Object[size()]; + System.arraycopy(elements, 0, array, 0, size()); + return array; + } + + @Override public <T> T[] toArray(T[] array) { + int size = size(); + if (array.length < size) { + array = ObjectArrays.newArray(array, size); + } else if (array.length > size) { + array[size] = null; + } + System.arraycopy(elements, 0, array, 0, size); + return array; + } + + @Override public boolean containsAll(Collection<?> targets) { + if (targets == this) { + return true; + } + if (!(targets instanceof ArrayImmutableSet)) { + return super.containsAll(targets); + } + if (targets.size() > size()) { + return false; + } + for (Object target : ((ArrayImmutableSet<?>) targets).elements) { + if (!contains(target)) { + return false; + } + } + return true; + } + } + + private static final class RegularImmutableSet<E> + extends ArrayImmutableSet<E> { + final Object[] table; // the same elements in hashed positions (plus nulls) + final int mask; // 'and' with an int to get a valid table index + final int hashCode; + + RegularImmutableSet(Object[] elements, int hashCode, + Object[] table, int mask) { + super(elements); + this.table = table; + this.mask = mask; + this.hashCode = hashCode; + } + + @Override public boolean contains(Object target) { + if (target == null) { + return false; + } + for (int i = Hashing.smear(target.hashCode()); true; i++) { + Object candidate = table[i & mask]; + if (candidate == null) { + return false; + } + if (candidate.equals(target)) { + return true; + } + } + } + + @Override public int hashCode() { + return hashCode; + } + + @Override boolean isHashCodeFast() { + return true; + } + } + + /** such as ImmutableMap.keySet() */ + abstract static class TransformedImmutableSet<D, E> extends ImmutableSet<E> { + final D[] source; + final int hashCode; + + TransformedImmutableSet(D[] source, int hashCode) { + this.source = source; + this.hashCode = hashCode; + } + + abstract E transform(D element); + + public int size() { + return source.length; + } + + @Override public boolean isEmpty() { + return false; + } + + public Iterator<E> iterator() { + return new AbstractIterator<E>() { + int index = 0; + @Override protected E computeNext() { + return index < source.length + ? transform(source[index++]) + : endOfData(); + } + }; + } + + @Override public Object[] toArray() { + return toArray(new Object[size()]); + } + + @SuppressWarnings("unchecked") + @Override public <T> T[] toArray(T[] array) { + int size = size(); + if (array.length < size) { + array = ObjectArrays.newArray(array, size); + } else if (array.length > size) { + array[size] = null; + } + + for (int i = 0; i < source.length; i++) { + array[i] = (T) transform(source[i]); + } + return array; + } + + @Override public final int hashCode() { + return hashCode; + } + + @Override boolean isHashCodeFast() { + return true; + } + } + + /* + * This class is used to serialize all ImmutableSet instances, regardless of + * implementation type. It captures their "logical contents" and they are + * reconstructed using public static factories. This is necessary to ensure + * that the existence of a particular implementation type is an implementation + * detail. + */ + private static class SerializedForm implements Serializable { + final Object[] elements; + SerializedForm(Object[] elements) { + this.elements = elements; + } + Object readResolve() { + return of(elements); + } + private static final long serialVersionUID = 0; + } + + @Override Object writeReplace() { + return new SerializedForm(toArray()); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ImmutableSortedSet.java b/plugins/com.google.collect/src/com/google/common/collect/ImmutableSortedSet.java new file mode 100644 index 0000000..3af93d5 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ImmutableSortedSet.java @@ -0,0 +1,832 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Objects; + +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedSet; + +/** + * An immutable {@code SortedSet} that stores its elements in a sorted array. + * Some instances are ordered by an explicit comparator, while others follow the + * natural sort ordering of their elements. Either way, null elements are not + * supported. + * + * <p>Unlike {@link Collections#unmodifiableSortedSet}, which is a <i>view</i> + * of a separate collection that can still change, an instance of {@code + * ImmutableSortedSet} contains its own private data and will <i>never</i> + * change. This class is convenient for {@code public static final} sets + * ("constant sets") and also lets you easily make a "defensive copy" of a set + * provided to your class by a caller. + * + * <p>The sets returned by {@link #headSet}, {@link #tailSet}, and + * {@link #subSet} methods share the same array as the original set, preventing + * that array from being garbage collected. If this is a concern, the data may + * be copied into a correctly-sized array by calling {@link #copyOfSorted}. + * + * <p><b>Note on element equivalence:</b> The {@link #contains(Object)}, + * {@link #containsAll(Collection)}, and {@link #equals(Object)} + * implementations must check whether a provided object is equivalent to an + * element in the collection. Unlike most collections, an + * {@code ImmutableSortedSet} doesn't use {@link Object#equals} to determine if + * two elements are equivalent. Instead, with an explicit comparator, the + * following relation determines whether elements {@code x} and {@code y} are + * equivalent: <pre> {@code + * + * {(x, y) | comparator.compare(x, y) == 0}}</pre> + * + * With natural ordering of elements, the following relation determines whether + * two elements are equivalent: <pre> {@code + * + * {(x, y) | x.compareTo(y) == 0}}</pre> + * + * <b>Warning:</b> Like most sets, an {@code ImmutableSortedSet} will not + * function correctly if an element is modified after being placed in the set. + * For this reason, and to avoid general confusion, it is strongly recommended + * to place only immutable objects into this collection. + * + * <p><b>Note</b>: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this type are + * guaranteed to be immutable. + * + * @see ImmutableSet + * @author Jared Levy + */ +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableSortedSet<E> extends ImmutableSet<E> + implements SortedSet<E> { + + // TODO: Can we find a way to remove these @SuppressWarnings? + @SuppressWarnings("unchecked") + private static final Comparator NATURAL_ORDER = Comparators.naturalOrder(); + @SuppressWarnings("unchecked") + private static final ImmutableSortedSet<Object> NATURAL_EMPTY_SET = + new EmptyImmutableSortedSet<Object>(NATURAL_ORDER); + + @SuppressWarnings("unchecked") + private static <E> ImmutableSortedSet<E> emptySet() { + return (ImmutableSortedSet<E>) NATURAL_EMPTY_SET; + } + + private static <E> ImmutableSortedSet<E> emptySet( + Comparator<? super E> comparator) { + if (comparator == NATURAL_ORDER) { + return emptySet(); + } else { + return new EmptyImmutableSortedSet<E>(comparator); + } + } + + /** + * Returns the empty immutable sorted set. + */ + public static <E> ImmutableSortedSet<E> of() { + return emptySet(); + } + + /** + * Returns an immutable sorted set containing a single element. + * + * <p>The type specification is {@code <E extends Comparable>}, instead of the + * more specific {@code <E extends Comparable<? super E>>}, to support + * classes defined without generics. + */ + @SuppressWarnings("unchecked") // See method Javadoc + public static <E extends Comparable> ImmutableSortedSet<E> of(E element) { + Object[] array = { checkNotNull(element) }; + return new RegularImmutableSortedSet<E>(array, NATURAL_ORDER); + } + + // TODO: Consider adding factory methods that throw an exception when given + // duplicate elements. + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@link Comparable#compareTo}, only the first one specified is included. + * + * <p>The type specification is {@code <E extends Comparable>}, instead of the + * more specific {@code <E extends Comparable<? super E>>}, to support + * classes defined without generics. + * + * @throws NullPointerException if any of {@code elements} is null + */ + @SuppressWarnings("unchecked") // See method Javadoc + public static <E extends Comparable> ImmutableSortedSet<E> of(E... elements) { + return ofInternal(Comparators.naturalOrder(), elements); + } + + private static <E> ImmutableSortedSet<E> ofInternal( + Comparator<? super E> comparator, E... elements) { + switch (elements.length) { + case 0: + return emptySet(comparator); + default: + Object[] array = elements.clone(); + for (Object element : array) { + checkNotNull(element); + } + sort(array, comparator); + array = removeDupes(array, comparator); + return new RegularImmutableSortedSet<E>(array, comparator); + } + } + + /** Sort the array, according to the comparator. */ + @SuppressWarnings("unchecked") // E comparator with Object array + private static <E> void sort( + Object[] array, Comparator<? super E> comparator) { + Arrays.sort(array, (Comparator<Object>) comparator); + } + + /** + * Returns an array that removes duplicate consecutive elements, according to + * the provided comparator. Note that the input array is modified. This method + * does not support empty arrays. + */ + private static <E> Object[] removeDupes(Object[] array, + Comparator<? super E> comparator) { + int size = 1; + for (int i = 1; i < array.length; i++) { + Object element = array[i]; + if ((compare(comparator, array[size - 1], element) != 0)) { + array[size] = element; + size++; + } + } + + // TODO: Move to ObjectArrays? + if (size == array.length) { + return array; + } else { + Object[] copy = new Object[size]; + System.arraycopy(array, 0, copy, 0, size); + return copy; + } + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@code compareTo()}, only the first one specified is included. To create a + * copy of a {@code SortedSet} that preserves the comparator, call + * {@link #copyOfSorted} instead. + * + * <p>Note that if {@code s} is a {@code Set<String>}, then + * {@code ImmutableSortedSet.copyOf(s)} returns a + * {@code ImmutableSortedSet<String>} containing each of the strings in + * {@code s}, while {@code ImmutableSortedSet.of(s)} returns a + * {@code ImmutableSortedSet<Set<String>>} containing one element (the given + * set itself). + * + * <p><b>Note:</b> Despite what the method name suggests, if {@code elements} + * is an {@code ImmutableSortedSet}, it may be returned instead of a copy. + * + * <p>The type specification is {@code <E extends Comparable>}, instead of the + * more specific {@code <E extends Comparable<? super E>>}, to support + * classes defined without generics. + * + * @throws NullPointerException if any of {@code elements} is null + */ + @SuppressWarnings("unchecked") + public static <E extends Comparable> ImmutableSortedSet<E> copyOf( + Iterable<? extends E> elements) { + return copyOfInternal(Comparators.naturalOrder(), elements, false); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@code compareTo()}, only the first one specified is included. + * + * <p>The type specification is {@code <E extends Comparable>}, instead of the + * more specific {@code <E extends Comparable<? super E>>}, to support + * classes defined without generics. + * + * @throws NullPointerException if any of {@code elements} is null + */ + @SuppressWarnings("unchecked") + public static <E extends Comparable> ImmutableSortedSet<E> copyOf( + Iterator<? extends E> elements) { + return copyOfInternal(Comparators.naturalOrder(), elements); + } + + /** + * Returns an immutable sorted set containing the elements of a sorted set, + * sorted by the same {@code Comparator}. That behavior differs from + * {@link #copyOf(Iterable)}, which always uses the natural ordering of the + * elements. + * + * <p><b>Note:</b> Despite what the method name suggests, if {@code sortedSet} + * is an {@code ImmutableSortedSet}, it may be returned instead of a copy. + * + * @throws NullPointerException if any of {@code elements} is null + */ + @SuppressWarnings("unchecked") + public static <E> ImmutableSortedSet<E> copyOfSorted(SortedSet<E> sortedSet) { + Comparator<? super E> comparator = sortedSet.comparator(); + if (comparator == null) { + comparator = NATURAL_ORDER; + } + return copyOfInternal(comparator, sortedSet, true); + } + + private static <E> ImmutableSortedSet<E> copyOfInternal( + Comparator<? super E> comparator, Iterable<? extends E> elements, + boolean fromSortedSet) { + boolean hasSameComparator + = fromSortedSet || hasSameComparator(elements, comparator); + + if (hasSameComparator && (elements instanceof ImmutableSortedSet)) { + @SuppressWarnings("unchecked") + ImmutableSortedSet<E> result = (ImmutableSortedSet<E>) elements; + if (!result.hasPartialArray()) { + return result; + } + } + + @SuppressWarnings("unchecked") + Object[] array + = Iterables.newArray((Iterable<Object>) elements, Object.class); + if (array.length == 0) { + return emptySet(comparator); + } + + for (Object e : array) { + checkNotNull(e); + } + if (!hasSameComparator) { + sort(array, comparator); + array = removeDupes(array, comparator); + } + return new RegularImmutableSortedSet<E>(array, comparator); + } + + private static <E> ImmutableSortedSet<E> copyOfInternal( + Comparator<? super E> comparator, Iterator<? extends E> elements) { + if (!elements.hasNext()) { + return emptySet(comparator); + } + List<E> list = Lists.newArrayList(); + while (elements.hasNext()) { + list.add(checkNotNull(elements.next())); + } + Object[] array = list.toArray(); + sort(array, comparator); + array = removeDupes(array, comparator); + return new RegularImmutableSortedSet<E>(array, comparator); + } + + /** + * Returns {@code true} if {@code elements} is a {@code SortedSet} that uses + * {@code comparator} to order its elements. Note that equivalent comparators + * may still return {@code false}, if {@code equals} doesn't consider them + * equal. If one comparator is {@code null} and the other is + * {@link Comparators#naturalOrder()}, this method returns {@code true}. + */ + private static boolean hasSameComparator(Object elements, + Comparator<?> comparator) { + if (elements instanceof SortedSet) { + SortedSet<?> sortedSet = (SortedSet<?>) elements; + Comparator<?> comparator2 = sortedSet.comparator(); + return Objects.equal(comparator2, comparator) + || (comparator == null && comparator2 == Comparators.naturalOrder()) + || (comparator2 == null && comparator == Comparators.naturalOrder()); + } + return false; + } + + /** + * Returns a factory that creates immutable sorted sets with an explicit + * comparator. If the comparator has a more general type than the set being + * generated, such as creating a {@code SortedSet<Integer>} with a + * {@code Comparator<Number>}, use the {@link Factory#Factory(Comparator)} + * constructor instead. + * + * @throws NullPointerException if {@code comparator} is null + */ + public static <E> Factory<E> orderedBy(Comparator<E> comparator) { + return new Factory<E>(comparator); + } + + /** + * Returns a factory that creates immutable sorted sets whose elements are + * ordered by the reverse of their natural ordering. The sorted sets use + * {@link Collections#reverseOrder()} as the comparator. + * + * <p>The type specification is {@code <E extends Comparable>}, instead of the + * more specific {@code <E extends Comparable<? super E>>}, to support + * classes defined without generics. + */ + @SuppressWarnings("unchecked") // See method Javadoc + public static <E extends Comparable> Factory<E> reverseOrder() { + return new Factory<E>(Collections.reverseOrder()); + } + + /** + * A factory for creating immutable sorted sets with an explicit comparator. + * + * <p>The factory is immutable and may be used to create multiple + * {@code ImmutableSortedSet} instances. + */ + public static class Factory<E> { + private final Comparator<? super E> comparator; + + /** + * Creates a new factory. The returned factory is equivalent to the factory + * generated by {@link ImmutableSortedSet#orderedBy}. + * + * @throws NullPointerException if {@code comparator} is null + */ + public Factory(Comparator<? super E> comparator) { + this.comparator = checkNotNull(comparator); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * the factory's comparator. When multiple elements are equivalent according + * to the comparator, only the first one specified is included. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public ImmutableSortedSet<E> of(E... elements) { + return ofInternal(comparator, elements); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * the factory's comparator. When multiple elements are equivalent according + * to the comparator, only the first one specified is included. + * + * <p><b>Note:</b> Despite what the method name suggests, if + * {@code elements} is an {@code ImmutableSortedSet} with an equivalent + * comparator, it may be returned instead of a copy. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public ImmutableSortedSet<E> copyOf(Iterable<? extends E> elements) { + return copyOfInternal(comparator, elements, false); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * the factory's comparator. When multiple elements are equivalent according + * to the comparator, only the first one specified is included. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public ImmutableSortedSet<E> copyOf(Iterator<? extends E> elements) { + return copyOfInternal(comparator, elements); + } + } + + @SuppressWarnings("unchecked") + private static <E> int compare(Comparator<? super E> comparator, Object a, + Object b) { + return comparator.compare((E) a, (E) b); + } + + final Comparator<? super E> comparator; + + private ImmutableSortedSet(Comparator<? super E> comparator) { + this.comparator = comparator; + } + + /** + * Returns the comparator that orders the elements, which is + * {@link Comparators#naturalOrder()} when the natural ordering of the + * elements is used. Note that its behavior is not consistent with + * {@link SortedSet#comparator()}, which returns {@code null} to indicate + * natural ordering. + */ + public Comparator<? super E> comparator() { + return comparator; + } + + /** + * {@inheritDoc} + * + * <p>This method returns a serializable {@code ImmutableSortedSet}. + * + * <p>The {@link SortedSet#headSet} documentation states that a subset of a + * subset throws an {@link IllegalArgumentException} if passed a + * {@code toElement} greater than an earlier {@code toElement}. However, this + * method doesn't throw an exception in that situation, but instead keeps the + * original {@code toElement}. + */ + public ImmutableSortedSet<E> headSet(E toElement) { + return headSetImpl(checkNotNull(toElement)); + } + + /** + * {@inheritDoc} + * + * <p>This method returns a serializable {@code ImmutableSortedSet}. + * + * <p>The {@link SortedSet#subSet} documentation states that a subset of a + * subset throws an {@link IllegalArgumentException} if passed a + * {@code fromElement} smaller than an earlier {@code toElement}. However, + * this method doesn't throw an exception in that situation, but instead keeps + * the original {@code fromElement}. Similarly, this method keeps the + * original {@code toElement}, instead of throwing an exception, if passed a + * {@code toElement} greater than an earlier {@code toElement}. + */ + public ImmutableSortedSet<E> subSet(E fromElement, E toElement) { + checkNotNull(fromElement); + checkNotNull(toElement); + checkArgument(compare(comparator, fromElement, toElement) <= 0); + return subSetImpl(fromElement, toElement); + } + + /** + * {@inheritDoc} + * + * <p>This method returns a serializable {@code ImmutableSortedSet}. + * + * <p>The {@link SortedSet#tailSet} documentation states that a subset of a + * subset throws an {@link IllegalArgumentException} if passed a + * {@code fromElement} smaller than an earlier {@code fromElement}. However, + * this method doesn't throw an exception in that situation, but instead keeps + * the original {@code fromElement}. + */ + public ImmutableSortedSet<E> tailSet(E fromElement) { + return tailSetImpl(checkNotNull(fromElement)); + } + + /* + * These methods perform most headSet, subSet, and tailSet logic, besides + * parameter validation. + */ + abstract ImmutableSortedSet<E> headSetImpl(E toElement); + abstract ImmutableSortedSet<E> subSetImpl(E fromElement, E toElement); + abstract ImmutableSortedSet<E> tailSetImpl(E fromElement); + + /** Returns whether the elements are stored in a subset of a larger array. */ + abstract boolean hasPartialArray(); + + /** An empty immutable sorted set. */ + private static class EmptyImmutableSortedSet<E> + extends ImmutableSortedSet<E> { + + EmptyImmutableSortedSet(Comparator<? super E> comparator) { + super(comparator); + } + + public int size() { + return 0; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public boolean contains(Object target) { + return false; + } + + public Iterator<E> iterator() { + return Iterators.emptyIterator(); + } + + @Override public Object[] toArray() { + return ObjectArrays.EMPTY_ARRAY; + } + + @Override public <T> T[] toArray(T[] a) { + if (a.length > 0) { + a[0] = null; + } + return a; + } + + @Override public boolean containsAll(Collection<?> targets) { + return targets.isEmpty(); + } + + @Override public boolean equals(Object object) { + return (object instanceof Set) && ((Set<?>) object).isEmpty(); + } + + @Override public int hashCode() { + return 0; + } + + @Override public String toString() { + return "[]"; + } + + public E first() { + throw new NoSuchElementException(); + } + + public E last() { + throw new NoSuchElementException(); + } + + @Override ImmutableSortedSet<E> headSetImpl(E toElement) { + return this; + } + + @Override ImmutableSortedSet<E> subSetImpl(E fromElement, E toElement) { + return this; + } + + @Override ImmutableSortedSet<E> tailSetImpl(E fromElement) { + return this; + } + + @Override boolean hasPartialArray() { + return false; + } + } + + /** + * An empty immutable sorted set with one or more elements. + * TODO: Consider creating a separate class for a single-element sorted set. + */ + @SuppressWarnings("serial") + private static final class RegularImmutableSortedSet<E> + extends ImmutableSortedSet<E> { + + final Object[] elements; + /** + * The index of the first element that's in the sorted set (inclusive + * index). + */ + final int fromIndex; + /** + * The index after the last element that's in the sorted set (exclusive + * index). + */ + final int toIndex; + + RegularImmutableSortedSet(Object[] elements, + Comparator<? super E> comparator) { + super(comparator); + this.elements = elements; + this.fromIndex = 0; + this.toIndex = elements.length; + } + + RegularImmutableSortedSet(Object[] elements, + Comparator<? super E> comparator, int fromIndex, int toIndex) { + super(comparator); + this.elements = elements; + this.fromIndex = fromIndex; + this.toIndex = toIndex; + } + + // The factory methods ensure that every element is an E. + @SuppressWarnings("unchecked") + public Iterator<E> iterator() { + return (Iterator<E>) Iterators.forArray(elements, fromIndex, size()); + } + + @Override public boolean isEmpty() { + return false; + } + + public int size() { + return toIndex - fromIndex; + } + + @Override public boolean contains(Object o) { + if (o == null) { + return false; + } + try { + return binarySearch(o) >= 0; + } catch (ClassCastException e) { + return false; + } + } + + @Override public boolean containsAll(Collection<?> targets) { + // TODO: For optimal performance, use a binary search when + // targets.size() < size() / log(size()) + if (!hasSameComparator(targets, comparator()) || (targets.size() <= 1)) { + return super.containsAll(targets); + } + + /* + * If targets is a sorted set with the same comparator, containsAll can + * run in O(n) time stepping through the two collections. + */ + int i = fromIndex; + Iterator<?> iterator = targets.iterator(); + Object target = iterator.next(); + + while (true) { + if (i >= toIndex) { + return false; + } + + int cmp = compare(comparator, elements[i], target); + + if (cmp < 0) { + i++; + } else if (cmp == 0) { + if (!iterator.hasNext()) { + return true; + } + target = iterator.next(); + i++; + } else if (cmp > 0) { + return false; + } + } + } + + int binarySearch(Object key) { + int lower = fromIndex; + int upper = toIndex - 1; + + while (lower <= upper) { + int middle = lower + (upper - lower) / 2; + int c = compare(comparator, key, elements[middle]); + if (c < 0) { + upper = middle - 1; + } else if (c > 0) { + lower = middle + 1; + } else { + return middle; + } + } + + return -lower - 1; + } + + @Override public Object[] toArray() { + Object[] array = new Object[size()]; + System.arraycopy(elements, fromIndex, array, 0, size()); + return array; + } + + // TODO: Move to ObjectArrays (same code in ImmutableList). + @Override public <T> T[] toArray(T[] array) { + int size = size(); + if (array.length < size) { + array = ObjectArrays.newArray(array, size); + } else if (array.length > size) { + array[size] = null; + } + System.arraycopy(elements, fromIndex, array, 0, size); + return array; + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof Set)) { + return false; + } + Set<?> set = (Set<?>) object; + if (size() != set.size()) { + return false; + } + + if (hasSameComparator(object, comparator)) { + Iterator<?> iterator = set.iterator(); + try { + for (int i = fromIndex; i < toIndex; i++) { + Object otherElement = iterator.next(); + if ((otherElement == null) + || (compare(comparator, elements[i], otherElement) != 0)) { + return false; + } + } + return true; + } catch (ClassCastException e) { + return false; + } catch (NoSuchElementException e) { + return false; // concurrent change to other set + } + } + + return containsAll(set); + } + + @Override public int hashCode() { + // not caching hash code since it could change if the elements are mutable + // in a way that modifies their hash codes + int hash = 0; + for (int i = fromIndex; i < toIndex; i++) { + hash += elements[i].hashCode(); + } + return hash; + } + + @Override public String toString() { + StringBuilder result = new StringBuilder(size() * 16); + result.append('[').append(elements[fromIndex]); + for (int i = fromIndex + 1; i < toIndex; i++) { + result.append(", ").append(elements[i]); + } + return result.append(']').toString(); + } + + // The factory methods ensure that every element is an E. + @SuppressWarnings("unchecked") + public E first() { + return (E) elements[fromIndex]; + } + + // The factory methods ensure that every element is an E. + @SuppressWarnings("unchecked") + public E last() { + return (E) elements[toIndex - 1]; + } + + @Override ImmutableSortedSet<E> headSetImpl(E toElement) { + return createSubset(fromIndex, findSubsetIndex(toElement)); + } + + @Override ImmutableSortedSet<E> subSetImpl(E fromElement, E toElement) { + return createSubset( + findSubsetIndex(fromElement), findSubsetIndex(toElement)); + } + + @Override ImmutableSortedSet<E> tailSetImpl(E fromElement) { + return createSubset(findSubsetIndex(fromElement), toIndex); + } + + int findSubsetIndex(E fromElement) { + int index = binarySearch(fromElement); + return (index >= 0) ? index : (-index - 1); + } + + ImmutableSortedSet<E> createSubset(int newFromIndex, int newToIndex) { + if (newFromIndex < newToIndex) { + return new RegularImmutableSortedSet<E>(elements, comparator, + newFromIndex, newToIndex); + } else { + return emptySet(comparator); + } + } + + @Override boolean hasPartialArray() { + return (fromIndex != 0) || (toIndex != elements.length); + } + } + + /* + * This class is used to serialize all ImmutableSortedSet instances, + * regardless of implementation type. It captures their "logical contents" + * only. This is necessary to ensure that the existence of a particular + * implementation type is an implementation detail. + */ + private static class SerializedForm<E> implements Serializable { + final Comparator<? super E> comparator; + final Object[] elements; + + public SerializedForm(Comparator<? super E> comparator, Object[] elements) { + this.comparator = comparator; + this.elements = elements; + } + + @SuppressWarnings("unchecked") + Object readResolve() { + return new Factory<E>(comparator).of((E[]) elements); + } + + private static final long serialVersionUID = 0; + } + + private void readObject(ObjectInputStream stream) + throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + @Override Object writeReplace() { + return new SerializedForm<E>(comparator, toArray()); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/Iterables.java b/plugins/com.google.collect/src/com/google/common/collect/Iterables.java new file mode 100644 index 0000000..64aa914 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/Iterables.java @@ -0,0 +1,798 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Function; +import com.google.common.base.Nullable; +import com.google.common.base.Objects; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkContentsNotNull; +import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.base.Predicate; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedSet; + +/** + * This class contains static utility methods that operate on or return objects + * of type {@code Iterable}. Also see the parallel implementations in {@link + * Iterators}. + * + * @author Kevin Bourrillion + * @author Scott Bonneau + */ +public final class Iterables { + private Iterables() {} + + private static final Iterable<Object> EMPTY_ITERABLE = new Iterable<Object>() + { + public Iterator<Object> iterator() { + return Iterators.EMPTY_ITERATOR; + } + @Override public String toString() { + return "[]"; + } + }; + + /** Returns the empty {@code Iterable}. */ + // Casting to any type is safe since there are no actual elements. + @SuppressWarnings("unchecked") + public static <T> Iterable<T> emptyIterable() { + return (Iterable<T>) EMPTY_ITERABLE; + } + + /** Returns an unmodifiable view of {@code iterable}. */ + public static <T> Iterable<T> unmodifiableIterable(final Iterable<T> iterable) + { + checkNotNull(iterable); + return new Iterable<T>() { + public Iterator<T> iterator() { + return Iterators.unmodifiableIterator(iterable.iterator()); + } + @Override public String toString() { + return iterable.toString(); + } + // no equals and hashCode; it would break the contract! + }; + } + + /** + * Returns the number of elements in {@code iterable}. + */ + public static int size(Iterable<?> iterable) { + return (iterable instanceof Collection) + ? ((Collection<?>) iterable).size() + : Iterators.size(iterable.iterator()); + } + + /** + * Returns {@code true} if {@code iterable} contains {@code element}; that is, + * any object for while {@code equals(element)} is true. + */ + public static boolean contains(Iterable<?> iterable, @Nullable Object element) + { + if (element == null) { + return containsNull(iterable); + } + if (iterable instanceof Collection) { + Collection<?> collection = (Collection<?>) iterable; + try { + return collection.contains(element); + } catch (ClassCastException e) { + return false; + } + } + return Iterators.contains(iterable.iterator(), element); + } + + /** + * Returns {@code true} if {@code iterable} contains at least one null + * element. + */ + public static boolean containsNull(Iterable<?> iterable) { + if (iterable instanceof Collection) { + Collection<?> collection = (Collection<?>) iterable; + try { + return collection.contains(null); + } catch (NullPointerException e) { + return false; + } + } + return Iterators.containsNull(iterable.iterator()); + } + + /** + * Removes, from an iterable, every element that belongs to the provided + * collection. + * + * <p>This method calls {@link Collection#removeAll} if {@code iterable} is a + * collection, and {@link Iterators#removeAll} otherwise. + * + * @param iterable the iterable to (potentially) remove elements from + * @param c the elements to remove + * @return {@code true} if any elements are removed from {@code iterable} + */ + public static boolean removeAll(Iterable<?> iterable, Collection<?> c) { + return (iterable instanceof Collection) + ? ((Collection<?>) iterable).removeAll(checkNotNull(c)) + : Iterators.removeAll(iterable.iterator(), c); + } + + /** + * Removes, from an iterable, every element that does not belong to the + * provided collection. + * + * <p>This method calls {@link Collection#retainAll} if {@code iterable} is a + * collection, and {@link Iterators#retainAll} otherwise. + * + * @param iterable the iterable to (potentially) remove elements from + * @param c the elements to retain + * @return {@code true} if any elements are removed from {@code iterable} + */ + public static boolean retainAll(Iterable<?> iterable, Collection<?> c) { + return (iterable instanceof Collection) + ? ((Collection<?>) iterable).retainAll(checkNotNull(c)) + : Iterators.retainAll(iterable.iterator(), c); + } + + /** + * Determines whether two iterables contain equal elements in the same order. + * More specifically, this method returns {@code true} if {@code iterable1} + * and {@code iterable2} contain the same number of elements and every element + * of {@code iterable1} is equal to the corresponding element of + * {@code iterable2}. + */ + public static boolean elementsEqual( + Iterable<?> iterable1, Iterable<?> iterable2) { + return Iterators.elementsEqual(iterable1.iterator(), iterable2.iterator()); + } + + /** + * Returns a string representation of {@code iterable}, with the format + * {@code [e1, e2, ..., en]}. + */ + public static String toString(Iterable<?> iterable) { + return Iterators.toString(iterable.iterator()); + } + + /** + * Returns the single element contained in {@code iterable}. + * + * @throws NoSuchElementException if the iterable is empty + * @throws IllegalArgumentException if the iterable contains multiple + * elements + */ + public static <T> T getOnlyElement(Iterable<T> iterable) { + return Iterators.getOnlyElement(iterable.iterator()); + } + + /** + * Returns the single element contained in {@code iterable}, or {@code + * defaultValue} if the iterable is empty. + * + * @throws IllegalArgumentException if the iterator contains multiple + * elements + */ + public static <T> T getOnlyElement( + Iterable<T> iterable, @Nullable T defaultValue) { + return Iterators.getOnlyElement(iterable.iterator(), defaultValue); + } + + /** + * Copies an iterable's elements into an array. + * + * @param iterable the iterable to copy + * @param type the type of the elements + * @return a newly-allocated array into which all the elements of the iterable + * have been copied + */ + public static <T> T[] newArray(Iterable<T> iterable, Class<T> type) { + Collection<T> collection = (iterable instanceof Collection) + ? (Collection<T>) iterable + : Lists.newArrayList(iterable); + T[] array = ObjectArrays.newArray(type, collection.size()); + return collection.toArray(array); + } + + /** + * Adds all elements in {@code iterable} to {@code collection}. + * + * @return {@code true} if {@code collection} was modified as a result of this + * operation. + */ + public static <T> boolean addAll( + Collection<T> collection, Iterable<? extends T> iterable) { + if (iterable instanceof Collection) { + @SuppressWarnings("unchecked") + Collection<? extends T> c = (Collection<? extends T>) iterable; + return collection.addAll(c); + } + return Iterators.addAll(collection, iterable.iterator()); + } + + /** + * Returns the number of elements in the specified iterable that equal the + * specified object. + * + * @see Collections#frequency + */ + public static int frequency(Iterable<?> iterable, @Nullable Object element) { + if ((iterable instanceof Multiset)) { + return ((Multiset<?>) iterable).count(element); + } + if ((iterable instanceof Set)) { + return ((Set<?>) iterable).contains(element) ? 1 : 0; + } + return Iterators.frequency(iterable.iterator(), element); + } + + /** + * Returns an iterable whose iterator cycles indefinitely over the elements of + * {@code iterable}. + * + * <p>That iterator supports {@code remove()} if {@code iterable.iterator()} + * does. After {@code remove()} is called, subsequent cycles omit the removed + * element, which is no longer in {@code iterable}. The iterator's + * {@code hasNext()} method returns {@code true} until {@code iterable} is + * empty. + * + * <p><b>Warning:</b> Typical uses of the resulting iterator may produce an + * infinite loop. You should use an explicit {@code break} or be certain that + * you will eventually remove all the elements. + * + * <p>To cycle over the iterable {@code n} times, use the following: + * {@code Iterables.concat(Collections.nCopies(n, iterable))} + */ + public static <T> Iterable<T> cycle(final Iterable<T> iterable) { + checkNotNull(iterable); + return new Iterable<T>() { + public Iterator<T> iterator() { + return Iterators.cycle(iterable); + } + @Override public String toString() { + return iterable.toString() + " (cycled)"; + } + }; + } + + /** + * Returns an iterable whose iterator cycles indefinitely over the provided + * elements. + * + * <p>That iterator supports {@code remove()} if {@code iterable.iterator()} + * does. After {@code remove()} is called, subsequent cycles omit the removed + * element, but {@code elements} does not change. The iterator's + * {@code hasNext()} method returns {@code true} until all of the original + * elements have been removed. + * + * <p><b>Warning:</b> Typical uses of the resulting iterator may produce an + * infinite loop. You should use an explicit {@code break} or be certain that + * you will eventually remove all the elements. + * + * <p>To cycle over the elements {@code n} times, use the following: + * {@code Iterables.concat(Collections.nCopies(n, Arrays.asList(elements)))} + */ + public static <T> Iterable<T> cycle(T... elements) { + return cycle(Lists.newArrayList(elements)); + } + + /** + * Combines two iterables into a single iterable. The returned iterable has an + * iterator that traverses the elements in {@code a}, followed by the elements + * in {@code b}. The source iterators are not polled until necessary. + * + * <p>The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + */ + @SuppressWarnings("unchecked") + public static <T> Iterable<T> concat( + Iterable<? extends T> a, Iterable<? extends T> b) { + checkNotNull(a); + checkNotNull(b); + return concat(Arrays.asList(a, b)); + } + + /** + * Combines multiple iterables into a single iterable. The returned iterable + * has an iterator that traverses the elements of each iterable in + * {@code inputs}. The input iterators are not polled until necessary. + * + * <p>The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + * + * @throws NullPointerException if any of the provided iterables is null + */ + public static <T> Iterable<T> concat(Iterable<? extends T>... inputs) { + return concat(checkContentsNotNull(Arrays.asList(inputs))); + } + + /** + * Combines multiple iterables into a single iterable. The returned iterable + * has an iterator that traverses the elements of each iterable in + * {@code inputs}. The input iterators are not polled until necessary. + * + * <p>The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. The methods of the returned + * iterable may throw {@code NullPointerException} if any of the input + * iterators are null. + */ + public static <T> Iterable<T> concat( + Iterable<? extends Iterable<? extends T>> inputs) { + /* + * Hint: if you let A represent Iterable<? extends T> and B represent + * Iterator<? extends T>, then this Function would look simply like: + * + * Function<A, B> function = new Function<A, B> { + * public B apply(A from) { + * return from.iterator(); + * } + * } + * + * TODO: there may be a better way to do this. + */ + + Function<Iterable<? extends T>, Iterator<? extends T>> function + = new Function<Iterable<? extends T>, Iterator<? extends T>>() { + public Iterator<? extends T> apply(Iterable<? extends T> from) { + return from.iterator(); + } + }; + final Iterable<Iterator<? extends T>> iterators + = transform(inputs, function); + return new AbstractIterable<T>() { + public Iterator<T> iterator() { + return Iterators.concat(iterators.iterator()); + } + }; + } + + /** + * Partition an iterable into sub-iterables of the given size. For example, + * <code>{A, B, C, D, E, F}</code> with partition size 3 yields + * <code>{A, B, C}</code> and <code>{D, E, F}</code>. The returned iterables + * have iterators that do not support {@code remove()}. + * + * <p>After {@code next()} is called on the returned iterable's iterator, the + * iterables from prior {@code next()} calls become invalid. + * + * @param iterable the iterable to partition + * @param partitionSize the size of each partition + * @param padToSize whether to pad the last partition to the partition size + * with {@code null}. + * @return an iterable across partitioned iterables + */ + public static <T> Iterable<Iterable<T>> partition( + final Iterable<? extends T> iterable, final int partitionSize, + final boolean padToSize) { + checkNotNull(iterable); + return new AbstractIterable<Iterable<T>>() { + public Iterator<Iterable<T>> iterator() { + final Iterator<Iterator<T>> iterator = Iterators.partition( + iterable.iterator(), partitionSize, padToSize); + return new AbstractIterator<Iterable<T>>() { + int howFarIn; + + @Override protected Iterable<T> computeNext() { + howFarIn++; + if (!iterator.hasNext()) { + return endOfData(); + } + return new AbstractIterable<T>() { + Iterator<T> innerIter = iterator.next(); + boolean firstIteratorRequest = true; + + public Iterator<T> iterator() { + if (firstIteratorRequest) { + firstIteratorRequest = false; + return innerIter; + } else { + Iterator<Iterator<T>> partitionIter = Iterators.partition( + iterable.iterator(), partitionSize, padToSize); + for (int i = 0; i < howFarIn; i++) { + innerIter = partitionIter.next(); + } + return innerIter; + } + } + }; + } + }; + } + }; + } + + /** + * Returns the elements of {@code unfiltered} that satisfy a predicate. The + * resulting iterable's iterator does not support {@code remove()}. + */ + public static <T> Iterable<T> filter( + final Iterable<T> unfiltered, final Predicate<? super T> predicate) { + checkNotNull(unfiltered); + checkNotNull(predicate); + return new AbstractIterable<T>() { + public Iterator<T> iterator() { + return Iterators.filter(unfiltered.iterator(), predicate); + } + }; + } + + /** + * Returns all instances of class {@code type} in {@code unfiltered}. The + * returned iterable has elements whose class is {@code type} or a subclass of + * {@code type}. The returned iterable's iterator does not support + * {@code remove()}. + * + * @param unfiltered an iterable containing objects of any type + * @param type the type of elements desired + * @return an unmodifiable iterable containing all elements of the original + * iterable that were of the requested type + */ + public static <T> Iterable<T> filter( + final Iterable<?> unfiltered, final Class<T> type) { + checkNotNull(unfiltered); + checkNotNull(type); + return new AbstractIterable<T>() { + public Iterator<T> iterator() { + return Iterators.filter(unfiltered.iterator(), type); + } + }; + } + + /** + * Returns {@code true} if one or more elements in {@code iterable} satisfy + * the predicate. + */ + public static <T> boolean any( + Iterable<T> iterable, Predicate<? super T> predicate) { + return Iterators.any(iterable.iterator(), predicate); + } + + /** + * Returns {@code true} if every element in {@code iterable} satisfies the + * predicate. If {@code iterable} is empty, {@code true} is returned. + */ + public static <T> boolean all( + Iterable<T> iterable, Predicate<? super T> predicate) { + return Iterators.all(iterable.iterator(), predicate); + } + + /** + * Returns the first element in {@code iterable} that satisfies the given + * predicate. + * + * @throws NoSuchElementException if no element in {@code iterable} matches + * the given predicate + */ + public static <E> E find(Iterable<E> iterable, + Predicate<? super E> predicate) { + return Iterators.find(iterable.iterator(), predicate); + } + + /** + * Returns an iterable that applies {@code function} to each element of {@code + * fromIterable}. + * + * <p>The returned iterable's iterator supports {@code remove()} if the + * provided iterator does. After a successful {@code remove()} call, + * {@code fromIterable} no longer contains the corresponding element. + */ + public static <F, T> Iterable<T> transform(final Iterable<F> fromIterable, + final Function<? super F, ? extends T> function) { + checkNotNull(fromIterable); + checkNotNull(function); + return new AbstractIterable<T>() { + public Iterator<T> iterator() { + return Iterators.transform(fromIterable.iterator(), function); + } + }; + } + + /** + * Returns the element at the specified position in an iterable. + * + * @param position position of the element to return + * @return the element at the specified position in {@code iterable} + * @throws IndexOutOfBoundsException if {@code position} is negative or + * greater than or equal to the size of {@code iterable} + */ + public static <T> T get(Iterable<T> iterable, int position) { + checkNotNull(iterable); + if (position < 0) { + throw new IndexOutOfBoundsException( + "position cannot be negative: " + position); + } + + if (iterable instanceof Collection) { + Collection<T> collection = (Collection<T>) iterable; + int size = collection.size(); + if (position >= size) { + throw new IndexOutOfBoundsException(String.format( + "position %d must be less than the iterable size %d", + position, size)); + } + + if (iterable instanceof List) { + List<T> list = (List<T>) iterable; + return list.get(position); + } + } + + return Iterators.get(iterable.iterator(), position); + } + + /** + * Returns the last element of {@code iterable}. + * + * @return the last element of {@code iterable} + * @throws NoSuchElementException if the iterable has no elements + */ + public static <T> T getLast(Iterable<T> iterable) { + if (iterable instanceof List) { + List<T> list = (List<T>) iterable; + // TODO: Support a concurrent list whose size changes while this method + // is running. + if (list.isEmpty()) { + throw new NoSuchElementException(); + } + return list.get(list.size() - 1); + } + + if (iterable instanceof SortedSet) { + SortedSet<T> sortedSet = (SortedSet<T>) iterable; + return sortedSet.last(); + } + + return Iterators.getLast(iterable.iterator()); + } + + /** + * Returns a view of {@code iterable} that skips its first + * {@code numberToSkip} elements. If {@code iterable} contains fewer than + * {@code numberToSkip} elements, the returned iterable skips all of its + * elements. + * + * <p>Modifications to the underlying {@link Iterable} before a call to + * {@code iterator()} are reflected in the returned iterator. That is, the + * iterator skips the first {@code numberToSkip} elements that exist when the + * {@code Iterator} is created, not when {@code skip()} is called. + * + * <p>The returned iterable's iterator supports {@code remove()} if the + * iterator of the underlying iterable supports it. Note that it is + * <i>not</i> possible to delete the last skipped element by immediately + * calling {@code remove()} on that iterator, as the {@code Iterator} + * contract states that a call to {@code remove()} before a call to + * {@code next()} will throw an {@link IllegalStateException}. + */ + public static <T> Iterable<T> skip(final Iterable<T> iterable, + final int numberToSkip) { + checkNotNull(iterable); + checkArgument(numberToSkip >= 0, "number to skip cannot be negative"); + + if (iterable instanceof List) { + final List<T> list = (List<T>) iterable; + return new AbstractIterable<T>() { + public Iterator<T> iterator() { + // TODO: Support a concurrent list whose size changes while this + // method is running. + return (numberToSkip >= list.size()) + ? Iterators.<T>emptyIterator() + : list.subList(numberToSkip, list.size()).iterator(); + } + }; + } + + return new AbstractIterable<T>() { + public Iterator<T> iterator() { + final Iterator<T> iterator = iterable.iterator(); + + Iterators.skip(iterator, numberToSkip); + + /* + * We can't just return the iterator because an immediate call to its + * remove() method would remove one of the skipped elements instead of + * throwing an IllegalStateException. + */ + return new Iterator<T>() { + boolean atStart = true; + + public boolean hasNext() { + return iterator.hasNext(); + } + + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + try { + return iterator.next(); + } finally { + atStart = false; + } + } + + public void remove() { + if (atStart) { + throw new IllegalStateException(); + } + iterator.remove(); + } + }; + } + }; + } + + /** + * Creates an iterable with the first {@code limitSize} elements of the given + * iterable. If the original iterable does not contain that many elements, the + * returned iterator will have the same behavior as the original iterable. The + * returned iterable's iterator supports {@code remove()} if the original + * iterator does. + * + * @param iterable the iterable to limit + * @param limitSize the maximum number of elements in the returned iterator + * @throws IllegalArgumentException if {@code limitSize} is negative + */ + public static <T> Iterable<T> limit( + final Iterable<T> iterable, final int limitSize) { + checkNotNull(iterable); + checkArgument(limitSize >= 0, "limit is negative"); + return new AbstractIterable<T>() { + public Iterator<T> iterator() { + return Iterators.limit(iterable.iterator(), limitSize); + } + }; + } + + // Methods only in Iterables, not in Iterators + + /** + * Adapts a list to an iterable with reversed iteration order. It is + * especially useful in foreach-style loops: + * <pre> + * List<String> mylist = ... + * for (String str : Iterables.reverse(mylist)) { + * ... + * } </pre> + * + * @return an iterable with the same elements as the list, in reverse. + */ + public static <T> Iterable<T> reverse(final List<T> list) { + checkNotNull(list); + return new AbstractIterable<T>() { + public Iterator<T> iterator() { + final ListIterator<T> listIter = list.listIterator(list.size()); + return new Iterator<T>() { + public boolean hasNext() { + return listIter.hasPrevious(); + } + public T next() { + return listIter.previous(); + } + public void remove() { + listIter.remove(); + } + }; + } + }; + } + + /** + * Provides a rotated view of a list. Differs from {@link Collections#rotate} + * in that it leaves the underlying list unchanged. Note that this is a + * "live" view of the list that will change as the list changes. However, the + * behavior of an {@link Iterator} retrieved from a rotated view of the list + * is undefined if the list is structurally changed after the iterator is + * retrieved. + * + * @param list the list to return a rotated view of + * @param distance the distance to rotate the list. There are no constraints + * on this value; it may be zero, negative, or greater than {@code + * list.size()}. + * @return a rotated view of the given list + */ + public static <T> Iterable<T> rotate(final List<T> list, final int distance) { + checkNotNull(list); + + + // If no rotation is requested, just return the original list + if (distance == 0) { + return list; + } + + return new AbstractIterable<T>() { + /** + * Determines the actual distance we need to rotate (distance provided + * might be larger than the size of the list or negative). + */ + int calcActualDistance(int size) { + // we already know distance and size are non-zero + int actualDistance = distance % size; + if (actualDistance < 0) { + // distance must have been negative + actualDistance += size; + } + return actualDistance; + } + + public Iterator<T> iterator() { + int size = list.size(); + if (size <= 1) { + return list.iterator(); + } + + int actualDistance = calcActualDistance(size); + // lists of a size that go into the distance evenly don't need rotation + if (actualDistance == 0) { + return list.iterator(); + } + + @SuppressWarnings("unchecked") + Iterable<T> rotated = concat(list.subList(actualDistance, size), + list.subList(0, actualDistance)); + return rotated.iterator(); + } + }; + } + + /** + * Returns whether the given iterable contains no elements. + * + * @return {@code true} if the iterable has no elements, {@code false} if the + * iterable has one or more elements + */ + public static <T> boolean isEmpty(Iterable<T> iterable) { + return !iterable.iterator().hasNext(); + } + + /** + * Removes the specified element from the specified iterable. + * + * <p>This method iterates over the iterable, checking each element returned + * by the iterator in turn to see if it equals the object {@code o}. If they + * are equal, it is removed from the iterable with the iterator's + * {@code remove} method. At most one element is removed, even if the iterable + * contains multiple members that equal {@code o}. + * + * <p><b>Warning</b>: Do not use this method for a collection, such as a + * {@link HashSet}, that has a fast {@code remove} method. + * + * @param iterable the iterable from which to remove + * @param o an element to remove from the collection + * @return {@code true} if the iterable changed as a result + * @throws UnsupportedOperationException if the iterator does not support the + * {@code remove} method and the iterable contains the object + */ + static boolean remove(Iterable<?> iterable, @Nullable Object o) { + Iterator<?> i = iterable.iterator(); + while (i.hasNext()) { + if (Objects.equal(i.next(), o)) { + i.remove(); + return true; + } + } + return false; + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/Iterators.java b/plugins/com.google.collect/src/com/google/common/collect/Iterators.java new file mode 100644 index 0000000..ade0000 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/Iterators.java @@ -0,0 +1,950 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkContentsNotNull; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.base.Function; +import com.google.common.base.Join; +import com.google.common.base.Nullable; +import com.google.common.base.Objects; +import com.google.common.base.Predicate; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +/** + * This class contains static utility methods that operate on or return objects + * of type {@code Iterator}. Also see the parallel implementations in {@link + * Iterables}. + * + * @author Kevin Bourrillion + * @author Scott Bonneau + */ +public final class Iterators { + private Iterators() {} + + static final Iterator<Object> EMPTY_ITERATOR = new Iterator<Object>() { + public boolean hasNext() { + return false; + } + public Object next() { + throw new NoSuchElementException(); + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + + + /** Returns the empty {@code Iterator}. */ + // Casting to any type is safe since there are no actual elements. + @SuppressWarnings("unchecked") + public static <T> Iterator<T> emptyIterator() { + return (Iterator<T>) EMPTY_ITERATOR; + } + + private static final ListIterator<Object> EMPTY_LIST_ITERATOR = + new ListIterator<Object>() { + public boolean hasNext() { + return false; + } + public boolean hasPrevious() { + return false; + } + public int nextIndex() { + return 0; + } + public int previousIndex() { + return -1; + } + public Object next() { + throw new NoSuchElementException(); + } + public Object previous() { + throw new NoSuchElementException(); + } + public void set(Object o) { + throw new UnsupportedOperationException(); + } + public void add(Object o) { + throw new UnsupportedOperationException(); + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + + /** Returns the empty {@code ListIterator}. */ + // Casting to any type is safe since there are no actual elements. + @SuppressWarnings("unchecked") + public static <T> ListIterator<T> emptyListIterator() { + return (ListIterator<T>) EMPTY_LIST_ITERATOR; + } + + private static final Iterator<Object> EMPTY_MODIFIABLE_ITERATOR = + new Iterator<Object>() { + /*@Override*/ public boolean hasNext() { + return false; + } + + /*@Override*/ public Object next() { + throw new NoSuchElementException(); + } + + /*@Override*/ public void remove() { + throw new IllegalStateException(); + } + }; + + /** + * Returns the empty {@code Iterator} that throws + * {@link IllegalStateException} instead of + * {@link UnsupportedOperationException} on a call to + * {@link Iterator#remove()}. + */ + // Casting to any type is safe since there are no actual elements. + @SuppressWarnings("unchecked") + static <T> Iterator<T> emptyModifiableIterator() { + return (Iterator<T>) EMPTY_MODIFIABLE_ITERATOR; + } + + /** Returns an unmodifiable view of {@code iterator}. */ + public static <T> Iterator<T> unmodifiableIterator( + final Iterator<T> iterator) { + checkNotNull(iterator); + return new Iterator<T>() { + public boolean hasNext() { + return iterator.hasNext(); + } + public T next() { + return iterator.next(); + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Returns the number of elements remaining in {@code iterator}. The iterator + * will be left exhausted: its {@code hasNext()} method will return + * {@code false}. + */ + public static int size(Iterator<?> iterator) { + int count = 0; + while (iterator.hasNext()) { + iterator.next(); + count++; + } + return count; + } + + /** + * Returns {@code true} if {@code iterator} contains {@code element}. + */ + public static boolean contains(Iterator<?> iterator, @Nullable Object element) + { + if (element == null) { + return containsNull(iterator); + } + while (iterator.hasNext()) { + if (element.equals(iterator.next())) { + return true; + } + } + return false; + } + + /** + * Returns {@code true} if {@code iterator} contains at least one null + * element. + */ + public static boolean containsNull(Iterator<?> iterator) { + while (iterator.hasNext()) { + if (iterator.next() == null) { + return true; + } + } + return false; + } + + /** + * Traverses an iterator and removes every element that belongs to the + * provided collection. The iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + * + * @param iterator the iterator to (potentially) remove elements from + * @param c the elements to remove + * @return {@code true} if any elements are removed from {@code iterator} + */ + public static boolean removeAll(Iterator<?> iterator, Collection<?> c) { + checkNotNull(c); + boolean modified = false; + while (iterator.hasNext()) { + if (c.contains(iterator.next())) { + iterator.remove(); + modified = true; + } + } + return modified; + } + + /** + * Traverses an iterator and removes every element that does not belong to the + * provided collection. The iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + * + * @param iterator the iterator to (potentially) remove elements from + * @param c the elements to retain + * @return {@code true} if any elements are removed from {@code iterator} + */ + public static boolean retainAll(Iterator<?> iterator, Collection<?> c) { + checkNotNull(c); + boolean modified = false; + while (iterator.hasNext()) { + if (!c.contains(iterator.next())) { + iterator.remove(); + modified = true; + } + } + return modified; + } + + /** + * Determines whether two iterators contain equal elements in the same order. + * More specifically, this method returns {@code true} if {@code iterator1} + * and {@code iterator2} contain the same number of elements and every element + * of {@code iterator1} is equal to the corresponding element of + * {@code iterator2}. + * + * <p>Note that this will modify the supplied iterators, since they will have + * been advanced some number of elements forward. + */ + public static boolean elementsEqual( + Iterator<?> iterator1, Iterator<?> iterator2) { + while (iterator1.hasNext()) { + if (!iterator2.hasNext()) { + return false; + } + Object o1 = iterator1.next(); + Object o2 = iterator2.next(); + if (!Objects.equal(o1, o2)) { + return false; + } + } + return !iterator2.hasNext(); + } + + /** + * Returns a string representation of {@code iterator}, with the format + * {@code [e1, e2, ..., en]}. The iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + */ + public static String toString(Iterator<?> iterator) { + StringBuilder builder = new StringBuilder().append('['); + Join.join(builder, ", ", iterator); + return builder.append(']').toString(); + } + + /** + * Returns the single element contained in {@code iterator}. + * + * @throws NoSuchElementException if the iterator is empty + * @throws IllegalArgumentException if the iterator contains multiple + * elements. The state of the iterator is unspecified. + */ + public static <T> T getOnlyElement(Iterator<T> iterator) { + T first = iterator.next(); + if (!iterator.hasNext()) { + return first; + } + + StringBuilder sb = new StringBuilder(); + sb.append("expected one element but was: <" + first); + for (int i = 0; i < 4 && iterator.hasNext(); i++) { + sb.append(", " + iterator.next()); + } + if (iterator.hasNext()) { + sb.append(", ..."); + } + sb.append(">"); + + throw new IllegalArgumentException(sb.toString()); + } + + /** + * Returns the single element contained in {@code iterator}, or {@code + * defaultValue} if the iterator is empty. + * + * @throws IllegalArgumentException if the iterator contains multiple + * elements. The state of the iterator is unspecified. + */ + public static <T> T getOnlyElement( + Iterator<T> iterator, @Nullable T defaultValue) { + return iterator.hasNext() ? getOnlyElement(iterator) : defaultValue; + } + + /** + * Copies an iterator's elements into an array. The iterator will be left + * exhausted: its {@code hasNext()} method will return {@code false}. + * + * @param iterator the iterator to copy + * @param type the type of the elements + * @return a newly-allocated array into which all the elements of the iterator + * have been copied + */ + public static <T> T[] newArray( + Iterator<? extends T> iterator, Class<T> type) { + List<T> list = Lists.newArrayList(iterator); + return Iterables.newArray(list, type); + } + + /** + * Adds all elements in {@code iterator} to {@code collection}. The iterator + * will be left exhausted: its {@code hasNext()} method will return + * {@code false}. + * + * @return {@code true} if {@code collection} was modified as a result of this + * operation + */ + public static <T> boolean addAll( + Collection<T> collection, Iterator<? extends T> iterator) { + checkNotNull(collection); + boolean wasModified = false; + while (iterator.hasNext()) { + wasModified |= collection.add(iterator.next()); + } + return wasModified; + } + + /** + * Returns the number of elements in the specified iterator that equal the + * specified object. The iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + * + * @see Collections#frequency + */ + public static int frequency(Iterator<?> iterator, @Nullable Object element) { + int result = 0; + if (element == null) { + while (iterator.hasNext()) { + if (iterator.next() == null) { + result++; + } + } + } else { + while (iterator.hasNext()) { + if (element.equals(iterator.next())) { + result++; + } + } + } + return result; + } + + /** + * Returns an iterator that cycles indefinitely over the elements of {@code + * iterable}. + * + * <p>The returned iterator supports {@code remove()} if the provided iterator + * does. After {@code remove()} is called, subsequent cycles omit the removed + * element, which is no longer in {@code iterable}. The iterator's + * {@code hasNext()} method returns {@code true} until {@code iterable} is + * empty. + * + * <p><b>Warning:</b> Typical uses of the resulting iterator may produce an + * infinite loop. You should use an explicit {@code break} or be certain that + * you will eventually remove all the elements. + */ + public static <T> Iterator<T> cycle(final Iterable<T> iterable) { + checkNotNull(iterable); + return new Iterator<T>() { + Iterator<T> iterator = emptyIterator(); + Iterator<T> removeFrom; + + public boolean hasNext() { + if (!iterator.hasNext()) { + iterator = iterable.iterator(); + } + return iterator.hasNext(); + } + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + removeFrom = iterator; + return iterator.next(); + } + public void remove() { + checkState(removeFrom != null, + "no calls to next() since last call to remove()"); + removeFrom.remove(); + removeFrom = null; + } + }; + } + + /** + * Returns an iterator that cycles indefinitely over the provided elements. + * + * <p>The returned iterator supports {@code remove()} if the provided iterator + * does. After {@code remove()} is called, subsequent cycles omit the removed + * element, but {@code elements} does not change. The iterator's + * {@code hasNext()} method returns {@code true} until all of the original + * elements have been removed. + * + * <p><b>Warning:</b> Typical uses of the resulting iterator may produce an + * infinite loop. You should use an explicit {@code break} or be certain that + * you will eventually remove all the elements. + */ + public static <T> Iterator<T> cycle(T... elements) { + return cycle(Lists.newArrayList(elements)); + } + + /** + * Combines two iterators into a single iterator. The returned iterator + * iterates across the elements in {@code a}, followed by the elements in + * {@code b}. The source iterators are not polled until necessary. + * + * <p>The returned iterator supports {@code remove()} when the corresponding + * input iterator supports it. + */ + @SuppressWarnings("unchecked") + public static <T> Iterator<T> concat(Iterator<? extends T> a, + Iterator<? extends T> b) { + checkNotNull(a); + checkNotNull(b); + return concat(Arrays.asList(a, b).iterator()); + } + + /** + * Combines multiple iterators into a single iterator. The returned iterator + * iterates across the elements of each iterator in {@code inputs}. The input + * iterators are not polled until necessary. + * + * <p>The returned iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @throws NullPointerException if any of the provided iterators is null + */ + public static <T> Iterator<T> concat(Iterator<? extends T>... inputs) { + return concat(checkContentsNotNull(Arrays.asList(inputs)).iterator()); + } + + /** + * Combines multiple iterators into a single iterator. The returned iterator + * iterates across the elements of each iterator in {@code inputs}. The input + * iterators are not polled until necessary. + * + * <p>The returned iterator supports {@code remove()} when the corresponding + * input iterator supports it. The methods of the returned iterator may throw + * {@code NullPointerException} if any of the input iterators are null. + */ + public static <T> Iterator<T> concat( + final Iterator<? extends Iterator<? extends T>> inputs) { + checkNotNull(inputs); + return new Iterator<T>() { + Iterator<? extends T> current = emptyIterator(); + Iterator<? extends T> removeFrom; + + public boolean hasNext() { + while (!current.hasNext() && inputs.hasNext()) { + current = inputs.next(); + } + return current.hasNext(); + } + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + removeFrom = current; + return current.next(); + } + public void remove() { + checkState(removeFrom != null, + "no calls to next() since last call to remove()"); + removeFrom.remove(); + removeFrom = null; + } + }; + } + + /** + * Partition an iterator into sub-iterators of the given size. For example, + * <code>{A, B, C, D, E, F}</code> with partition size 3 yields + * <code>{A, B, C}</code> and <code>{D, E, F}</code>. The returned + * iterators do not support {@code remove()}. + * + * <p>After {@code next()} is called on the returned iterator, the iterators + * from prior {@code next()} calls become invalid. + * + * @param iterator the iterator to partition + * @param partitionSize the size of each partition + * @param padToSize whether to pad the last partition to the partition size + * with {@code null} + * @return an iterator across partitioned iterators + */ + public static <T> Iterator<Iterator<T>> partition( + final Iterator<? extends T> iterator, + final int partitionSize, final boolean padToSize) { + checkNotNull(iterator); + return new AbstractIterator<Iterator<T>>() { + Iterator<T> currentRow; + + @Override protected Iterator<T> computeNext() { + if (currentRow != null) { + while (currentRow.hasNext()) { + currentRow.next(); + } + } + if (!iterator.hasNext()) { + return endOfData(); + } + currentRow = new AbstractIterator<T>() { + int count = partitionSize; + + @Override protected T computeNext() { + if (count == 0) { + return endOfData(); + } + count--; + if (iterator.hasNext()) { + return iterator.next(); + } else { + if (!padToSize) { + endOfData(); + } + return null; + } + } + }; + return currentRow; + } + }; + } + + /** + * Returns the elements of {@code unfiltered} that satisfy a predicate. The + * resulting iterator does not support {@code remove()}. + */ + public static <T> Iterator<T> filter( + final Iterator<T> unfiltered, final Predicate<? super T> predicate) { + checkNotNull(unfiltered); + checkNotNull(predicate); + return new AbstractIterator<T>() { + @Override protected T computeNext() { + while (unfiltered.hasNext()) { + T element = unfiltered.next(); + if (predicate.apply(element)) { + return element; + } + } + return endOfData(); + } + }; + } + + /** + * Returns all instances of class {@code type} in {@code unfiltered}. The + * returned iterator has elements whose class is {@code type} or a subclass of + * {@code type}. The returned iterator does not support {@code remove()}. + * + * @param unfiltered an iterator containing objects of any type + * @param type the type of elements desired + * @return an unmodifiable iterator containing all elements of the original + * iterator that were of the requested type + */ + @SuppressWarnings("unchecked") + public static <T> Iterator<T> filter( + Iterator<?> unfiltered, final Class<T> type) { + checkNotNull(type); + Predicate<Object> predicate = new Predicate<Object>() { + public boolean apply(Object object) { + return type.isInstance(object); + } + }; + return (Iterator<T>) filter(unfiltered, predicate); + } + + /** + * Returns {@code true} if one or more elements returned by {@code iterator} + * satisfy the given predicate. + */ + public static <T> boolean any( + Iterator<T> iterator, Predicate<? super T> predicate) { + checkNotNull(predicate); + while (iterator.hasNext()) { + T element = iterator.next(); + if (predicate.apply(element)) { + return true; + } + } + return false; + } + + /** + * Returns {@code true} if every element returned by {@code iterator} + * satisfies the given predicate. If {@code iterator} is empty, {@code true} + * is returned. + */ + public static <T> boolean all( + Iterator<T> iterator, Predicate<? super T> predicate) { + checkNotNull(predicate); + while (iterator.hasNext()) { + T element = iterator.next(); + if (!predicate.apply(element)) { + return false; + } + } + return true; + } + + /** + * Returns the first element in {@code iterator} that satisfies the given + * predicate. If a matching element is found, the iterator will be left in a + * state such that calling {@code iterator.remove()} will remove the found + * item. If no such element is found, the iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + * + * @return the first matching element in {@code iterator} + * @throws NoSuchElementException if no element in {@code iterator} matches + * the given predicate + */ + public static <E> E find(Iterator<E> iterator, Predicate<? super E> predicate) + { + return filter(iterator, predicate).next(); + } + + /** + * Returns an iterator that applies {@code function} to each element of {@code + * fromIterator}. + * + * <p>The returned iterator supports {@code remove()} if the provided iterator + * does. After a successful {@code remove()} call, {@code fromIterator} no + * longer contains the corresponding element. + */ + public static <F, T> Iterator<T> transform(final Iterator<F> fromIterator, + final Function<? super F, ? extends T> function) { + checkNotNull(fromIterator); + checkNotNull(function); + return new Iterator<T>() { + public boolean hasNext() { + return fromIterator.hasNext(); + } + public T next() { + F from = fromIterator.next(); + return function.apply(from); + } + public void remove() { + fromIterator.remove(); + } + }; + } + + /** + * Advances {@code iterator} {@code position + 1} times, returning the element + * at the {@code position}th position. + * + * @param position position of the element to return + * @return the element at the specified position in {@code iterator} + * @throws IndexOutOfBoundsException if {@code position} is negative or + * greater than or equal to the number of elements remaining in + * {@code iterator} + */ + public static <T> T get(Iterator<T> iterator, int position) { + checkNotNull(iterator); + if (position < 0) { + throw new IndexOutOfBoundsException( + "position cannot be negative: " + position); + } + + int skipped = skip(iterator, position); + if (skipped < position || !iterator.hasNext()) { + throw new IndexOutOfBoundsException(String.format( + "position (%d) must be less than the number of elements that " + + "remained (%d)", position, skipped)); + } else { + return iterator.next(); + } + } + + /** + * Advances {@code iterator} to the end, returning the last element. + * + * @return the last element of {@code iterator} + * @throws NoSuchElementException if the iterator has no remaining elements + */ + public static <T> T getLast(Iterator<T> iterator) { + while (true) { + T current = iterator.next(); + if (!iterator.hasNext()) { + return current; + } + } + } + + /** + * Calls {@code next()} on {@code iterator}, either {@code numberToSkip} times + * or until {@code hasNext()} returns {@code false}, whichever comes first. + * + * @return the number of elements skipped + */ + public static <T> int skip(Iterator<T> iterator, int numberToSkip) { + checkNotNull(iterator); + checkArgument(numberToSkip >= 0, "number to skip cannot be negative"); + + int i; + for (i = 0; i < numberToSkip && iterator.hasNext(); i++) { + iterator.next(); + } + return i; + } + + /** + * Creates an iterator returning the first {@code limitSize} elements of the + * given iterator. If the original iterator does not contain that many + * elements, the returned iterator will have the same behavior as the original + * iterator. The returned iterator supports {@code remove()} if the original + * iterator does. + * + * @param iterator the iterator to limit + * @param limitSize the maximum number of elements in the returned iterator + * @throws IllegalArgumentException if {@code limitSize} is negative + */ + public static <T> Iterator<T> limit( + final Iterator<T> iterator, final int limitSize) { + checkNotNull(iterator); + checkArgument(limitSize >= 0, "limit is negative"); + return new Iterator<T>() { + private int count; + + public boolean hasNext() { + return count < limitSize && iterator.hasNext(); + } + + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + count++; + return iterator.next(); + } + + public void remove() { + iterator.remove(); + } + }; + } + + // Methods only in Iterators, not in Iterables + + /** + * Returns an iterator containing the elements of {@code array} in order. Note + * that you can also use the iterator of {@link Arrays#asList}. + */ + static <T> Iterator<T> forArray(T[] array) { + return forArray(array, 0, array.length); + } + + /** + * Returns an iterator containing the elements in the specified range of + * {@code array} in order. + * + * @param array array to read elements out of + * @param offset index of first array element to retrieve + * @length length number of elements in iteration + * + * @throws IndexOutOfBoundsException if {@code offset} is negative, + * {@code length} is negative, or {@code offset + length > array.length} + */ + public static <T> Iterator<T> forArray( + final T[] array, final int offset, final int length) { + checkNotNull(array); + if (length < 0) { + throw new IndexOutOfBoundsException("Negative length " + length); + } + if (offset < 0) { + throw new IndexOutOfBoundsException("Negative offset " + offset); + } + if (offset + length > array.length) { + throw new IndexOutOfBoundsException( + "offset (" + offset + ") + length (" + length + ") > " + + "array.length (" + array.length + ")"); + } + if (length == 0) { + return emptyIterator(); + } + return new Iterator<T>() { + int i = offset; + public boolean hasNext() { + return i < offset + length; + } + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return array[i++]; + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Returns an iterator containing only {@code value}. + */ + static <T> Iterator<T> singletonIterator(final T value) { + return new Iterator<T>() { + boolean done; + public boolean hasNext() { + return !done; + } + public T next() { + if (done) { + throw new NoSuchElementException(); + } + done = true; + return value; + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Adapts an {@code Enumeration} to the {@code Iterator} interface. The + * returned iterator does not support {@code remove()}. + */ + public static <T> Iterator<T> forEnumeration(final Enumeration<T> enumeration) + { + checkNotNull(enumeration); + return new Iterator<T>() { + public boolean hasNext() { + return enumeration.hasMoreElements(); + } + public T next() { + return enumeration.nextElement(); + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Adapts an {@code Iterator} to the {@code Enumeration} interface. + * + * @see Collections#enumeration(Collection) + */ + public static <T> Enumeration<T> asEnumeration(final Iterator<T> iterator) { + checkNotNull(iterator); + return new Enumeration<T>() { + public boolean hasMoreElements() { + return iterator.hasNext(); + } + public T nextElement() { + return iterator.next(); + } + }; + } + + /** + * Implementation of PeekingIterator that avoids peeking unless necessary. + */ + private static class PeekingImpl<E> implements PeekingIterator<E> { + + private final Iterator<? extends E> iterator; + private boolean hasPeeked; + private E peekedElement; + + public PeekingImpl(Iterator<? extends E> iterator) { + this.iterator = checkNotNull(iterator); + } + + public boolean hasNext() { + return hasPeeked || iterator.hasNext(); + } + + public E next() { + if (!hasPeeked) { + return iterator.next(); + } + E result = peekedElement; + hasPeeked = false; + peekedElement = null; + return result; + } + + public void remove() { + checkState(!hasPeeked, "Can't remove after you've peeked at next"); + iterator.remove(); + } + + public E peek() { + if (!hasPeeked) { + peekedElement = iterator.next(); + hasPeeked = true; + } + return peekedElement; + } + } + + /** + * Wraps the supplied iterator in a {@code PeekingIterator}. The + * {@link PeekingIterator} assumes ownership of the supplied iterator, so + * users should cease making direct calls to it after calling this method. + * + * <p>If the {@link PeekingIterator#peek()} method of the constructed + * {@code PeekingIterator} is never called, the returned iterator will + * behave exactly the same as the supplied iterator. + * + * <p>Subsequent calls to {@code peek()} with no intervening calls to + * {@code next()} do not affect the iteration, and hence return the same + * object each time. After a call to {@code peek()}, the next call to + * {@code next()} is guaranteed to return the same object that the + * {@code peek()} call returned. For example: + * + * <pre> + * PeekingIterator<E> peekingIterator = ...; + * // Either the next three calls will each throw + * // NoSuchElementExceptions, or... + * E e1 = peekingIterator.peek(); + * E e2 = peekingIterator.peek(); // e2 is the same as e1 + * E e3 = peekingIterator.next(); // e3 is the same as e1/e2 + * </pre> + * + * <p>Calling {@link Iterator#remove()} after {@link PeekingIterator#peek()} + * is unsupported by the returned iterator and will throw an + * {@link IllegalStateException}. + */ + public static <T> PeekingIterator<T> peekingIterator( + Iterator<? extends T> iterator) { + return new PeekingImpl<T>(iterator); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/LinkedHashMultimap.java b/plugins/com.google.collect/src/com/google/common/collect/LinkedHashMultimap.java new file mode 100644 index 0000000..d31ad84 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/LinkedHashMultimap.java @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Nullable; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +/** + * Implementation of {@code Multimap} that does not allow duplicate key-value + * entries and that returns collections whose iterators follow the ordering in + * which the data was added to the multimap. + * + * <p>The collections returned by {@code keySet}, {@code keys}, and {@code + * asMap} iterate through the keys in the order they were first added to the + * multimap. Similarly, {@code get}, {@code removeAll}, and {@code + * replaceValues} return collections that iterate through the values in the + * order they were added. The collections generated by {@code entries} and + * {@code values} iterate across the key-value mappings in the order they were + * added to the multimap. + * + * <p>The iteration ordering of the collections generated by {@code keySet}, + * {@code keys}, and {@code asMap} has a few subtleties. As long as the set of + * keys remains unchanged, adding or removing mappings does not affect the key + * iteration order. However, if you remove all values associated with a key and + * then add the key back to the multimap, that key will come last in the key + * iteration order. + * + * <p>The multimap does not store duplicate key-value pairs. Adding a new + * key-value pair equal to an existing key-value pair has no effect. + * + * <p>Keys and values may be null. All optional multimap methods are supported, + * and all returned views are modifiable. + * + * <p>This class is not threadsafe when any concurrent operations update the + * multimap. Concurrent read operations will work correctly. To allow concurrent + * update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedSetMultimap}. + * + * @author Jared Levy + */ +public final class LinkedHashMultimap<K, V> extends StandardSetMultimap<K, V> { + /** + * Map entries with an iteration order corresponding to the order in which the + * key-value pairs were added to the multimap. + */ + private transient Collection<Map.Entry<K, V>> linkedEntries; + + /** Constructs an empty {@code LinkedHashMultimap}. */ + public LinkedHashMultimap() { + super(new LinkedHashMap<K, Collection<V>>()); + linkedEntries = Sets.newLinkedHashSet(); + } + + /** + * Constructs a {@code LinkedHashMultimap} with the same mappings as the + * specified {@code Multimap}. If a key-value mapping appears multiple times + * in the input multimap, it only appears once in the constructed multimap. + * The new multimap has the same {@link Multimap#entries()} iteration order as + * the input multimap, except for excluding duplicate mappings. + */ + public LinkedHashMultimap(Multimap<? extends K, ? extends V> multimap) { + super(new LinkedHashMap<K, Collection<V>>( + Maps.capacity(multimap.keySet().size()))); + linkedEntries + = new LinkedHashSet<Map.Entry<K, V>>(Maps.capacity(multimap.size())); + putAll(multimap); + } + + /** + * {@inheritDoc} + * + * <p>Creates an empty {@code LinkedHashSet} for a collection of values for + * one key. + * + * @return a new {@code LinkedHashSet} containing a collection of values for + * one key + */ + @Override Set<V> createCollection() { + return new LinkedHashSet<V>(); + } + + /** + * {@inheritDoc} + * + * <p>Creates a decorated {@code LinkedHashSet} that also keeps track of the + * order in which key-value pairs are added to the multimap. + * + * @param key key to associate with values in the collection + * @return a new decorated {@code LinkedHashSet} containing a collection of + * values for one key + */ + @Override Collection<V> createCollection(@Nullable K key) { + return new SetDecorator(key, createCollection()); + } + + private class SetDecorator extends ForwardingSet<V> { + final Set<V> delegate; + final K key; + + SetDecorator(K key, Set<V> delegate) { + this.delegate = delegate; + this.key = key; + } + + @Override protected Set<V> delegate() { + return delegate; + } + + <E> Map.Entry<K, E> createEntry(@Nullable E value) { + return Maps.immutableEntry(key, value); + } + + <E> Collection<Map.Entry<K, E>> createEntries(Collection<E> values) { + // converts a collection of values into a list of key/value map entries + Collection<Map.Entry<K, E>> entries + = Lists.newArrayListWithExpectedSize(values.size()); + for (E value : values) { + entries.add(createEntry(value)); + } + return entries; + } + + @Override public boolean add(@Nullable V value) { + boolean changed = delegate.add(value); + if (changed) { + linkedEntries.add(createEntry(value)); + } + return changed; + } + + @Override public boolean addAll(Collection<? extends V> values) { + boolean changed = delegate.addAll(values); + if (changed) { + linkedEntries.addAll(createEntries(delegate())); + } + return changed; + } + + @Override public void clear() { + linkedEntries.removeAll(createEntries(delegate())); + delegate.clear(); + } + + @Override public Iterator<V> iterator() { + final Iterator<V> delegateIterator = delegate.iterator(); + return new Iterator<V>() { + V value; + + public boolean hasNext() { + return delegateIterator.hasNext(); + } + public V next() { + value = delegateIterator.next(); + return value; + } + public void remove() { + delegateIterator.remove(); + linkedEntries.remove(createEntry(value)); + } + }; + } + + @Override public boolean remove(@Nullable Object value) { + boolean changed = delegate.remove(value); + if (changed) { + /* + * linkedEntries.remove() will return false when this method is called + * by entries().iterator().remove() + */ + linkedEntries.remove(createEntry(value)); + } + return changed; + } + + @Override public boolean removeAll(Collection<?> values) { + boolean changed = delegate.removeAll(values); + if (changed) { + linkedEntries.removeAll(createEntries(values)); + } + return changed; + } + + @Override public boolean retainAll(Collection<?> values) { + /* + * Calling linkedEntries.retainAll() would incorrectly remove values + * with other keys. + */ + boolean changed = false; + Iterator<V> iterator = delegate.iterator(); + while (iterator.hasNext()) { + V value = iterator.next(); + if (!values.contains(value)) { + iterator.remove(); + linkedEntries.remove(Maps.immutableEntry(key, value)); + changed = true; + } + } + return changed; + } + } + + /** + * {@inheritDoc} + * + * <p>Generates an iterator across map entries that follows the ordering in + * which the key-value pairs were added to the multimap. + * + * @return a key-value iterator with the correct ordering + */ + @Override Iterator<Map.Entry<K, V>> createEntryIterator() { + final Iterator<Map.Entry<K, V>> delegateIterator = linkedEntries.iterator(); + + return new Iterator<Map.Entry<K, V>>() { + Map.Entry<K, V> entry; + + public boolean hasNext() { + return delegateIterator.hasNext(); + } + + public Map.Entry<K, V> next() { + entry = delegateIterator.next(); + return entry; + } + + public void remove() { + // Remove from iterator first to keep iterator valid. + delegateIterator.remove(); + LinkedHashMultimap.this.remove(entry.getKey(), entry.getValue()); + } + }; + } + + /** + * {@inheritDoc} + * + * <p>If {@code values} is not empty and the multimap already contains a + * mapping for {@code key}, the {@code keySet()} ordering is unchanged. + * However, the provided values always come last in the {@link #entries()} and + * {@link #values()} iteration orderings. + */ + @Override public Set<V> replaceValues( + @Nullable K key, Iterable<? extends V> values) { + return super.replaceValues(key, values); + } + + /** + * Returns a set of all key-value pairs. Changes to the returned set will + * update the underlying multimap, and vice versa. The entries set does not + * support the {@code add} or {@code addAll} operations. + * + * <p>The iterator generated by the returned set traverses the entries in the + * order they were added to the multimap. + */ + @Override public Set<Map.Entry<K, V>> entries() { + return super.entries(); + } + + /** + * Returns a collection of all values in the multimap. Changes to the returned + * collection will update the underlying multimap, and vice versa. + * + * <p>The iterator generated by the returned collection traverses the values + * in the order they were added to the multimap. + */ + @Override public Collection<V> values() { + return super.values(); + } + + // Unfortunately, the entries() ordering does not determine the key ordering; + // see the example in the LinkedListMultimap class Javadoc. + + /** + * @serialData the number of distinct keys, and then for each distinct key: + * the first key, the number of values for that key, and the key's values, + * followed by successive keys and values from the entries() ordering + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultimap(this, stream); + for (Map.Entry<K, V> entry : linkedEntries) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + setMap(new LinkedHashMap<K, Collection<V>>()); + linkedEntries = Sets.newLinkedHashSet(); + Serialization.populateMultimap(this, stream); + linkedEntries.clear(); // will clear and repopulate entries + for (int i = 0; i < size(); i++) { + @SuppressWarnings("unchecked") // reading data stored by writeObject + K key = (K) stream.readObject(); + @SuppressWarnings("unchecked") // reading data stored by writeObject + V value = (V) stream.readObject(); + linkedEntries.add(Maps.immutableEntry(key, value)); + } + } + + private static final long serialVersionUID = 0; +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/LinkedHashMultiset.java b/plugins/com.google.collect/src/com/google/common/collect/LinkedHashMultiset.java new file mode 100644 index 0000000..c426bbd --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/LinkedHashMultiset.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.LinkedHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A {@code Multiset} implementation with predictable iteration order. Its + * iterator orders elements according to when the first occurrence of the + * element was added. When the multiset contains multiple instances of an + * element, those instances are consecutive in the iteration order. If all + * occurrences of an element are removed, after which that element is added to + * the multiset, the element will appear at the end of the iteration. + * + * @author Kevin Bourrillion + * @author Jared Levy + */ +@SuppressWarnings("serial") // we're overriding default serialization +public final class LinkedHashMultiset<E> extends AbstractMapBasedMultiset<E> { + /** + * Constructs a new empty {@code LinkedHashMultiset} using the default initial + * capacity (16 distinct elements) and load factor (0.75). + */ + public LinkedHashMultiset() { + super(new LinkedHashMap<E, AtomicInteger>()); + } + + /** + * Constructs a new empty {@code LinkedHashMultiset} with the specified + * expected number of distinct elements and the default load factor (0.75). + * + * @param distinctElements the expected number of distinct elements + * @throws IllegalArgumentException if {@code distinctElements} is negative + */ + public LinkedHashMultiset(int distinctElements) { + // Could use newLinkedHashMapWithExpectedSize() if it existed + super(new LinkedHashMap<E, AtomicInteger>(Maps.capacity(distinctElements))); + } + + /** + * Constructs a new {@code LinkedHashMultiset} containing the specified + * elements. + * + * @param elements the elements that the multiset should contain + */ + public LinkedHashMultiset(Iterable<? extends E> elements) { + this(Multisets.inferDistinctElements(elements)); + Iterables.addAll(this, elements); // careful if we make this class non-final + } + + /** + * @serialData the number of distinct elements, the first element, its count, + * the second element, its count, and so on + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultiset(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + setBackingMap(new LinkedHashMap<E, AtomicInteger>()); + Serialization.populateMultiset(this, stream); + } + + private static final long serialVersionUID = 0; +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/ListMultimap.java b/plugins/com.google.collect/src/com/google/common/collect/ListMultimap.java new file mode 100644 index 0000000..5bf349b --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/ListMultimap.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * A {@code Multimap} that can hold duplicate key-value pairs and that maintains + * the insertion ordering of values for a given key. + * + * <p>The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods + * each return a {@link List} of values. Though the method signature doesn't say + * so explicitly, the map returned by {@link #asMap} has {@code List} values. + * + * @author Jared Levy + */ +public interface ListMultimap<K, V> extends Multimap<K, V> { + /** + * {@inheritDoc} + * + * <p>Because the values for a given key may have duplicates and follow the + * insertion ordering, this method returns a {@link List}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + */ + List<V> get(@Nullable K key); + + /** + * {@inheritDoc} + * + * <p>Because the values for a given key may have duplicates and follow the + * insertion ordering, this method returns a {@link List}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + */ + List<V> removeAll(@Nullable Object key); + + /** + * {@inheritDoc} + * + * <p>Because the values for a given key may have duplicates and follow the + * insertion ordering, this method returns a {@link List}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + */ + List<V> replaceValues(K key, Iterable<? extends V> values); + + /** + * {@inheritDoc} + * + * <p>Though the method signature doesn't say so explicitly, the returned map + * has {@link List} values. + */ + Map<K, Collection<V>> asMap(); + + /** + * Compares the specified object to this multimap for equality. + * + * <p>Two {@code ListMultimap} instances are equal if, for each key, they + * contain the same values in the same order. If the value orderings disagree, + * the multimaps will not be considered equal. + */ + boolean equals(@Nullable Object obj); +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/Lists.java b/plugins/com.google.collect/src/com/google/common/collect/Lists.java new file mode 100644 index 0000000..cd9e21f --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/Lists.java @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Function; +import com.google.common.base.Nullable; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.AbstractSequentialList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.RandomAccess; + +/** + * Static utility methods pertaining to {@link List} instances. Also see this + * class's counterparts {@link Sets} and {@link Maps}. + * + * @author Kevin Bourrillion + * @author Mike Bostock + */ +public final class Lists { + private Lists() {} + + // ArrayList + + /** + * Creates an empty {@code ArrayList} instance. + * + * <p><b>Note:</b> if you need an immutable empty list, use {@link + * Collections#emptyList} instead. + * + * @return a newly-created, initially-empty {@code ArrayList} + */ + public static <E> ArrayList<E> newArrayList() { + return new ArrayList<E>(); + } + + /** + * Creates an {@code ArrayList} instance containing the given elements. + * + * <p><b>Note:</b> if you need an immutable List, use {@link ImmutableList} + * instead. + * + * <p><b>Note:</b> due to a bug in javac 1.5.0_06, we cannot support the + * following: + * + * <p>{@code List<Base> list = Lists.newArrayList(sub1, sub2);} + * + * <p>where {@code sub1} and {@code sub2} are references to subtypes of {@code + * Base}, not of {@code Base} itself. To get around this, you must use: + * + * <p>{@code List<Base> list = Lists.<Base>newArrayList(sub1, sub2);} + * + * @param elements the elements that the list should contain, in order + * @return a newly-created {@code ArrayList} containing those elements + */ + public static <E> ArrayList<E> newArrayList(E... elements) { + // Avoid integer overflow when a large array is passed in + int capacity = computeArrayListCapacity(elements.length); + ArrayList<E> list = new ArrayList<E>(capacity); + Collections.addAll(list, elements); + return list; + } + + static int computeArrayListCapacity(int arraySize) { + return (int) Math.min(5L + arraySize + (arraySize / 10), Integer.MAX_VALUE); + } + + /** + * Creates an {@code ArrayList} instance containing the given elements. + * + * @param elements the elements that the list should contain, in order + * @return a newly-created {@code ArrayList} containing those elements + */ + public static <E> ArrayList<E> newArrayList(Iterable<? extends E> elements) { + // Let ArrayList's sizing logic work, if possible + if (elements instanceof Collection) { + @SuppressWarnings("unchecked") + Collection<? extends E> collection = (Collection<? extends E>) elements; + return new ArrayList<E>(collection); + } else { + return newArrayList(elements.iterator()); + } + } + + /** + * Creates an {@code ArrayList} instance containing the given elements. + * + * @param elements the elements that the list should contain, in order + * @return a newly-created {@code ArrayList} containing those elements + */ + public static <E> ArrayList<E> newArrayList(Iterator<? extends E> elements) { + ArrayList<E> list = newArrayList(); + while (elements.hasNext()) { + list.add(elements.next()); + } + return list; + } + + /** + * Creates an {@code ArrayList} instance with the given expected size. + * + * @param expectedSize the expected size of the list + * @return a newly-created, initially empty {@code ArrayList} with enough + * capacity for the given expected size + * @throws IllegalArgumentException if the specified expected size is negative + */ + public static <E> ArrayList<E> newArrayListWithExpectedSize(int expectedSize) + { + return new ArrayList<E>(computeArrayListCapacity(expectedSize)); + } + + // LinkedList + + /** + * Creates an empty {@code LinkedList} instance. + * + * <p><b>Note:</b> if you need an immutable empty {@link List}, use + * {@link Collections#emptyList} instead. + * + * @return a newly-created, initially-empty {@code LinkedList} + */ + public static <E> LinkedList<E> newLinkedList() { + return new LinkedList<E>(); + } + + /** + * Creates a {@code LinkedList} instance containing the given elements. + * + * @param elements the elements that the list should contain, in order + * @return a newly-created {@code LinkedList} containing those elements + */ + public static <E> LinkedList<E> newLinkedList( + Iterable<? extends E> elements) { + return newLinkedList(elements.iterator()); + } + + /** + * Creates a {@code LinkedList} instance containing the given elements. + * + * @param elements the elements that the list should contain, in order + * @return a newly-created {@code LinkedList} containing those elements + */ + private static <E> LinkedList<E> newLinkedList( + Iterator<? extends E> elements) { + LinkedList<E> list = newLinkedList(); + while (elements.hasNext()) { + list.add(elements.next()); + } + return list; + } + + /** + * Returns a copy of the given iterable sorted by the natural ordering of its + * elements. The input is not modified. The returned list is modifiable, + * serializable, and implements {@link RandomAccess}. + * + * <p>Unlike {@link Sets#newTreeSet(Iterable)}, this method does not collapse + * equal elements, and the resulting collection does not maintain its own sort + * order. + * + * @param iterable the elements to be copied and sorted + * @return a new list containing the given elements in sorted order + * @throws ClassCastException if {@code iterable} contains elements that are + * not mutually comparable + */ + @SuppressWarnings("unchecked") + public static <E extends Comparable> List<E> sortedCopy(Iterable<E> iterable) + { + List<E> list = Lists.newArrayList(iterable); + Collections.sort(list); + return list; + } + + /** + * Returns a copy of the given iterable sorted by an explicit comparator. The + * input is not modified. The returned list is modifiable, serializable, and + * implements {@link RandomAccess}. + * + * <p>Unlike {@link Sets#newTreeSet(Comparator, Iterable)}, this method does + * not collapse elements that the comparator treats as equal, and the + * resulting collection does not maintain its own sort order. + * + * @param iterable the elements to be copied and sorted + * @param comparator a comparator capable of sorting the given elements + * @return a new list containing the given elements in sorted order + */ + public static <E> List<E> sortedCopy( + Iterable<E> iterable, Comparator<? super E> comparator) { + List<E> list = Lists.newArrayList(iterable); + Collections.sort(list, checkNotNull(comparator)); + return list; + } + + /** + * Returns an unmodifiable list containing the specified first element and + * backed by the specified array of additional elements. Changes to the {@code + * rest} array will be reflected in the returned list. Unlike {@link + * Arrays#asList}, the returned list is unmodifiable. + * + * <p>This is useful when a varargs method needs to use a signature such as + * {@code (Foo firstFoo, Foo... moreFoos)}, in order to avoid overload + * ambiguity or to enforce a minimum argument count. + * + * <p>The returned list is serializable and implements {@link RandomAccess}. + * + * @param first the first element + * @param rest an array of additional elements, possibly empty + * @return an unmodifiable list containing the specified elements + */ + public static <E> List<E> asList(@Nullable E first, E[] rest) { + return new OnePlusArrayList<E>(first, rest); + } + + /** @see Lists#asList(Object,Object[]) */ + private static class OnePlusArrayList<E> extends AbstractList<E> + implements Serializable, RandomAccess { + final E first; + final E[] rest; + + OnePlusArrayList(@Nullable E first, E[] rest) { + this.first = first; + this.rest = checkNotNull(rest); + } + @Override public int size() { + return rest.length + 1; + } + @Override public E get(int index) { + return (index == 0) ? first : rest[index - 1]; // allow IOOBE to throw + } + private static final long serialVersionUID = 0; + } + + /** + * Returns an unmodifiable list containing the specified first and second + * element, and backed by the specified array of additional elements. Changes + * to the {@code rest} array will be reflected in the returned list. Unlike + * {@link Arrays#asList}, the returned list is unmodifiable. + * + * <p>This is useful when a varargs method needs to use a signature such as + * {@code (Foo firstFoo, Foo secondFoo, Foo... moreFoos)}, in order to avoid + * overload ambiguity or to enforce a minimum argument count. + * + * <p>The returned list is serializable and implements {@link RandomAccess}. + * + * @param first the first element + * @param second the second element + * @param rest an array of additional elements, possibly empty + * @return an unmodifiable list containing the specified elements + */ + public static <E> List<E> asList( + @Nullable E first, @Nullable E second, E[] rest) { + return new TwoPlusArrayList<E>(first, second, rest); + } + + /** @see Lists#asList(Object,Object,Object[]) */ + private static class TwoPlusArrayList<E> extends AbstractList<E> + implements Serializable, RandomAccess { + final E first; + final E second; + final E[] rest; + + TwoPlusArrayList(@Nullable E first, @Nullable E second, E[] rest) { + this.first = first; + this.second = second; + this.rest = checkNotNull(rest); + } + @Override public int size() { + return rest.length + 2; + } + @Override public E get(int index) { + switch (index) { + case 0: + return first; + case 1: + return second; + default: + return rest[index - 2]; // allow IOOBE to throw + } + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a list that applies {@code function} to each element of {@code + * fromList}. The returned list is a transformed view of {@code fromList}; + * changes to {@code fromList} will be reflected in the returned list and vice + * versa. + * + * <p>Since functions are not reversible, the transform is one-way and new + * items cannot be stored in the returned list. The {@code add}, + * {@code addAll} and {@code set} methods are unsupported in the returned + * list. + * + * <p>The function is applied lazily, invoked when needed. This is necessary + * for the returned list to be a view, but it means that the function will be + * applied many times for bulk operations like {@link List#contains} and + * {@link List#hashCode}. For this to perform well, {@code function} should be + * fast. To avoid lazy evaluation when the returned list doesn't need to be a + * view, copy the returned list into a new list of your choosing. + * + * <p>If {@code fromList} implements {@link RandomAccess}, so will the + * returned list. The returned list always implements {@link Serializable}, + * but serialization will succeed only when {@code fromList} and + * {@code function} are serializable. The returned list is threadsafe if the + * supplied list and function are. + */ + public static <F, T> List<T> transform( + List<F> fromList, Function<? super F, ? extends T> function) { + return (fromList instanceof RandomAccess) + ? new TransformingRandomAccessList<F, T>(fromList, function) + : new TransformingSequentialList<F, T>(fromList, function); + } + + /** + * Implementation of a sequential transforming list. + * + * @see Lists#transform + */ + private static class TransformingSequentialList<F, T> + extends AbstractSequentialList<T> implements Serializable { + final List<F> fromList; + final Function<? super F, ? extends T> function; + + TransformingSequentialList( + List<F> fromList, Function<? super F, ? extends T> function) { + this.fromList = checkNotNull(fromList); + this.function = checkNotNull(function); + } + /** + * The default implementation inherited is based on iteration and removal of + * each element which can be overkill. That's why we forward this call + * directly to the backing list. + */ + @Override public void clear() { + fromList.clear(); + } + @Override public int size() { + return fromList.size(); + } + @Override public ListIterator<T> listIterator(final int index) { + final ListIterator<F> delegate = fromList.listIterator(index); + return new ListIterator<T>() { + public void add(T e) { + throw new UnsupportedOperationException(); + } + + public boolean hasNext() { + return delegate.hasNext(); + } + + public boolean hasPrevious() { + return delegate.hasPrevious(); + } + + public T next() { + return function.apply(delegate.next()); + } + + public int nextIndex() { + return delegate.nextIndex(); + } + + public T previous() { + return function.apply(delegate.previous()); + } + + public int previousIndex() { + return delegate.previousIndex(); + } + + public void remove() { + delegate.remove(); + } + + public void set(T e) { + throw new UnsupportedOperationException("not supported"); + } + }; + } + @Override public boolean removeAll(Collection<?> c) { + return super.removeAll(checkNotNull(c)); + } + @Override public boolean retainAll(Collection<?> c) { + return super.retainAll(checkNotNull(c)); + } + private static final long serialVersionUID = 0; + } + + /** + * Implementation of a transforming random access list. We try to make as many + * of these methods pass-through to the source list as possible so that the + * performance characteristics of the source list and transformed list are + * similar. + * + * @see Lists#transform + */ + private static class TransformingRandomAccessList<F, T> + extends AbstractList<T> implements RandomAccess, Serializable { + final List<F> fromList; + final Function<? super F, ? extends T> function; + + TransformingRandomAccessList( + List<F> fromList, Function<? super F, ? extends T> function) { + this.fromList = checkNotNull(fromList); + this.function = checkNotNull(function); + } + @Override public void clear() { + fromList.clear(); + } + @Override public T get(int index) { + return function.apply(fromList.get(index)); + } + @Override public boolean isEmpty() { + return fromList.isEmpty(); + } + @Override public T remove(int index) { + return function.apply(fromList.remove(index)); + } + @Override public int size() { + return fromList.size(); + } + @Override public boolean removeAll(Collection<?> c) { + return super.removeAll(checkNotNull(c)); + } + @Override public boolean retainAll(Collection<?> c) { + return super.retainAll(checkNotNull(c)); + } + private static final long serialVersionUID = 0; + } + + private static class ImmutableArrayList<E> extends AbstractList<E> + implements RandomAccess, Serializable { + final E[] array; + + /** + * @param array underlying array for this ImmutableArrayList. Note that the + * array is <b>not</b> cloned. The caller is responsible for ensuring + * that the array can't "escape". + */ + ImmutableArrayList(E[] array) { + this.array = array; + } + @Override public E get(int index) { + return array[index]; + } + @Override public int size() { + return array.length; + } + + // optimizations + + @Override public Object[] toArray() { + Object[] newArray = new Object[array.length]; + System.arraycopy(array, 0, newArray, 0, array.length); + return newArray; + } + @Override public String toString() { + return Arrays.toString(array); + } + @Override public int hashCode() { + return Arrays.hashCode(array); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/MapConstraint.java b/plugins/com.google.collect/src/com/google/common/collect/MapConstraint.java new file mode 100644 index 0000000..5c649cc --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/MapConstraint.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Nullable; + +/** + * A constraint on the keys and values that may be added to a {@code Map} or + * {@code Multimap}. For example, {@link MapConstraints#NOT_NULL}, which + * prevents a map from including any null keys or values, could be implemented + * like this: <pre> {@code + * + * public void checkKeyValue(Object key, Object value) { + * if (key == null || value == null) { + * throw new NullPointerException(); + * } + * }}</pre> + * + * In order to be effective, constraints should be deterministic; that is, they + * should not depend on state that can change (such as external state, random + * variables, and time) and should only depend on the value of the passed-in key + * and value. A non-deterministic constraint cannot reliably enforce that all + * the collection's elements meet the constraint, since the constraint is only + * enforced when elements are added. + * + * @author Mike Bostock + * @see MapConstraints + * @see Constraint + */ +public interface MapConstraint<K, V> { + /** + * Throws a suitable {@code RuntimeException} if the specified key or value is + * illegal. Typically this is either a {@link NullPointerException}, an + * {@link IllegalArgumentException}, or a {@link ClassCastException}, though + * an application-specific exception class may be used if appropriate. + */ + void checkKeyValue(@Nullable K key, @Nullable V value); + + /** + * Returns a brief human readable description of this constraint, such as + * "Not null". + */ + String toString(); +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/MapConstraints.java b/plugins/com.google.collect/src/com/google/common/collect/MapConstraints.java new file mode 100644 index 0000000..2013720 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/MapConstraints.java @@ -0,0 +1,752 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Factory and utilities pertaining to the {@code MapConstraint} interface. + * + * @see Constraints + * @author Mike Bostock + */ +public final class MapConstraints { + private MapConstraints() {} + + /** + * A constraint that verifies that neither the key nor the value is null. If + * either is null, a {@link NullPointerException} is thrown. + */ + public static final MapConstraint<Object, Object> NOT_NULL = + NotNullMapConstraint.INSTANCE; + + // enum singleton pattern + private enum NotNullMapConstraint implements MapConstraint<Object, Object> { + INSTANCE; + + public void checkKeyValue(Object key, Object value) { + checkNotNull(key); + checkNotNull(value); + } + + @Override public String toString() { + return "Not null"; + } + } + + /** + * Returns a constrained view of the specified map, using the specified + * constraint. Any operations that add new mappings will call the provided + * constraint. However, this method does not verify that existing mappings + * satisfy the constraint. + * + * <p>The returned map is not serializable. + * + * @param map the map to constrain + * @param constraint the constraint that validates added entries + * @return a constrained view of the specified map + */ + public static <K, V> Map<K, V> constrainedMap( + Map<K, V> map, MapConstraint<? super K, ? super V> constraint) { + return new ConstrainedMap<K, V>(map, constraint); + } + + /** + * Returns a constrained view of the specified multimap, using the specified + * constraint. Any operations that add new mappings will call the provided + * constraint. However, this method does not verify that existing mappings + * satisfy the constraint. + * + * <p>Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are not + * constrained. + * <p>The returned multimap is not serializable. + * + * @param multimap the multimap to constrain + * @param constraint the constraint that validates added entries + * @return a constrained view of the multimap + */ + public static <K, V> Multimap<K, V> constrainedMultimap( + Multimap<K, V> multimap, MapConstraint<? super K, ? super V> constraint) { + return new ConstrainedMultimap<K, V>(multimap, constraint); + } + + /** + * Returns a constrained view of the specified list multimap, using the + * specified constraint. Any operations that add new mappings will call the + * provided constraint. However, this method does not verify that existing + * mappings satisfy the constraint. + * + * <p>Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are not + * constrained. + * + * <p>The returned multimap is not serializable. + * + * @param multimap the multimap to constrain + * @param constraint the constraint that validates added entries + * @return a constrained view of the specified multimap + */ + public static <K, V> ListMultimap<K, V> constrainedListMultimap( + ListMultimap<K, V> multimap, + MapConstraint<? super K, ? super V> constraint) { + return new ConstrainedListMultimap<K, V>(multimap, constraint); + } + + /** + * Returns a constrained view of the specified set multimap, using the + * specified constraint. Any operations that add new mappings will call the + * provided constraint. However, this method does not verify that existing + * mappings satisfy the constraint. + * + * <p>Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are not + * constrained. + * <p>The returned multimap is not serializable. + * + * @param multimap the multimap to constrain + * @param constraint the constraint that validates added entries + * @return a constrained view of the specified multimap + */ + public static <K, V> SetMultimap<K, V> constrainedSetMultimap( + SetMultimap<K, V> multimap, + MapConstraint<? super K, ? super V> constraint) { + return new ConstrainedSetMultimap<K, V>(multimap, constraint); + } + + /** + * Returns a constrained view of the specified sorted-set multimap, using the + * specified constraint. Any operations that add new mappings will call the + * provided constraint. However, this method does not verify that existing + * mappings satisfy the constraint. + * + * <p>Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are not + * constrained. + * <p>The returned multimap is not serializable. + * + * @param multimap the multimap to constrain + * @param constraint the constraint that validates added entries + * @return a constrained view of the specified multimap + */ + public static <K, V> SortedSetMultimap<K, V> constrainedSortedSetMultimap( + SortedSetMultimap<K, V> multimap, + MapConstraint<? super K, ? super V> constraint) { + return new ConstrainedSortedSetMultimap<K, V>(multimap, constraint); + } + + /** + * Returns a constrained view of the specified entry, using the specified + * constraint. The {@link Entry#setValue} operation will be verified with the + * constraint. + * + * @param entry the entry to constrain + * @param constraint the constraint for the entry + * @return a constrained view of the specified entry + */ + private static <K, V> Entry<K, V> constrainedEntry( + final Entry<K, V> entry, + final MapConstraint<? super K, ? super V> constraint) { + checkNotNull(entry); + checkNotNull(constraint); + return new ForwardingMapEntry<K, V>() { + @Override protected Entry<K, V> delegate() { + return entry; + } + @Override public V setValue(V value) { + constraint.checkKeyValue(getKey(), value); + return entry.setValue(value); + } + }; + } + + /** + * Returns a constrained view of the specified {@code asMap} entry, using the + * specified constraint. The {@link Entry#setValue} operation will be verified + * with the constraint, and the collection returned by {@link Entry#getValue} + * will be similarly constrained. + * + * @param entry the {@code asMap} entry to constrain + * @param constraint the constraint for the entry + * @return a constrained view of the specified entry + */ + private static <K, V> Entry<K, Collection<V>> constrainedAsMapEntry( + final Entry<K, Collection<V>> entry, + final MapConstraint<? super K, ? super V> constraint) { + checkNotNull(entry); + checkNotNull(constraint); + return new ForwardingMapEntry<K, Collection<V>>() { + @Override protected Entry<K, Collection<V>> delegate() { + return entry; + } + @Override public Collection<V> getValue() { + return Constraints.constrainedTypePreservingCollection( + entry.getValue(), new Constraint<V>() { + public V checkElement(V value) { + constraint.checkKeyValue(getKey(), value); + return value; + } + }); + } + }; + } + + /** + * Returns a constrained view of the specified set of {@code asMap} entries, + * using the specified constraint. The {@link Entry#setValue} operation will + * be verified with the constraint, and the collection returned by {@link + * Entry#getValue} will be similarly constrained. The {@code add} and {@code + * addAll} operations simply forward to the underlying set, which throws an + * {@link UnsupportedOperationException} per the multimap specification. + * + * @param entries the entries to constrain + * @param constraint the constraint for the entries + * @return a constrained view of the entries + */ + private static <K, V> Set<Entry<K, Collection<V>>> constrainedAsMapEntries( + Set<Entry<K, Collection<V>>> entries, + MapConstraint<? super K, ? super V> constraint) { + return new ConstrainedAsMapEntries<K, V>(entries, constraint); + } + + /** + * Returns a constrained view of the specified collection (or set) of entries, + * using the specified constraint. The {@link Entry#setValue} operation will + * be verified with the constraint, along with add operations on the returned + * collection. The {@code add} and {@code addAll} operations simply forward to + * the underlying collection, which throws an {@link + * UnsupportedOperationException} per the map and multimap specification. + * + * @param entries the entries to constrain + * @param constraint the constraint for the entries + * @return a constrained view of the specified entries + */ + private static <K, V> Collection<Entry<K, V>> constrainedEntries( + Collection<Entry<K, V>> entries, + MapConstraint<? super K, ? super V> constraint) { + if (entries instanceof Set) { + return constrainedEntrySet((Set<Entry<K, V>>) entries, constraint); + } + return new ConstrainedEntries<K, V>(entries, constraint); + } + + /** + * Returns a constrained view of the specified set of entries, using the + * specified constraint. The {@link Entry#setValue} operation will be verified + * with the constraint, along with add operations on the returned set. The + * {@code add} and {@code addAll} operations simply forward to the underlying + * set, which throws an {@link UnsupportedOperationException} per the map and + * multimap specification. + * + * <p>The returned multimap is not serializable. + * + * @param entries the entries to constrain + * @param constraint the constraint for the entries + * @return a constrained view of the specified entries + */ + private static <K, V> Set<Entry<K, V>> constrainedEntrySet( + Set<Entry<K, V>> entries, + MapConstraint<? super K, ? super V> constraint) { + return new ConstrainedEntrySet<K, V>(entries, constraint); + } + + /** @see MapConstraints#constrainedMap */ + static class ConstrainedMap<K, V> extends ForwardingMap<K, V> { + final Map<K, V> delegate; + final MapConstraint<? super K, ? super V> constraint; + private transient volatile Set<Entry<K, V>> entrySet; + + ConstrainedMap( + Map<K, V> delegate, MapConstraint<? super K, ? super V> constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + @Override protected Map<K, V> delegate() { + return delegate; + } + @Override public Set<Entry<K, V>> entrySet() { + if (entrySet == null) { + entrySet = constrainedEntrySet(delegate.entrySet(), constraint); + } + return entrySet; + } + @Override public V put(K key, V value) { + constraint.checkKeyValue(key, value); + return delegate.put(key, value); + } + @Override public void putAll(Map<? extends K, ? extends V> map) { + delegate.putAll(checkMap(map, constraint)); + } + } + + /** + * Returns a constrained view of the specified bimap, using the specified + * constraint. Any operations that modify the bimap will have the associated + * keys and values verified with the constraint. + * + * <p>The returned bimap is not serializable. + * + * @param map the bimap to constrain + * @param constraint the constraint that validates added entries + * @return a constrained view of the specified bimap + */ + public static <K, V> BiMap<K, V> constrainedBiMap( + BiMap<K, V> map, MapConstraint<? super K, ? super V> constraint) { + return new ConstrainedBiMap<K, V>(map, null, constraint); + } + + /** @see MapConstraints#constrainedBiMap */ + private static class ConstrainedBiMap<K, V> extends ConstrainedMap<K, V> + implements BiMap<K, V> { + transient volatile BiMap<V, K> inverse; + + ConstrainedBiMap(BiMap<K, V> delegate, BiMap<V, K> inverse, + MapConstraint<? super K, ? super V> constraint) { + super(delegate, constraint); + this.inverse = inverse; + } + + @SuppressWarnings("unchecked") + @Override protected BiMap<K, V> delegate() { + return (BiMap<K, V>) super.delegate(); + } + + public V forcePut(K key, V value) { + constraint.checkKeyValue(key, value); + return delegate().forcePut(key, value); + } + + public BiMap<V, K> inverse() { + if (inverse == null) { + inverse = new ConstrainedBiMap<V, K>(delegate().inverse(), this, + new InverseConstraint<V, K>(constraint)); + } + return inverse; + } + + @Override public Set<V> values() { + return delegate().values(); + } + } + + /** @see MapConstraints#constrainedBiMap */ + private static class InverseConstraint<K, V> implements MapConstraint<K, V> { + final MapConstraint<? super V, ? super K> constraint; + + public InverseConstraint(MapConstraint<? super V, ? super K> constraint) { + this.constraint = checkNotNull(constraint); + } + public void checkKeyValue(K key, V value) { + constraint.checkKeyValue(value, key); + } + } + + /** @see MapConstraints#constrainedMultimap */ + private static class ConstrainedMultimap<K, V> + extends ForwardingMultimap<K, V> { + final MapConstraint<? super K, ? super V> constraint; + final Multimap<K, V> delegate; + transient volatile Collection<Entry<K, V>> entries; + transient volatile Map<K, Collection<V>> asMap; + + public ConstrainedMultimap(Multimap<K, V> delegate, + MapConstraint<? super K, ? super V> constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + + @Override protected Multimap<K, V> delegate() { + return delegate; + } + + @Override public Map<K, Collection<V>> asMap() { + if (asMap == null) { + final Map<K, Collection<V>> asMapDelegate = delegate.asMap(); + + asMap = new ForwardingMap<K, Collection<V>>() { + volatile Set<Entry<K, Collection<V>>> entrySet; + volatile Collection<Collection<V>> values; + + @Override protected Map<K, Collection<V>> delegate() { + return asMapDelegate; + } + + @Override public Set<Entry<K, Collection<V>>> entrySet() { + if (entrySet == null) { + entrySet = constrainedAsMapEntries( + asMapDelegate.entrySet(), constraint); + } + return entrySet; + } + + + @SuppressWarnings("unchecked") + @Override public Collection<V> get(Object key) { + try { + Collection<V> collection = ConstrainedMultimap.this.get((K) key); + return collection.isEmpty() ? null : collection; + } catch (ClassCastException e) { + return null; // key wasn't a K + } + } + + @Override public Collection<Collection<V>> values() { + if (values == null) { + values = new ConstrainedAsMapValues<K, V>( + delegate().values(), entrySet()); + } + return values; + } + + @Override public boolean containsValue(Object o) { + return values().contains(o); + } + }; + } + return asMap; + } + + @Override public Collection<Entry<K, V>> entries() { + if (entries == null) { + entries = constrainedEntries(delegate.entries(), constraint); + } + return entries; + } + + @Override public Collection<V> get(final K key) { + return Constraints.constrainedTypePreservingCollection( + delegate.get(key), new Constraint<V>() { + public V checkElement(V value) { + constraint.checkKeyValue(key, value); + return value; + } + }); + } + + @Override public boolean put(K key, V value) { + constraint.checkKeyValue(key, value); + return delegate.put(key, value); + } + + @Override public boolean putAll(K key, Iterable<? extends V> values) { + return delegate.putAll(key, checkValues(key, values, constraint)); + } + + @Override public boolean putAll(Multimap<? extends K, ? extends V> multimap) { + return delegate.putAll(checkMultimap(multimap, constraint)); + } + + @Override public Collection<V> replaceValues( + K key, Iterable<? extends V> values) { + return delegate.replaceValues(key, checkValues(key, values, constraint)); + } + } + + /** @see ConstrainedMultimap#asMap */ + private static class ConstrainedAsMapValues<K, V> + extends ForwardingCollection<Collection<V>> { + final Collection<Collection<V>> delegate; + final Set<Entry<K, Collection<V>>> entrySet; + + /** + * @param entrySet map entries, linking each key with its corresponding + * values, that already enforce the constraint + */ + ConstrainedAsMapValues(Collection<Collection<V>> delegate, + Set<Entry<K, Collection<V>>> entrySet) { + this.delegate = delegate; + this.entrySet = entrySet; + } + @Override protected Collection<Collection<V>> delegate() { + return delegate; + } + + @Override public Iterator<Collection<V>> iterator() { + final Iterator<Entry<K, Collection<V>>> iterator = entrySet.iterator(); + return new Iterator<Collection<V>>() { + public boolean hasNext() { + return iterator.hasNext(); + } + public Collection<V> next() { + return iterator.next().getValue(); + } + public void remove() { + iterator.remove(); + } + }; + } + + @Override public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + @Override public <T> T[] toArray(T[] array) { + return ObjectArrays.toArrayImpl(this, array); + } + @Override public boolean contains(Object o) { + return Iterators.contains(iterator(), o); + } + @Override public boolean containsAll(Collection<?> c) { + return Collections2.containsAll(this, c); + } + @Override public boolean remove(Object o) { + return Iterables.remove(this, o); + } + @Override public boolean removeAll(Collection<?> c) { + return Iterators.removeAll(iterator(), c); + } + @Override public boolean retainAll(Collection<?> c) { + return Iterators.retainAll(iterator(), c); + } + } + + /** @see MapConstraints#constrainedEntries */ + private static class ConstrainedEntries<K, V> + extends ForwardingCollection<Entry<K, V>> { + final MapConstraint<? super K, ? super V> constraint; + final Collection<Entry<K, V>> entries; + + ConstrainedEntries(Collection<Entry<K, V>> entries, + MapConstraint<? super K, ? super V> constraint) { + this.entries = entries; + this.constraint = constraint; + } + @Override protected Collection<Entry<K, V>> delegate() { + return entries; + } + + @Override public Iterator<Entry<K, V>> iterator() { + final Iterator<Entry<K, V>> iterator = entries.iterator(); + return new ForwardingIterator<Entry<K, V>>() { + @Override public Entry<K, V> next() { + return constrainedEntry(iterator.next(), constraint); + } + @Override protected Iterator<Entry<K, V>> delegate() { + return iterator; + } + }; + } + + // See Collections.CheckedMap.CheckedEntrySet for details on attacks. + + @Override public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + @Override public <T> T[] toArray(T[] array) { + return ObjectArrays.toArrayImpl(this, array); + } + @Override public boolean contains(Object o) { + return Maps.containsEntryImpl(delegate(), o); + } + @Override public boolean containsAll(Collection<?> c) { + return Collections2.containsAll(this, c); + } + @Override public boolean remove(Object o) { + return Maps.removeEntryImpl(delegate(), o); + } + @Override public boolean removeAll(Collection<?> c) { + return Iterators.removeAll(iterator(), c); + } + @Override public boolean retainAll(Collection<?> c) { + return Iterators.retainAll(iterator(), c); + } + } + + /** @see MapConstraints#constrainedEntrySet */ + static class ConstrainedEntrySet<K, V> + extends ConstrainedEntries<K, V> implements Set<Entry<K, V>> { + ConstrainedEntrySet(Set<Entry<K, V>> entries, + MapConstraint<? super K, ? super V> constraint) { + super(entries, constraint); + } + + // See Collections.CheckedMap.CheckedEntrySet for details on attacks. + + @Override public boolean equals(Object o) { + return Sets.equalsImpl(this, o); + } + + @Override public int hashCode() { + return Sets.hashCodeImpl(this); + } + } + + /** @see MapConstraints#constrainedAsMapEntries */ + static class ConstrainedAsMapEntries<K, V> + extends ForwardingSet<Entry<K, Collection<V>>> { + private final MapConstraint<? super K, ? super V> constraint; + private final Set<Entry<K, Collection<V>>> entries; + + ConstrainedAsMapEntries(Set<Entry<K, Collection<V>>> entries, + MapConstraint<? super K, ? super V> constraint) { + this.entries = entries; + this.constraint = constraint; + } + + @Override protected Set<Entry<K, Collection<V>>> delegate() { + return entries; + } + + @Override public Iterator<Entry<K, Collection<V>>> iterator() { + final Iterator<Entry<K, Collection<V>>> iterator = entries.iterator(); + return new ForwardingIterator<Entry<K, Collection<V>>>() { + @Override public Entry<K, Collection<V>> next() { + return constrainedAsMapEntry(iterator.next(), constraint); + } + @Override protected Iterator<Entry<K, Collection<V>>> delegate() { + return iterator; + } + }; + } + + // See Collections.CheckedMap.CheckedEntrySet for details on attacks. + + @Override public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + + @Override public <T> T[] toArray(T[] array) { + return ObjectArrays.toArrayImpl(this, array); + } + + @Override public boolean contains(Object o) { + return Maps.containsEntryImpl(delegate(), o); + } + + @Override public boolean containsAll(Collection<?> c) { + return Collections2.containsAll(this, c); + } + + @Override public boolean equals(Object o) { + return Sets.equalsImpl(this, o); + } + + @Override public int hashCode() { + return Sets.hashCodeImpl(this); + } + + @Override public boolean remove(Object o) { + return Maps.removeEntryImpl(delegate(), o); + } + + @Override public boolean removeAll(Collection<?> c) { + return Iterators.removeAll(iterator(), c); + } + + @Override public boolean retainAll(Collection<?> c) { + return Iterators.retainAll(iterator(), c); + } + } + + private static class ConstrainedListMultimap<K, V> + extends ConstrainedMultimap<K, V> implements ListMultimap<K, V> { + ConstrainedListMultimap(ListMultimap<K, V> delegate, + MapConstraint<? super K, ? super V> constraint) { + super(delegate, constraint); + } + @Override public List<V> get(K key) { + return (List<V>) super.get(key); + } + @Override public List<V> removeAll(Object key) { + return (List<V>) super.removeAll(key); + } + @Override public List<V> replaceValues( + K key, Iterable<? extends V> values) { + return (List<V>) super.replaceValues(key, values); + } + } + + private static class ConstrainedSetMultimap<K, V> + extends ConstrainedMultimap<K, V> implements SetMultimap<K, V> { + ConstrainedSetMultimap(SetMultimap<K, V> delegate, + MapConstraint<? super K, ? super V> constraint) { + super(delegate, constraint); + } + @Override public Set<V> get(K key) { + return (Set<V>) super.get(key); + } + @Override public Set<Map.Entry<K, V>> entries() { + return (Set<Map.Entry<K, V>>) super.entries(); + } + @Override public Set<V> removeAll(Object key) { + return (Set<V>) super.removeAll(key); + } + @Override public Set<V> replaceValues( + K key, Iterable<? extends V> values) { + return (Set<V>) super.replaceValues(key, values); + } + } + + private static class ConstrainedSortedSetMultimap<K, V> + extends ConstrainedSetMultimap<K, V> implements SortedSetMultimap<K, V> { + ConstrainedSortedSetMultimap(SortedSetMultimap<K, V> delegate, + MapConstraint<? super K, ? super V> constraint) { + super(delegate, constraint); + } + @Override public SortedSet<V> get(K key) { + return (SortedSet<V>) super.get(key); + } + @Override public SortedSet<V> removeAll(Object key) { + return (SortedSet<V>) super.removeAll(key); + } + @Override public SortedSet<V> replaceValues( + K key, Iterable<? extends V> values) { + return (SortedSet<V>) super.replaceValues(key, values); + } + public Comparator<? super V> valueComparator() { + return ((SortedSetMultimap<K, V>) delegate()).valueComparator(); + } + } + + private static <K, V> Collection<V> checkValues(K key, + Iterable<? extends V> values, + MapConstraint<? super K, ? super V> constraint) { + Collection<V> copy = Lists.newArrayList(values); + for (V value : copy) { + constraint.checkKeyValue(key, value); + } + return copy; + } + + private static <K, V> Map<K, V> checkMap(Map<? extends K, ? extends V> map, + MapConstraint<? super K, ? super V> constraint) { + Map<K, V> copy = new LinkedHashMap<K, V>(map); + for (Entry<K, V> entry : copy.entrySet()) { + constraint.checkKeyValue(entry.getKey(), entry.getValue()); + } + return copy; + } + + private static <K, V> Multimap<K, V> checkMultimap( + Multimap<? extends K, ? extends V> map, + MapConstraint<? super K, ? super V> constraint) { + Multimap<K, V> copy = new ArrayListMultimap<K, V>(map); + for (Entry<K, V> entry : copy.entries()) { + constraint.checkKeyValue(entry.getKey(), entry.getValue()); + } + return copy; + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/Maps.java b/plugins/com.google.collect/src/com/google/common/collect/Maps.java new file mode 100644 index 0000000..46c6d36 --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/Maps.java @@ -0,0 +1,597 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Function; +import com.google.common.base.Nullable; +import com.google.common.base.Objects; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.collect.MapConstraints.ConstrainedMap; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Static utility methods pertaining to {@link Map} instances. Also see this + * class's counterparts {@link Lists} and {@link Sets}. + * + * @author Kevin Bourrillion + * @author Mike Bostock + */ +public final class Maps { + private Maps() {} + + /** + * Creates a {@code HashMap} instance. + * + * <p><b>Note:</b> if {@code K} is an {@code enum} type, use {@link + * #newEnumMap} instead. + * + * <p><b>Note:</b> if you don't actually need the resulting map to be mutable, + * use {@link Collections#emptyMap} instead. + * + * @return a newly-created, initially-empty {@code HashMap} + */ + public static <K, V> HashMap<K, V> newHashMap() { + return new HashMap<K, V>(); + } + + /** + * Creates a {@code HashMap} instance with enough capacity to hold the + * specified number of elements without rehashing. + * + * @param expectedSize the expected size + * @return a newly-created {@code HashMap}, initially empty, with enough + * capacity to hold {@code expectedSize} elements without rehashing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static <K, V> HashMap<K, V> newHashMapWithExpectedSize( + int expectedSize) { + /* + * The HashMap is constructed with an initialCapacity that's greater than + * expectedSize. The larger value is necessary because HashMap resizes + * its internal array if the map size exceeds loadFactor * initialCapacity. + */ + return new HashMap<K, V>(capacity(expectedSize)); + } + + /** + * Returns an appropriate value for the "capacity" (in reality, "minimum + * table size") parameter of a {@link HashMap} constructor, such that the + * resulting table will be between 25% and 50% full when it contains + * {@code expectedSize} entries. + * + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + static int capacity(int expectedSize) { + checkArgument(expectedSize >= 0); + return Math.max(expectedSize * 2, 16); + } + + /** + * Creates a {@code HashMap} instance with the same mappings as the specified + * map. + * + * <p><b>Note:</b> if {@code K} is an {@link Enum} type, use {@link + * #newEnumMap} instead. + * + * @param map the mappings to be placed in the new map + * @return a newly-created {@code HashMap} initialized with the mappings from + * {@code map} + */ + public static <K, V> HashMap<K, V> newHashMap( + Map<? extends K, ? extends V> map) { + return new HashMap<K, V>(map); + } + + /** + * Creates an insertion-ordered {@code LinkedHashMap} instance. + * + * @return a newly-created, initially-empty {@code LinkedHashMap} + */ + public static <K, V> LinkedHashMap<K, V> newLinkedHashMap() { + return new LinkedHashMap<K, V>(); + } + + /** + * Creates an insertion-ordered {@code LinkedHashMap} instance with the same + * mappings as the specified map. + * + * @param map the mappings to be placed in the new map + * @return a newly-created, {@code LinkedHashMap} initialized with the + * mappings from {@code map} + */ + public static <K, V> LinkedHashMap<K, V> + newLinkedHashMap(Map<? extends K, ? extends V> map) { + return new LinkedHashMap<K, V>(map); + } + + /** + * Creates a {@code ConcurrentHashMap} instance. + * + * @return a newly-created, initially-empty {@code ConcurrentHashMap} + */ + public static <K, V> ConcurrentHashMap<K, V> newConcurrentHashMap() { + return new ConcurrentHashMap<K, V>(); + } + + /** + * Creates a {@code TreeMap} instance using the natural ordering of its + * elements. + * + * @return a newly-created, initially-empty {@code TreeMap} + */ + @SuppressWarnings("unchecked") // allow ungenerified Comparable types + public static <K extends Comparable, V> TreeMap<K, V> newTreeMap() { + return new TreeMap<K, V>(); + } + + /** + * Creates a {@code TreeMap} instance using the given comparator. + * + * @param comparator the comparator to sort the keys with + * @return a newly-created, initially-empty {@code TreeMap} + */ + public static <C, K extends C, V> TreeMap<K, V> newTreeMap( + @Nullable Comparator<C> comparator) { + // Ideally, the extra type parameter "C" shouldn't be necessary. It is a + // work-around of a compiler type inference quirk that prevents the + // following code from being compiled: + // Comparator<Class<?>> comparator = null; + // Map<Class<? extends Throwable>, String> map = newTreeMap(comparator); + return new TreeMap<K, V>(comparator); + } + + /** + * Creates an {@code EnumMap} instance. + * + * @param type the key type for this map + * @return a newly-created, initially-empty {@code EnumMap} + */ + public static <K extends Enum<K>, V> EnumMap<K, V> newEnumMap(Class<K> type) { + return new EnumMap<K, V>(type); + } + + /** + * Creates an {@code IdentityHashMap} instance. + * + * @return a newly-created, initially-empty {@code IdentityHashMap} + */ + public static <K, V> IdentityHashMap<K, V> newIdentityHashMap() { + return new IdentityHashMap<K, V>(); + } + + /** + * Returns {@code true} if {@code map} contains an entry mapping {@code key} + * to {@code value}. If you are not concerned with null-safety you can simply + * use {@code map.get(key).equals(value)}. + */ + public static boolean containsEntry( + Map<?, ?> map, @Nullable Object key, @Nullable Object value) { + Object valueForKey = map.get(key); + return (valueForKey == null) + ? value == null && map.containsKey(key) + : valueForKey.equals(value); + } + + /** + * Returns a synchronized (thread-safe) bimap backed by the specified bimap. + * In order to guarantee serial access, it is critical that <b>all</b> access + * to the backing bimap is accomplished through the returned bimap. + * + * <p>It is imperative that the user manually synchronize on the returned map + * when accessing any of its collection views: + * + * <pre> Bimap<K,V> m = Maps.synchronizedBiMap( + * new HashBiMap<K,V>()); + * ... + * Set<K> s = m.keySet(); // Needn't be in synchronized block + * ... + * synchronized (m) { // Synchronizing on m, not s! + * Iterator<K> i = s.iterator(); // Must be in synchronized block + * while (i.hasNext()) { + * foo(i.next()); + * } + * }</pre> + * + * Failure to follow this advice may result in non-deterministic behavior. + * + * @param bimap the bimap to be wrapped in a synchronized view + * @return a sychronized view of the specified bimap + */ + public static <K, V> BiMap<K, V> synchronizedBiMap(BiMap<K, V> bimap) { + return Synchronized.biMap(bimap, null); + } + + /** + * Returns an immutable map for which the {@link Map#values} are the given + * elements in the given order, and each key is the product of invoking a + * supplied function on its corresponding value. + * + * @param values the values to use when constructing the {@code Map} + * @param keyFunction the function used to produce the key for each value + * @return a map mapping the result of evaluating the function {@code + * keyFunction} on each value in the input collection to that value + * @throws IllegalArgumentException if {@code keyFunction} produces the same + * key for more than one value in the input collection + * @throws NullPointerException if any elements of {@code values} is null, or + * if {@code keyFunction} produces {@code null} for any value + */ + // TODO: consider returning a bimap, whose inverse view does lookups by + // invoking the function. + public static <K, V> ImmutableMap<K, V> uniqueIndex( + Iterable<V> values, Function<? super V, K> keyFunction) { + checkNotNull(keyFunction); + ImmutableMap.Builder<K, V> builder = ImmutableMap.builder(); + for (V value : values) { + builder.put(keyFunction.apply(value), value); + } + return builder.build(); + } + + /** + * Creates a {@code Map<String, String>} from a {@code Properties} instance. + * Properties normally derive from {@code Map<Object, Object>}, but they + * typically contain strings, which is awkward. This method lets you get a + * plain-old-{@code Map} out of a {@code Properties}. The returned map won't + * include any null keys or values. The returned map is modifiable and + * serializable. + * + * @param properties a {@code Properties} object to be converted + * @return a map containing all the entries in {@code properties} + */ + public static Map<String, String> fromProperties(Properties properties) { + Map<String, String> ret = newHashMapWithExpectedSize(properties.size()); + for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) { + Object k = e.nextElement(); + /* + * It is unlikely that a 'null' could be inserted into a Properties, but + * possible in a derived class. + */ + String key = (k != null) ? k.toString() : null; + ret.put(key, properties.getProperty(key)); + } + return ret; + } + + /** + * Returns an immutable map entry with the specified key and value. The {@link + * Entry#setValue} operation throws an {@link UnsupportedOperationException}. + * + * <p>The returned entry is serializable. + * + * @param key the key to be associated with the returned entry + * @param value the value to be associated with the returned entry + */ + public static <K, V> Entry<K, V> immutableEntry( + @Nullable final K key, @Nullable final V value) { + return new ImmutableEntry<K, V>(key, value); + } + + /** @see Maps#immutableEntry(Object,Object) */ + private static class ImmutableEntry<K, V> extends AbstractMapEntry<K, V> + implements Serializable { + final K key; + final V value; + + ImmutableEntry(K key, V value) { + this.key = key; + this.value = value; + } + @Override public K getKey() { + return key; + } + @Override public V getValue() { + return value; + } + private static final long serialVersionUID = 0; + } + + /** + * Returns an unmodifiable view of the specified set of entries. The {@link + * Entry#setValue} operation throws an {@link UnsupportedOperationException}, + * as do any operations that would modify the returned set. + * + * @param entrySet the entries for which to return an unmodifiable view + * @return an unmodifiable view of the entries + */ + static <K, V> Set<Entry<K, V>> unmodifiableEntrySet( + final Set<Entry<K, V>> entrySet) { + return new UnmodifiableEntrySet<K, V>(Collections.unmodifiableSet( + entrySet)); + } + + /** + * Returns an unmodifiable view of the specified map entry. The {@link + * Entry#setValue} operation throws an {@link UnsupportedOperationException}. + * This also has the side-effect of redefining {@code equals} to comply with + * the Entry contract, to avoid a possible nefarious implementation of + * equals. + * + * @param entry the entry for which to return an unmodifiable view + * @return an unmodifiable view of the entry + */ + private static <K, V> Entry<K, V> unmodifiableEntry(final Entry<K, V> entry) { + checkNotNull(entry); + return new AbstractMapEntry<K, V>() { + @Override public K getKey() { + return entry.getKey(); + } + @Override public V getValue() { + return entry.getValue(); + } + }; + } + + /** @see Multimaps#unmodifiableEntries */ + static class UnmodifiableEntries<K, V> + extends ForwardingCollection<Entry<K, V>> { + private final Collection<Entry<K, V>> entries; + + UnmodifiableEntries(Collection<Entry<K, V>> entries) { + this.entries = entries; + } + + @Override protected Collection<Entry<K, V>> delegate() { + return entries; + } + + @Override public Iterator<Entry<K, V>> iterator() { + final Iterator<Entry<K, V>> delegate = super.iterator(); + return new ForwardingIterator<Entry<K, V>>() { + @Override public Entry<K, V> next() { + return unmodifiableEntry(super.next()); + } + @Override protected Iterator<Entry<K, V>> delegate() { + return delegate; + } + }; + } + + // See java.util.Collections.UnmodifiableEntrySet for details on attacks. + + @Override public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + + @Override public <T> T[] toArray(T[] array) { + return ObjectArrays.toArrayImpl(this, array); + } + + @Override public boolean contains(Object o) { + return containsEntryImpl(delegate(), o); + } + + @Override public boolean containsAll(Collection<?> c) { + return Collections2.containsAll(this, c); + } + } + + /** @see Maps#unmodifiableEntrySet(Set) */ + static class UnmodifiableEntrySet<K, V> + extends UnmodifiableEntries<K, V> + implements Set<Entry<K, V>> { + UnmodifiableEntrySet(Set<Entry<K, V>> entries) { + super(entries); + } + + // See java.util.Collections.UnmodifiableEntrySet for details on attacks. + + @Override public boolean equals(Object o) { + return Sets.equalsImpl(this, o); + } + + @Override public int hashCode() { + return Sets.hashCodeImpl(this); + } + } + + /** + * Returns a new empty {@code HashBiMap} with the default initial capacity + * (16). + */ + public static <K, V> HashBiMap<K, V> newHashBiMap() { + return new HashBiMap<K, V>(); + } + + /** + * Returns a new empty {@code EnumHashBiMap} using the specified key type. + * + * @param keyType the key type + */ + public static <K extends Enum<K>, V> EnumHashBiMap<K, V> newEnumHashBiMap( + Class<K> keyType) { + return new EnumHashBiMap<K, V>(keyType); + } + + /** + * Returns a new empty {@code EnumBiMap} using the specified key and value + * types. + * + * @param keyType the key type + * @param valueType the value type + */ + public static <K extends Enum<K>, V extends Enum<V>> EnumBiMap<K, V> + newEnumBiMap(Class<K> keyType, Class<V> valueType) { + return new EnumBiMap<K, V>(keyType, valueType); + } + + /** + * Returns an unmodifiable view of the specified bimap. This method allows + * modules to provide users with "read-only" access to internal bimaps. Query + * operations on the returned bimap "read through" to the specified bimap, and + * attemps to modify the returned map, whether direct or via its collection + * views, result in an {@code UnsupportedOperationException}. + * + * <p>The returned bimap will be serializable if the specified bimap is + * serializable. + * + * @param bimap the bimap for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified bimap + */ + public static <K, V> BiMap<K, V> unmodifiableBiMap(BiMap<K, V> bimap) { + return new UnmodifiableBiMap<K, V>(bimap, null); + } + + /** @see Maps#unmodifiableBiMap(BiMap) */ + private static class UnmodifiableBiMap<K, V> extends ForwardingMap<K, V> + implements BiMap<K, V>, Serializable { + final Map<K, V> unmodifiableMap; + final BiMap<K, V> delegate; + transient BiMap<V, K> inverse; + transient Set<V> values; + + UnmodifiableBiMap(BiMap<K, V> delegate, BiMap<V, K> inverse) { + unmodifiableMap = Collections.unmodifiableMap(delegate); + this.delegate = delegate; + this.inverse = inverse; + } + @Override protected Map<K, V> delegate() { + return unmodifiableMap; + } + public V forcePut(K key, V value) { + throw new UnsupportedOperationException(); + } + public BiMap<V, K> inverse() { + BiMap<V, K> result = inverse; + return (result == null) + ? inverse = new UnmodifiableBiMap<V, K>(delegate.inverse(), this) + : result; + } + @Override public Set<V> values() { + Set<V> result = values; + return (result == null) + ? values = Collections.unmodifiableSet(delegate.values()) + : result; + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a new {@code ClassToInstanceMap} instance backed by a {@link + * HashMap} using the default initial capacity and load factor. + */ + public static <B> ClassToInstanceMap<B> newClassToInstanceMap() { + return newClassToInstanceMap(new HashMap<Class<? extends B>, B>()); + } + + /** + * Returns a new {@code ClassToInstanceMap} instance backed by a given empty + * {@code backingMap}. The caller surrenders control of the backing map, and + * thus should not allow any direct references to it to remain accessible. + */ + public static <B> ClassToInstanceMap<B> newClassToInstanceMap( + Map<Class<? extends B>, B> backingMap) { + return new SimpleClassToInstanceMap<B>(backingMap); + } + + private static final MapConstraint<Class<?>, Object> VALUE_CAN_BE_CAST_TO_KEY + = new MapConstraint<Class<?>, Object>() { + public void checkKeyValue(Class<?> key, Object value) { + wrap(key).cast(value); + } + }; + + private static class SimpleClassToInstanceMap<B> extends + ConstrainedMap<Class<? extends B>, B> implements ClassToInstanceMap<B> { + SimpleClassToInstanceMap(Map<Class<? extends B>, B> delegate) { + super(delegate, VALUE_CAN_BE_CAST_TO_KEY); + } + public <T extends B> T putInstance(Class<T> type, T value) { + B oldValue = put(type, value); + return wrap(type).cast(oldValue); + } + public <T extends B> T getInstance(Class<T> type) { + B value = get(type); + return wrap(type).cast(value); + } + private static final long serialVersionUID = 0; + } + + @SuppressWarnings("unchecked") + private static <T> Class<T> wrap(Class<T> c) { + return c.isPrimitive() ? (Class<T>) PRIMITIVES_TO_WRAPPERS.get(c) : c; + } + + private static final Map<Class<?>, Class<?>> PRIMITIVES_TO_WRAPPERS + = new ImmutableMap.Builder<Class<?>, Class<?>>() + .put(boolean.class, Boolean.class) + .put(byte.class, Byte.class) + .put(char.class, Character.class) + .put(double.class, Double.class) + .put(float.class, Float.class) + .put(int.class, Integer.class) + .put(long.class, Long.class) + .put(short.class, Short.class) + .put(void.class, Void.class) + .build(); + + /** + * Implements {@code Collection.contains} safely for forwarding collections of + * map entries. If {@code o} is an instance of {@code Map.Entry}, it is + * wrapped using {@link #unmodifiableEntry} to protect against a possible + * nefarious equals method. + * + * <p>Note that {@code c} is the backing (delegate) collection, rather than + * the forwarding collection. + * + * @param c the delegate (unwrapped) collection of map entries + * @param o the object that might be contained in {@code c} + * @return {@code true} if {@code c} contains {@code o} + */ + static <K, V> boolean containsEntryImpl(Collection<Entry<K, V>> c, Object o) { + if (!(o instanceof Entry)) { + return false; + } + return c.contains(unmodifiableEntry((Entry<?, ?>) o)); + } + + /** + * Implements {@code Collection.remove} safely for forwarding collections of + * map entries. If {@code o} is an instance of {@code Map.Entry}, it is + * wrapped using {@link #unmodifiableEntry} to protect against a possible + * nefarious equals method. + * + * <p>Note that {@code c} is backing (delegate) collection, rather than the + * forwarding collection. + * + * @param c the delegate (unwrapped) collection of map entries + * @param o the object to remove from {@code c} + * @return {@code true} if {@code c} was changed + */ + static <K, V> boolean removeEntryImpl(Collection<Entry<K, V>> c, Object o) { + if (!(o instanceof Entry)) { + return false; + } + return c.remove(unmodifiableEntry((Entry<?, ?>) o)); + } +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/Multimap.java b/plugins/com.google.collect/src/com/google/common/collect/Multimap.java new file mode 100644 index 0000000..030a3de --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/Multimap.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Nullable; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * A collection similar to a {@code Map}, but which may associate multiple + * values with a single key. If you call {@link #put} twice, with the same key + * but different values, the multimap contains mappings from the key to both + * values. + * + * <p>The methods {@link #get}, {@link #keySet}, {@link #keys}, {@link #values}, + * {@link #entries}, and {@link #asMap} return collections that are views of the + * multimap. If the multimap is modifiable, updating it can change the contents + * of those collections, and updating the collections will change the multimap. + * In contrast, {@link #replaceValues} and {@link #removeAll} return collections + * that are independent of subsequent multimap changes. + * + * <p>Depending on the implementation, a multimap may or may not allow duplicate + * key-value pairs. In other words, the multimap contents after adding the same + * key and value twice varies between implementations. In multimaps allowing + * duplicates, the multimap will contain two mappings, and {@code get} will + * return a collection that includes the value twice. In multimaps not + * supporting duplicates, the multimap will contain a single mapping from the + * key to the value, and {@code get} will return a collection that includes the + * value once. + * + * <p>All methods that alter the multimap are optional, and the views returned + * by the multimap may or may not be modifiable. When modification isn't + * supported, those methods will throw an {@link UnsupportedOperationException}. + * + * @author Jared Levy + * @param <K> the type of keys maintained by this multimap + * @param <V> the type of mapped values + */ +public interface Multimap<K, V> { + // Query Operations + + /** Returns the number of key-value pairs in the multimap. */ + int size(); + + /** Returns {@code true} if the multimap contains no key-value pairs. */ + boolean isEmpty(); + + /** + * Returns {@code true} if the multimap contains any values for the specified + * key. + * + * @param key key to search for in multimap + */ + boolean containsKey(@Nullable Object key); + + /** + * Returns {@code true} if the multimap contains the specified value for any + * key. + * + * @param value value to search for in multimap + */ + boolean containsValue(@Nullable Object value); + + /** + * Returns {@code true} if the multimap contains the specified key-value pair. + * + * @param key key to search for in multimap + * @param value value to search for in multimap + */ + boolean containsEntry(@Nullable Object key, @Nullable Object value); + + // Modification Operations + + /** + * Stores a key-value pair in the multimap. + * + * <p>Some multimap implementations allow duplicate key-value pairs, in which + * case {@code put} always adds a new key-value pair and increases the + * multimap size by 1. Other implementations prohibit duplicates, and storing + * a key-value pair that's already in the multimap has no effect. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} if the method increased the size of the multimap, or + * {@code false} if the multimap already contained the key-value pair and + * doesn't allow duplicates + */ + boolean put(K key, V value); + + /** + * Removes a key-value pair from the multimap. + * + * @param key key of entry to remove from the multimap + * @param value value of entry to remove the multimap + * @return {@code true} if the multimap changed + */ + boolean remove(@Nullable Object key, @Nullable Object value); + + // Bulk Operations + + /** + * Stores a collection of values with the same key. + * + * @param key key to store in the multimap + * @param values values to store in the multimap + * @return {@code true} if the multimap changed + */ + boolean putAll(@Nullable K key, Iterable<? extends V> values); + + /** + * Copies all of another multimap's key-value pairs into this multimap. The + * order in which the mappings are added is determined by + * {@code multimap.entries()}. + * + * @param multimap mappings to store in this multimap + * @return {@code true} if the multimap changed + */ + boolean putAll(Multimap<? extends K, ? extends V> multimap); + + /** + * Stores a collection of values with the same key, replacing any existing + * values for that key. + * + * @param key key to store in the multimap + * @param values values to store in the multimap + * @return the collection of replaced values, or an empty collection if no + * values were previously associated with the key. The collection + * <i>may</i> be modifiable, but updating it will have no effect on the + * multimap. + */ + Collection<V> replaceValues(K key, Iterable<? extends V> values); + + /** + * Removes all values associated with a given key. + * + * @param key key of entries to remove from the multimap + * @return the collection of removed values, or an empty collection if no + * values were associated with the provided key. The collection + * <i>may</i> be modifiable, but updating it will have no effect on the + * multimap. + */ + Collection<V> removeAll(@Nullable Object key); + + /** + * Removes all key-value pairs from the multimap. + */ + void clear(); + + // Views + + /** + * Returns a collection view of all values associated with a key. If no + * mappings in the multimap have the provided key, an empty collection is + * returned. + * + * <p>Changes to the returned collection will update the underlying multimap, + * and vice versa. + * + * @param key key to search for in multimap + * @return the collection of values that the key maps to + */ + Collection<V> get(@Nullable K key); + + /** + * Returns the set of all keys, each appearing once in the returned set. + * Changes to the returned set will update the underlying multimap, and vice + * versa. + * + * @return the collection of distinct keys + */ + Set<K> keySet(); + + /** + * Returns a collection, which may contain duplicates, of all keys. The number + * of times of key appears in the returned multiset equals the number of + * mappings the key has in the multimap. Changes to the returned multiset will + * update the underlying multimap, and vice versa. + * + * @return a multiset with keys corresponding to the distinct keys of the + * multimap and frequencies corresponding to the number of values that + * each key maps to + */ + Multiset<K> keys(); + + /** + * Returns a collection of all values in the multimap. Changes to the returned + * collection will update the underlying multimap, and vice versa. + * + * @return collection of values, which may include the same value multiple + * times if it occurs in multiple mappings + */ + Collection<V> values(); + + /** + * Returns a collection of all key-value pairs. Changes to the returned + * collection will update the underlying multimap, and vice versa. The entries + * collection does not support the {@code add} or {@code addAll} operations. + * + * @return collection of map entries consisting of key-value pairs + */ + Collection<Map.Entry<K, V>> entries(); + + /** + * Returns a map view that associates each key with the corresponding values + * in the multimap. Changes to the returned map, such as element removal, + * will update the underlying multimap. The map does not support + * {@code setValue()} on its entries, {@code put}, or {@code putAll}. + * + * <p>The collections returned by {@code asMap().get(Object)} have the same + * behavior as those returned by {@link #get}. + * + * @return a map view from a key to its collection of values + */ + Map<K, Collection<V>> asMap(); + + // Comparison and hashing + + /** + * Compares the specified object with this multimap for equality. Two + * multimaps are equal when their map views, as returned by {@link #asMap}, + * are also equal. + * + * <p>In general, two multimaps with identical key-value mappings may or may + * not be equal, depending on the implementation. For example, two + * {@link SetMultimap} instances with the same key-value mappings are equal, + * but equality of two {@link ListMultimap} instances depends on the ordering + * of the values for each key. + * + * <p>A non-empty {@link SetMultimap} cannot be equal to a non-empty + * {@link ListMultimap}, since their {@link #asMap} views contain unequal + * collections as values. However, any two empty multimaps are equal, because + * they both have empty {@link #asMap} views. + */ + boolean equals(@Nullable Object obj); + + /** + * Returns the hash code for this multimap. + * + * <p>The hash code of a multimap is defined as the hash code of the map view, + * as returned by {@link Multimap#asMap}. + */ + int hashCode(); +} diff --git a/plugins/com.google.collect/src/com/google/common/collect/Multimaps.java b/plugins/com.google.collect/src/com/google/common/collect/Multimaps.java new file mode 100644 index 0000000..22551ae --- a/dev/null +++ b/plugins/com.google.collect/src/com/google/common/collect/Multimaps.java @@ -0,0 +1,1400 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.base.Function; +import com.google.common.base.Nullable; +import com.google.common.base.Supplier; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedSet; +import java.util.Map.Entry; + +/** + * Provides static methods acting on or generating a {@code Multimap}. + * + * @author Jared Levy + * @author Robert Konigsberg + * @author Mike Bostock + */ +public final class Multimaps { + private Multimaps() {} + + /** + * Creates an empty {@code HashMultimap} instance. + * + * @return a newly-created, initially-empty {@code HashMultimap} + */ + public static <K, V> HashMultimap<K, V> newHashMultimap() { + return new HashMultimap<K, V>(); + } + + /** + * Creates a {@code HashMultimap} instance initialized with all elements from + * the supplied {@code Multimap}. If the supplied multimap contains duplicate + * key-value pairs, those duplicate pairs will only be stored once in the new + * multimap. + * + * @param multimap the multimap whose contents are copied to this multimap. + * @return a newly-created and initialized {@code HashMultimap} + */ + public static <K, V> HashMultimap<K, V> newHashMultimap( + Multimap<? extends K, ? extends V> multimap) { + return new HashMultimap<K, V>(multimap); + } + + /** + * Creates an empty {@code ArrayListMultimap} instance. + * + * @return a newly-created, initially-empty {@code ArrayListMultimap} + */ + public static <K, V> ArrayListMultimap<K, V> newArrayListMultimap() { + return new ArrayListMultimap<K, V>(); + } + + /** + * Creates an {@code ArrayListMultimap} instance initialized with all elements + * from the supplied {@code Multimap}. + * + * @param multimap the multimap whose contents are copied to this multimap. + * @return a newly-created and initialized {@code ArrayListMultimap} + */ + public static <K, V> ArrayListMultimap<K, V> newArrayListMultimap( + Multimap<? extends K, ? extends V> multimap) { + return new ArrayListMultimap<K, V>(multimap); + } + + /** + * Creates an empty {@code LinkedHashMultimap} instance. + * + * @return a newly-created, initially-empty {@code LinkedHashMultimap} + */ + public static <K, V> LinkedHashMultimap<K, V> newLinkedHashMultimap() { + return new LinkedHashMultimap<K, V>(); + } + + /** + * Creates a {@code LinkedHashMultimap} instance initialized with all elements + * from the supplied {@code Multimap}. If the supplied multimap contains + * duplicate key-value pairs, those duplicate pairs will only be stored once + * in the new multimap. The new multimap has the same + * {@link Multimap#entries()} iteration order as the input multimap, except + * for excluding duplicate mappings. + * + * @param multimap the multimap whose contents are copied to this multimap. + * @return a newly-created and initialized {@code LinkedHashMultimap} + */ + public static <K, V> LinkedHashMultimap<K, V> newLinkedHashMultimap( + Multimap<? extends K, ? extends V> multimap) { + return new LinkedHashMultimap<K, V>(multimap); + } + + /** + * Creates an empty {@code TreeMultimap} instance using the natural ordering + * of keys and values. If the supplied multimap contains duplicate key-value + * pairs, those duplicate pairs will only be stored once in the new multimap. + * + * @return a newly-created, initially-empty {@code TreeMultimap} + */ + @SuppressWarnings("unchecked") // allow ungenerified Comparable types + public static <K extends Comparable, V extends Comparable> + TreeMultimap<K, V> newTreeMultimap() { + return new TreeMultimap<K, V>(); + } + + /** + * Constructs a {@code TreeMultimap} with the same mappings as the specified + * {@code Multimap}. + * + * <p>If the supplied multimap is an instance of {@code TreeMultimap}, the + * supplied multimap's comparators are copied to the new instance. + * + * <p>If the supplied multimap is not an instance of {@code TreeMultimap}, the + * new multimap is ordered using the natural ordering of the key and value + * classes. The key and value classes must satisfy the {@link Comparable} + * interface. + * + * @param multimap the multimap whose contents are copied to this multimap. + * @return a newly-created and initialized {@code TreeMultimap} + */ + public static <K, V> TreeMultimap<K, V> newTreeMultimap( + Multimap<? extends K, ? extends V> multimap) { + return new TreeMultimap<K, V>(multimap); + } + + /** + * Creates an empty {@code TreeMultimap} instance using explicit comparators. + * + * @param keyComparator the comparator that determines the key ordering. If + * it's {@code null}, the natural ordering of the keys is used. + * @param valueComparator the comparator that determines the value ordering. + * If it's {@code null}, the natural ordering of the values is used. + * @return a newly-created, initially-empty {@code TreeMultimap} + */ + public static <K, V> TreeMultimap<K, V> newTreeMultimap( + @Nullable Comparator<? super K> keyComparator, + @Nullable Comparator<? super V> valueComparator) { + return new TreeMultimap<K, V>(keyComparator, valueComparator); + } + + /** + * Creates a {@code TreeMultimap} instance using explicit comparators, + * initialized with all elements from the supplied {@code Multimap}. + * + * @param multimap the multimap whose contents are copied to this multimap. + * @return a newly-created and initialized {@code TreeMultimap} + */ + public static <K, V> TreeMultimap<K, V> newTreeMultimap( + @Nullable Comparator<? super K> keyComparator, + @Nullable Comparator<? super V> valueComparator, + Multimap<? extends K, ? extends V> multimap) { + return new TreeMultimap<K, V>(keyComparator, valueComparator, multimap); + } + + /** + * Creates a new {@code Multimap} that uses the provided map and factory. It + * can generate a multimap based on arbitrary {@link Map} and + * {@link Collection} classes. + * + * <p>The {@code factory}-generated and {@code map} classes determine the + * multimap iteration order. They also specify the behavior of the + * {@code equals}, {@code hashCode}, and {@code toString} methods for the + * multimap and its returned views. However, the multimaps's {@code get} + * method returns instances of a different class than {@code factory.get()} + * does. + * + * <p>The multimap is serializable if {@code map}, {@code factory}, the + * collections generated by {@code factory}, and the multimap contents are all + * serializable. + * + * <p>The multimap is not threadsafe when any concurrent operations update the + * multimap, even if {@code map} and the instances generated by + * {@code factory} are. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap the multimap with a call to + * {@link #synchronizedMultimap}. + * + * <p>Call this method only when the simpler methods + * {@link #newArrayListMultimap()}, {@link #newHashMultimap()}, + * {@link #newLinkedHashMultimap()} and {@link #newTreeMultimap()} won't + * suffice. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @param factory supplier of new empty collections that will each hold all + * values for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static <K, V> Multimap<K, V> newMultimap(Map<K, Collection<V>> map, + final Supplier<? extends Collection<V>> factory) { + return new CustomMultimap<K, V>(map, factory); + } + + private static class CustomMultimap<K, V> extends StandardMultimap<K, V> { + transient Supplier<? extends Collection<V>> factory; + + CustomMultimap(Map<K, Collection<V>> map, + Supplier<? extends Collection<V>> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override protected Collection<V> createCollection() { + return factory.get(); + } + + // can't use Serialization writeMultimap and populateMultimap methods since + // there's no way to generate the empty backing map. + + /** @serialData the factory and the backing map */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier<? extends Collection<V>>) stream.readObject(); + Map<K, Collection<V>> map = (Map<K, Collection<V>>) stream.readObject(); + setMap(map); + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code ListMultimap} that uses the provided map and factory. + * It can generate a multimap based on arbitrary {@link Map} and {@link List} + * classes. + * + * <p>The {@code factory}-generated and {@code map} classes determine the + * multimap iteration order. They also specify the behavior of the + * {@code equals}, {@code hashCode}, and {@code toString} methods for the + * multimap and its returned views. However, the multimaps's {@code get} + * method returns instances of a different class than {@code factory.get()} + * does. + * + * <p>The multimap is serializable if {@code map}, {@code factory}, the + * lists generated by {@code factory}, and the multimap contents are all + * serializable. + * + * <p>The multimap is not threadsafe when any concurrent operations update the + * multimap, even if {@code map} and the instances generated by + * {@code factory} are. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap the multimap with a call to + * {@link #synchronizedListMultimap}. + * + * <p>Call this method only when the simpler method {@link + * #newArrayListMultimap()} won't suffice. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @param factory supplier of new empty lists that will each hold all values + * for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static <K, V> ListMultimap<K, V> newListMultimap( + Map<K, Collection<V>> map, final Supplier<? extends List<V>> factory) { + return new CustomListMultimap<K, V>(map, factory); + } + + private static class CustomListMultimap<K, V> + extends StandardListMultimap<K, V> { + transient Supplier<? extends List<V>> factory; + + CustomListMultimap(Map<K, Collection<V>> map, + Supplier<? extends List<V>> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override protected List<V> createCollection() { + return factory.get(); + } + + /** @serialData the factory and the backing map */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier<? extends List<V>>) stream.readObject(); + Map<K, Collection<V>> map = (Map<K, Collection<V>>) stream.readObject(); + setMap(map); + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code SetMultimap} that uses the provided map and factory. + * It can generate a multimap based on arbitrary {@link Map} and {@link Set} + * classes. + * + * <p>The {@code factory}-generated and {@code map} classes determine the + * multimap iteration order. They also specify the behavior of the + * {@code equals}, {@code hashCode}, and {@code toString} methods for the + * multimap and its returned views. However, the multimaps's {@code get} + * method returns instances of a different class than {@code factory.get()} + * does. + * + * <p>The multimap is serializable if {@code map}, {@code factory}, the + * sets generated by {@code factory}, and the multimap contents are all + * serializable. + * + * <p>The multimap is not threadsafe when any concurrent operations update the + * multimap, even if {@code map} and the instances generated by + * {@code factory} are. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap the multimap with a call to + * {@link #synchronizedSetMultimap}. + * + * <p>Call this method only when the simpler methods + * {@link #newHashMultimap()}, {@link #newLinkedHashMultimap()}, and + * {@link #newTreeMultimap()} won't suffice. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @param factory supplier of new empty sets that will each hold all values + * for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static <K, V> SetMultimap<K, V> newSetMultimap( + Map<K, Collection<V>> map, final Supplier<? extends Set<V>> factory) { + return new CustomSetMultimap<K, V>(map, factory); + } + + private static class CustomSetMultimap<K, V> + extends StandardSetMultimap<K, V> { + transient Supplier<? extends Set<V>> factory; + + CustomSetMultimap(Map<K, Collection<V>> map, + Supplier<? extends Set<V>> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override protected Set<V> createCollection() { + return factory.get(); + } + + /** @serialData the factory and the backing map */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier<? extends Set<V>>) stream.readObject(); + Map<K, Collection<V>> map = (Map<K, Collection<V>>) stream.readObject(); + setMap(map); + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code SortedSetMultimap} that uses the provided map and + * factory. It can generate a multimap based on arbitrary {@link Map} and + * {@link SortedSet} classes. + * + * <p>The {@code factory}-generated and {@code map} classes determine the + * multimap iteration order. They also specify the behavior of the + * {@code equals}, {@code hashCode}, and {@code toString} methods for the + * multimap and its returned views. However, the multimaps's {@code get} + * method returns instances of a different class than {@code factory.get()} + * does. + * + * <p>The multimap is serializable if {@code map}, {@code factory}, the + * sets generated by {@code factory}, and the multimap contents are all + * serializable. + * + * <p>The multimap is not threadsafe when any concurrent operations update the + * multimap, even if {@code map} and the instances generated by + * {@code factory} are. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap the multimap with a call to + * {@link #synchronizedSortedSetMultimap}. + * + * <p>Call this method only when the simpler method {@link #newTreeMultimap()} + * won't suffice. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @param factory supplier of new empty sorted sets that will each hold + * all values for a given key + * @throws IllegalArgumentException if {@code map} is not empty + |

