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.ThriftField; 019import com.facebook.swift.codec.ThriftUnion; 020import com.facebook.swift.codec.ThriftUnionId; 021import com.facebook.swift.codec.metadata.ThriftStructMetadata.MetadataType; 022import com.google.common.base.Optional; 023import com.google.common.collect.ImmutableList; 024 025import javax.annotation.concurrent.NotThreadSafe; 026 027import java.lang.reflect.Field; 028import java.lang.reflect.Method; 029import java.lang.reflect.Modifier; 030import java.lang.reflect.Type; 031import java.util.Collection; 032import java.util.List; 033 034import static com.facebook.swift.codec.ThriftField.Requiredness; 035import static com.facebook.swift.codec.metadata.FieldKind.THRIFT_UNION_ID; 036import static com.facebook.swift.codec.metadata.ReflectionHelper.findAnnotatedMethods; 037 038@NotThreadSafe 039public class ThriftUnionMetadataBuilder 040 extends AbstractThriftMetadataBuilder 041{ 042 public ThriftUnionMetadataBuilder(ThriftCatalog catalog, Type structType) 043 { 044 super(catalog, structType); 045 046 // verify the class is public and has the correct annotations 047 verifyClass(ThriftUnion.class); 048 049 // extract the @ThriftUnionId fields 050 extractThriftUnionId(); 051 052 // finally normalize the field metadata using things like 053 normalizeThriftFields(catalog); 054 } 055 056 @Override 057 protected String extractName() 058 { 059 ThriftUnion annotation = getStructClass().getAnnotation(ThriftUnion.class); 060 if (annotation == null) { 061 return getStructClass().getSimpleName(); 062 } 063 else if (!annotation.value().isEmpty()) { 064 return annotation.value(); 065 } 066 else { 067 return getStructClass().getSimpleName(); 068 } 069 } 070 071 @Override 072 protected Class<?> extractBuilderClass() 073 { 074 ThriftUnion annotation = getStructClass().getAnnotation(ThriftUnion.class); 075 if (annotation != null && !annotation.builder().equals(void.class)) { 076 return annotation.builder(); 077 } 078 else { 079 return null; 080 } 081 } 082 083 private void extractThriftUnionId() 084 { 085 Collection<Field> idFields = ReflectionHelper.findAnnotatedFields(getStructClass(), ThriftUnionId.class); 086 Collection<Method> idMethods = findAnnotatedMethods(getStructClass(), ThriftUnionId.class); 087 088 if (idFields.size() + idMethods.size() != 1) { 089 if (idFields.size() + idMethods.size() == 0) { 090 metadataErrors.addError("Neither a field nor a method is annotated with @ThriftUnionId"); 091 } 092 else if (idFields.size() > 1) { 093 metadataErrors.addError("More than one @ThriftUnionId field present"); 094 } 095 else if (idMethods.size() > 1) { 096 metadataErrors.addError("More than one @ThriftUnionId method present"); 097 } 098 else { 099 metadataErrors.addError("Both fields and methods annotated with @ThriftUnionId"); 100 } 101 return; 102 } 103 104 for (Field idField : idFields) { 105 FieldExtractor fieldExtractor = new FieldExtractor(structType, idField, null, THRIFT_UNION_ID); 106 fields.add(fieldExtractor); 107 extractors.add(fieldExtractor); 108 109 FieldInjection fieldInjection = new FieldInjection(structType, idField, null, THRIFT_UNION_ID); 110 fields.add(fieldInjection); 111 fieldInjections.add(fieldInjection); 112 } 113 114 for (Method idMethod: idMethods) { 115 if (!Modifier.isPublic(idMethod.getModifiers())) { 116 metadataErrors.addError("@ThriftUnionId method [%s] is not public", idMethod.toGenericString()); 117 continue; 118 } 119 if (Modifier.isStatic(idMethod.getModifiers())) { 120 metadataErrors.addError("@ThriftUnionId method [%s] is static", idMethod.toGenericString()); 121 continue; 122 } 123 124 if (isValidateGetter(idMethod)) { 125 MethodExtractor methodExtractor = new MethodExtractor(structType, idMethod, null, THRIFT_UNION_ID); 126 fields.add(methodExtractor); 127 extractors.add(methodExtractor); 128 } 129 } 130 } 131 132 @Override 133 protected void validateConstructors() 134 { 135 for (ConstructorInjection constructorInjection : constructorInjections) { 136 if (constructorInjection.getParameters().size() > 1) { 137 metadataErrors.addError("@ThriftConstructor [%s] takes %d arguments, this is illegal for an union", 138 constructorInjection.getConstructor().toGenericString(), 139 constructorInjection.getParameters().size()); 140 } 141 } 142 } 143 144 @Override 145 protected boolean isValidateSetter(Method method) 146 { 147 // Unions only allow setters with exactly one parameters 148 return method.getParameterTypes().length == 1; 149 } 150 151 // 152 // Build final metadata 153 // 154 @Override 155 public ThriftStructMetadata build() 156 { 157 // this code assumes that metadata is clean 158 metadataErrors.throwIfHasErrors(); 159 160 // builder constructor injection 161 ThriftMethodInjection builderMethodInjection = buildBuilderConstructorInjections(); 162 163 // constructor injection (or factory method for builder) 164 ThriftConstructorInjection constructorInjection = buildConstructorInjection(); 165 166 // fields injections 167 Iterable<ThriftFieldMetadata> fieldsMetadata = buildFieldInjections(); 168 169 // methods injections 170 List<ThriftMethodInjection> methodInjections = buildMethodInjections(); 171 172 return new ThriftStructMetadata( 173 structName, 174 structType, 175 builderType, 176 MetadataType.UNION, 177 Optional.fromNullable(builderMethodInjection), 178 ImmutableList.copyOf(documentation), 179 ImmutableList.copyOf(fieldsMetadata), 180 Optional.fromNullable(constructorInjection), 181 methodInjections 182 ); 183 } 184 185 private ThriftConstructorInjection buildConstructorInjection() 186 { 187 for (ConstructorInjection constructorInjection : constructorInjections) { 188 if (constructorInjection.getParameters().size() == 0) { 189 return new ThriftConstructorInjection(constructorInjection.getConstructor(), buildParameterInjections(constructorInjection.getParameters())); 190 } 191 } 192 193 // This is actually legal for a ThriftUnion, all c'tors available take arguments and are associated with the FieldMetadata... 194 return null; 195 } 196 197 @Override 198 protected ThriftFieldMetadata buildField(Collection<FieldMetadata> input) 199 { 200 short id = -1; 201 String name = null; 202 Requiredness requiredness = Requiredness.UNSPECIFIED; 203 FieldKind fieldType = FieldKind.THRIFT_FIELD; 204 ThriftType thriftType = null; 205 ThriftConstructorInjection thriftConstructorInjection = null; 206 ThriftMethodInjection thriftMethodInjection = null; 207 208 // process field injections and extractions 209 ImmutableList.Builder<ThriftInjection> injections = ImmutableList.builder(); 210 ThriftExtraction extraction = null; 211 for (FieldMetadata fieldMetadata : input) { 212 id = fieldMetadata.getId(); 213 name = fieldMetadata.getName(); 214 requiredness = fieldMetadata.getRequiredness(); 215 fieldType = fieldMetadata.getType(); 216 thriftType = catalog.getThriftType(fieldMetadata.getJavaType()); 217 218 switch (requiredness) { 219 case REQUIRED: 220 case OPTIONAL: 221 metadataErrors.addError( 222 "Thrift union '%s' field '%s(%s)' should not be marked required or optional", 223 structName, 224 name, 225 id); 226 break; 227 228 default: 229 break; 230 } 231 232 if (fieldMetadata instanceof FieldInjection) { 233 FieldInjection fieldInjection = (FieldInjection) fieldMetadata; 234 injections.add(new ThriftFieldInjection(fieldInjection.getId(), fieldInjection.getName(), fieldInjection.getField(), fieldInjection.getType())); 235 } 236 else if (fieldMetadata instanceof ParameterInjection) { 237 ParameterInjection parameterInjection = (ParameterInjection) fieldMetadata; 238 ThriftParameterInjection thriftParameterInjection = new ThriftParameterInjection( 239 parameterInjection.getId(), 240 parameterInjection.getName(), 241 parameterInjection.getParameterIndex(), 242 fieldMetadata.getJavaType() 243 ); 244 injections.add(thriftParameterInjection); 245 246 for (ConstructorInjection constructorInjection : constructorInjections) { 247 if (constructorInjection.getParameters().size() == 1 && constructorInjection.getParameters().get(0).equals(parameterInjection)) { 248 thriftConstructorInjection = new ThriftConstructorInjection(constructorInjection.getConstructor(), thriftParameterInjection); 249 break; 250 } 251 } 252 253 for (MethodInjection methodInjection : methodInjections) { 254 if (methodInjection.getParameters().size() == 1 && methodInjection.getParameters().get(0).equals(parameterInjection)) { 255 thriftMethodInjection = new ThriftMethodInjection(methodInjection.getMethod(), thriftParameterInjection); 256 } 257 } 258 } 259 else if (fieldMetadata instanceof FieldExtractor) { 260 FieldExtractor fieldExtractor = (FieldExtractor) fieldMetadata; 261 extraction = new ThriftFieldExtractor(fieldExtractor.getId(), fieldExtractor.getName(), fieldExtractor.getType(), fieldExtractor.getField(), fieldExtractor.getJavaType()); 262 } 263 else if (fieldMetadata instanceof MethodExtractor) { 264 MethodExtractor methodExtractor = (MethodExtractor) fieldMetadata; 265 extraction = new ThriftMethodExtractor(methodExtractor.getId(), methodExtractor.getName(), methodExtractor.getType(), methodExtractor.getMethod(), methodExtractor.getJavaType()); 266 } 267 } 268 269 // add type coercion 270 TypeCoercion coercion = null; 271 if (thriftType.isCoerced()) { 272 coercion = catalog.getDefaultCoercion(thriftType.getJavaType()); 273 } 274 275 ThriftFieldMetadata thriftFieldMetadata = new ThriftFieldMetadata( 276 id, 277 requiredness, 278 thriftType, 279 name, 280 fieldType, 281 injections.build(), 282 Optional.fromNullable(thriftConstructorInjection), 283 Optional.fromNullable(thriftMethodInjection), 284 Optional.fromNullable(extraction), 285 Optional.fromNullable(coercion) 286 ); 287 return thriftFieldMetadata; 288 } 289}