http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/base/ExceptionalClosure.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/base/ExceptionalClosure.java b/commons/src/main/java/com/twitter/common/base/ExceptionalClosure.java new file mode 100644 index 0000000..99c6993 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/base/ExceptionalClosure.java @@ -0,0 +1,38 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.base; + +/** + * An interface that captures a unit of work against an item. + * + * @param <T> The closure type. + * @param <E> The exception type thrown by the closure. + * + * @author John Sirois + */ +public interface ExceptionalClosure<T, E extends Exception> { + + /** + * Performs a unit of work on item, possibly throwing {@code E} in the process. + * + * <p>TODO(John Sirois): consider supporting @Nullable + * + * @param item the item to perform work against + * @throws E if there was a problem performing the work + */ + void execute(T item) throws E; +}
http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/base/ExceptionalCommand.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/base/ExceptionalCommand.java b/commons/src/main/java/com/twitter/common/base/ExceptionalCommand.java new file mode 100644 index 0000000..7825199 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/base/ExceptionalCommand.java @@ -0,0 +1,34 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.base; + +/** + * An interface that captures a unit of work. + * + * @param <E> The type of exception that the command throws. + * + * @author John Sirois + */ +public interface ExceptionalCommand<E extends Exception> { + + /** + * Performs a unit of work, possibly throwing {@code E} in the process. + * + * @throws E if there was a problem performing the work + */ + void execute() throws E; +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/base/ExceptionalFunction.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/base/ExceptionalFunction.java b/commons/src/main/java/com/twitter/common/base/ExceptionalFunction.java new file mode 100644 index 0000000..9f9d6b0 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/base/ExceptionalFunction.java @@ -0,0 +1,40 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.base; + +/** + * An interface that captures a unit of work against an item. + * + * @param <S> The argument type for the function. + * @param <T> The return type for the function. + * @param <E> The exception type that the function throws. + * + * @author John Sirois + */ +public interface ExceptionalFunction<S, T, E extends Exception> { + + /** + * Performs a unit of work on item, possibly throwing {@code E} in the process. + * + * <p>TODO(John Sirois): consider supporting @Nullable + * + * @param item The item to perform work against. + * @return The result of the computation. + * @throws E if there was a problem performing the work. + */ + T apply(S item) throws E; +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/base/ExceptionalFunctions.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/base/ExceptionalFunctions.java b/commons/src/main/java/com/twitter/common/base/ExceptionalFunctions.java new file mode 100644 index 0000000..9adcc4d --- /dev/null +++ b/commons/src/main/java/com/twitter/common/base/ExceptionalFunctions.java @@ -0,0 +1,156 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.base; + +import com.google.common.collect.ImmutableList; + +/** + * Utility functions for working with exceptional functions. + * + * @author John Sirois + */ +public final class ExceptionalFunctions { + + private ExceptionalFunctions() { + // utility + } + + /** + * Returns an {@link ExceptionalSupplier}/{@link java.util.concurrent.Callable} object that will + * return the result of {@code function} applied to {@code argument}. Evaluation is lazy and + * un-memoized. + */ + public static <S, T, E extends Exception> CallableExceptionalSupplier<T, E> curry( + final ExceptionalFunction<S, T, E> function, final S argument) { + + return new CallableExceptionalSupplier<T, E>() { + @Override + public T get() throws E { + return function.apply(argument); + } + }; + } + + /** + * Returns an ExceptionalFunction that is a composition of multiple ExceptionalFunctions. + */ + public static <T, E extends Exception> ExceptionalFunction<T, T, E> compose( + final Iterable<ExceptionalFunction<T, T, E>> functions) { + return new ExceptionalFunction<T, T, E>() { + @Override + public T apply(T input) throws E { + T result = input; + for (ExceptionalFunction<T, T, E> f : functions) { + result = f.apply(result); + } + return result; + } + }; + } + + /** + * Returns a List of ExceptionalFunctions from variable number of ExceptionalFunctions. + */ + public static <T, E extends Exception> ExceptionalFunction<T, T, E> compose( + ExceptionalFunction<T, T, E> function, ExceptionalFunction<T, T, E>... functions) { + return compose(ImmutableList.<ExceptionalFunction<T, T, E>>builder() + .add(function) + .add(functions) + .build()); + } + + /** + * Returns a new ExceptionalFunction which composes two ExceptionalFunctions of compatible types. + * + * @param second function to apply to result of first. + * @param first function to apply to input item. + * @param <A> input type of first. + * @param <B> input type of second. + * @param <C> output type of second. + * @param <E> exception type. + * @return new composed ExceptionalFunction. + */ + public static <A, B, C, E extends Exception> ExceptionalFunction<A, C, E> compose( + final ExceptionalFunction<B, C, ? extends E> second, + final ExceptionalFunction<A, ? extends B, ? extends E> first) { + return new ExceptionalFunction<A, C, E>() { + @Override + public C apply(A item) throws E { + return second.apply(first.apply(item)); + } + }; + } + + /** + * Builds an ExceptionalFunction from {@link com.google.common.base.Function}. + * + * @param function guava Function. + * @param <S> input type. + * @param <T> output type. + * @param <E> exception type. + * @return new ExceptionalFunction. + */ + public static <S, T, E extends Exception> ExceptionalFunction<S, T, E> forFunction( + final com.google.common.base.Function<S, T> function) { + return new ExceptionalFunction<S, T, E>() { + @Override + public T apply(S item) { + return function.apply(item); + } + }; + } + + /** + * Builds an ExceptionalFunction from a return value. The returned ExceptionalFunction will always + * return the given value. + * + * @param value value to return. + * @param <S> input type. + * @param <T> output type. + * @param <E> exception type. + * @return new ExceptionalFunction. + */ + public static <S, T, E extends Exception> ExceptionalFunction<S, T, E> constant( + final T value) { + return new ExceptionalFunction<S, T, E>() { + @Override + public T apply(S item) throws E { + return value; + } + }; + } + + /** + * Builds an ExceptionalFunction from an Exception. The returned ExceptionalFunction will always + * throw the given Exception. + * + * @param exception exception to throw. + * @param <S> input type. + * @param <T> output type. + * @param <E> exception type. + * @return new ExceptionalFunction. + */ + public static <S, T, E extends Exception> ExceptionalFunction<S, T, E> forException( + final E exception) { + return new ExceptionalFunction<S, T, E>() { + @Override + public T apply(S item) throws E { + throw exception; + } + }; + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/base/ExceptionalSupplier.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/base/ExceptionalSupplier.java b/commons/src/main/java/com/twitter/common/base/ExceptionalSupplier.java new file mode 100644 index 0000000..6f8e877 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/base/ExceptionalSupplier.java @@ -0,0 +1,36 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.base; + +/** + * An interface that captures a source of data. + * + * @param <T> The supplied value type. + * @param <E> The type of exception that the supplier throws. + * + * @author John Sirois + */ +public interface ExceptionalSupplier<T, E extends Exception> { + + /** + * Supplies an item, possibly throwing {@code E} in the process of obtaining the item. + * + * @return the result of the computation + * @throws E if there was a problem performing the work + */ + T get() throws E; +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/base/Function.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/base/Function.java b/commons/src/main/java/com/twitter/common/base/Function.java new file mode 100644 index 0000000..1eaa2cc --- /dev/null +++ b/commons/src/main/java/com/twitter/common/base/Function.java @@ -0,0 +1,32 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.base; + +/** + * A convenience typedef that also ties into google's {@code Function}. + * + * @param <S> The argument type for the function. + * @param <T> The return type for the function. + * + * @author John Sirois + */ +public interface Function<S, T> + extends ExceptionalFunction<S, T, RuntimeException>, com.google.common.base.Function<S, T> { + + @Override + T apply(S item); +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/base/MorePreconditions.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/base/MorePreconditions.java b/commons/src/main/java/com/twitter/common/base/MorePreconditions.java new file mode 100644 index 0000000..9da8d82 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/base/MorePreconditions.java @@ -0,0 +1,147 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.base; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; + +import org.apache.commons.lang.StringUtils; + +/** + * A utility helpful in concisely checking preconditions on arguments. This utility is a complement + * to {@link com.google.common.base.Preconditions}. + * + * @author John Sirois + */ +public final class MorePreconditions { + + private static final String ARG_NOT_BLANK_MSG = "Argument cannot be blank"; + + private MorePreconditions() { + // utility + } + + /** + * Checks that a string is both non-null and non-empty. + * + * @see #checkNotBlank(String, String, Object...) + */ + public static String checkNotBlank(String argument) { + return checkNotBlank(argument, ARG_NOT_BLANK_MSG); + } + + /** + * Checks that a string is both non-null and non-empty. + * + * @param argument the argument to validate + * @param message the message template for validation exception messages where %s serves as the + * sole argument placeholder + * @param args any arguments needed by the message template + * @return the argument if it is valid + * @throws NullPointerException if the argument is null + * @throws IllegalArgumentException if the argument is the empty string or a pure whitespace + * string + */ + public static String checkNotBlank(String argument, String message, Object... args) { + Preconditions.checkNotNull(argument, message, args); + Preconditions.checkArgument(!StringUtils.isBlank(argument), message, args); + return argument; + } + + /** + * Checks that an Iterable is both non-null and non-empty. This method does not check individual + * elements in the Iterable. + * + * @see #checkNotBlank(Iterable, String, Object...) + */ + public static <S, T extends Iterable<S>> T checkNotBlank(T argument) { + return checkNotBlank(argument, ARG_NOT_BLANK_MSG); + } + + /** + * Checks that an Iterable is both non-null and non-empty. This method does not check individual + * elements in the Iterable, it just checks that the Iterable has at least one element. + * + * @param argument the argument to validate + * @param message the message template for validation exception messages where %s serves as the + * sole argument placeholder + * @param args any arguments needed by the message template + * @return the argument if it is valid + * @throws NullPointerException if the argument is null + * @throws IllegalArgumentException if the argument has no iterable elements + */ + public static <S, T extends Iterable<S>> T checkNotBlank(T argument, String message, + Object... args) { + Preconditions.checkNotNull(argument, message, args); + Preconditions.checkArgument(!Iterables.isEmpty(argument), message, args); + return argument; + } + + /** + * Checks that a double falls within a specified range, inclusive + * + * @param argument argument to validate. + * @param minimum minimum possible valid value for the argument. + * @param maximum maximum possible valid value for the argument. + * @param message the message template for validation exception messages where %s serves as the + * sole argument placeholder. + * @return the argument if it is valid. + * @throws IllegalArgumentException if the argument falls outside of the specified range. + */ + public static double checkArgumentRange(double argument, double minimum, double maximum, + String message) { + Preconditions.checkArgument(minimum <= argument, message, argument); + Preconditions.checkArgument(argument <= maximum, message, argument); + return argument; + } + + /** + * Checks that an int falls within a specified range, inclusive + * + * @param argument argument to validate. + * @param minimum minimum possible valid value for the argument. + * @param maximum maximum possible valid value for the argument. + * @param message the message template for validation exception messages where %s serves as the + * sole argument placeholder. + * @return the argument if it is valid. + * @throws IllegalArgumentException if the argument falls outside of the specified range. + */ + public static int checkArgumentRange(int argument, int minimum, int maximum, + String message) { + Preconditions.checkArgument(minimum <= argument, message, argument); + Preconditions.checkArgument(argument <= maximum, message, argument); + return argument; + } + + /** + * Checks that at least one of the specified arguments is true. + * + * @param message the message for validation exception messages. + * @param arguments one or more arguments to check. + * @return true if at least one of the arguments is true. + * @throws IllegalArgumentException if none of the arguments are true. + */ + public static boolean checkArguments(String message, + Boolean... arguments) { + for (Boolean argument : arguments) { + if (argument) { + return true; + } + } + throw new IllegalArgumentException(message); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/base/MoreSuppliers.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/base/MoreSuppliers.java b/commons/src/main/java/com/twitter/common/base/MoreSuppliers.java new file mode 100644 index 0000000..2b77b72 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/base/MoreSuppliers.java @@ -0,0 +1,106 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.base; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; + +import javax.annotation.Nullable; + +import com.google.common.base.Preconditions; + +/** + * Utility methods for working with Suppliers. + * + * @author John Sirois + */ +public final class MoreSuppliers { + + private MoreSuppliers() { + // utility + } + + /** + * Creates a Supplier that uses the no-argument constructor of {@code type} to supply new + * instances. + * + * @param type the type of object this supplier creates + * @param <T> the type of object this supplier creates + * @return a Supplier that created a new obeject of type T on each call to {@link Supplier#get()} + * @throws IllegalArgumentException if the given {@code type} does not have a no-arg constructor + */ + public static <T> Supplier<T> of(final Class<? extends T> type) { + Preconditions.checkNotNull(type); + + try { + final Constructor<? extends T> constructor = getNoArgConstructor(type); + return new Supplier<T>() { + @Override public T get() { + try { + return constructor.newInstance(); + } catch (InstantiationException e) { + throw instantiationFailed(e, type); + } catch (IllegalAccessException e) { + throw instantiationFailed(e, type); + } catch (InvocationTargetException e) { + throw instantiationFailed(e, type); + } + } + }; + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("No accessible no-arg constructor for " + type, e); + } + } + + private static RuntimeException instantiationFailed(Exception cause, Object type) { + return new RuntimeException("Could not create a new instance of type: " + type, cause); + } + + private static <T> Constructor<T> getNoArgConstructor(Class<T> type) + throws NoSuchMethodException { + + try { + Constructor<T> constructor = type.getConstructor(); + if (!MoreSuppliers.class.getPackage().equals(type.getPackage()) + && !Modifier.isPublic(type.getModifiers())) { + // Handle a public no-args constructor in a non-public class + constructor.setAccessible(true); + } + return constructor; + } catch (NoSuchMethodException e) { + Constructor<T> declaredConstructor = type.getDeclaredConstructor(); + declaredConstructor.setAccessible(true); + return declaredConstructor; + } + } + + /** + * Returns an {@link ExceptionalSupplier} that always supplies {@code item} without error. + * + * @param item The item to supply. + * @param <T> The type of item being supplied. + * @return A supplier that will always supply {@code item}. + */ + public static <T> Supplier<T> ofInstance(@Nullable final T item) { + return new Supplier<T>() { + @Override public T get() { + return item; + } + }; + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/base/Supplier.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/base/Supplier.java b/commons/src/main/java/com/twitter/common/base/Supplier.java new file mode 100644 index 0000000..4270b16 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/base/Supplier.java @@ -0,0 +1,31 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.base; + +/** + * A convenience typedef that also ties into google's {@code Supplier}. + * + * @param <T> The supplied type. + * + * @author John Sirois + */ +public interface Supplier<T> + extends ExceptionalSupplier<T, RuntimeException>, com.google.common.base.Supplier<T> { + + @Override + T get(); +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/base/SupplierE.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/base/SupplierE.java b/commons/src/main/java/com/twitter/common/base/SupplierE.java new file mode 100644 index 0000000..3b15cc3 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/base/SupplierE.java @@ -0,0 +1,28 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.base; + +/** + * A convenience typedef for suppliers that throw multiple exception types. + * + * @param <T> The supplied type. + * + * @author John Sirois + */ +public interface SupplierE<T> extends ExceptionalSupplier<T, Exception> { + // typedef +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/collections/Bits.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/collections/Bits.java b/commons/src/main/java/com/twitter/common/collections/Bits.java new file mode 100644 index 0000000..5b4be70 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/collections/Bits.java @@ -0,0 +1,119 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.collections; + +import com.google.common.base.Preconditions; + +/** + * Convenience class for doing bit-level operations on ints and longs. + * + * @author William Farner + */ +public final class Bits { + + private static final int LSB = 0; + private static final int INT_MSB = 31; + private static final int LONG_MSB = 63; + + private Bits() { + // Utility. + } + + /** + * Tests whether a bit is set in an int value. + * + * @param value The bit field to test. + * @param bit The index of the bit to test, where bit 0 is the LSB. + * @return {@code true} if the bit is set, {@code false} otherwise. + */ + public static boolean isBitSet(int value, int bit) { + Preconditions.checkState(bit >= LSB); + Preconditions.checkState(bit <= INT_MSB); + int mask = 1 << bit; + return (value & mask) != 0; + } + + /** + * Tests whether a bit is set in a long value. + * + * @param value The bit field to test. + * @param bit The index of the bit to test, where bit 0 is the LSB. + * @return {@code true} if the bit is set, {@code false} otherwise. + */ + public static boolean isBitSet(long value, int bit) { + Preconditions.checkState(bit >= LSB); + Preconditions.checkState(bit <= LONG_MSB); + long mask = 1L << bit; + return (value & mask) != 0; + } + + /** + * Sets a bit in an int value. + * + * @param value The bit field to modify. + * @param bit The index of the bit to set, where bit 0 is the LSB. + * @return The original value, with the indexed bit set. + */ + public static int setBit(int value, int bit) { + Preconditions.checkState(bit >= LSB); + Preconditions.checkState(bit <= INT_MSB); + int mask = 1 << bit; + return value | mask; + } + + /** + * Sets a bit in a long value. + * + * @param value The bit field to modify. + * @param bit The index of the bit to set, where bit 0 is the LSB. + * @return The original value, with the indexed bit set. + */ + public static long setBit(long value, int bit) { + Preconditions.checkState(bit >= LSB); + Preconditions.checkState(bit <= LONG_MSB); + long mask = 1L << bit; + return value | mask; + } + + /** + * Clears a bit in an int value. + * + * @param value The bit field to modify. + * @param bit The index of the bit to clear, where bit 0 is the LSB. + * @return The original value, with the indexed bit clear. + */ + public static int clearBit(int value, int bit) { + Preconditions.checkState(bit >= LSB); + Preconditions.checkState(bit <= INT_MSB); + int mask = ~setBit(0, bit); + return value & mask; + } + + /** + * Clears a bit in a long value. + * + * @param value The bit field to modify. + * @param bit The index of the bit to clear, where bit 0 is the LSB. + * @return The original value, with the indexed bit clear. + */ + public static long clearBit(long value, int bit) { + Preconditions.checkState(bit >= LSB); + Preconditions.checkState(bit <= LONG_MSB); + long mask = ~setBit(0L, bit); + return value & mask; + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/collections/BoundedQueue.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/collections/BoundedQueue.java b/commons/src/main/java/com/twitter/common/collections/BoundedQueue.java new file mode 100644 index 0000000..58c5fad --- /dev/null +++ b/commons/src/main/java/com/twitter/common/collections/BoundedQueue.java @@ -0,0 +1,79 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.collections; + +import java.util.Iterator; +import java.util.concurrent.LinkedBlockingDeque; + +/** + * A limited implementation of a bounded queue. Values can be added and iterated over, and will + * automatically expire when the queue exceeds capacity. + * + * @param <T> The type that this queue contains. + * + * @author William Farner +*/ +public class BoundedQueue<T> implements Iterable<T> { + private final LinkedBlockingDeque<T> values; + + /** + * Creates a new bounded queue. + * + * @param limit Maximum number of items that can be in the queue at any time. + */ + public BoundedQueue(int limit) { + values = new LinkedBlockingDeque<T>(limit); + } + + /** + * Adds a value to head of the queue, evicting the oldest item if the queue is at capacity. + * + * @param value Value to add. + */ + public synchronized void add(T value) { + if (values.remainingCapacity() == 0) { + values.removeFirst(); + } + values.addLast(value); + } + + /** + * Removes all values from the queue. + */ + public synchronized void clear() { + values.clear(); + } + + /** + * Returns the size of the queue. + * + * @return The current queue length. + */ + public synchronized int size() { + return values.size(); + } + + @Override + public synchronized Iterator<T> iterator() { + return values.iterator(); + } + + @Override + public synchronized String toString() { + return values.toString(); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/collections/Iterables2.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/collections/Iterables2.java b/commons/src/main/java/com/twitter/common/collections/Iterables2.java new file mode 100644 index 0000000..1086558 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/collections/Iterables2.java @@ -0,0 +1,151 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.collections; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import com.google.common.base.Function; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +/** + * Utility functions for dealing with iterables. + * + * @author William Farner + */ +public final class Iterables2 { + + private Iterables2() { + // Utility class. + } + + /** + * An iterator that zips multiple iterables into a single list iterator, filling missing values + * with a provided default. + * + * @param <T> The value type for the iterator. + */ + private static class ZippingIterator<T> implements Iterator<List<T>> { + + private final Iterable<Iterable<T>> iterables; + private final T defaultValue; + + private List<Iterator<T>> iterators = null; + private final LoadingCache<Iterator<T>, Boolean> overflowing = CacheBuilder.newBuilder().build( + new CacheLoader<Iterator<T>, Boolean>() { + @Override public Boolean load(Iterator<T> iterator) { + return false; + } + }); + + ZippingIterator(Iterable<Iterable<T>> iterables, T defaultValue) { + this.iterables = iterables; + this.defaultValue = defaultValue; + } + + private void init() { + if (iterators == null) { + // Iterables -> Iterators. + iterators = ImmutableList.copyOf(Iterables.transform(iterables, + new Function<Iterable<T>, Iterator<T>>() { + @Override public Iterator<T> apply(Iterable<T> it) { return it.iterator(); } + })); + } + } + + @Override public boolean hasNext() { + init(); + for (Iterator<T> it : iterators) { + if (it.hasNext()) { + return true; + } + } + + return false; + } + + @Override public List<T> next() { + init(); + List<T> data = new ArrayList<T>(iterators.size()); + + for (Iterator<T> it : iterators) { + if (it.hasNext()) { + data.add(it.next()); + } else { + overflowing.asMap().put(it, true); + data.add(defaultValue); + } + } + + return data; + } + + @Override public void remove() { + init(); + for (Iterator<T> it : iterators) { + if (!overflowing.getUnchecked(it)) { + it.remove(); + } + } + } + + @Override public String toString() { + return Lists.newArrayList(iterables).toString(); + } + } + + /** + * Zips multiple iterables into one iterable that will return iterators to step over + * rows of the input iterators (columns). The order of the returned values within each row will + * match the ordering of the input iterables. The iterators will iterate the length of the longest + * input iterable, filling other columns with {@code defaultValue}. + * The returned iterator is lazy, in that 'rows' are constructed as they are requested. + * + * @param iterables Columns to iterate over. + * @param defaultValue Default fill value when an input iterable is exhausted. + * @param <T> Type of value being iterated over. + * @return An iterator that iterates over rows of the input iterables. + */ + public static <T> Iterable<List<T>> zip(final Iterable<Iterable<T>> iterables, + final T defaultValue) { + + return new Iterable<List<T>>() { + @Override public Iterator<List<T>> iterator() { + return new ZippingIterator<T>(iterables, defaultValue); + } + }; + } + + /** + * Varargs convenience function to call {@link #zip(Iterable, Object)}. + * + * @param defaultValue Default fill value when an input iterable is exhausted. + * @param iterables Columns to iterate over. + * @param <T> Type of value being iterated over. + * @return An iterator that iterates over rows of the input iterables. + */ + public static <T> Iterable<List<T>> zip(T defaultValue, Iterable<T>... iterables) { + return zip(Arrays.asList(iterables), defaultValue); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/collections/Multimaps.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/collections/Multimaps.java b/commons/src/main/java/com/twitter/common/collections/Multimaps.java new file mode 100644 index 0000000..c51b6fe --- /dev/null +++ b/commons/src/main/java/com/twitter/common/collections/Multimaps.java @@ -0,0 +1,137 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.collections; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multiset; +import com.google.common.collect.Ordering; +import com.google.common.collect.Sets; + +/** + * Utility class for functions related to Multimaps in the java collections library. + * + * @author William Farner + */ +public final class Multimaps { + + private Multimaps() { + // Utility. + } + + /** + * Prunes a multimap based on a predicate, returning the pruned values. The input map will be + * modified. + * + * @param map The multimap to prune. + * @param filterRule The pruning rule. When the predicate returns {@code false} for an entry, it + * will be pruned, otherwise it will be retained. + * @param <K> The key type in the multimap. + * @param <V> The value type in the multimap. + * @return A new multimap, containing the pruned keys/values. + */ + public static <K, V> Multimap<K, V> prune(Multimap<K, V> map, + Predicate<? super Collection<V>> filterRule) { + Preconditions.checkNotNull(map); + Preconditions.checkNotNull(filterRule); + Multimap<K, V> pruned = ArrayListMultimap.create(); + Iterator<Map.Entry<K, Collection<V>>> asMapItr = map.asMap().entrySet().iterator(); + while (asMapItr.hasNext()) { + Map.Entry<K, Collection<V>> asMapEntry = asMapItr.next(); + if (!filterRule.apply(asMapEntry.getValue())) { + pruned.putAll(asMapEntry.getKey(), asMapEntry.getValue()); + asMapItr.remove(); + } + } + + return pruned; + } + + private static final class AtLeastSize implements Predicate<Collection<?>> { + private final int minSize; + + AtLeastSize(int minSize) { + Preconditions.checkArgument(minSize >= 0); + this.minSize = minSize; + } + + @Override + public boolean apply(Collection<?> c) { + return c.size() >= minSize; + } + } + + /** + * Convenience method to prune key/values pairs where the size of the value collection is below a + * threshold. + * + * @param map The multimap to prune. + * @param minSize The minimum size for retained value collections. + * @param <K> The key type in the multimap. + * @param <V> The value type in the multimap. + * @return A new multimap, containing the pruned keys/values. + * @throws IllegalArgumentException if minSize < 0 + */ + public static <K, V> Multimap<K, V> prune(Multimap<K, V> map, int minSize) { + return prune(map, new AtLeastSize(minSize)); + } + + /** + * Returns the set of keys associated with groups of a size greater than or equal to a given size. + * + * @param map The multimap to search. + * @param minSize The minimum size to return associated keys for. + * @param <K> The key type for the multimap. + * @return The keys associated with groups of size greater than or equal to {@code minSize}. + * @throws IllegalArgumentException if minSize < 0 + */ + public static <K> Set<K> getLargeGroups(Multimap<K, ?> map, int minSize) { + return Sets.newHashSet( + Maps.filterValues(map.asMap(), new AtLeastSize(minSize)).keySet()); + } + + /** + * Returns the set of keys associated with the largest values in the multimap. + * + * @param map The multimap to search. + * @param topValues Number of groupings to find the keys for. + * @return The keys associated with the largest groups, of maximum size {@code topValues}. + */ + public static <K> Set<K> getLargestGroups(Multimap<K, ?> map, int topValues) { + Ordering<Multiset.Entry<K>> groupOrdering = new Ordering<Multiset.Entry<K>>() { + @Override + public int compare(Multiset.Entry<K> entry1, Multiset.Entry<K> entry2) { + return entry1.getCount() - entry2.getCount(); + // overflow-safe, since sizes are nonnegative + } + }; + Set<K> topKeys = Sets.newHashSetWithExpectedSize(topValues); + for (Multiset.Entry<K> entry + : groupOrdering.greatestOf(map.keys().entrySet(), topValues)) { + topKeys.add(entry.getElement()); + } + return topKeys; + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/collections/Pair.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/collections/Pair.java b/commons/src/main/java/com/twitter/common/collections/Pair.java new file mode 100644 index 0000000..5f2e3f6 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/collections/Pair.java @@ -0,0 +1,130 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.collections; + +import javax.annotation.Nullable; + +import com.google.common.base.Function; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + + +/** + * An immutable 2-tuple with value-equals semantics. + * + * @param <A> The type of the 1st item in the pair. + * @param <B> The type of the 2nd item in the pair. + * + * @author William Farner + */ +public class Pair<A, B> { + + @Nullable + private final A first; + @Nullable + private final B second; + + /** + * Creates a new pair. + * + * @param first The first value. + * @param second The second value. + */ + public Pair(@Nullable A first, @Nullable B second) { + this.first = first; + this.second = second; + } + + @Nullable + public A getFirst() { + return first; + } + + @Nullable + public B getSecond() { + return second; + } + + @Override + public boolean equals(Object o) { + if (o == this) { return true; } + if (!(o instanceof Pair)) { return false; } + + Pair<?, ?> that = (Pair<?, ?>) o; + return new EqualsBuilder() + .append(this.first, that.first) + .append(this.second, that.second) + .isEquals(); + } + + @Override + public String toString() { + return String.format("(%s, %s)", getFirst(), getSecond()); + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .append(first) + .append(second) + .toHashCode(); + } + + /** + * Creates a function that can extract the first item of pairs of the given type parametrization. + * + * @param <S> The type of the 1st item in the pair. + * @param <T> The type of the 2nd item in the pair. + * @return A function that will extract the 1st item in a pair. + */ + public static <S, T> Function<Pair<S, T>, S> first() { + return new Function<Pair<S, T>, S>() { + @Override public S apply(Pair<S, T> pair) { + return pair.first; + } + }; + } + + /** + * Creates a function that can extract the second item of pairs of the given type parametrization. + * + * @param <S> The type of the 1st item in the pair. + * @param <T> The type of the 2nd item in the pair. + * @return A function that will extract the 2nd item in a pair. + */ + public static <S, T> Function<Pair<S, T>, T> second() { + return new Function<Pair<S, T>, T>() { + @Override public T apply(Pair<S, T> pair) { + return pair.second; + } + }; + } + + /** + * Convenience method to create a pair. + * + * @param a The first value. + * @param b The second value. + * @param <A> The type of the 1st item in the pair. + * @param <B> The type of the 2nd item in the pair. + * @return A new pair of [a, b]. + */ + public static <A, B> Pair<A, B> of(@Nullable A a, @Nullable B b) { + return new Pair<A, B>(a, b); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/inject/Bindings.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/inject/Bindings.java b/commons/src/main/java/com/twitter/common/inject/Bindings.java new file mode 100644 index 0000000..e0acb1a --- /dev/null +++ b/commons/src/main/java/com/twitter/common/inject/Bindings.java @@ -0,0 +1,319 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.inject; + +import java.lang.annotation.Annotation; + +import javax.inject.Qualifier; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.BindingAnnotation; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.PrivateModule; +import com.google.inject.TypeLiteral; +import com.google.inject.binder.LinkedBindingBuilder; +import com.google.inject.multibindings.Multibinder; +import com.google.inject.name.Names; + +/** + * A utility that helps with guice bindings. + * + * @author John Sirois + */ +public final class Bindings { + + + private Bindings() { + // utility + } + + /** + * Equivalent to calling {@code requireBinding(binder, Key.get(required, Names.named(namedKey)))}. + */ + public static void requireNamedBinding(Binder binder, Class<?> required, String namedKey) { + requireBinding(binder, Key.get(Preconditions.checkNotNull(required), + Names.named(Preconditions.checkNotNull(namedKey)))); + } + + /** + * Equivalent to calling {@code requireBinding(binder, Key.get(required))}. + */ + public static void requireBinding(Binder binder, Class<?> required) { + requireBinding(binder, Key.get(Preconditions.checkNotNull(required))); + } + + /** + * Registers {@code required} as non-optional dependency in the {@link com.google.inject.Injector} + * associated with {@code binder}. + * + * @param binder A binder to require bindings against. + * @param required The dependency that is required. + */ + public static void requireBinding(Binder binder, final Key<?> required) { + Preconditions.checkNotNull(binder); + Preconditions.checkNotNull(required); + + binder.install(new AbstractModule() { + @Override protected void configure() { + requireBinding(required); + } + }); + } + + /** + * A convenient version of {@link #exposing(Iterable, com.google.inject.Module)} when you just + * want to expose a single binding. + */ + public static Module exposing(Key<?> key, Module module) { + return exposing(ImmutableList.of(key), module); + } + + /** + * Creates a module that hides all the given module's bindings and only exposes bindings for + * the given key. + * + * @param keys The keys of the bindings to expose. + * @param module The module to hide most bindings for. + * @return A limited visibility module. + */ + public static Module exposing(final Iterable<? extends Key<?>> keys, final Module module) { + Preconditions.checkNotNull(keys); + Preconditions.checkNotNull(module); + + return new PrivateModule() { + @Override protected void configure() { + install(module); + for (Key<?> key : keys) { + expose(key); + } + } + }; + } + + /** + * A guice binding helper that allows for any combination of Class, TypeLiteral or Key binding + * without forcing guiced implementation to provide all the overloaded binding methods they would + * otherwise have to. + * + * @param <T> The type this helper can be used to bind implementations for. + */ + public interface BindHelper<T> { + + /** + * Associates this BindHelper with an Injector instance. + * + * @param binder The binder for the injector implementations will be bound in. + * @return A binding builder that can be used to bind an implementation with. + */ + LinkedBindingBuilder<T> with(Binder binder); + } + + /** + * Creates a BindHelper for the given binding key that can be used to bind a single instance. + * + * @param key The binding key the returned BindHelper can be use to bind implementations for. + * @param <T> The type the returned BindHelper can be used to bind implementations for. + * @return A BindHelper that can be used to bind an implementation with. + */ + public static <T> BindHelper<T> binderFor(final Key<T> key) { + return new BindHelper<T>() { + public LinkedBindingBuilder<T> with(Binder binder) { + return binder.bind(key); + } + }; + } + + /** + * Creates a BindHelper for the given type that can be used to add a binding of to a set. + * + * @param type The type the returned BindHelper can be use to bind implementations for. + * @param <T> The type the returned BindHelper can be used to bind implementations for. + * @return A BindHelper that can be used to bind an implementation with. + */ + public static <T> BindHelper<T> multiBinderFor(final Class<T> type) { + return new BindHelper<T>() { + public LinkedBindingBuilder<T> with(Binder binder) { + return Multibinder.newSetBinder(binder, type).addBinding(); + } + }; + } + + /** + * Checks that the given annotation instance is a {@link BindingAnnotation @BindingAnnotation}. + * + * @param annotation The annotation instance to check. + * @param <T> The type of the binding annotation. + * @return The checked binding annotation. + * @throws NullPointerException If the given {@code annotation} is null. + * @throws IllegalArgumentException If the given {@code annotation} is not a + * {@literal @BindingAnnotation}. + */ + public static <T extends Annotation> T checkBindingAnnotation(T annotation) { + Preconditions.checkNotNull(annotation); + checkBindingAnnotation(annotation.annotationType()); + return annotation; + } + + /** + * Checks that the given annotation type is a {@link BindingAnnotation @BindingAnnotation}. + * + * @param annotationType The annotation type to check. + * @param <T> The type of the binding annotation. + * @return The checked binding annotation type. + * @throws NullPointerException If the given {@code annotationType} is null. + * @throws IllegalArgumentException If the given {@code annotationType} is not a + * {@literal @BindingAnnotation}. + */ + public static <T extends Annotation> Class<T> checkBindingAnnotation(Class<T> annotationType) { + Preconditions.checkNotNull(annotationType); + boolean bindingAnnotation = annotationType.isAnnotationPresent(BindingAnnotation.class); + boolean qualifier = annotationType.isAnnotationPresent(Qualifier.class); + Preconditions.checkArgument(bindingAnnotation || qualifier, + "%s is not a @BindingAnnotation or @Qualifier", annotationType); + return annotationType; + } + + /** + * A factory for binding {@link Key keys}. + */ + public interface KeyFactory { + + /** + * Creates plain un-annotated keys. + */ + KeyFactory PLAIN = new KeyFactory() { + @Override public <T> Key<T> create(Class<T> type) { + return Key.get(type); + } + @Override public <T> Key<T> create(TypeLiteral<T> type) { + return Key.get(type); + } + }; + + /** + * Creates a key for the given type. + * + * @param type The type to create a key for. + * @param <T> The keyed type. + * @return A key. + */ + <T> Key<T> create(Class<T> type); + + /** + * Creates a key for the given type. + * + * @param type The type to create a key for. + * @param <T> The keyed type. + * @return A key. + */ + <T> Key<T> create(TypeLiteral<T> type); + } + + /** + * Creates a key factory that produces keys for a given annotation instance. + * + * @param annotation The annotation instance to apply to all keys. + * @return A key factory that creates annotated keys. + */ + public static KeyFactory annotatedKeyFactory(final Annotation annotation) { + checkBindingAnnotation(annotation); + return new KeyFactory() { + @Override public <T> Key<T> create(Class<T> type) { + return Key.get(type, annotation); + } + @Override public <T> Key<T> create(TypeLiteral<T> type) { + return Key.get(type, annotation); + } + }; + } + + /** + * Creates a key factory that produces keys for a given annotation type. + * + * @param annotationType The annotation type to apply to all keys. + * @return A key factory that creates annotated keys. + */ + public static KeyFactory annotatedKeyFactory(final Class<? extends Annotation> annotationType) { + checkBindingAnnotation(annotationType); + return new KeyFactory() { + @Override public <T> Key<T> create(Class<T> type) { + return Key.get(type, annotationType); + } + @Override public <T> Key<T> create(TypeLiteral<T> type) { + return Key.get(type, annotationType); + } + }; + } + + /** + * A utility that helps rebind keys. + */ + public static final class Rebinder { + private final Binder binder; + private final KeyFactory bindToFactory; + + /** + * Creates a Rebinder that links bindings to keys from the given {@code bindToFactory}. + * + * @param binder A binder to rebind keys in. + * @param bindToFactory A factory for the rebinding key. + */ + public Rebinder(Binder binder, KeyFactory bindToFactory) { + this.binder = Preconditions.checkNotNull(binder); + this.bindToFactory = Preconditions.checkNotNull(bindToFactory); + } + + /** + * Rebinds the given key to another, linking bindings. + * + * @param fromKey The source key to rebind. + * @return The key that {@code key} was rebound to. + */ + public <T> Key<T> rebind(Key<T> fromKey) { + Key<T> toKey = bindToFactory.create(fromKey.getTypeLiteral()); + binder.bind(toKey).to(fromKey); + requireBinding(binder, fromKey); + return toKey; + } + } + + /** + * Creates a Rebinder that rebinds keys to the given annotation instance. + * + * @param binder A binder to rebind keys in. + * @param annotation The annotation instance to rebind keys to. + * @return A Rebinder targeting the given {@code annotationType}. + */ + public static Rebinder rebinder(Binder binder, Annotation annotation) { + return new Rebinder(binder, annotatedKeyFactory(annotation)); + } + + /** + * Creates a Rebinder that rebinds keys to the given annotation type. + * + * @param binder A binder to rebind keys in. + * @param annotationType The annotation type to rebind keys to. + * @return A Rebinder targeting the given {@code annotationType}. + */ + public static Rebinder rebinder(Binder binder, Class<? extends Annotation> annotationType) { + return new Rebinder(binder, annotatedKeyFactory(annotationType)); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/inject/DefaultProvider.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/inject/DefaultProvider.java b/commons/src/main/java/com/twitter/common/inject/DefaultProvider.java new file mode 100644 index 0000000..8dc1c40 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/inject/DefaultProvider.java @@ -0,0 +1,169 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.inject; + +import com.google.common.base.Preconditions; +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; +import com.google.inject.name.Named; +import com.google.inject.name.Names; + +/** + * Provider that has a default value which can be overridden. + * + * The intended use of this class is: + * <pre> + * Default installer: + * bind(DefaultProvider.makeDefaultKey(Runnable.class, "mykey").toInstance(defaultRunnable); + * DefaultProvider.bindOrElse(Runnable.class, "mykey", binder()); + * + * Custom override: + * bind(DefaultProvider.makeCustomKey(Runnable.class, "mykey")).toInstance(myCustomRunnable); + * + * Injection: + * {@literal Inject} Named("myKey") Runnable runnable; + * + * </pre> + * + * @param <T> the type of object this provides + * + * @author William Farner + * @author John Sirois + */ +public class DefaultProvider<T> implements Provider<T> { + private static final String DEFAULT_BINDING_KEY_SUFFIX = "_default"; + private static final String CUSTOM_BINDING_KEY_SUFFIX = "_custom"; + + private final Key<T> defaultProviderKey; + private final Key<T> customProviderKey; + + private Injector injector; + + public DefaultProvider(Key<T> defaultProviderKey, Key<T> customProviderKey) { + this.defaultProviderKey = Preconditions.checkNotNull(defaultProviderKey); + this.customProviderKey = Preconditions.checkNotNull(customProviderKey); + Preconditions.checkArgument(!defaultProviderKey.equals(customProviderKey)); + } + + @Inject + public void setInjector(Injector injector) { + this.injector = injector; + } + + @Override + public T get() { + Preconditions.checkNotNull(injector); + return injector.getBindings().containsKey(customProviderKey) + ? injector.getInstance(customProviderKey) + : injector.getInstance(defaultProviderKey); + } + + /** + * Creates a DefaultProvider and installs a new module to {@code binder}, which will serve as + * an indirection layer for swapping the default binding with a custom one. + * + * @param customBinding The custom binding key. + * @param defaultBinding The default binding key. + * @param exposedBinding The exposed binding key. + * @param binder The binder to install bindings to. + * @param <T> The type of binding to make. + */ + public static <T> void bindOrElse(final Key<T> customBinding, final Key<T> defaultBinding, + final Key<T> exposedBinding, Binder binder) { + Preconditions.checkNotNull(customBinding); + Preconditions.checkNotNull(defaultBinding); + Preconditions.checkNotNull(exposedBinding); + Preconditions.checkArgument(!customBinding.equals(defaultBinding) + && !customBinding.equals(exposedBinding)); + + binder.install(new AbstractModule() { + @Override protected void configure() { + Provider<T> defaultProvider = new DefaultProvider<T>(defaultBinding, customBinding); + requestInjection(defaultProvider); + bind(exposedBinding).toProvider(defaultProvider); + } + }); + } + + /** + * Convenience function for creating and installing a DefaultProvider. This will use internal + * suffixes to create names for the custom and default bindings. When bound this way, callers + * should use one of the functions such as {@link #makeDefaultBindingKey(String)} to set default + * and custom bindings. + * + * @param type The type of object to bind. + * @param exposedKey The exposed key. + * @param binder The binder to install to. + * @param <T> The type of binding to make. + */ + public static <T> void bindOrElse(TypeLiteral<T> type, String exposedKey, Binder binder) { + bindOrElse(Key.get(type, Names.named(makeCustomBindingKey(exposedKey))), + Key.get(type, Names.named(makeDefaultBindingKey(exposedKey))), + Key.get(type, Names.named(exposedKey)), + binder); + } + + /** + * Convenience method for calls to {@link #bindOrElse(TypeLiteral, String, Binder)}, that are not + * binding a parameterized type. + * + * @param type The class of the object to bind. + * @param exposedKey The exposed key. + * @param binder The binder to install to. + * @param <T> The type of binding to make. + */ + public static <T> void bindOrElse(Class<T> type, String exposedKey, Binder binder) { + bindOrElse(TypeLiteral.get(type), exposedKey, binder); + } + + public static String makeDefaultBindingKey(String rootKey) { + return rootKey + DEFAULT_BINDING_KEY_SUFFIX; + } + + public static Named makeDefaultBindingName(String rootKey) { + return Names.named(makeDefaultBindingKey(rootKey)); + } + + public static <T> Key<T> makeDefaultKey(TypeLiteral<T> type, String rootKey) { + return Key.get(type, makeDefaultBindingName(rootKey)); + } + + public static <T> Key<T> makeDefaultKey(Class<T> type, String rootKey) { + return makeDefaultKey(TypeLiteral.get(type), rootKey); + } + + public static String makeCustomBindingKey(String rootKey) { + return rootKey + CUSTOM_BINDING_KEY_SUFFIX; + } + + public static Named makeCustomBindingName(String rootKey) { + return Names.named(makeCustomBindingKey(rootKey)); + } + + public static <T> Key<T> makeCustomKey(Class<T> type, String rootKey) { + return Key.get(type, makeCustomBindingName(rootKey)); + } + + public static <T> Key<T> makeCustomKey(TypeLiteral<T> type, String rootKey) { + return Key.get(type, makeCustomBindingName(rootKey)); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/inject/ProviderMethodModule.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/inject/ProviderMethodModule.java b/commons/src/main/java/com/twitter/common/inject/ProviderMethodModule.java new file mode 100644 index 0000000..64a4d3e --- /dev/null +++ b/commons/src/main/java/com/twitter/common/inject/ProviderMethodModule.java @@ -0,0 +1,35 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.inject; + +import com.google.inject.AbstractModule; + +/** + * A convenience base class for modules that do all their binding via provider methods. + * + * @author John Sirois + */ +public abstract class ProviderMethodModule extends AbstractModule { + + /** + * Does no binding; subclasses should implement provider methods. + */ + @Override + protected final void configure() { + // noop + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/inject/TimedInterceptor.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/inject/TimedInterceptor.java b/commons/src/main/java/com/twitter/common/inject/TimedInterceptor.java new file mode 100644 index 0000000..d0aabd7 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/inject/TimedInterceptor.java @@ -0,0 +1,109 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.inject; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; + +import com.google.common.base.Preconditions; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.inject.Binder; +import com.google.inject.matcher.Matchers; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.lang.StringUtils; + +import com.twitter.common.stats.SlidingStats; +import com.twitter.common.stats.TimeSeriesRepository; + +/** + * A method interceptor that exports timing information for methods annotated with + * {@literal @Timed}. + * + * @author John Sirois + */ +public final class TimedInterceptor implements MethodInterceptor { + + /** + * Marks a method as a target for timing. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Timed { + + /** + * The base name to export timing data with; empty to use the annotated method's name. + */ + String value() default ""; + } + + private final LoadingCache<Method, SlidingStats> stats = + CacheBuilder.newBuilder().build(new CacheLoader<Method, SlidingStats>() { + @Override public SlidingStats load(Method method) { + return createStats(method); + } + }); + + private TimedInterceptor() { + // preserve for guice + } + + private SlidingStats createStats(Method method) { + Timed timed = method.getAnnotation(Timed.class); + Preconditions.checkArgument(timed != null, + "TimedInterceptor can only be applied to @Timed methods"); + + String name = timed.value(); + String statName = !StringUtils.isEmpty(name) ? name : method.getName(); + return new SlidingStats(statName, "nanos"); + } + + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + // TODO(John Sirois): consider including a SlidingRate tracking thrown exceptions + SlidingStats stat = stats.get(methodInvocation.getMethod()); + long start = System.nanoTime(); + try { + return methodInvocation.proceed(); + } finally { + stat.accumulate(System.nanoTime() - start); + } + } + + /** + * Installs an interceptor in a guice {@link com.google.inject.Injector}, enabling + * {@literal @Timed} method interception in guice-provided instances. Requires that a + * {@link TimeSeriesRepository} is bound elsewhere. + * + * @param binder a guice binder to require bindings against + */ + public static void bind(Binder binder) { + Preconditions.checkNotNull(binder); + + Bindings.requireBinding(binder, TimeSeriesRepository.class); + + TimedInterceptor interceptor = new TimedInterceptor(); + binder.requestInjection(interceptor); + binder.bindInterceptor(Matchers.any(), Matchers.annotatedWith(Timed.class), interceptor); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/io/Base64ZlibCodec.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/io/Base64ZlibCodec.java b/commons/src/main/java/com/twitter/common/io/Base64ZlibCodec.java new file mode 100644 index 0000000..e412659 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/io/Base64ZlibCodec.java @@ -0,0 +1,156 @@ +package com.twitter.common.io; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; + +import com.google.common.base.Preconditions; +import com.google.common.base.Throwables; +import com.google.common.io.ByteStreams; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Base64OutputStream; + +/** + * Utility class providing encoding and decoding methods to and from a string to a utf-8 encoded, + * zlib compressed, Base64 encoded representation of the string. For wider compatibility, the + * decoder can also automatically recognize GZIP (instead of plain zlib) compressed data too and + * decode it accordingly. + * + * @author Attila Szegedi + */ +public final class Base64ZlibCodec { + /** + * Thrown to indicate invalid data while decoding or unzipping. + * + * @author Attila Szegedi + */ + public static class InvalidDataException extends Exception { + private static final long serialVersionUID = 1L; + + public InvalidDataException(String message) { + super(message); + } + + public InvalidDataException(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * Text encoding used by the Base64 output stream. + */ + public static final String BASE64_TEXT_ENCODING = "ASCII"; + private static final int ESTIMATED_PLAINTEXT_TO_ENCODED_RATIO = 4; + + // Prefix all Base64-encoded, zlib compressed data must have + private static final byte[] ZLIB_HEADER_PREFIX = new byte[] { 120 }; + // Prefix all Base64-encoded, GZIP compressed data must have + private static final byte[] GZIP_HEADER_PREFIX = new byte[] {31, -117, 8, 0, 0, 0, 0, 0, 0 }; + private static final int DIAGNOSTIC_PREFIX_LENGTH = 16; + // Text encoding for char-to-byte transformation before compressing a stack trace + private static final Charset TEXT_ENCODING = com.google.common.base.Charsets.UTF_8; + + private Base64ZlibCodec() { + // Utility class + } + + /** + * Decodes a string. In addition to zlib, it also automatically detects GZIP compressed data and + * adjusts accordingly. + * + * @param encoded the encoded string, represented as a byte array of ASCII-encoded characters + * @return the decoded string + * @throws InvalidDataException if the string can not be decoded. + */ + public static byte[] decode(String encoded) throws InvalidDataException { + Preconditions.checkNotNull(encoded); + return decompress(new Base64().decode(encoded)); + } + + private static byte[] decompress(byte[] compressed) throws InvalidDataException { + byte[] bytes; + try { + final InputStream bin = new ByteArrayInputStream(compressed); + final InputStream zin; + if (startsWith(compressed, GZIP_HEADER_PREFIX)) { + zin = new GZIPInputStream(bin); + } else if (startsWith(compressed, ZLIB_HEADER_PREFIX)) { + zin = new InflaterInputStream(bin); + } else { + throw new Base64ZlibCodec.InvalidDataException("Value doesn't start with either GZIP or zlib header"); + } + try { + bytes = ByteStreams.toByteArray(zin); + } finally { + zin.close(); + } + } catch (IOException e) { + throw new Base64ZlibCodec.InvalidDataException("zlib/GZIP decoding error", e); + } + return bytes; + } + + private static boolean startsWith(byte[] value, byte[] prefix) { + final int pl = prefix.length; + if (value.length < pl) { + return false; + } + for (int i = 0; i < pl; ++i) { + if (value[i] != prefix[i]) { + return false; + } + } + return true; + } + + /** + * Encodes a set of bytes. + * + * @param plain the non-encoded bytes + * @return the encoded string + */ + public static String encode(byte[] plain) { + final ByteArrayOutputStream out = new ByteArrayOutputStream(plain.length + / ESTIMATED_PLAINTEXT_TO_ENCODED_RATIO); + final OutputStream w = getDeflatingEncodingStream(out); + try { + w.write(plain); + w.close(); + return out.toString(BASE64_TEXT_ENCODING); + } catch (UnsupportedEncodingException e) { + throw reportUnsupportedEncoding(); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + private static OutputStream getDeflatingEncodingStream(OutputStream out) { + return new DeflaterOutputStream(new Base64OutputStream(out, true, + Integer.MAX_VALUE, null)); + } + + /** + * Returns a writer that writes through to the specified output stream, utf-8 encoding, + * zlib compressing, and Base64 encoding its input along the way. + * + * @param out the output stream that receives the final output + * @return a writer for the input + */ + public static Writer getEncodingWriter(OutputStream out) { + return new OutputStreamWriter(getDeflatingEncodingStream(out), TEXT_ENCODING); + } + + private static AssertionError reportUnsupportedEncoding() { + return new AssertionError(String.format("JVM doesn't support the %s encoding", TEXT_ENCODING)); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/java/com/twitter/common/io/Codec.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/io/Codec.java b/commons/src/main/java/com/twitter/common/io/Codec.java new file mode 100644 index 0000000..e3ef6b7 --- /dev/null +++ b/commons/src/main/java/com/twitter/common/io/Codec.java @@ -0,0 +1,56 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * A Codec represents a reversible encoding for a given type. Codecs are able to both + * {@link #deserialize(java.io.InputStream) read} items from streams and + * {@link #serialize(Object, java.io.OutputStream) write} items to streams. + * + * <p> TODO(John Sirois): consider whether this interface should optionally support null items to be + * read and written. + * + * @param <T> The type of object the Codec can handle. + * + * @author John Sirois + */ +public interface Codec<T> { + + /** + * Writes a representation of {@code item} to the {@code sink} that can be read back by + * {@link #deserialize(java.io.InputStream)}. + * + * @param item the item to serialize + * @param sink the stream to write the item out to + * @throws IOException if there is a problem serializing the item + */ + void serialize(T item, OutputStream sink) throws IOException; + + /** + * Reads an item from the {@code source} stream that was written by + * {@link #serialize(Object, java.io.OutputStream)}. + * + * @param source the stream to read an item from + * @return the deserialized item + * @throws IOException if there is a problem reading an item + */ + T deserialize(InputStream source) throws IOException; +}
