private void validateClass(Class<?> source, ValidationProblemCollector problems) {
    int modifiers = source.getModifiers();

    if (Modifier.isInterface(modifiers)) {
      problems.add("Must be a class, not an interface");
    }

    if (source.getEnclosingClass() != null) {
      if (Modifier.isStatic(modifiers)) {
        if (Modifier.isPrivate(modifiers)) {
          problems.add("Class cannot be private");
        }
      } else {
        problems.add("Enclosed classes must be static and non private");
      }
    }

    Constructor<?>[] constructors = source.getDeclaredConstructors();
    for (Constructor<?> constructor : constructors) {
      if (constructor.getParameterTypes().length > 0) {
        problems.add("Cannot declare a constructor that takes arguments");
        break;
      }
    }

    Field[] fields = source.getDeclaredFields();
    for (Field field : fields) {
      int fieldModifiers = field.getModifiers();
      if (!field.isSynthetic()
          && !(Modifier.isStatic(fieldModifiers) && Modifier.isFinal(fieldModifiers))) {
        problems.add(field, "Fields must be static final.");
      }
    }
  }
  private <T> CachedRuleSource doExtract(final Class<T> source) {
    final ModelType<T> type = ModelType.of(source);
    DefaultMethodModelRuleExtractionContext context =
        new DefaultMethodModelRuleExtractionContext(type, this);

    // TODO - exceptions thrown here should point to some extensive documentation on the concept of
    // class rule sources

    StructSchema<T> schema = getSchema(source, context);
    if (schema == null) {
      throw new InvalidModelRuleDeclarationException(context.problems.format());
    }

    // sort for determinism
    Set<Method> methods = new TreeSet<Method>(Ordering.usingToString());
    methods.addAll(Arrays.asList(source.getDeclaredMethods()));

    ImmutableList.Builder<ModelProperty<?>> implicitInputs = ImmutableList.builder();
    ModelProperty<?> target = null;
    for (ModelProperty<?> property : schema.getProperties()) {
      if (property.isAnnotationPresent(RuleTarget.class)) {
        target = property;
      } else if (property.isAnnotationPresent(RuleInput.class)
          && !(property.getSchema() instanceof ScalarValueSchema)) {
        implicitInputs.add(property);
      }
      for (WeaklyTypeReferencingMethod<?, ?> method : property.getAccessors()) {
        methods.remove(method.getMethod());
      }
    }

    ImmutableList.Builder<ExtractedRuleDetails> rules = ImmutableList.builder();
    for (Method method : methods) {
      MethodRuleDefinition<?, ?> ruleDefinition =
          DefaultMethodRuleDefinition.create(source, method);
      ExtractedModelRule rule = getMethodHandler(ruleDefinition, method, context);
      if (rule != null) {
        rules.add(new ExtractedRuleDetails(ruleDefinition, rule));
      }
    }

    if (context.hasProblems()) {
      throw new InvalidModelRuleDeclarationException(context.problems.format());
    }

    StructBindings<T> bindings = structBindingsStore.getBindings(schema);

    if (schema.getProperties().isEmpty()) {
      return new StatelessRuleSource(
          rules.build(),
          Modifier.isAbstract(source.getModifiers())
              ? new AbstractRuleSourceFactory<T>(schema, bindings, proxyFactory)
              : new ConcreteRuleSourceFactory<T>(type));
    } else {
      return new ParameterizedRuleSource(
          rules.build(), target, implicitInputs.build(), schema, bindings, proxyFactory);
    }
  }