| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.jclouds.json.internal; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Predicates.in; |
| import static com.google.common.collect.Iterables.filter; |
| import static com.google.common.collect.Iterables.get; |
| import static com.google.common.collect.Iterables.size; |
| import static com.google.common.collect.Iterables.transform; |
| import static com.google.common.collect.Iterables.tryFind; |
| import static org.jclouds.reflect.Reflection2.constructors; |
| |
| import java.beans.ConstructorProperties; |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Modifier; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.inject.Named; |
| |
| import org.jclouds.json.SerializedNames; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Function; |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Supplier; |
| import com.google.common.collect.FluentIterable; |
| import com.google.common.collect.Maps; |
| import com.google.common.reflect.Invokable; |
| import com.google.common.reflect.Parameter; |
| import com.google.common.reflect.TypeToken; |
| import com.google.gson.FieldNamingStrategy; |
| import com.google.gson.annotations.SerializedName; |
| |
| /** |
| * NamingStrategies used for JSON deserialization using GSON |
| */ |
| public class NamingStrategies { |
| /** |
| * Specifies how to extract the name from an annotation for use in determining the serialized name. |
| * |
| * @see com.google.gson.annotations.SerializedName |
| * @see ExtractSerializedName |
| */ |
| public abstract static class NameExtractor<A extends Annotation> implements Function<Annotation, String>, |
| Supplier<Predicate<Annotation>> { |
| protected final Class<A> annotationType; |
| protected final Predicate<Annotation> predicate; |
| |
| protected NameExtractor(final Class<A> annotationType) { |
| this.annotationType = checkNotNull(annotationType, "annotationType"); |
| this.predicate = new Predicate<Annotation>() { |
| public boolean apply(Annotation input) { |
| return input.getClass().equals(annotationType); |
| } |
| }; |
| } |
| |
| @SuppressWarnings("unchecked") |
| public Class<Annotation> annotationType() { |
| return (Class<Annotation>) annotationType; |
| } |
| |
| @Override |
| public String apply(Annotation in) { |
| return extractName(annotationType.cast(in)); |
| } |
| |
| protected abstract String extractName(A cast); |
| |
| @Override |
| public Predicate<Annotation> get() { |
| return predicate; |
| } |
| |
| @Override |
| public String toString() { |
| return "nameExtractor(" + annotationType.getSimpleName() + ")"; |
| } |
| |
| @Override |
| public int hashCode() { |
| return annotationType.hashCode(); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) |
| return true; |
| if (obj == null || getClass() != obj.getClass()) |
| return false; |
| return annotationType.equals(NameExtractor.class.cast(obj).annotationType); |
| } |
| } |
| |
| public static class ExtractSerializedName extends NameExtractor<SerializedName> { |
| public ExtractSerializedName() { |
| super(SerializedName.class); |
| } |
| |
| public String extractName(SerializedName in) { |
| return checkNotNull(in, "input annotation").value(); |
| } |
| } |
| |
| public static class ExtractNamed extends NameExtractor<Named> { |
| public ExtractNamed() { |
| super(Named.class); |
| } |
| |
| @Override |
| public String extractName(Named in) { |
| return checkNotNull(in, "input annotation").value(); |
| } |
| } |
| |
| public abstract static class AnnotationBasedNamingStrategy { |
| protected final Map<Class<? extends Annotation>, ? extends NameExtractor<?>> annotationToNameExtractor; |
| protected final String forToString; |
| |
| public AnnotationBasedNamingStrategy(Iterable<? extends NameExtractor<?>> extractors) { |
| checkNotNull(extractors, "means to extract names by annotations"); |
| |
| this.annotationToNameExtractor = Maps.uniqueIndex(extractors, |
| new Function<NameExtractor<?>, Class<? extends Annotation>>() { |
| @Override |
| public Class<? extends Annotation> apply(NameExtractor<?> input) { |
| return input.annotationType(); |
| } |
| }); |
| this.forToString = Joiner.on(",").join(transform(extractors, new Function<NameExtractor<?>, String>() { |
| @Override |
| public String apply(NameExtractor<?> input) { |
| return input.annotationType().getName(); |
| } |
| })); |
| } |
| |
| @Override |
| public String toString() { |
| return "AnnotationBasedNamingStrategy requiring one of " + forToString; |
| } |
| } |
| |
| /** |
| * Definition of field naming policy for annotation-based field |
| */ |
| public static class AnnotationFieldNamingStrategy extends AnnotationBasedNamingStrategy implements |
| FieldNamingStrategy { |
| |
| public AnnotationFieldNamingStrategy(Iterable<? extends NameExtractor<?>> extractors) { |
| super(extractors); |
| checkArgument(extractors.iterator().hasNext(), "you must supply at least one name extractor, for example: " |
| + ExtractSerializedName.class.getSimpleName()); |
| } |
| |
| @Override |
| public String translateName(Field f) { |
| // Determining if AutoValue is tough, since annotations are SOURCE retention. |
| if (Modifier.isAbstract(f.getDeclaringClass().getSuperclass().getModifiers())) { // AutoValue means abstract. |
| for (Invokable<?, ?> target : constructors(TypeToken.of(f.getDeclaringClass().getSuperclass()))) { |
| SerializedNames names = target.getAnnotation(SerializedNames.class); |
| if (names != null && target.isStatic()) { // == factory method |
| // Fields and constructor params are written by AutoValue in same order as methods are declared. |
| // By contract, SerializedNames factory methods must declare its names in the same order. |
| List<Field> declaredFields = Arrays.asList(f.getDeclaringClass().getDeclaredFields()); |
| // Instrumentation libraries, such as JaCoCo might introduce synthetic fields to the class. |
| // We should ignore them |
| Iterable<Field> fields = filter(declaredFields, new Predicate<Field>() { |
| @Override |
| public boolean apply(Field input) { |
| return !input.isSynthetic(); |
| } |
| }); |
| int numFields = size(fields); |
| if (numFields != names.value().length) { |
| String message = "Incorrect number of names on %s. Class [%s]. Annotation config: [%s]. Fields in object; [%s]"; |
| String types = Joiner.on(",").join(transform(fields, new Function<Field, String>() { |
| @Override |
| public String apply(Field input) { |
| return input.getType().getName(); |
| } |
| })); |
| throw new IllegalStateException(String.format(message, names, f.getDeclaringClass().getName(), |
| Joiner.on(",").join(names.value()), types)); |
| } |
| for (int i = 0; i < numFields; i++) { |
| if (get(fields, i).equals(f)) { |
| return names.value()[i]; |
| } |
| } |
| // The input field was not a declared field. Accidentally placed on something not AutoValue? |
| throw new IllegalStateException("Inconsistent state. Ensure type is AutoValue on " + target); |
| } |
| } |
| } |
| for (Annotation annotation : f.getAnnotations()) { |
| if (annotationToNameExtractor.containsKey(annotation.annotationType())) { |
| return annotationToNameExtractor.get(annotation.annotationType()).apply(annotation); |
| } |
| } |
| return null; |
| } |
| } |
| |
| public static class AnnotationOrNameFieldNamingStrategy extends AnnotationFieldNamingStrategy implements |
| FieldNamingStrategy { |
| |
| public AnnotationOrNameFieldNamingStrategy(Iterable<? extends NameExtractor<?>> extractors) { |
| super(extractors); |
| } |
| |
| @Override |
| public String translateName(Field f) { |
| String result = super.translateName(f); |
| return result == null ? f.getName() : result; |
| } |
| } |
| |
| /** |
| * Determines field naming from constructor annotations |
| */ |
| public static final class AnnotationConstructorNamingStrategy extends AnnotationBasedNamingStrategy { |
| private final Predicate<Invokable<?, ?>> hasMarker; |
| private final Collection<? extends Class<? extends Annotation>> markers; |
| |
| public AnnotationConstructorNamingStrategy(Collection<? extends Class<? extends Annotation>> markers, |
| Iterable<? extends NameExtractor<?>> extractors) { |
| super(extractors); |
| this.markers = checkNotNull(markers, |
| "you must supply at least one annotation to mark deserialization constructors"); |
| this.hasMarker = hasAnnotationIn(markers); |
| } |
| |
| private static Predicate<Invokable<?, ?>> hasAnnotationIn( |
| final Collection<? extends Class<? extends Annotation>> markers) { |
| return new Predicate<Invokable<?, ?>>() { |
| public boolean apply(Invokable<?, ?> input) { |
| return FluentIterable.from(Arrays.asList(input.getAnnotations())) |
| .transform(new Function<Annotation, Class<? extends Annotation>>() { |
| public Class<? extends Annotation> apply(Annotation input) { |
| return input.annotationType(); |
| } |
| }).anyMatch((Predicate<Class<? extends Annotation>>) in(markers)); |
| } |
| }; |
| } |
| |
| @VisibleForTesting |
| <T> Invokable<T, T> getDeserializer(TypeToken<T> token) { |
| return tryFind(constructors(token), hasMarker).orNull(); |
| } |
| |
| @SuppressWarnings("CollectionIncompatibleType") |
| @VisibleForTesting |
| <T> String translateName(Invokable<T, T> c, int index) { |
| String name = null; |
| |
| if ((markers.contains(ConstructorProperties.class) && c.getAnnotation(ConstructorProperties.class) != null) |
| || (markers.contains(SerializedNames.class) && c.getAnnotation(SerializedNames.class) != null)) { |
| |
| String[] names = c.getAnnotation(SerializedNames.class) != null |
| ? c.getAnnotation(SerializedNames.class).value() |
| : c.getAnnotation(ConstructorProperties.class).value(); |
| |
| if (names.length != c.getParameters().size()) { |
| String message = "Incorrect count of names on annotation of %s. Class: [%s]. Annotation config: [%s]. Parameters; [%s]"; |
| String types = Joiner.on(",").join(transform(c.getParameters(), new Function<Parameter, String>() { |
| @Override |
| public String apply(Parameter input) { |
| return input.getClass().getName(); |
| } |
| })); |
| throw new IllegalArgumentException(String.format(message, c, c.getDeclaringClass().getName(), |
| Joiner.on(",").join(names), types)); |
| } |
| |
| if (names != null && names.length > index) { |
| name = names[index]; |
| } |
| |
| } else { |
| for (Annotation annotation : c.getParameters().get(index).getAnnotations()) { |
| if (annotationToNameExtractor.containsKey(annotation.annotationType())) { |
| name = annotationToNameExtractor.get(annotation.annotationType()).apply(annotation); |
| break; |
| } |
| } |
| } |
| return name; |
| } |
| } |
| } |