Java tutorial
/* * Copyright (C) 2012 Facebook, Inc. * * 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 com.facebook.swift.codec.metadata; import com.facebook.swift.codec.ThriftConstructor; import com.facebook.swift.codec.ThriftField; import com.facebook.swift.codec.ThriftStruct; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.google.common.reflect.TypeToken; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Set; import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; import static com.facebook.swift.codec.metadata.ReflectionHelper.extractParameterNames; import static com.facebook.swift.codec.metadata.ReflectionHelper.findAnnotatedMethods; import static com.facebook.swift.codec.metadata.ReflectionHelper.getAllDeclaredFields; import static com.facebook.swift.codec.metadata.ReflectionHelper.getAllDeclaredMethods; import static com.facebook.swift.codec.metadata.ThriftStructMetadataBuilder.FieldMetadata.extractThriftFieldName; import static com.facebook.swift.codec.metadata.ThriftStructMetadataBuilder.FieldMetadata.getOrExtractThriftFieldName; import static com.facebook.swift.codec.metadata.ThriftStructMetadataBuilder.FieldMetadata.getThriftFieldId; import static com.facebook.swift.codec.metadata.ThriftStructMetadataBuilder.FieldMetadata.getThriftFieldName; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Predicates.notNull; import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Lists.newArrayListWithCapacity; import static com.google.common.collect.Sets.newTreeSet; import static java.util.Arrays.asList; @NotThreadSafe public class ThriftStructMetadataBuilder<T> { private final String structName; private final Class<T> structClass; private final Class<?> builderClass; private final List<FieldMetadata> fields = newArrayList(); // readers private final List<Extractor> extractors = newArrayList(); // writers private final List<MethodInjection> builderMethodInjections = newArrayList(); private final List<ConstructorInjection> constructorInjections = newArrayList(); private final List<FieldInjection> fieldInjections = newArrayList(); private final List<MethodInjection> methodInjections = newArrayList(); private final ThriftCatalog catalog; private final MetadataErrors metadataErrors; public ThriftStructMetadataBuilder(ThriftCatalog catalog, Class<T> structClass) { this.catalog = checkNotNull(catalog, "catalog is null"); this.structClass = checkNotNull(structClass, "structClass is null"); this.metadataErrors = new MetadataErrors(catalog.getMonitor()); // verify the class is public and has the correct annotations verifyStructClass(); // assign the struct name from the annotation or from the Java class structName = extractStructName(); // get the builder class from the annotation or from the Java class builderClass = extractBuilderClass(); // extract all of the annotated constructor and report an error if // there is more than one or none // also extract thrift fields from the annotated parameters and verify extractFromConstructors(); // extract thrift fields from the annotated fields and verify extractFromFields(); // extract thrift fields from the annotated methods (and parameters) and verify extractFromMethods(); // finally normalize the field metadata using things like normalizeThriftFields(catalog); } public MetadataErrors getMetadataErrors() { return metadataErrors; } private void verifyStructClass() { // Verify struct class is public and not abstract if (Modifier.isAbstract(structClass.getModifiers())) { metadataErrors.addError("Struct class [%s] is abstract", structClass.getName()); } if (!Modifier.isPublic(structClass.getModifiers())) { metadataErrors.addError("Struct class [%s] is not public", structClass.getName()); } if (!structClass.isAnnotationPresent(ThriftStruct.class)) { metadataErrors.addError("Struct class [%s] does not have a @ThriftStruct annotation", structClass.getName()); } } private String extractStructName() { ThriftStruct annotation = structClass.getAnnotation(ThriftStruct.class); if (annotation == null) { return structClass.getSimpleName(); } else if (!annotation.value().isEmpty()) { return annotation.value(); } else { return structClass.getSimpleName(); } } private Class<?> extractBuilderClass() { ThriftStruct annotation = structClass.getAnnotation(ThriftStruct.class); if (annotation != null && !annotation.builder().equals(void.class)) { return annotation.builder(); } else { return null; } } private void extractFromConstructors() { if (builderClass == null) { // struct class must have a valid constructor addConstructors(structClass); } else { // builder class must have a valid constructor addConstructors(builderClass); // builder class must have a build method annotated with @ThriftConstructor addBuilderMethods(); // verify struct class does not have @ThriftConstructors for (Constructor<?> constructor : structClass.getConstructors()) { if (constructor.isAnnotationPresent(ThriftConstructor.class)) { metadataErrors.addWarning( "Struct class [%s] has a builder class, but constructor %s annotated with @ThriftConstructor", structClass.getName(), constructor); } } } } private void addConstructors(Class<?> clazz) { for (Constructor<?> constructor : clazz.getConstructors()) { if (constructor.isSynthetic()) { continue; } if (!constructor.isAnnotationPresent(ThriftConstructor.class)) { continue; } if (!Modifier.isPublic(constructor.getModifiers())) { metadataErrors.addError("@ThriftConstructor [%s] is not public", constructor.toGenericString()); continue; } List<ParameterInjection> parameters = getParameterInjections(constructor.getParameterAnnotations(), constructor.getGenericParameterTypes(), extractParameterNames(constructor)); if (parameters != null) { fields.addAll(parameters); constructorInjections.add(new ConstructorInjection(constructor, parameters)); } } // add the default constructor if (constructorInjections.isEmpty()) { try { Constructor<?> constructor = clazz.getDeclaredConstructor(); if (!Modifier.isPublic(constructor.getModifiers())) { metadataErrors.addError("Default constructor [%s] is not public", constructor.toGenericString()); } constructorInjections.add(new ConstructorInjection(constructor)); } catch (NoSuchMethodException e) { metadataErrors.addError("Struct class [%s] does not have a public no-arg constructor", clazz.getName()); } } if (constructorInjections.size() > 1) { metadataErrors.addError("Multiple constructors are annotated with @ThriftConstructor ", constructorInjections); } } private void addBuilderMethods() { for (Method method : findAnnotatedMethods(builderClass, ThriftConstructor.class)) { List<ParameterInjection> parameters = getParameterInjections(method.getParameterAnnotations(), method.getGenericParameterTypes(), extractParameterNames(method)); // parameters are null if the method is misconfigured if (parameters != null) { fields.addAll(parameters); builderMethodInjections.add(new MethodInjection(method, parameters)); } } // find invalid methods not skipped by findAnnotatedMethods() for (Method method : getAllDeclaredMethods(builderClass)) { if (method.isAnnotationPresent(ThriftConstructor.class) || hasThriftFieldAnnotation(method)) { if (!Modifier.isPublic(method.getModifiers())) { metadataErrors.addError("@ThriftConstructor method [%s] is not public", method.toGenericString()); } if (Modifier.isStatic(method.getModifiers())) { metadataErrors.addError("@ThriftConstructor method [%s] is static", method.toGenericString()); } } } if (builderMethodInjections.isEmpty()) { metadataErrors.addError( "Struct builder class [%s] does not have a public builder method annotated with @ThriftConstructor", builderClass.getName()); } if (builderMethodInjections.size() > 1) { metadataErrors.addError("Multiple builder methods are annotated with @ThriftConstructor ", builderMethodInjections); } } private void extractFromFields() { if (builderClass == null) { // struct fields are readable and writable addFields(structClass, true, true); } else { // builder fields are writable addFields(builderClass, false, true); // struct fields are readable addFields(structClass, true, false); } } private void addFields(Class<?> clazz, boolean allowReaders, boolean allowWriters) { for (Field fieldField : ReflectionHelper.findAnnotatedFields(clazz, ThriftField.class)) { addField(fieldField, allowReaders, allowWriters); } // find invalid fields not skipped by findAnnotatedFields() for (Field field : getAllDeclaredFields(clazz)) { if (field.isAnnotationPresent(ThriftField.class)) { if (!Modifier.isPublic(field.getModifiers())) { metadataErrors.addError("@ThriftField field [%s] is not public", field.toGenericString()); } if (Modifier.isStatic(field.getModifiers())) { metadataErrors.addError("@ThriftField field [%s] is static", field.toGenericString()); } } } } private void addField(Field fieldField, boolean allowReaders, boolean allowWriters) { checkArgument(fieldField.isAnnotationPresent(ThriftField.class)); ThriftField annotation = fieldField.getAnnotation(ThriftField.class); if (allowReaders) { FieldExtractor fieldExtractor = new FieldExtractor(fieldField, annotation); fields.add(fieldExtractor); extractors.add(fieldExtractor); } if (allowWriters) { FieldInjection fieldInjection = new FieldInjection(fieldField, annotation); fields.add(fieldInjection); fieldInjections.add(fieldInjection); } } private void extractFromMethods() { if (builderClass != null) { // builder methods are writable addMethods(builderClass, false, true); // struct methods are readable addMethods(structClass, true, false); } else { // struct methods are readable and writable addMethods(structClass, true, true); } } private void addMethods(Class<?> clazz, boolean allowReaders, boolean allowWriters) { for (Method fieldMethod : findAnnotatedMethods(clazz, ThriftField.class)) { addMethod(clazz, fieldMethod, allowReaders, allowWriters); } // find invalid methods not skipped by findAnnotatedMethods() for (Method method : getAllDeclaredMethods(clazz)) { if (method.isAnnotationPresent(ThriftField.class) || hasThriftFieldAnnotation(method)) { if (!Modifier.isPublic(method.getModifiers())) { metadataErrors.addError("@ThriftField method [%s] is not public", method.toGenericString()); } if (Modifier.isStatic(method.getModifiers())) { metadataErrors.addError("@ThriftField method [%s] is static", method.toGenericString()); } } } } private void addMethod(Class<?> clazz, Method method, boolean allowReaders, boolean allowWriters) { checkArgument(method.isAnnotationPresent(ThriftField.class)); ThriftField annotation = method.getAnnotation(ThriftField.class); // verify parameters if (isValidateGetter(method)) { if (allowReaders) { MethodExtractor methodExtractor = new MethodExtractor(method, annotation); fields.add(methodExtractor); extractors.add(methodExtractor); } else { metadataErrors.addError("Reader method %s.%s is not allowed on a builder class", clazz.getName(), method.getName()); } } else if (isValidateSetter(method)) { if (allowWriters) { List<ParameterInjection> parameters; if (method.getParameterTypes().length > 1 || Iterables.any( asList(method.getParameterAnnotations()[0]), Predicates.instanceOf(ThriftField.class))) { parameters = getParameterInjections(method.getParameterAnnotations(), method.getGenericParameterTypes(), extractParameterNames(method)); if (annotation.value() != Short.MIN_VALUE) { metadataErrors.addError( "A method with annotated parameters can not have a field id specified: %s.%s ", clazz.getName(), method.getName()); } if (!annotation.name().isEmpty()) { metadataErrors.addError( "A method with annotated parameters can not have a field name specified: %s.%s ", clazz.getName(), method.getName()); } if (annotation.required()) { metadataErrors.addError( "A method with annotated parameters can not be marked as required: %s.%s ", clazz.getName(), method.getName()); } } else { parameters = ImmutableList.of(new ParameterInjection(0, annotation, extractFieldName(method), method.getGenericParameterTypes()[0])); } fields.addAll(parameters); methodInjections.add(new MethodInjection(method, parameters)); } else { metadataErrors.addError( "Inject method %s.%s is not allowed on struct class, since struct has a builder", clazz.getName(), method.getName()); } } else { metadataErrors.addError("Method %s.%s is not a supported getter or setter", clazz.getName(), method.getName()); } } private boolean hasThriftFieldAnnotation(Method method) { for (Annotation[] parameterAnnotations : method.getParameterAnnotations()) { for (Annotation parameterAnnotation : parameterAnnotations) { if (parameterAnnotation instanceof ThriftField) { return true; } } } return false; } private static String extractFieldName(Method method) { checkNotNull(method, "method is null"); String methodName = method.getName(); if ((methodName.startsWith("get") || methodName.startsWith("set")) && methodName.length() > 3) { String name = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4); return name; } else if (methodName.startsWith("is") && methodName.length() > 2) { String name = Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3); return name; } else { return methodName; } } private boolean isValidateGetter(Method method) { return method.getParameterTypes().length == 0 && method.getReturnType() != void.class; } private boolean isValidateSetter(Method method) { return method.getParameterTypes().length >= 1; } private List<ParameterInjection> getParameterInjections(Annotation[][] parameterAnnotations, Type[] parameterTypes, String[] parameterNames) { List<ParameterInjection> parameters = newArrayListWithCapacity(parameterAnnotations.length); for (int parameterIndex = 0; parameterIndex < parameterAnnotations.length; parameterIndex++) { Annotation[] annotations = parameterAnnotations[parameterIndex]; Type parameterType = parameterTypes[parameterIndex]; ThriftField thriftField = null; for (Annotation annotation : annotations) { if (annotation instanceof ThriftField) { thriftField = (ThriftField) annotation; } } ParameterInjection parameterInjection = new ParameterInjection(parameterIndex, thriftField, parameterNames[parameterIndex], parameterType); parameters.add(parameterInjection); } return parameters; } private void normalizeThriftFields(ThriftCatalog catalog) { // assign all fields an id (if possible) Set<String> fieldsWithConflictingIds = inferThriftFieldIds(); // group fields by id Multimap<Optional<Short>, FieldMetadata> fieldsById = Multimaps.index(fields, getThriftFieldId()); for (Entry<Optional<Short>, Collection<FieldMetadata>> entry : fieldsById.asMap().entrySet()) { Collection<FieldMetadata> fields = entry.getValue(); // fields must have an id if (!entry.getKey().isPresent()) { for (String fieldName : newTreeSet(transform(fields, getOrExtractThriftFieldName()))) { // only report errors for fields that don't have conflicting ids if (!fieldsWithConflictingIds.contains(fieldName)) { metadataErrors.addError("ThriftStruct %s fields %s do not have an id", structName, newTreeSet(transform(fields, getOrExtractThriftFieldName()))); } } continue; } short fieldId = entry.getKey().get(); // assure all fields for this ID have the same name String fieldName = extractFieldName(fieldId, fields); for (FieldMetadata field : fields) { field.setName(fieldName); } // verify fields have a supported java type and all fields // for this ID have the same thrift type verifyFieldType(fieldId, fieldName, fields, catalog); } } /** * Assigns all fields an id if possible. Fields are grouped by name and for each group, if there * is a single id, all fields in the group are assigned this id. If the group has multiple ids, * an error is reported. */ private Set<String> inferThriftFieldIds() { Set<String> fieldsWithConflictingIds = new HashSet<>(); // group fields by explicit name or by name extracted from field, method or property Multimap<String, FieldMetadata> fieldsByExplicitOrExtractedName = Multimaps.index(fields, getOrExtractThriftFieldName()); inferThriftFieldIds(fieldsByExplicitOrExtractedName, fieldsWithConflictingIds); // group fields by name extracted from field, method or property // this allows thrift name to be set explicitly without having to duplicate the name on getters and setters // todo should this be the only way this works? Multimap<String, FieldMetadata> fieldsByExtractedName = Multimaps.index(fields, extractThriftFieldName()); inferThriftFieldIds(fieldsByExtractedName, fieldsWithConflictingIds); return fieldsWithConflictingIds; } private void inferThriftFieldIds(Multimap<String, FieldMetadata> fieldsByName, Set<String> fieldsWithConflictingIds) { // for each name group, set the ids on the fields without ids for (Entry<String, Collection<FieldMetadata>> entry : fieldsByName.asMap().entrySet()) { Collection<FieldMetadata> fields = entry.getValue(); // skip all entries without a name or singleton groups... we'll deal with these later if (fields.size() <= 1) { continue; } // all ids used by this named field Set<Short> ids = ImmutableSet.copyOf(Optional.presentInstances(transform(fields, getThriftFieldId()))); // multiple conflicting ids if (ids.size() > 1) { String fieldName = entry.getKey(); if (!fieldsWithConflictingIds.contains(fieldName)) { metadataErrors.addError("ThriftStruct '%s' field '%s' has multiple ids: %s", structName, fieldName, ids); fieldsWithConflictingIds.add(fieldName); } continue; } // single id, so set on all fields in this group (groups with no id are handled later) if (ids.size() == 1) { // propagate the id to all fields in this group short id = Iterables.getOnlyElement(ids); for (FieldMetadata field : fields) { field.setId(id); } } } } private String extractFieldName(short id, Collection<FieldMetadata> fields) { // get the names used by these fields Set<String> names = ImmutableSet.copyOf(filter(transform(fields, getThriftFieldName()), notNull())); String name; if (!names.isEmpty()) { if (names.size() > 1) { metadataErrors.addWarning("ThriftStruct %s field %s has multiple names %s", structName, id, names); } name = names.iterator().next(); } else { // pick a name for this field name = Iterables.find(transform(fields, extractThriftFieldName()), notNull()); } return name; } /** * Verifies that the the fields all have a supported Java type and that all fields map to the * exact same ThriftType. */ private void verifyFieldType(short id, String name, Collection<FieldMetadata> fields, ThriftCatalog catalog) { boolean isSupportedType = true; for (FieldMetadata field : fields) { if (!catalog.isSupportedStructFieldType(field.getJavaType())) { metadataErrors.addError("ThriftStruct %s field %s(%s) type %s is not a supported Java type", structName, name, id, TypeToken.of(field.getJavaType())); isSupportedType = false; // only report the error once break; } } // fields must have the same type if (isSupportedType) { Set<ThriftType> types = new HashSet<>(); for (FieldMetadata field : fields) { types.add(catalog.getThriftType(field.getJavaType())); } if (types.size() > 1) { metadataErrors.addWarning("ThriftStruct %s field %s(%s) has multiple types %s", structName, name, id, types); } } } // // Build final metadata // public ThriftStructMetadata<T> build() { // this code assumes that metadata is clean metadataErrors.throwIfHasErrors(); // builder constructor injection ThriftMethodInjection builderMethodInjection = buildBuilderConstructorInjections(); // constructor injection (or factory method for builder) ThriftConstructorInjection constructorInjection = buildConstructorInjections(); // fields injections Iterable<ThriftFieldMetadata> fieldsMetadata = buildFieldInjections(); // methods injections List<ThriftMethodInjection> methodInjections = buildMethodInjections(); return new ThriftStructMetadata<>(structName, structClass, builderClass, builderMethodInjection, ImmutableList.copyOf(fieldsMetadata), constructorInjection, methodInjections); } private ThriftMethodInjection buildBuilderConstructorInjections() { ThriftMethodInjection builderMethodInjection = null; if (builderClass != null) { MethodInjection builderMethod = builderMethodInjections.get(0); builderMethodInjection = new ThriftMethodInjection(builderMethod.getMethod(), buildParameterInjections(builderMethod.getParameters())); } return builderMethodInjection; } private ThriftConstructorInjection buildConstructorInjections() { ConstructorInjection constructor = constructorInjections.get(0); return new ThriftConstructorInjection(constructor.getConstructor(), buildParameterInjections(constructor.getParameters())); } private Iterable<ThriftFieldMetadata> buildFieldInjections() { Multimap<Optional<Short>, FieldMetadata> fieldsById = Multimaps.index(fields, getThriftFieldId()); return Iterables.transform(fieldsById.asMap().values(), new Function<Collection<FieldMetadata>, ThriftFieldMetadata>() { @Override public ThriftFieldMetadata apply(Collection<FieldMetadata> input) { checkArgument(!input.isEmpty(), "input is empty"); return buildField(input); } }); } private ThriftFieldMetadata buildField(Collection<FieldMetadata> input) { short id = -1; String name = null; ThriftType type = null; // process field injections and extractions ImmutableList.Builder<ThriftInjection> injections = ImmutableList.builder(); ThriftExtraction extraction = null; for (FieldMetadata fieldMetadata : input) { id = fieldMetadata.getId(); name = fieldMetadata.getName(); type = catalog.getThriftType(fieldMetadata.getJavaType()); if (fieldMetadata instanceof FieldInjection) { FieldInjection fieldInjection = (FieldInjection) fieldMetadata; injections.add(new ThriftFieldInjection(fieldInjection.getId(), fieldInjection.getName(), fieldInjection.getField())); } else if (fieldMetadata instanceof ParameterInjection) { ParameterInjection parameterInjection = (ParameterInjection) fieldMetadata; injections .add(new ThriftParameterInjection(parameterInjection.getId(), parameterInjection.getName(), parameterInjection.getParameterIndex(), fieldMetadata.getJavaType())); } else if (fieldMetadata instanceof FieldExtractor) { FieldExtractor fieldExtractor = (FieldExtractor) fieldMetadata; extraction = new ThriftFieldExtractor(fieldExtractor.getId(), fieldExtractor.getName(), fieldExtractor.getField()); } else if (fieldMetadata instanceof MethodExtractor) { MethodExtractor methodExtractor = (MethodExtractor) fieldMetadata; extraction = new ThriftMethodExtractor(methodExtractor.getId(), methodExtractor.getName(), methodExtractor.getMethod()); } } // add type coercion TypeCoercion coercion = null; if (type.isCoerced()) { coercion = catalog.getDefaultCoercion(type.getJavaType()); } ThriftFieldMetadata thriftFieldMetadata = new ThriftFieldMetadata(id, type, name, injections.build(), extraction, coercion); return thriftFieldMetadata; } private List<ThriftMethodInjection> buildMethodInjections() { return Lists.transform(methodInjections, new Function<MethodInjection, ThriftMethodInjection>() { @Override public ThriftMethodInjection apply(MethodInjection injection) { return new ThriftMethodInjection(injection.getMethod(), buildParameterInjections(injection.getParameters())); } }); } private List<ThriftParameterInjection> buildParameterInjections(List<ParameterInjection> parameters) { return Lists.transform(parameters, new Function<ParameterInjection, ThriftParameterInjection>() { @Override public ThriftParameterInjection apply(ParameterInjection injection) { return new ThriftParameterInjection(injection.getId(), injection.getName(), injection.getParameterIndex(), injection.getJavaType()); } }); } static abstract class FieldMetadata { private Short id; private String name; private FieldMetadata(ThriftField annotation) { if (annotation != null) { if (annotation.value() != Short.MIN_VALUE) { id = annotation.value(); } if (!annotation.name().isEmpty()) { name = annotation.name(); } } } public Short getId() { return id; } public void setId(short id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public abstract Type getJavaType(); public abstract String extractName(); static <T extends FieldMetadata> Function<T, Optional<Short>> getThriftFieldId() { return new Function<T, Optional<Short>>() { @Override public Optional<Short> apply(@Nullable T input) { if (input == null) { return Optional.absent(); } Short value = input.getId(); return Optional.fromNullable(value); } }; } static <T extends FieldMetadata> Function<T, String> getThriftFieldName() { return new Function<T, String>() { @Override public String apply(@Nullable T input) { if (input == null) { return null; } return input.getName(); } }; } static <T extends FieldMetadata> Function<T, String> getOrExtractThriftFieldName() { return new Function<T, String>() { @Override public String apply(@Nullable T input) { if (input == null) { return null; } String name = input.getName(); if (name == null) { name = input.extractName(); } if (name == null) { throw new NullPointerException(String.valueOf("name is null")); } return name; } }; } static <T extends FieldMetadata> Function<T, String> extractThriftFieldName() { return new Function<T, String>() { @Override public String apply(@Nullable T input) { if (input == null) { return null; } return input.extractName(); } }; } } private static abstract class Extractor extends FieldMetadata { protected Extractor(ThriftField annotation) { super(annotation); } } private static class FieldExtractor extends Extractor { private final Field field; private FieldExtractor(Field field, ThriftField annotation) { super(annotation); this.field = field; } public Field getField() { return field; } @Override public String extractName() { return field.getName(); } @Override public Type getJavaType() { return field.getGenericType(); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("FieldExtractor"); sb.append("{field=").append(field); sb.append('}'); return sb.toString(); } } public static class MethodExtractor extends Extractor { private final Method method; public MethodExtractor(Method method, ThriftField annotation) { super(annotation); this.method = method; } public Method getMethod() { return method; } @Override public String extractName() { return extractFieldName(method); } @Override public Type getJavaType() { return method.getGenericReturnType(); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("MethodExtractor"); sb.append("{method=").append(method); sb.append('}'); return sb.toString(); } } private static abstract class Injection extends FieldMetadata { protected Injection(ThriftField annotation) { super(annotation); } } private static class FieldInjection extends Injection { private final Field field; private FieldInjection(Field field, ThriftField annotation) { super(annotation); this.field = field; } public Field getField() { return field; } @Override public String extractName() { return field.getName(); } @Override public Type getJavaType() { return field.getGenericType(); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("FieldInjection"); sb.append("{field=").append(field); sb.append('}'); return sb.toString(); } } public class ConstructorInjection { private final Constructor<?> constructor; private final List<ParameterInjection> parameters; public ConstructorInjection(Constructor<?> constructor, List<ParameterInjection> parameters) { this.constructor = constructor; this.parameters = ImmutableList.copyOf(parameters); } public ConstructorInjection(Constructor<?> constructor, ParameterInjection... parameters) { this.constructor = constructor; this.parameters = ImmutableList.copyOf(parameters); } public Constructor<?> getConstructor() { return constructor; } public List<ParameterInjection> getParameters() { return parameters; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("ConstructorInjection"); sb.append("{constructor=").append(constructor); sb.append(", parameters=").append(parameters); sb.append('}'); return sb.toString(); } } public class MethodInjection { private final Method method; private final List<ParameterInjection> parameters; public MethodInjection(Method method, List<ParameterInjection> parameters) { this.method = method; this.parameters = ImmutableList.copyOf(parameters); } public Method getMethod() { return method; } public List<ParameterInjection> getParameters() { return parameters; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("MethodInjection"); sb.append("{method=").append(method); sb.append(", parameters=").append(parameters); sb.append('}'); return sb.toString(); } } private static class ParameterInjection extends Injection { private final int parameterIndex; private final String extractedName; private final Type parameterJavaType; private ParameterInjection(int parameterIndex, ThriftField annotation, String extractedName, Type parameterJavaType) { super(annotation); checkNotNull(parameterJavaType, "parameterJavaType is null"); this.parameterIndex = parameterIndex; this.extractedName = extractedName; this.parameterJavaType = parameterJavaType; if (void.class.equals(parameterJavaType)) { throw new AssertionError(); } checkArgument(getName() != null || extractedName != null, "Parameter must have an explicit name or an extractedName"); } public int getParameterIndex() { return parameterIndex; } @Override public String extractName() { return extractedName; } @Override public Type getJavaType() { return parameterJavaType; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("ParameterInjection"); sb.append("{parameterIndex=").append(parameterIndex); sb.append(", extractedName='").append(extractedName).append('\''); sb.append(", parameterJavaType=").append(parameterJavaType); sb.append('}'); return sb.toString(); } } }