/* Copyright 2013 Jonatan Jönsson * * 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 se.softhouse.common.guavaextensions; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.io.Files; /** * Gives you static access to additional implementations of the {@link Function} interface, as a * complement to {@link Functions} */ @Immutable public final class Functions2 { private Functions2() { } /** * Runs several {@link Function}s in the same order as they are * given as arguments here.<br> * If one of the {@link Function}s makes {@code T} {@link Immutable}, * make sure to pass it in last as it's hard to modify * an {@link Immutable} value. This works exactly like * {@link Functions#compose(Function, Function)} except that if {@code first} doesn't do * anything, {@code second} is returned directly instead. * * @param first a {@link Function} * @param second another {@link Function} * @return a merged {@link Function} */ @Nonnull public static <T> Function<T, T> compound(Function<T, T> first, Function<T, T> second) { checkNotNull(first); checkNotNull(second); if(first == Functions.identity()) return second; return new CompoundFunction<T>(first, second); } private static final class CompoundFunction<T> implements Function<T, T> { @Nonnull private final Function<T, T> first; @Nonnull private final Function<T, T> second; private CompoundFunction(Function<T, T> first, Function<T, T> second) { this.first = first; this.second = second; } @Nullable @Override public T apply(@Nullable T value) { return second.apply(first.apply(value)); } } /** * Creates a {@link Function} that applies {@code elementTransformer} to each element of the * input {@link List} and puts the new elements in a new , immutable, list and returns it. */ @Nonnull public static <E> Function<List<E>, List<E>> listTransformer(Function<E, E> elementTransformer) { if(elementTransformer == Functions.identity()) return Functions.identity(); return new ListTransformer<E>(elementTransformer); } private static final class ListTransformer<E> implements Function<List<E>, List<E>> { private final Function<E, E> elementTransformer; private ListTransformer(Function<E, E> elementTransformer) { this.elementTransformer = checkNotNull(elementTransformer); } @Override public List<E> apply(List<E> values) { if(values == null) return null; return ImmutableList.copyOf(Lists.transform(values, elementTransformer)); } } /** * Creates a {@link Function} that applies {@code valueTransformer} to each value of the * input {@link Map} and puts them in a new , immutable, map and returns it. */ @Nonnull public static <K, V> Function<Map<K, V>, Map<K, V>> mapValueTransformer(Function<V, V> valueTransformer) { if(valueTransformer == Functions.identity()) return Functions.identity(); return new MapValueTransformer<K, V>(valueTransformer); } private static final class MapValueTransformer<K, V> implements Function<Map<K, V>, Map<K, V>> { private final Function<V, V> valueTransformer; private MapValueTransformer(Function<V, V> valueTransformer) { this.valueTransformer = checkNotNull(valueTransformer); } @Override public Map<K, V> apply(Map<K, V> map) { if(map == null) return null; return ImmutableMap.copyOf(Maps.transformValues(map, valueTransformer)); } } /** * Creates a {@link Function} that wraps {@link List}s with * {@link Collections#unmodifiableList(List)} */ @Nonnull public static <E> Function<List<E>, List<E>> unmodifiableList() { return new UnmodifiableListMaker<E>(); } private static final class UnmodifiableListMaker<E> implements Function<List<E>, List<E>> { @Override public List<E> apply(List<E> value) { if(value == null) return null; return Collections.unmodifiableList(value); } } /** * Creates a {@link Function} that wraps {@link Map}s with * {@link Collections#unmodifiableMap(Map)} */ @Nonnull public static <K, V> Function<Map<K, V>, Map<K, V>> unmodifiableMap() { return new UnmodifiableMapMaker<K, V>(); } private static final class UnmodifiableMapMaker<K, V> implements Function<Map<K, V>, Map<K, V>> { @Override public Map<K, V> apply(Map<K, V> value) { if(value == null) return null; return Collections.unmodifiableMap(value); } } /** * Runs {@code function} {@code times} times. * The result of the first call is passed to the next apply in the chain and so on. * For instance: * * <pre class="prettyprint"> * <code class="language-java"> * assertThat(Functions2.repeat(ADD_ONE, 2).apply(0)).isEqualTo(2); * </code> * </pre> */ @Nonnull public static <T> Function<T, T> repeat(Function<T, T> function, long times) { checkNotNull(function); checkArgument(times >= 0, "times (%s) must be positive", times); return new FunctionRepeater<T>(function, times); } private static final class FunctionRepeater<T> implements Function<T, T> { private final long times; @Nonnull private final Function<T, T> function; private FunctionRepeater(Function<T, T> function, long times) { this.function = function; this.times = times; } @Override public T apply(T input) { T output = input; for(long i = 0; i < times; i++) { output = function.apply(output); } return output; } } /** * Returns a {@link Function} that reads whole {@link File}s into {@link String}s using the * {@link Charsets#UTF_8 UTF-8} charset. */ public static Function<File, String> fileToString() { return FileToString.INSTANCE; } private static final class FileToString implements Function<File, String> { private static final Function<File, String> INSTANCE = new FileToString(); @Override public String apply(@Nonnull File input) { if(input.isDirectory()) throw new IllegalArgumentException(input.getAbsolutePath() + " is a directory, not a file"); try { return Files.toString(input, Charsets.UTF_8); } catch(IOException e) { throw new IllegalArgumentException("I/O error occured while reading: " + input.getAbsolutePath(), e); } } } }