package sk.stuba.fiit.perconik.utilities.configuration;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.base.Equivalence;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;
import sk.stuba.fiit.perconik.utilities.Optionals;
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
import static com.google.common.base.Optional.absent;
import static com.google.common.base.Optional.of;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.not;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Lists.asList;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.filterEntries;
import static sk.stuba.fiit.perconik.utilities.MoreMaps.newHashMap;
import static sk.stuba.fiit.perconik.utilities.MoreSets.newHashSet;
/**
* TODO
*
* @author Pavol Zbell
* @since 1.0
*/
public final class Configurables {
@SuppressWarnings("serial")
private static final TypeToken<Entry<String, Object>> rawOptionType = new TypeToken<Entry<String, Object>>() {};
@SuppressWarnings("serial")
private static final TypeToken<OptionMapping<?>> wildcardMappingType = new TypeToken<OptionMapping<?>>() {};
@SuppressWarnings("serial")
private static final TypeToken<OptionAccessor<?>> wildcardAccessorType = new TypeToken<OptionAccessor<?>>() {};
private Configurables() {}
public static MapOptions defaults(final Class<?> schema) {
return defaults(mappings(schema));
}
public static MapOptions defaults(final Class<?> schema, final Class<?> type) {
return defaults(mappings(schema, type));
}
public static MapOptions defaults(final Class<?> schema, final TypeToken<?> type) {
return defaults(mappings(schema, type));
}
public static MapOptions defaults(final Iterable<? extends OptionMapping<?>> mappings) {
Builder<String, Object> builder = ImmutableMap.builder();
for (OptionMapping<?> mapping: mappings) {
Object value = mapping.getDefaultValue();
if (value != null) {
builder.put(mapping.getKey(), value);
}
}
return MapOptions.from(builder.build());
}
public static List<OptionMapping<?>> mappings(final Class<?> schema) {
return mappings(schema, Optionals.<OptionMapping<?>, OptionMapping<?>>fromNonnullFunction());
}
public static <T> List<OptionMapping<T>> mappings(final Class<?> schema, final Class<T> type) {
return mappings(schema, TypeToken.of(type));
}
public static <T> List<OptionMapping<T>> mappings(final Class<?> schema, final TypeToken<T> type) {
return mappings(schema, mappingFunction(mappingOf(type), type));
}
public static <M extends OptionMapping<?>> List<M> mappings(final Class<?> schema, final Function<? super OptionMapping<?>, Optional<M>> function) {
List<M> mappings = newArrayList();
for (Field field: schema.getFields()) {
int modifiers = field.getModifiers();
if (!isPublic(modifiers) || !isStatic(modifiers)) {
continue;
}
if (!OptionMapping.class.isAssignableFrom(field.getType())) {
continue;
}
try {
OptionMapping<?> mapping = (OptionMapping<?>) field.get(null);
checkState(mapping != null, "%s.%s is null", schema.getName(), field.getName());
Optional<? extends M> result = function.apply(mapping);
if (result.isPresent()) {
mappings.add(result.get());
}
} catch (IllegalAccessException failure) {
propagate(failure);
}
}
return mappings;
}
public static List<OptionAccessor<?>> accessors(final Class<?> schema) {
// potentially unsafe raw cast to List is correct and safe in this case
return List.class.cast(accessors(schema, Object.class));
}
public static <T> List<OptionAccessor<T>> accessors(final Class<?> schema, final Class<T> type) {
return accessors(schema, TypeToken.of(type));
}
public static <T> List<OptionAccessor<T>> accessors(final Class<?> schema, final TypeToken<T> type) {
return mappings(schema, mappingFunction(accessorOf(type), type));
}
public static <M extends OptionMapping<T>, T> Function<OptionMapping<?>, Optional<M>> mappingFunction(final TypeToken<? extends M> map, final Class<T> type) {
return mappingFunction(map, TypeToken.of(type));
}
public static <M extends OptionMapping<T>, T> Function<OptionMapping<?>, Optional<M>> mappingFunction(final TypeToken<? extends M> map, final TypeToken<T> type) {
return new Function<OptionMapping<?>, Optional<M>>() {
public Optional<M> apply(@Nonnull final OptionMapping<?> raw) {
if (map.getRawType().isInstance(raw) && type.isAssignableFrom(raw.getType())) {
// cast is safe since types were checked already
@SuppressWarnings("unchecked")
M mapping = (M) raw;
return of(mapping);
}
return absent();
}
};
}
public static <M extends Map<String, Object>> M values(final Iterable<? extends OptionAccessor<?>> accessors, final Options source, final M destination) {
return values(accessors, newReader(source), destination);
}
public static <M extends Map<String, Object>> M values(final Iterable<? extends OptionAccessor<?>> accessors, final Options source, final Supplier<? extends M> destination) {
return values(accessors, source, destination.get());
}
public static <M extends Map<String, Object>> M values(final Iterable<? extends OptionAccessor<?>> accessors, final OptionsReader source, final M destination) {
for (OptionAccessor<?> accessor: accessors) {
destination.put(accessor.getKey(), accessor.getValue(source));
}
return destination;
}
public static <M extends Map<String, Object>> M values(final Iterable<? extends OptionAccessor<?>> accessors, final OptionsReader source, final Supplier<? extends M> destination) {
return values(accessors, source, destination.get());
}
public static <M extends Map<String, Object>> M rawValues(final Iterable<? extends OptionAccessor<?>> accessors, final Options source, final M destination) {
return rawValues(accessors, newReader(source), destination);
}
public static <M extends Map<String, Object>> M rawValues(final Iterable<? extends OptionAccessor<?>> accessors, final Options source, final Supplier<? extends M> destination) {
return rawValues(accessors, source, destination.get());
}
public static <M extends Map<String, Object>> M rawValues(final Iterable<? extends OptionAccessor<?>> accessors, final OptionsReader source, final M destination) {
for (OptionAccessor<?> accessor: accessors) {
destination.put(accessor.getKey(), accessor.getRawValue(source));
}
return destination;
}
public static <M extends Map<String, Object>> M rawValues(final Iterable<? extends OptionAccessor<?>> accessors, final OptionsReader source, final Supplier<? extends M> destination) {
return rawValues(accessors, source, destination.get());
}
public static <T> OptionMapping<T> option(final TypeToken<T> type, final String key) {
return option(type, key, null);
}
public static <T> OptionMapping<T> option(final TypeToken<T> type, final String key, @Nullable final T defaultValue) {
return new SimpleOptionMapping<>(type, key, defaultValue);
}
public static <T> OptionAccessor<T> option(final OptionParser<T> parser, final String key) {
return option(parser, key, null);
}
public static <T> OptionAccessor<T> option(final OptionParser<T> parser, final String key, @Nullable final T defaultValue) {
return new RegularOptionAccessor<>(parser.type(), parser, key, defaultValue);
}
public static <T> OptionAccessor<T> option(final OptionParser<? extends T> parser, final OptionMapping<T> mapping) {
return option(mapping.getType(), parser, mapping.getKey(), mapping.getDefaultValue());
}
public static <T> OptionAccessor<T> option(final TypeToken<T> type, final OptionParser<? extends T> parser, final String key) {
return option(type, parser, key, null);
}
public static <T> OptionAccessor<T> option(final TypeToken<T> type, final OptionParser<? extends T> parser, final String key, @Nullable final T defaultValue) {
return new RegularOptionAccessor<>(type, parser, key, defaultValue);
}
public static <E extends Entry<String, Object>> Equivalence<E> optionEquivalence() {
@SuppressWarnings("unchecked")
Equivalence<E> equivalence = (Equivalence<E>) OptionEquivalence.INSTANCE;
return equivalence;
}
public static Equivalence<String> optionKeyEquivalence() {
return OptionEquivalence.KeyEquivalence.INSTANCE;
}
public static <T> Equivalence<T> optionValueEquivalence() {
@SuppressWarnings("unchecked")
Equivalence<T> equivalence = (Equivalence<T>) OptionEquivalence.ValueEquivalence.INSTANCE;
return equivalence;
}
public static OptionsReader newReader(final Options options) {
return new RegularOptionsReader(options);
}
public static OptionsWriter newWriter(final Options options) {
return new RegularOptionsWriter(options);
}
public static MapOptions emptyOptions() {
return MapOptions.empty();
}
public static Options compound(final Options primary, final Options secondary) {
return new CompoundOptions(primary, secondary);
}
public static Options compound(final Options primary, final Options secondary, final Options ... rest) {
return new CompoundOptions(asList(primary, secondary, rest));
}
public static Options compound(final Iterable<? extends Options> options) {
return new CompoundOptions(options);
}
public static Options fromMap(@Nullable final Map<String, Object> map) {
return map != null ? MapOptions.from(map) : emptyOptions();
}
public static Map<String, Object> toMap(@Nullable final Options options) {
return options != null ? options.toMap() : Collections.<String, Object>emptyMap();
}
static Predicate<Entry<String, Object>> rawOptionKeyMatcher(final Equivalence<String> keyEquivalence, final Iterable<String> matchingKeys) {
final Set<Equivalence.Wrapper<String>> matches = newHashSet(keyEquivalence, matchingKeys);
return new Predicate<Entry<String, Object>>() {
public boolean apply(@Nonnull final Entry<String, Object> rawOption) {
return matches.contains(keyEquivalence.wrap(rawOption.getKey()));
}
};
}
public static Map<String, Object> knownRawOptions(final Map<String, Object> rawOptions, final Iterable<String> knownKeys) {
return knownRawOptions(rawOptions, knownKeys, optionKeyEquivalence());
}
public static Map<String, Object> knownRawOptions(final Map<String, Object> rawOptions, final Iterable<String> knownKeys, final Equivalence<String> keyEquivalence) {
return filterEntries(rawOptions, rawOptionKeyMatcher(keyEquivalence, knownKeys));
}
public static Map<String, Object> unknownRawOptions(final Map<String, Object> rawOptions, final Iterable<String> knownKeys) {
return unknownRawOptions(rawOptions, knownKeys, optionKeyEquivalence());
}
public static Map<String, Object> unknownRawOptions(final Map<String, Object> rawOptions, final Iterable<String> knownKeys, final Equivalence<String> keyEquivalence) {
return filterEntries(rawOptions, not(rawOptionKeyMatcher(keyEquivalence, knownKeys)));
}
static Predicate<Entry<String, Object>> rawOptionMatcher(final Equivalence<String> keyEquivalence, final Equivalence<Object> valueEquivalence, final Map<String, Object> matchingRawOptions) {
final Map<Equivalence.Wrapper<String>, Object> matches = newHashMap(keyEquivalence, matchingRawOptions.entrySet());
return new Predicate<Entry<String, Object>>() {
public boolean apply(@Nonnull final Entry<String, Object> rawOption) {
Object value = matches.get(keyEquivalence.wrap(rawOption.getKey()));
return valueEquivalence.equivalent(rawOption.getValue(), value);
}
};
}
public static Map<String, Object> inheritedRawOptions(final Map<String, Object> rawOptions, final Map<String, Object> parentRawOptions) {
return inheritedRawOptions(rawOptions, parentRawOptions, optionKeyEquivalence(), optionValueEquivalence());
}
public static Map<String, Object> inheritedRawOptions(final Map<String, Object> rawOptions, final Map<String, Object> parentRawOptions, final Equivalence<String> keyEquivalence, final Equivalence<Object> valueEquivalence) {
return filterEntries(rawOptions, rawOptionMatcher(keyEquivalence, valueEquivalence, parentRawOptions));
}
public static Map<String, Object> customRawOptions(final Map<String, Object> rawOptions, final Map<String, Object> parentRawOptions) {
return customRawOptions(rawOptions, parentRawOptions, optionKeyEquivalence(), optionValueEquivalence());
}
public static Map<String, Object> customRawOptions(final Map<String, Object> rawOptions, final Map<String, Object> parentRawOptions, final Equivalence<String> keyEquivalence, final Equivalence<Object> valueEquivalence) {
return filterEntries(rawOptions, not(rawOptionMatcher(keyEquivalence, valueEquivalence, parentRawOptions)));
}
public static <T> TypeToken<OptionMapping<T>> mappingOf(final Class<T> type) {
return mappingOf(TypeToken.of(type));
}
public static <T> TypeToken<OptionMapping<T>> mappingOf(final TypeToken<T> type) {
@SuppressWarnings("serial")
TypeToken<OptionMapping<T>> token = new TypeToken<OptionMapping<T>>() {}.where(new TypeParameter<T>() {}, type);
return token;
}
public static <T> TypeToken<OptionAccessor<T>> accessorOf(final Class<T> type) {
return accessorOf(TypeToken.of(type));
}
public static <T> TypeToken<OptionAccessor<T>> accessorOf(final TypeToken<T> type) {
@SuppressWarnings("serial")
TypeToken<OptionAccessor<T>> token = new TypeToken<OptionAccessor<T>>() {}.where(new TypeParameter<T>() {}, type);
return token;
}
public static TypeToken<Entry<String, Object>> rawOptionType() {
return rawOptionType;
}
public static TypeToken<OptionMapping<?>> wildcardMappingType() {
return wildcardMappingType;
}
public static TypeToken<OptionAccessor<?>> wildcardAccessorType() {
return wildcardAccessorType;
}
}