/* 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.strings; import static com.google.common.base.Preconditions.checkNotNull; import java.io.Serializable; import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; /** * Gives you static access to implementations of the {@link Describable} interface. */ @Immutable public final class Describables { private Describables() { } /** * Returns an empty string as a describable. */ @Nonnull public static final SerializableDescription EMPTY_STRING = asSerializable(withString("")); /** * Supplies an already created {@link String} as a {@link Describable}. * Also useful for caching {@link Describable}s that won't change. */ @Nonnull @CheckReturnValue public static Describable withString(String description) { return new NonLazyDescription(description); } private static final class NonLazyDescription implements Describable { private final String description; private NonLazyDescription(String description) { this.description = checkNotNull(description); } @Override public String description() { return description; } @Override public String toString() { return description(); } } /** * Lazily calls {@link String#format(String, Object...)} */ @Nonnull @CheckReturnValue public static Describable format(String formatTemplate, Object ... args) { return new FormatDescription(formatTemplate, args); } private static final class FormatDescription implements Describable { private final String formattingTemplate; private final Object[] args; private FormatDescription(String formattingTemplate, Object ... args) { this.formattingTemplate = checkNotNull(formattingTemplate); this.args = checkNotNull(args); } @Override public String description() { return String.format(formattingTemplate, args); } @Override public String toString() { return description(); } } /** * Lazily caches the result of running {@link Describable#description()} on {@code describable} * so that it's only run once. */ public static Describable cache(Describable describable) { return new CachingDescription(checkNotNull(describable)); } private static final class CachingDescription implements Describable { private final Supplier<String> description; private CachingDescription(final Describable describable) { this.description = Suppliers.memoize(new Supplier<String>(){ @Override public String get() { return describable.description(); } }); } @Override public String description() { return description.get(); } @Override public String toString() { return description(); } } /** * Lazily calls the {@link #toString()} of {@code value} as a describable * * @param value the object to call {@link #toString()} on */ @Nonnull @CheckReturnValue public static Describable toString(Object value) { return new ToStringDescription(value); } private static final class ToStringDescription implements Describable { private final Object value; private ToStringDescription(Object value) { this.value = checkNotNull(value); } @Override public String description() { return value.toString(); } @Override public String toString() { return description(); } } /** * Creates an {@link IllegalArgumentException} where the {@link Describable#description()} of * {@code message} is used as the detail message. */ @Nonnull @CheckReturnValue public static IllegalArgumentException illegalArgument(Describable message) { return new IllegalArgument(message); } /** * Creates an {@link IllegalArgumentException} where the {@link Describable#description()} of * {@code message} is used as the detail message. {@code cause} is set as the cause. */ @Nonnull @CheckReturnValue public static IllegalArgumentException illegalArgument(Describable message, Throwable cause) { return new IllegalArgument(message, cause); } private static final class IllegalArgument extends IllegalArgumentException { private final SerializableDescription message; private IllegalArgument(final Describable message) { this.message = asSerializable(message); } private IllegalArgument(final Describable message, Throwable cause) { this(message); initCause(checkNotNull(cause)); } @Override public String getMessage() { return message.description(); } /** * For {@link Serializable} */ private static final long serialVersionUID = 1L; } /** * Returns a version of {@code describable} that is serializable. Note that after serialization * the describable is fixed, that is {@link Describable#description()} won't be called on * {@code describable} any more. */ @Nonnull @CheckReturnValue public static SerializableDescription asSerializable(Describable describable) { return new SerializableDescription(describable); } /** * A {@link Serializable} wrapper for {@link Describable}s */ public static final class SerializableDescription implements Serializable, Describable { private final transient Describable describable; private SerializableDescription(Describable descriptionToSerialize) { describable = checkNotNull(descriptionToSerialize); } private static final class SerializationProxy implements Serializable { /** * @serial the detail message for this describable. Constructed lazily when serialized. */ private final String message; private static final long serialVersionUID = 1L; private SerializationProxy(Describable descriptionToSerialize) { message = descriptionToSerialize.description(); } private Object readResolve() { return new SerializableDescription(Describables.withString(message)); } } Object writeReplace() { return new SerializationProxy(this); } @Override public String description() { return describable.description(); } @Override public String toString() { return description(); } /** * For {@link Serializable} */ private static final long serialVersionUID = 1L; } }