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.ThriftStruct;
019import com.facebook.swift.codec.metadata.ThriftStructMetadata.MetadataType;
020import com.google.common.base.Function;
021import com.google.common.base.Optional;
022import com.google.common.collect.ImmutableList;
023import com.google.common.collect.Iterables;
024import com.google.common.collect.Lists;
025
026import javax.annotation.concurrent.NotThreadSafe;
027
028import java.lang.reflect.Method;
029import java.lang.reflect.Type;
030import java.util.Collection;
031import java.util.List;
032
033import static com.facebook.swift.codec.ThriftField.Requiredness;
034import static com.facebook.swift.codec.metadata.FieldKind.THRIFT_FIELD;
035
036@NotThreadSafe
037public class ThriftStructMetadataBuilder
038    extends AbstractThriftMetadataBuilder
039{
040    public ThriftStructMetadataBuilder(ThriftCatalog catalog, Type structType)
041    {
042        super(catalog, structType);
043
044        // verify the class is public and has the correct annotations
045        verifyClass(ThriftStruct.class);
046
047        // finally normalize the field metadata using things like
048        normalizeThriftFields(catalog);
049    }
050
051    @Override
052    protected String extractName()
053    {
054        ThriftStruct annotation = getStructClass().getAnnotation(ThriftStruct.class);
055        if (annotation == null) {
056            return getStructClass().getSimpleName();
057        }
058        else if (!annotation.value().isEmpty()) {
059            return annotation.value();
060        }
061        else {
062            return getStructClass().getSimpleName();
063        }
064    }
065
066    @Override
067    protected Class<?> extractBuilderClass()
068    {
069        ThriftStruct annotation = getStructClass().getAnnotation(ThriftStruct.class);
070        if (annotation != null && !annotation.builder().equals(void.class)) {
071            return annotation.builder();
072        }
073        else {
074            return null;
075        }
076    }
077
078    @Override
079    protected void validateConstructors()
080    {
081        if (constructorInjections.size() > 1) {
082            metadataErrors.addError("Multiple constructors are annotated with @ThriftConstructor ", constructorInjections);
083        }
084    }
085
086    @Override
087    protected boolean isValidateSetter(Method method)
088    {
089        return method.getParameterTypes().length >= 1;
090    }
091
092    //
093    // Build final metadata
094    //
095    @Override
096    public ThriftStructMetadata build()
097    {
098        // this code assumes that metadata is clean
099        metadataErrors.throwIfHasErrors();
100
101        // builder constructor injection
102        ThriftMethodInjection builderMethodInjection = buildBuilderConstructorInjections();
103
104        // constructor injection (or factory method for builder)
105        ThriftConstructorInjection constructorInjections = buildConstructorInjection();
106
107        // fields injections
108        Iterable<ThriftFieldMetadata> fieldsMetadata = buildFieldInjections();
109
110        // methods injections
111        List<ThriftMethodInjection> methodInjections = buildMethodInjections();
112
113        return new ThriftStructMetadata(
114                structName,
115                structType,
116                builderType,
117                MetadataType.STRUCT,
118                Optional.fromNullable(builderMethodInjection),
119                ImmutableList.copyOf(documentation),
120                ImmutableList.copyOf(fieldsMetadata),
121                Optional.of(constructorInjections),
122                methodInjections
123        );
124    }
125
126    private ThriftConstructorInjection buildConstructorInjection()
127    {
128        return Iterables.getOnlyElement(Lists.transform(constructorInjections, new Function<ConstructorInjection, ThriftConstructorInjection>()
129        {
130            @Override
131            public ThriftConstructorInjection apply(ConstructorInjection injection)
132            {
133                return new ThriftConstructorInjection(injection.getConstructor(), buildParameterInjections(injection.getParameters()));
134            }
135        }));
136    }
137
138    @Override
139    protected ThriftFieldMetadata buildField(Collection<FieldMetadata> input)
140    {
141        short id = -1;
142        String name = null;
143        Requiredness requiredness = Requiredness.UNSPECIFIED;
144        ThriftType type = null;
145
146        // process field injections and extractions
147        ImmutableList.Builder<ThriftInjection> injections = ImmutableList.builder();
148        ThriftExtraction extraction = null;
149        for (FieldMetadata fieldMetadata : input) {
150            id = fieldMetadata.getId();
151            name = fieldMetadata.getName();
152            requiredness = fieldMetadata.getRequiredness();
153            type = catalog.getThriftType(fieldMetadata.getJavaType());
154
155            if (fieldMetadata instanceof FieldInjection) {
156                FieldInjection fieldInjection = (FieldInjection) fieldMetadata;
157                injections.add(new ThriftFieldInjection(fieldInjection.getId(), fieldInjection.getName(), fieldInjection.getField(), fieldInjection.getType()));
158            }
159            else if (fieldMetadata instanceof ParameterInjection) {
160                ParameterInjection parameterInjection = (ParameterInjection) fieldMetadata;
161                injections.add(new ThriftParameterInjection(
162                        parameterInjection.getId(),
163                        parameterInjection.getName(),
164                        parameterInjection.getParameterIndex(),
165                        fieldMetadata.getJavaType()
166                ));
167            }
168            else if (fieldMetadata instanceof FieldExtractor) {
169                FieldExtractor fieldExtractor = (FieldExtractor) fieldMetadata;
170                extraction = new ThriftFieldExtractor(fieldExtractor.getId(), fieldExtractor.getName(), fieldExtractor.getType(), fieldExtractor.getField(), fieldExtractor.getJavaType());
171            }
172            else if (fieldMetadata instanceof MethodExtractor) {
173                MethodExtractor methodExtractor = (MethodExtractor) fieldMetadata;
174                extraction = new ThriftMethodExtractor(methodExtractor.getId(), methodExtractor.getName(), methodExtractor.getType(), methodExtractor.getMethod(), methodExtractor.getJavaType());
175            }
176        }
177
178        // add type coercion
179        TypeCoercion coercion = null;
180        if (type.isCoerced()) {
181            coercion = catalog.getDefaultCoercion(type.getJavaType());
182        }
183
184        ThriftFieldMetadata thriftFieldMetadata = new ThriftFieldMetadata(
185                id,
186                requiredness,
187                type,
188                name,
189                THRIFT_FIELD,
190                injections.build(),
191                Optional.<ThriftConstructorInjection>absent(),
192                Optional.<ThriftMethodInjection>absent(),
193                Optional.fromNullable(extraction),
194                Optional.fromNullable(coercion)
195        );
196        return thriftFieldMetadata;
197    }
198}