private Type.Parameterized matcherType(IntrospectedType introspected) {
    final TypeMirror type = introspected.type;
    String name;

    if (introspected.useOptional()) {
      IntrospectedType param = introspected.optionalParameter();
      // use optional intersection-types ?
      if (param.isString()) {
        name = "org.immutables.criteria.matcher.OptionalStringMatcher.Template";
      } else if (param.isBoolean()) {
        name = "org.immutables.criteria.matcher.OptionalBooleanMatcher.Template";
      } else if (param.isNumber()) {
        if (param.isInteger()) {
          name = "org.immutables.criteria.matcher.OptionalIntegerMatcher.Template";
        } else if (param.isLong()) {
          name = "org.immutables.criteria.matcher.OptionalLongMatcher.Template";
        } else if (param.isDouble()) {
          name = "org.immutables.criteria.matcher.OptionalDoubleMatcher.Template";
        } else if (param.isBigInteger()) {
          name = "org.immutables.criteria.matcher.OptionalBigIntegerMatcher.Template";
        } else if (param.isBigDecimal()) {
          name = "org.immutables.criteria.matcher.OptionalBigDecimalMatcher.Template";
        } else {
          // generic number
          name = "org.immutables.criteria.matcher.OptionalNumberMatcher.Template";
        }
      } else if (param.isComparable()) {
        name = "org.immutables.criteria.matcher.OptionalComparableMatcher.Template";
      } else {
        name = "org.immutables.criteria.matcher.OptionalObjectMatcher.Template";
      }
    } else if (introspected.hasCriteria()) {
      name = topLevelCriteriaClassName(type);
    } else if (introspected.isBoolean()) {
      name = "org.immutables.criteria.matcher.BooleanMatcher.Template";
    } else if (introspected.isNumber()) {
      if (introspected.isInteger()) {
        name = "org.immutables.criteria.matcher.IntegerMatcher.Template";
      } else if (introspected.isLong()) {
        name = "org.immutables.criteria.matcher.LongMatcher.Template";
      } else if (introspected.isDouble()) {
        name = "org.immutables.criteria.matcher.DoubleMatcher.Template";
      } else if (introspected.isBigInteger()) {
        name = "org.immutables.criteria.matcher.BigIntegerMatcher.Template";
      } else if (introspected.isBigDecimal()) {
        name = "org.immutables.criteria.matcher.BigDecimalMatcher.Template";
      } else {
        // generic number
        name = "org.immutables.criteria.matcher.NumberMatcher.Template";
      }
    } else if (introspected.isString()) {
      name = "org.immutables.criteria.matcher.StringMatcher.Template";
    } else if (introspected.isIterable() || introspected.isArray()) {
      name = "org.immutables.criteria.matcher.IterableMatcher.Template";
    } else if (introspected.isComparable()) {
      name = "org.immutables.criteria.matcher.ComparableMatcher.Template";
    } else {
      name = "org.immutables.criteria.matcher.ObjectMatcher.Template";
    }

    final Element element = elements.getTypeElement(name);
    final Type.Parameterized matcherType;
    if (element == null) {
      // means type not found in classpath. probably not yet generated criteria
      // create PersonCriteria<R> manually with Type.Parameterized
      final Type.Variable variable = factory.parameters().introduce("R", NO_BOUNDS).variable("R");
      matcherType = factory.parameterized(factory.reference(name), Collections.singleton(variable));
    } else {
      matcherType = (Type.Parameterized) toType(element.asType());
    }

    return matcherType;
  }
Criteria templates are always generated as top-level class (separate file).
Construct criteria name from {@linkplain TypeMirror}

@return fully qualified criteria (template) class name
value-processor/src/org/immutables/value/processor/meta/CriteriaModel.java