|
22 | 22 | import org.apache.beam.sdk.options.Validation.Required; |
23 | 23 | import org.apache.beam.sdk.runners.PipelineRunner; |
24 | 24 | import org.apache.beam.sdk.runners.PipelineRunnerRegistrar; |
| 25 | +import org.apache.beam.sdk.transforms.display.DisplayData; |
25 | 26 | import org.apache.beam.sdk.util.StringUtils; |
26 | 27 | import org.apache.beam.sdk.util.common.ReflectHelpers; |
27 | | - |
| 28 | +import com.google.common.annotations.VisibleForTesting; |
28 | 29 | import com.google.common.base.Function; |
29 | 30 | import com.google.common.base.Joiner; |
30 | 31 | import com.google.common.base.Optional; |
31 | 32 | import com.google.common.base.Preconditions; |
32 | 33 | import com.google.common.base.Predicate; |
33 | 34 | import com.google.common.base.Strings; |
34 | 35 | import com.google.common.base.Throwables; |
35 | | -import com.google.common.collect.ArrayListMultimap; |
36 | 36 | import com.google.common.collect.Collections2; |
37 | 37 | import com.google.common.collect.FluentIterable; |
38 | 38 | import com.google.common.collect.ImmutableListMultimap; |
|
43 | 43 | import com.google.common.collect.ListMultimap; |
44 | 44 | import com.google.common.collect.Lists; |
45 | 45 | import com.google.common.collect.Maps; |
| 46 | +import com.google.common.collect.Ordering; |
| 47 | +import com.google.common.collect.RowSortedTable; |
46 | 48 | import com.google.common.collect.Sets; |
47 | 49 | import com.google.common.collect.SortedSetMultimap; |
| 50 | +import com.google.common.collect.TreeBasedTable; |
48 | 51 | import com.google.common.collect.TreeMultimap; |
49 | 52 |
|
50 | 53 | import com.fasterxml.jackson.annotation.JsonIgnore; |
|
77 | 80 | import java.util.Set; |
78 | 81 | import java.util.SortedMap; |
79 | 82 | import java.util.SortedSet; |
| 83 | +import java.util.TreeMap; |
80 | 84 | import java.util.TreeSet; |
81 | 85 |
|
82 | 86 | import javax.annotation.Nullable; |
@@ -444,6 +448,7 @@ Class<T> getProxyClass() { |
444 | 448 | @SuppressWarnings("rawtypes") |
445 | 449 | private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class[0]; |
446 | 450 | private static final ObjectMapper MAPPER = new ObjectMapper(); |
| 451 | + private static final ClassLoader CLASS_LOADER; |
447 | 452 | private static final Map<String, Class<? extends PipelineRunner<?>>> SUPPORTED_PIPELINE_RUNNERS; |
448 | 453 |
|
449 | 454 | /** Classes that are used as the boundary in the stack trace to find the callers class name. */ |
@@ -510,33 +515,22 @@ static ClassLoader findClassLoader() { |
510 | 515 | throw new ExceptionInInitializerError(e); |
511 | 516 | } |
512 | 517 |
|
513 | | - ClassLoader classLoader = findClassLoader(); |
| 518 | + CLASS_LOADER = findClassLoader(); |
514 | 519 |
|
515 | 520 | // Store the list of all available pipeline runners. |
516 | 521 | ImmutableMap.Builder<String, Class<? extends PipelineRunner<?>>> builder = |
517 | 522 | ImmutableMap.builder(); |
518 | 523 | Set<PipelineRunnerRegistrar> pipelineRunnerRegistrars = |
519 | 524 | Sets.newTreeSet(ObjectsClassComparator.INSTANCE); |
520 | 525 | pipelineRunnerRegistrars.addAll( |
521 | | - Lists.newArrayList(ServiceLoader.load(PipelineRunnerRegistrar.class, classLoader))); |
| 526 | + Lists.newArrayList(ServiceLoader.load(PipelineRunnerRegistrar.class, CLASS_LOADER))); |
522 | 527 | for (PipelineRunnerRegistrar registrar : pipelineRunnerRegistrars) { |
523 | 528 | for (Class<? extends PipelineRunner<?>> klass : registrar.getPipelineRunners()) { |
524 | 529 | builder.put(klass.getSimpleName(), klass); |
525 | 530 | } |
526 | 531 | } |
527 | 532 | SUPPORTED_PIPELINE_RUNNERS = builder.build(); |
528 | | - |
529 | | - // Load and register the list of all classes that extend PipelineOptions. |
530 | | - register(PipelineOptions.class); |
531 | | - Set<PipelineOptionsRegistrar> pipelineOptionsRegistrars = |
532 | | - Sets.newTreeSet(ObjectsClassComparator.INSTANCE); |
533 | | - pipelineOptionsRegistrars.addAll( |
534 | | - Lists.newArrayList(ServiceLoader.load(PipelineOptionsRegistrar.class, classLoader))); |
535 | | - for (PipelineOptionsRegistrar registrar : pipelineOptionsRegistrars) { |
536 | | - for (Class<? extends PipelineOptions> klass : registrar.getPipelineOptions()) { |
537 | | - register(klass); |
538 | | - } |
539 | | - } |
| 533 | + initializeRegistry(); |
540 | 534 | } |
541 | 535 |
|
542 | 536 | /** |
@@ -565,6 +559,33 @@ public static synchronized void register(Class<? extends PipelineOptions> iface) |
565 | 559 | REGISTERED_OPTIONS.add(iface); |
566 | 560 | } |
567 | 561 |
|
| 562 | + /** |
| 563 | + * Resets the set of interfaces registered with this factory to the default state. |
| 564 | + * |
| 565 | + * @see PipelineOptionsFactory#register(Class) |
| 566 | + */ |
| 567 | + @VisibleForTesting |
| 568 | + static synchronized void resetRegistry() { |
| 569 | + REGISTERED_OPTIONS.clear(); |
| 570 | + initializeRegistry(); |
| 571 | + } |
| 572 | + |
| 573 | + /** |
| 574 | + * Load and register the list of all classes that extend PipelineOptions. |
| 575 | + */ |
| 576 | + private static void initializeRegistry() { |
| 577 | + register(PipelineOptions.class); |
| 578 | + Set<PipelineOptionsRegistrar> pipelineOptionsRegistrars = |
| 579 | + Sets.newTreeSet(ObjectsClassComparator.INSTANCE); |
| 580 | + pipelineOptionsRegistrars.addAll( |
| 581 | + Lists.newArrayList(ServiceLoader.load(PipelineOptionsRegistrar.class, CLASS_LOADER))); |
| 582 | + for (PipelineOptionsRegistrar registrar : pipelineOptionsRegistrars) { |
| 583 | + for (Class<? extends PipelineOptions> klass : registrar.getPipelineOptions()) { |
| 584 | + register(klass); |
| 585 | + } |
| 586 | + } |
| 587 | + } |
| 588 | + |
568 | 589 | /** |
569 | 590 | * Validates that the interface conforms to the following: |
570 | 591 | * <ul> |
@@ -674,32 +695,20 @@ public static void printHelp(PrintStream out, Class<? extends PipelineOptions> i |
674 | 695 | Preconditions.checkNotNull(iface); |
675 | 696 | validateWellFormed(iface, REGISTERED_OPTIONS); |
676 | 697 |
|
677 | | - Iterable<Method> methods = |
678 | | - Iterables.filter( |
679 | | - ReflectHelpers.getClosureOfMethodsOnInterface(iface), NOT_SYNTHETIC_PREDICATE); |
680 | | - ListMultimap<Class<?>, Method> ifaceToMethods = ArrayListMultimap.create(); |
681 | | - for (Method method : methods) { |
682 | | - // Process only methods that are not marked as hidden. |
683 | | - if (method.getAnnotation(Hidden.class) == null) { |
684 | | - ifaceToMethods.put(method.getDeclaringClass(), method); |
685 | | - } |
| 698 | + Set<PipelineOptionSpec> properties = |
| 699 | + PipelineOptionsReflector.getOptionSpecs(iface); |
| 700 | + |
| 701 | + RowSortedTable<Class<?>, String, Method> ifacePropGetterTable = TreeBasedTable.create( |
| 702 | + ClassNameComparator.INSTANCE, Ordering.natural()); |
| 703 | + for (PipelineOptionSpec prop : properties) { |
| 704 | + ifacePropGetterTable.put(prop.getDefiningInterface(), prop.getName(), prop.getGetterMethod()); |
686 | 705 | } |
687 | | - SortedSet<Class<?>> ifaces = new TreeSet<>(ClassNameComparator.INSTANCE); |
688 | | - // Keep interfaces that are not marked as hidden. |
689 | | - ifaces.addAll(Collections2.filter(ifaceToMethods.keySet(), new Predicate<Class<?>>() { |
690 | | - @Override |
691 | | - public boolean apply(Class<?> input) { |
692 | | - return input.getAnnotation(Hidden.class) == null; |
693 | | - } |
694 | | - })); |
695 | | - for (Class<?> currentIface : ifaces) { |
696 | | - Map<String, Method> propertyNamesToGetters = |
697 | | - getPropertyNamesToGetters(ifaceToMethods.get(currentIface)); |
698 | 706 |
|
699 | | - // Don't output anything if there are no defined options |
700 | | - if (propertyNamesToGetters.isEmpty()) { |
701 | | - continue; |
702 | | - } |
| 707 | + for (Map.Entry<Class<?>, Map<String, Method>> ifaceToPropertyMap : |
| 708 | + ifacePropGetterTable.rowMap().entrySet()) { |
| 709 | + Class<?> currentIface = ifaceToPropertyMap.getKey(); |
| 710 | + Map<String, Method> propertyNamesToGetters = ifaceToPropertyMap.getValue(); |
| 711 | + |
703 | 712 | SortedSetMultimap<String, String> requiredGroupNameToProperties = |
704 | 713 | getRequiredGroupNamesToProperties(propertyNamesToGetters); |
705 | 714 |
|
@@ -838,15 +847,21 @@ static List<PropertyDescriptor> getPropertyDescriptors( |
838 | 847 | * <p>TODO: Swap back to using Introspector once the proxy class issue with AppEngine is |
839 | 848 | * resolved. |
840 | 849 | */ |
841 | | - private static List<PropertyDescriptor> getPropertyDescriptors(Class<?> beanClass) |
| 850 | + private static List<PropertyDescriptor> getPropertyDescriptors( |
| 851 | + Class<? extends PipelineOptions> beanClass) |
842 | 852 | throws IntrospectionException { |
843 | 853 | // The sorting is important to make this method stable. |
844 | 854 | SortedSet<Method> methods = Sets.newTreeSet(MethodComparator.INSTANCE); |
845 | 855 | methods.addAll( |
846 | 856 | Collections2.filter(Arrays.asList(beanClass.getMethods()), NOT_SYNTHETIC_PREDICATE)); |
847 | | - SortedMap<String, Method> propertyNamesToGetters = getPropertyNamesToGetters(methods); |
848 | | - List<PropertyDescriptor> descriptors = Lists.newArrayList(); |
849 | 857 |
|
| 858 | + SortedMap<String, Method> propertyNamesToGetters = new TreeMap<>(); |
| 859 | + for (Map.Entry<String, Method> entry : |
| 860 | + PipelineOptionsReflector.getPropertyNamesToGetters(methods).entries()) { |
| 861 | + propertyNamesToGetters.put(entry.getKey(), entry.getValue()); |
| 862 | + } |
| 863 | + |
| 864 | + List<PropertyDescriptor> descriptors = Lists.newArrayList(); |
850 | 865 | List<TypeMismatch> mismatches = new ArrayList<>(); |
851 | 866 | /* |
852 | 867 | * Add all the getter/setter pairs to the list of descriptors removing the getter once |
@@ -918,28 +933,6 @@ private static void throwForTypeMismatches(List<TypeMismatch> mismatches) { |
918 | 933 | } |
919 | 934 | } |
920 | 935 |
|
921 | | - /** |
922 | | - * Returns a map of the property name to the getter method it represents. |
923 | | - * If there are duplicate methods with the same bean name, then it is indeterminate |
924 | | - * as to which method will be returned. |
925 | | - */ |
926 | | - private static SortedMap<String, Method> getPropertyNamesToGetters(Iterable<Method> methods) { |
927 | | - SortedMap<String, Method> propertyNamesToGetters = Maps.newTreeMap(); |
928 | | - for (Method method : methods) { |
929 | | - String methodName = method.getName(); |
930 | | - if ((!methodName.startsWith("get") |
931 | | - && !methodName.startsWith("is")) |
932 | | - || method.getParameterTypes().length != 0 |
933 | | - || method.getReturnType() == void.class) { |
934 | | - continue; |
935 | | - } |
936 | | - String propertyName = Introspector.decapitalize( |
937 | | - methodName.startsWith("is") ? methodName.substring(2) : methodName.substring(3)); |
938 | | - propertyNamesToGetters.put(propertyName, method); |
939 | | - } |
940 | | - return propertyNamesToGetters; |
941 | | - } |
942 | | - |
943 | 936 | /** |
944 | 937 | * Returns a map of required groups of arguments to the properties that satisfy the requirement. |
945 | 938 | */ |
@@ -981,21 +974,22 @@ private static SortedSetMultimap<String, String> getRequiredGroupNamesToProperti |
981 | 974 | */ |
982 | 975 | private static List<PropertyDescriptor> validateClass(Class<? extends PipelineOptions> iface, |
983 | 976 | Set<Class<? extends PipelineOptions>> validatedPipelineOptionsInterfaces, |
984 | | - Class<?> klass) throws IntrospectionException { |
| 977 | + Class<? extends PipelineOptions> klass) throws IntrospectionException { |
985 | 978 | Set<Method> methods = Sets.newHashSet(IGNORED_METHODS); |
986 | | - // Ignore static methods, "equals", "hashCode", "toString" and "as" on the generated class. |
987 | 979 | // Ignore synthetic methods |
988 | 980 | for (Method method : klass.getMethods()) { |
989 | 981 | if (Modifier.isStatic(method.getModifiers()) || method.isSynthetic()) { |
990 | 982 | methods.add(method); |
991 | 983 | } |
992 | 984 | } |
| 985 | + // Ignore standard infrastructure methods on the generated class. |
993 | 986 | try { |
994 | 987 | methods.add(klass.getMethod("equals", Object.class)); |
995 | 988 | methods.add(klass.getMethod("hashCode")); |
996 | 989 | methods.add(klass.getMethod("toString")); |
997 | 990 | methods.add(klass.getMethod("as", Class.class)); |
998 | 991 | methods.add(klass.getMethod("cloneAs", Class.class)); |
| 992 | + methods.add(klass.getMethod("populateDisplayData", DisplayData.Builder.class)); |
999 | 993 | } catch (NoSuchMethodException | SecurityException e) { |
1000 | 994 | throw Throwables.propagate(e); |
1001 | 995 | } |
|
0 commit comments