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