001/*
002 * Copyright (C) 2012 Facebook, Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may
005 * not use this file except in compliance with the License. You may obtain
006 * a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations
014 * under the License.
015 */
016package com.facebook.swift.codec.metadata;
017
018import com.facebook.swift.codec.ThriftConstructor;
019import com.facebook.swift.codec.ThriftField;
020import com.google.common.base.Function;
021import com.google.common.base.Optional;
022import com.google.common.base.Predicate;
023import com.google.common.base.Predicates;
024import com.google.common.collect.ImmutableList;
025import com.google.common.collect.ImmutableSet;
026import com.google.common.collect.Iterables;
027import com.google.common.collect.Lists;
028import com.google.common.collect.Multimap;
029import com.google.common.collect.Multimaps;
030import com.google.common.reflect.TypeToken;
031import com.google.inject.internal.MoreTypes;
032
033import javax.annotation.Nullable;
034import javax.annotation.concurrent.NotThreadSafe;
035
036import java.lang.annotation.Annotation;
037import java.lang.reflect.Constructor;
038import java.lang.reflect.Field;
039import java.lang.reflect.Method;
040import java.lang.reflect.Modifier;
041import java.lang.reflect.ParameterizedType;
042import java.lang.reflect.Type;
043import java.util.Collection;
044import java.util.HashSet;
045import java.util.List;
046import java.util.Map.Entry;
047import java.util.Set;
048
049import static com.facebook.swift.codec.ThriftField.Requiredness;
050import static com.facebook.swift.codec.metadata.FieldMetadata.extractThriftFieldName;
051import static com.facebook.swift.codec.metadata.FieldMetadata.getOrExtractThriftFieldName;
052import static com.facebook.swift.codec.metadata.FieldMetadata.getThriftFieldId;
053import static com.facebook.swift.codec.metadata.FieldMetadata.getThriftFieldName;
054import static com.facebook.swift.codec.metadata.FieldKind.THRIFT_FIELD;
055import static com.facebook.swift.codec.metadata.FieldMetadata.*;
056import static com.facebook.swift.codec.metadata.ReflectionHelper.extractParameterNames;
057import static com.facebook.swift.codec.metadata.ReflectionHelper.findAnnotatedMethods;
058import static com.facebook.swift.codec.metadata.ReflectionHelper.getAllDeclaredFields;
059import static com.facebook.swift.codec.metadata.ReflectionHelper.getAllDeclaredMethods;
060import static com.facebook.swift.codec.metadata.ReflectionHelper.resolveFieldTypes;
061import static com.google.common.base.Preconditions.checkArgument;
062import static com.google.common.base.Preconditions.checkNotNull;
063import static com.google.common.base.Predicates.notNull;
064import static com.google.common.collect.Iterables.filter;
065import static com.google.common.collect.Iterables.transform;
066import static com.google.common.collect.Lists.newArrayList;
067import static com.google.common.collect.Lists.newArrayListWithCapacity;
068import static com.google.common.collect.Sets.newTreeSet;
069
070import static java.util.Arrays.asList;
071
072@NotThreadSafe
073public abstract class AbstractThriftMetadataBuilder
074{
075    protected final String structName;
076    protected final Type structType;
077    protected final Type builderType;
078
079    protected final List<String> documentation;
080    protected final List<FieldMetadata> fields = newArrayList();
081
082    // readers
083    protected final List<Extractor> extractors = newArrayList();
084
085    // writers
086    protected final List<MethodInjection> builderMethodInjections = newArrayList();
087    protected final List<ConstructorInjection> constructorInjections = newArrayList();
088    protected final List<FieldInjection> fieldInjections = newArrayList();
089    protected final List<MethodInjection> methodInjections = newArrayList();
090
091    protected final ThriftCatalog catalog;
092    protected final MetadataErrors metadataErrors;
093
094    protected AbstractThriftMetadataBuilder(ThriftCatalog catalog, Type structType)
095    {
096        this.catalog = checkNotNull(catalog, "catalog is null");
097        this.structType = checkNotNull(structType, "structType is null");
098        this.metadataErrors = new MetadataErrors(catalog.getMonitor());
099
100        // assign the struct name from the annotation or from the Java class
101        structName = extractName();
102        // get the builder type from the annotation or from the Java class
103        builderType = extractBuilderType();
104        // grab any documentation from the annotation or saved JavaDocs
105        documentation = ThriftCatalog.getThriftDocumentation(getStructClass());
106        // extract all of the annotated constructor and report an error if
107        // there is more than one or none
108        // also extract thrift fields from the annotated parameters and verify
109        extractFromConstructors();
110        // extract thrift fields from the annotated fields and verify
111        extractFromFields();
112        // extract thrift fields from the annotated methods (and parameters) and verify
113        extractFromMethods();
114    }
115
116    protected abstract String extractName();
117
118    protected abstract Class<?> extractBuilderClass();
119
120    protected abstract void validateConstructors();
121
122    protected abstract boolean isValidateSetter(Method method);
123
124    protected abstract ThriftFieldMetadata buildField(Collection<FieldMetadata> input);
125
126    public abstract ThriftStructMetadata build();
127
128    public MetadataErrors getMetadataErrors()
129    {
130        return metadataErrors;
131    }
132
133    public Class<?> getStructClass()
134    {
135        return TypeToken.of(structType).getRawType();
136    }
137
138    public Class<?> getBuilderClass()
139    {
140        return TypeToken.of(builderType).getRawType();
141    }
142
143    private Type extractBuilderType()
144    {
145        Class<?> builderClass = extractBuilderClass();
146
147        if (builderClass == null) {
148            return null;
149        }
150
151        if (builderClass.getTypeParameters().length == 0) {
152            return builderClass;
153        }
154
155        if (!(structType instanceof ParameterizedType)) {
156            metadataErrors.addError("Builder class '%s' may only be generic if the type it builds ('%s') is also generic", builderClass.getName(), getStructClass().getName());
157            return builderClass;
158        }
159
160        if (builderClass.getTypeParameters().length != getStructClass().getTypeParameters().length) {
161            metadataErrors.addError("Generic builder class '%s' must have the same number of type parameters as the type it builds ('%s')", builderClass.getName(), getStructClass().getName());
162            return builderClass;
163        }
164
165        ParameterizedType parameterizedStructType = (ParameterizedType) structType;
166
167        return new MoreTypes.ParameterizedTypeImpl(builderClass.getEnclosingClass(), builderClass, parameterizedStructType.getActualTypeArguments());
168    }
169
170
171    protected final void verifyClass(Class<? extends Annotation> annotation)
172    {
173        String annotationName = annotation.getSimpleName();
174        String structClassName = getStructClass().getName();
175
176        // Verify struct class is public and final
177        if (!Modifier.isPublic(getStructClass().getModifiers())) {
178            metadataErrors.addError("%s class '%s' is not public", annotationName, structClassName);
179        }
180        if (!Modifier.isFinal(getStructClass().getModifiers())) {
181            metadataErrors.addError("%s class '%s' is not final (thrift does not support polymorphic data types)", annotationName, structClassName);
182        }
183
184        if (!getStructClass().isAnnotationPresent(annotation)) {
185            metadataErrors.addError("%s class '%s' does not have a @%s annotation", annotationName, structClassName, annotationName);
186        }
187    }
188
189    protected final void extractFromConstructors()
190    {
191        if (builderType == null) {
192            // struct class must have a valid constructor
193            addConstructors(structType);
194        }
195        else {
196            // builder class must have a valid constructor
197            addConstructors(builderType);
198
199            // builder class must have a build method annotated with @ThriftConstructor
200            addBuilderMethods();
201
202            // verify struct class does not have @ThriftConstructors
203            for (Constructor<?> constructor : getStructClass().getConstructors()) {
204                if (constructor.isAnnotationPresent(ThriftConstructor.class)) {
205                    metadataErrors.addWarning("Thrift class '%s' has a builder class, but constructor '%s' annotated with @ThriftConstructor", getStructClass().getName(), constructor);
206                }
207            }
208        }
209    }
210
211    protected final void addConstructors(Type type)
212    {
213        Class<?> clazz = TypeToken.of(type).getRawType();
214
215        for (Constructor<?> constructor : clazz.getConstructors()) {
216            if (constructor.isSynthetic()) {
217                continue;
218            }
219            if (!constructor.isAnnotationPresent(ThriftConstructor.class)) {
220                continue;
221            }
222
223            if (!Modifier.isPublic(constructor.getModifiers())) {
224                metadataErrors.addError("@ThriftConstructor '%s' is not public", constructor.toGenericString());
225                continue;
226            }
227
228            List<ParameterInjection> parameters = getParameterInjections(
229                    type,
230                    constructor.getParameterAnnotations(),
231                    resolveFieldTypes(structType, constructor.getGenericParameterTypes()),
232                    extractParameterNames(constructor));
233            if (parameters != null) {
234                fields.addAll(parameters);
235                constructorInjections.add(new ConstructorInjection(constructor, parameters));
236            }
237        }
238
239        // add the default constructor
240        if (constructorInjections.isEmpty()) {
241            try {
242                Constructor<?> constructor = clazz.getDeclaredConstructor();
243                if (!Modifier.isPublic(constructor.getModifiers())) {
244                    metadataErrors.addError("Default constructor '%s' is not public", constructor.toGenericString());
245                }
246                constructorInjections.add(new ConstructorInjection(constructor));
247            }
248            catch (NoSuchMethodException e) {
249                metadataErrors.addError("Struct class '%s' does not have a public no-arg constructor", clazz.getName());
250            }
251        }
252
253        validateConstructors();
254    }
255
256    protected final void addBuilderMethods()
257    {
258        for (Method method : findAnnotatedMethods(getBuilderClass(), ThriftConstructor.class)) {
259            List<ParameterInjection> parameters = getParameterInjections(
260                    builderType,
261                    method.getParameterAnnotations(),
262                    resolveFieldTypes(builderType, method.getGenericParameterTypes()),
263                    extractParameterNames(method));
264
265            // parameters are null if the method is misconfigured
266            if (parameters != null) {
267                fields.addAll(parameters);
268                builderMethodInjections.add(new MethodInjection(method, parameters));
269            }
270
271            if (!getStructClass().isAssignableFrom(method.getReturnType())) {
272                metadataErrors.addError(
273                        "'%s' says that '%s' is its builder class, but @ThriftConstructor method '%s' in the builder does not build an instance assignable to that type",
274                        structType,
275                        builderType,
276                        method.getName());
277            }
278        }
279
280        // find invalid methods not skipped by findAnnotatedMethods()
281        for (Method method : getAllDeclaredMethods(getBuilderClass())) {
282            if (method.isAnnotationPresent(ThriftConstructor.class) || hasThriftFieldAnnotation(method)) {
283                if (!Modifier.isPublic(method.getModifiers())) {
284                    metadataErrors.addError("@ThriftConstructor method '%s' is not public", method.toGenericString());
285                }
286                if (Modifier.isStatic(method.getModifiers())) {
287                    metadataErrors.addError("@ThriftConstructor method '%s' is static", method.toGenericString());
288                }
289            }
290        }
291
292        if (builderMethodInjections.isEmpty()) {
293            metadataErrors.addError("Struct builder class '%s' does not have a public builder method annotated with @ThriftConstructor", getBuilderClass().getName());
294        }
295        if (builderMethodInjections.size() > 1) {
296            metadataErrors.addError("Multiple builder methods are annotated with @ThriftConstructor ", builderMethodInjections);
297        }
298    }
299
300    protected final void extractFromFields()
301    {
302        if (builderType == null) {
303            // struct fields are readable and writable
304            addFields(getStructClass(), true, true);
305        }
306        else {
307            // builder fields are writable
308            addFields(getBuilderClass(), false, true);
309            // struct fields are readable
310            addFields(getStructClass(), true, false);
311        }
312    }
313
314    protected final void addFields(Class<?> clazz, boolean allowReaders, boolean allowWriters)
315    {
316        for (Field fieldField : ReflectionHelper.findAnnotatedFields(clazz, ThriftField.class)) {
317            addField(fieldField, allowReaders, allowWriters);
318        }
319
320        // find invalid fields not skipped by findAnnotatedFields()
321        for (Field field : getAllDeclaredFields(clazz)) {
322            if (field.isAnnotationPresent(ThriftField.class)) {
323                if (!Modifier.isPublic(field.getModifiers())) {
324                    metadataErrors.addError("@ThriftField field '%s' is not public", field.toGenericString());
325                }
326                if (Modifier.isStatic(field.getModifiers())) {
327                    metadataErrors.addError("@ThriftField field '%s' is static", field.toGenericString());
328                }
329            }
330        }
331    }
332
333    protected final void addField(Field fieldField, boolean allowReaders, boolean allowWriters)
334    {
335        checkArgument(fieldField.isAnnotationPresent(ThriftField.class));
336
337        ThriftField annotation = fieldField.getAnnotation(ThriftField.class);
338        if (allowReaders) {
339            FieldExtractor fieldExtractor = new FieldExtractor(structType, fieldField, annotation, THRIFT_FIELD);
340            fields.add(fieldExtractor);
341            extractors.add(fieldExtractor);
342        }
343        if (allowWriters) {
344            FieldInjection fieldInjection = new FieldInjection(structType, fieldField, annotation, THRIFT_FIELD);
345            fields.add(fieldInjection);
346            fieldInjections.add(fieldInjection);
347        }
348    }
349
350    protected final void extractFromMethods()
351    {
352        if (builderType != null) {
353            // builder methods are writable
354            addMethods(builderType, false, true);
355            // struct methods are readable
356            addMethods(structType, true, false);
357        }
358        else {
359            // struct methods are readable and writable
360            addMethods(structType, true, true);
361        }
362    }
363
364    protected final void addMethods(Type type, boolean allowReaders, boolean allowWriters)
365    {
366        Class<?> clazz = TypeToken.of(type).getRawType();
367
368        for (Method fieldMethod : findAnnotatedMethods(clazz, ThriftField.class)) {
369            addMethod(type, fieldMethod, allowReaders, allowWriters);
370        }
371
372        // find invalid methods not skipped by findAnnotatedMethods()
373        for (Method method : getAllDeclaredMethods(clazz)) {
374            if (method.isAnnotationPresent(ThriftField.class) || hasThriftFieldAnnotation(method)) {
375                if (!Modifier.isPublic(method.getModifiers())) {
376                    metadataErrors.addError("@ThriftField method '%s' is not public", method.toGenericString());
377                }
378                if (Modifier.isStatic(method.getModifiers())) {
379                    metadataErrors.addError("@ThriftField method '%s' is static", method.toGenericString());
380                }
381            }
382        }
383    }
384
385    protected final void addMethod(Type type, Method method, boolean allowReaders, boolean allowWriters)
386    {
387        checkArgument(method.isAnnotationPresent(ThriftField.class));
388
389        ThriftField annotation = method.getAnnotation(ThriftField.class);
390        Class<?> clazz = TypeToken.of(type).getRawType();
391
392        // verify parameters
393        if (isValidateGetter(method)) {
394            if (allowReaders) {
395                MethodExtractor methodExtractor = new MethodExtractor(type, method, annotation, THRIFT_FIELD);
396                fields.add(methodExtractor);
397                extractors.add(methodExtractor);
398            }
399            else {
400                metadataErrors.addError("Reader method %s.%s is not allowed on a builder class", clazz.getName(), method.getName());
401            }
402        }
403        else if (isValidateSetter(method)) {
404            if (allowWriters) {
405                List<ParameterInjection> parameters;
406                if (method.getParameterTypes().length > 1 || Iterables.any(asList(method.getParameterAnnotations()[0]), Predicates.instanceOf(ThriftField.class))) {
407                    parameters = getParameterInjections(
408                            type,
409                            method.getParameterAnnotations(),
410                            resolveFieldTypes(type, method.getGenericParameterTypes()),
411                            extractParameterNames(method));
412                    if (annotation.value() != Short.MIN_VALUE) {
413                        metadataErrors.addError("A method with annotated parameters can not have a field id specified: %s.%s ", clazz.getName(), method.getName());
414                    }
415                    if (!annotation.name().isEmpty()) {
416                        metadataErrors.addError("A method with annotated parameters can not have a field name specified: %s.%s ", clazz.getName(), method.getName());
417                    }
418                    if (annotation.requiredness() == Requiredness.REQUIRED) {
419                        metadataErrors.addError("A method with annotated parameters can not be marked as required: %s.%s ", clazz.getName(), method.getName());
420                    }
421                }
422                else {
423                    Type parameterType = resolveFieldTypes(type, method.getGenericParameterTypes())[0];
424                    parameters = ImmutableList.of(new ParameterInjection(type, 0, annotation, ReflectionHelper.extractFieldName(method), parameterType));
425                }
426                fields.addAll(parameters);
427                methodInjections.add(new MethodInjection(method, parameters));
428            }
429            else {
430                metadataErrors.addError("Inject method %s.%s is not allowed on struct class, since struct has a builder", clazz.getName(), method.getName());
431            }
432        }
433        else {
434            metadataErrors.addError("Method %s.%s is not a supported getter or setter", clazz.getName(), method.getName());
435        }
436    }
437
438    protected final boolean hasThriftFieldAnnotation(Method method)
439    {
440        for (Annotation[] parameterAnnotations : method.getParameterAnnotations()) {
441            for (Annotation parameterAnnotation : parameterAnnotations) {
442                if (parameterAnnotation instanceof ThriftField) {
443                    return true;
444                }
445            }
446        }
447        return false;
448    }
449
450    protected final boolean isValidateGetter(Method method)
451    {
452        return method.getParameterTypes().length == 0 && method.getReturnType() != void.class;
453    }
454
455    protected final List<ParameterInjection> getParameterInjections(Type type, Annotation[][] parameterAnnotations, Type[] parameterTypes, String[] parameterNames)
456    {
457        List<ParameterInjection> parameters = newArrayListWithCapacity(parameterAnnotations.length);
458        for (int parameterIndex = 0; parameterIndex < parameterAnnotations.length; parameterIndex++) {
459            Annotation[] annotations = parameterAnnotations[parameterIndex];
460            Type parameterType = parameterTypes[parameterIndex];
461
462            ThriftField thriftField = null;
463            for (Annotation annotation : annotations) {
464                if (annotation instanceof ThriftField) {
465                    thriftField = (ThriftField) annotation;
466                }
467            }
468
469            ParameterInjection parameterInjection = new ParameterInjection(
470                    type,
471                    parameterIndex,
472                    thriftField,
473                    parameterNames[parameterIndex],
474                    parameterType
475            );
476
477            parameters.add(parameterInjection);
478        }
479        return parameters;
480    }
481
482
483    protected final void normalizeThriftFields(ThriftCatalog catalog)
484    {
485        // assign all fields an id (if possible)
486        Set<String> fieldsWithConflictingIds = inferThriftFieldIds();
487
488        // group fields by id
489        Multimap<Optional<Short>, FieldMetadata> fieldsById = Multimaps.index(fields, getThriftFieldId());
490        for (Entry<Optional<Short>, Collection<FieldMetadata>> entry : fieldsById.asMap().entrySet()) {
491            Collection<FieldMetadata> fields = entry.getValue();
492
493            // fields must have an id
494            if (!entry.getKey().isPresent()) {
495                for (String fieldName : newTreeSet(transform(fields, getOrExtractThriftFieldName()))) {
496                    // only report errors for fields that don't have conflicting ids
497                    if (!fieldsWithConflictingIds.contains(fieldName)) {
498                        metadataErrors.addError("Thrift class '%s' fields %s do not have an id", structName, newTreeSet(transform(fields, getOrExtractThriftFieldName())));
499                    }
500                }
501                continue;
502            }
503
504            short fieldId = entry.getKey().get();
505
506            // assure all fields for this ID have the same name
507            String fieldName = extractFieldName(fieldId, fields);
508            for (FieldMetadata field : fields) {
509                field.setName(fieldName);
510            }
511
512            Requiredness requiredness = extractFieldRequiredness(fieldId, fieldName, fields);
513            for (FieldMetadata field : fields) {
514                field.setRequiredness(requiredness);
515            }
516
517            // verify fields have a supported java type and all fields
518            // for this ID have the same thrift type
519            verifyFieldType(fieldId, fieldName, fields, catalog);
520        }
521    }
522
523    /**
524     * Assigns all fields an id if possible.  Fields are grouped by name and for each group, if there
525     * is a single id, all fields in the group are assigned this id.  If the group has multiple ids,
526     * an error is reported.
527     */
528    protected final Set<String> inferThriftFieldIds()
529    {
530        Set<String> fieldsWithConflictingIds = new HashSet<>();
531
532        // group fields by explicit name or by name extracted from field, method or property
533        Multimap<String, FieldMetadata> fieldsByExplicitOrExtractedName = Multimaps.index(fields, getOrExtractThriftFieldName());
534        inferThriftFieldIds(fieldsByExplicitOrExtractedName, fieldsWithConflictingIds);
535
536        // group fields by name extracted from field, method or property
537        // this allows thrift name to be set explicitly without having to duplicate the name on getters and setters
538        // todo should this be the only way this works?
539        Multimap<String, FieldMetadata> fieldsByExtractedName = Multimaps.index(fields, extractThriftFieldName());
540        inferThriftFieldIds(fieldsByExtractedName, fieldsWithConflictingIds);
541
542        return fieldsWithConflictingIds;
543    }
544
545    protected final void inferThriftFieldIds(Multimap<String, FieldMetadata> fieldsByName, Set<String> fieldsWithConflictingIds)
546    {
547        // for each name group, set the ids on the fields without ids
548        for (Entry<String, Collection<FieldMetadata>> entry : fieldsByName.asMap().entrySet()) {
549            Collection<FieldMetadata> fields = entry.getValue();
550
551            // skip all entries without a name or singleton groups... we'll deal with these later
552            if (fields.size() <= 1) {
553                continue;
554            }
555
556            // all ids used by this named field
557            Set<Short> ids = ImmutableSet.copyOf(Optional.presentInstances(transform(fields, getThriftFieldId())));
558
559            // multiple conflicting ids
560            if (ids.size() > 1) {
561                String fieldName = entry.getKey();
562                if (!fieldsWithConflictingIds.contains(fieldName)) {
563                    metadataErrors.addError("Thrift class '%s' field '%s' has multiple ids: %s", structName, fieldName, ids.toString());
564                    fieldsWithConflictingIds.add(fieldName);
565                }
566                continue;
567            }
568
569            // single id, so set on all fields in this group (groups with no id are handled later)
570            if (ids.size() == 1) {
571                // propagate the id to all fields in this group
572                short id = Iterables.getOnlyElement(ids);
573                for (FieldMetadata field : fields) {
574                    field.setId(id);
575                }
576            }
577        }
578    }
579
580    protected final String extractFieldName(short id, Collection<FieldMetadata> fields)
581    {
582        // get the names used by these fields
583        Set<String> names = ImmutableSet.copyOf(filter(transform(fields, getThriftFieldName()), notNull()));
584
585        String name;
586        if (!names.isEmpty()) {
587            if (names.size() > 1) {
588                metadataErrors.addWarning("Thrift class %s field %s has multiple names %s", structName, id, names);
589            }
590            name = names.iterator().next();
591        }
592        else {
593            // pick a name for this field
594            name = Iterables.find(transform(fields, extractThriftFieldName()), notNull());
595        }
596        return name;
597    }
598
599    protected final Requiredness extractFieldRequiredness(short fieldId, String fieldName, Collection<FieldMetadata> fields)
600    {
601        Predicate<Requiredness> specificRequiredness = new Predicate<Requiredness>()
602        {
603            @Override
604            public boolean apply(@Nullable Requiredness input)
605            {
606                return (input != null) && (input != Requiredness.UNSPECIFIED);
607            }
608        };
609
610        Set<Requiredness> requirednessValues = ImmutableSet.copyOf(filter(transform(fields, getThriftFieldRequiredness()), specificRequiredness));
611
612        if (requirednessValues.size() > 1) {
613            metadataErrors.addError("Thrift class '%s' field '%s(%d)' has multiple requiredness values: %s", structName, fieldName, fieldId, requirednessValues.toString());
614        }
615
616        Requiredness resolvedRequiredness;
617        if (requirednessValues.isEmpty()) {
618            resolvedRequiredness = Requiredness.NONE;
619        }
620        else {
621            resolvedRequiredness = requirednessValues.iterator().next();
622        }
623
624        return resolvedRequiredness;
625    }
626
627    /**
628     * Verifies that the the fields all have a supported Java type and that all fields map to the
629     * exact same ThriftType.
630     */
631    protected final void verifyFieldType(short id, String name, Collection<FieldMetadata> fields, ThriftCatalog catalog)
632    {
633        boolean isSupportedType = true;
634        for (FieldMetadata field : fields) {
635            if (!catalog.isSupportedStructFieldType(field.getJavaType())) {
636                metadataErrors.addError("Thrift class '%s' field '%s(%s)' type '%s' is not a supported Java type", structName, name, id, TypeToken.of(field.getJavaType()));
637                isSupportedType = false;
638                // only report the error once
639                break;
640            }
641        }
642
643        // fields must have the same type
644        if (isSupportedType) {
645            Set<ThriftType> types = new HashSet<>();
646            for (FieldMetadata field : fields) {
647                types.add(catalog.getThriftType(field.getJavaType()));
648            }
649            if (types.size() > 1) {
650                metadataErrors.addError("Thrift class '%s' field '%s(%s)' has multiple types: %s", structName, name, id, types);
651            }
652        }
653    }
654
655
656    protected final ThriftMethodInjection buildBuilderConstructorInjections()
657    {
658        ThriftMethodInjection builderMethodInjection = null;
659        if (builderType != null) {
660            MethodInjection builderMethod = builderMethodInjections.get(0);
661            builderMethodInjection = new ThriftMethodInjection(builderMethod.getMethod(), buildParameterInjections(builderMethod.getParameters()));
662        }
663        return builderMethodInjection;
664    }
665
666    protected final Iterable<ThriftFieldMetadata> buildFieldInjections()
667    {
668        Multimap<Optional<Short>, FieldMetadata> fieldsById = Multimaps.index(fields, getThriftFieldId());
669        return Iterables.transform(fieldsById.asMap().values(), new Function<Collection<FieldMetadata>, ThriftFieldMetadata>()
670        {
671            @Override
672            public ThriftFieldMetadata apply(Collection<FieldMetadata> input)
673            {
674                checkArgument(!input.isEmpty(), "input is empty");
675                return buildField(input);
676            }
677        });
678    }
679
680    protected final List<ThriftMethodInjection> buildMethodInjections()
681    {
682        return Lists.transform(methodInjections, new Function<MethodInjection, ThriftMethodInjection>()
683        {
684            @Override
685            public ThriftMethodInjection apply(MethodInjection injection)
686            {
687                return new ThriftMethodInjection(injection.getMethod(), buildParameterInjections(injection.getParameters()));
688            }
689        });
690    }
691
692    protected final List<ThriftParameterInjection> buildParameterInjections(List<ParameterInjection> parameters)
693    {
694        return Lists.transform(parameters, new Function<ParameterInjection, ThriftParameterInjection>()
695        {
696            @Override
697            public ThriftParameterInjection apply(ParameterInjection injection)
698            {
699                return new ThriftParameterInjection(
700                        injection.getId(),
701                        injection.getName(),
702                        injection.getParameterIndex(),
703                        injection.getJavaType()
704                );
705            }
706        });
707    }
708}
709