private Type.Parameterized buildMatcher(IntrospectedType introspected) {
    Preconditions.checkNotNull(introspected, introspected);
    final TypeMirror type = introspected.type;
    final Type.Parameterized matcher = matcherType(introspected);
    if (matcher.arguments.size() > 1) {
      // replace second and maybe third argument
      // first type argument R unchanged
      Type.VariableResolver resolver = Type.VariableResolver.empty();

      final Type valueType;
      final Type.Variable arg1 = (Type.Variable) matcher.arguments.get(1);
      // resolve P (which is projection type) for Optional
      for (Type.Nonprimitive arg: matcher.arguments) {
        if (arg instanceof Type.Variable && ((Type.Variable) arg).name.equals("P")) {
          // resolve projection type (P) for .Template<R, P>.
          // projection type is identical to attribute type
          // Example .Template<R, Optional<Boolean>>
          resolver = resolver.bind((Type.Variable) arg, factory.reference(type.toString()));
        }
      }

      if (introspected.useOptional()) {
        final IntrospectedType newType = introspected.optionalParameter();
        valueType = toType(newType.type());
        if (newType.hasOptionalMatcher()) {
          // don't recurse if optional matcher is present like OptionalComparableMatcher
          resolver = resolver.bind(arg1, (Type.Nonprimitive) valueType);
        } else {
          resolver = resolver.bind(arg1, buildMatcher(newType));
        }
      } else if (introspected.isScalar()) {
        // this is leaf no need to recurse
        valueType = toType(introspected.box());
        resolver = resolver.bind(arg1, (Type.Nonprimitive) valueType);
      } else if (introspected.isArray()) {
        final TypeMirror mirror = MoreTypes.asArray(type).getComponentType();
        valueType = toType(mirror);
        resolver = resolver.bind(arg1, buildMatcher(introspected.withType(mirror)));
      } else {
        final TypeMirror mirror = MoreTypes.asDeclared(type).getTypeArguments().get(0);
        valueType = toType(mirror);
        resolver = resolver.bind(arg1, buildMatcher(introspected.withType(mirror)));
      }

      if (matcher.arguments.size() > 2 && ((Type.Variable) matcher.arguments.get(2)).name.equals("V")) {
        // parameter called V is usually value type
        resolver = resolver.bind((Type.Variable) matcher.arguments.get(2), (Type.Nonprimitive) valueType);
      }

      // resolve P (which is projection type) for Array / Iterable
      if (introspected.isArray() || introspected.isIterable()) {
        for (Type.Nonprimitive arg: matcher.arguments) {
          if (arg instanceof Type.Variable && ((Type.Variable) arg).name.equals("P")) {
            // resolve projection type (P) for .Template<R, P>.
            // projection type is identical to attribute type
            // Example .Template<R, Optional<Boolean>>
            resolver = resolver.bind((Type.Variable) arg, factory.reference(type.toString()));
          }
        }
      }

      return (Type.Parameterized) matcher.accept(resolver);
    }

    return matcher;
  }
Composite matcher for containers like Iterable / Map / Optional / @Nullable
value-processor/src/org/immutables/value/processor/meta/CriteriaModel.java