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);
    }
  }
  private <T> StructSchema<T> getSchema(
      Class<T> source, DefaultMethodModelRuleExtractionContext context) {
    if (!RuleSource.class.isAssignableFrom(source)
        || !source.getSuperclass().equals(RuleSource.class)) {
      context.add("Rule source classes must directly extend " + RuleSource.class.getName());
    }

    ModelSchema<T> schema = schemaStore.getSchema(source);
    if (!(schema instanceof StructSchema)) {
      return null;
    }

    validateClass(source, context);
    return (StructSchema<T>) schema;
  }