private static List validateClass()

in sdk/src/main/java/com/google/cloud/dataflow/sdk/options/PipelineOptionsFactory.java [978:1099]


  private static List<PropertyDescriptor> validateClass(Class<? extends PipelineOptions> iface,
      Set<Class<? extends PipelineOptions>> validatedPipelineOptionsInterfaces,
      Class<?> klass) throws IntrospectionException {
    Set<Method> methods = Sets.newHashSet(IGNORED_METHODS);
    // Ignore static methods, "equals", "hashCode", "toString" and "as" on the generated class.
    for (Method method : klass.getMethods()) {
      if (Modifier.isStatic(method.getModifiers())) {
        methods.add(method);
      }
    }
    try {
      methods.add(klass.getMethod("equals", Object.class));
      methods.add(klass.getMethod("hashCode"));
      methods.add(klass.getMethod("toString"));
      methods.add(klass.getMethod("as", Class.class));
      methods.add(klass.getMethod("cloneAs", Class.class));
    } catch (NoSuchMethodException | SecurityException e) {
      throw Throwables.propagate(e);
    }

    // Verify that there are no methods with the same name with two different return types.
    Iterable<Method> interfaceMethods = FluentIterable
        .from(ReflectHelpers.getClosureOfMethodsOnInterface(iface))
        .toSortedSet(MethodComparator.INSTANCE);
    SortedSetMultimap<Method, Method> methodNameToMethodMap =
        TreeMultimap.create(MethodNameComparator.INSTANCE, MethodComparator.INSTANCE);
    for (Method method : interfaceMethods) {
      methodNameToMethodMap.put(method, method);
    }
    for (Map.Entry<Method, Collection<Method>> entry
        : methodNameToMethodMap.asMap().entrySet()) {
      Set<Class<?>> returnTypes = FluentIterable.from(entry.getValue())
          .transform(ReturnTypeFetchingFunction.INSTANCE).toSet();
      SortedSet<Method> collidingMethods = FluentIterable.from(entry.getValue())
          .toSortedSet(MethodComparator.INSTANCE);
      Preconditions.checkArgument(returnTypes.size() == 1,
          "Method [%s] has multiple definitions %s with different return types for [%s].",
          entry.getKey().getName(),
          collidingMethods,
          iface.getName());
    }

    // Verify that there is no getter with a mixed @JsonIgnore annotation and verify
    // that no setter has @JsonIgnore.
    Iterable<Method> allInterfaceMethods = FluentIterable
        .from(ReflectHelpers.getClosureOfMethodsOnInterfaces(validatedPipelineOptionsInterfaces))
        .append(ReflectHelpers.getClosureOfMethodsOnInterface(iface))
        .toSortedSet(MethodComparator.INSTANCE);
    SortedSetMultimap<Method, Method> methodNameToAllMethodMap =
        TreeMultimap.create(MethodNameComparator.INSTANCE, MethodComparator.INSTANCE);
    for (Method method : allInterfaceMethods) {
      methodNameToAllMethodMap.put(method, method);
    }

    List<PropertyDescriptor> descriptors = getPropertyDescriptors(klass);

    for (PropertyDescriptor descriptor : descriptors) {
      if (descriptor.getReadMethod() == null
          || descriptor.getWriteMethod() == null
          || IGNORED_METHODS.contains(descriptor.getReadMethod())
          || IGNORED_METHODS.contains(descriptor.getWriteMethod())) {
        continue;
      }
      SortedSet<Method> getters = methodNameToAllMethodMap.get(descriptor.getReadMethod());
      SortedSet<Method> gettersWithJsonIgnore = Sets.filter(getters, JsonIgnorePredicate.INSTANCE);

      Iterable<String> getterClassNames = FluentIterable.from(getters)
          .transform(MethodToDeclaringClassFunction.INSTANCE)
          .transform(ReflectHelpers.CLASS_NAME);
      Iterable<String> gettersWithJsonIgnoreClassNames = FluentIterable.from(gettersWithJsonIgnore)
          .transform(MethodToDeclaringClassFunction.INSTANCE)
          .transform(ReflectHelpers.CLASS_NAME);

      Preconditions.checkArgument(gettersWithJsonIgnore.isEmpty()
          || getters.size() == gettersWithJsonIgnore.size(),
          "Expected getter for property [%s] to be marked with @JsonIgnore on all %s, "
          + "found only on %s",
          descriptor.getName(), getterClassNames, gettersWithJsonIgnoreClassNames);

      SortedSet<Method> settersWithJsonIgnore =
          Sets.filter(methodNameToAllMethodMap.get(descriptor.getWriteMethod()),
              JsonIgnorePredicate.INSTANCE);

      Iterable<String> settersWithJsonIgnoreClassNames = FluentIterable.from(settersWithJsonIgnore)
          .transform(MethodToDeclaringClassFunction.INSTANCE)
          .transform(ReflectHelpers.CLASS_NAME);

      Preconditions.checkArgument(settersWithJsonIgnore.isEmpty(),
          "Expected setter for property [%s] to not be marked with @JsonIgnore on %s",
          descriptor.getName(), settersWithJsonIgnoreClassNames);
    }

    // Verify that each property has a matching read and write method.
    for (PropertyDescriptor propertyDescriptor : descriptors) {
      Preconditions.checkArgument(
          IGNORED_METHODS.contains(propertyDescriptor.getWriteMethod())
          || propertyDescriptor.getReadMethod() != null,
          "Expected getter for property [%s] of type [%s] on [%s].",
          propertyDescriptor.getName(),
          propertyDescriptor.getPropertyType().getName(),
          iface.getName());
      Preconditions.checkArgument(
          IGNORED_METHODS.contains(propertyDescriptor.getReadMethod())
          || propertyDescriptor.getWriteMethod() != null,
          "Expected setter for property [%s] of type [%s] on [%s].",
          propertyDescriptor.getName(),
          propertyDescriptor.getPropertyType().getName(),
          iface.getName());
      methods.add(propertyDescriptor.getReadMethod());
      methods.add(propertyDescriptor.getWriteMethod());
    }

    // Verify that no additional methods are on an interface that aren't a bean property.
    SortedSet<Method> unknownMethods = new TreeSet<>(MethodComparator.INSTANCE);
    unknownMethods.addAll(Sets.difference(Sets.newHashSet(klass.getMethods()), methods));
    Preconditions.checkArgument(unknownMethods.isEmpty(),
        "Methods %s on [%s] do not conform to being bean properties.",
        FluentIterable.from(unknownMethods).transform(ReflectHelpers.METHOD_FORMATTER),
        iface.getName());

    return descriptors;
  }