/* * Copyright (C) 2014 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dagger.internal.codegen; import dagger.Provides; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.lang.model.element.AnnotationMirror; /** * The collection of error messages to be reported back to users. * * @author Gregory Kick * @since 2.0 */ final class ErrorMessages { /* * Common constants. */ static final String INDENT = " "; static final int DUPLICATE_SIZE_LIMIT = 10; /* * JSR-330 errors * * These are errors that are explicitly outlined in the JSR-330 APIs */ /* constructors */ static final String MULTIPLE_INJECT_CONSTRUCTORS = "Types may only contain one @Inject constructor."; /* fields */ static final String FINAL_INJECT_FIELD = "@Inject fields may not be final"; /* methods */ static final String ABSTRACT_INJECT_METHOD = "Methods with @Inject may not be abstract."; static final String GENERIC_INJECT_METHOD = "Methods with @Inject may not declare type parameters."; /* qualifiers */ static final String MULTIPLE_QUALIFIERS = "A single injection site may not use more than one @Qualifier."; /* scope */ static final String MULTIPLE_SCOPES = "A single binding may not declare more than one @Scope."; /* * Dagger errors * * These are errors that arise due to restrictions imposed by the dagger implementation. */ /* constructors */ static final String INJECT_ON_PRIVATE_CONSTRUCTOR = "Dagger does not support injection into private constructors"; static final String INJECT_CONSTRUCTOR_ON_INNER_CLASS = "@Inject constructors are invalid on inner classes"; static final String INJECT_CONSTRUCTOR_ON_ABSTRACT_CLASS = "@Inject is nonsense on the constructor of an abstract class"; static final String QUALIFIER_ON_INJECT_CONSTRUCTOR = "@Qualifier annotations are not allowed on @Inject constructors."; /* fields */ static final String PRIVATE_INJECT_FIELD = "Dagger does not support injection into private fields"; static final String STATIC_INJECT_FIELD = "Dagger does not support injection into static fields"; /* methods */ static final String PRIVATE_INJECT_METHOD = "Dagger does not support injection into private methods"; static final String STATIC_INJECT_METHOD = "Dagger does not support injection into static methods"; /* all */ static final String INJECT_INTO_PRIVATE_CLASS = "Dagger does not support injection into private classes"; /* * Configuration errors * * These are errors that relate specifically to the Dagger configuration API (@Module, @Provides, * etc.) */ static final String DUPLICATE_BINDINGS_FOR_KEY_FORMAT = "%s is bound multiple times:"; static String duplicateMapKeysError(String key) { return "The same map key is bound more than once for " + key; } static String inconsistentMapKeyAnnotationsError(String key) { return key + " uses more than one @MapKey annotation type"; } static final String PROVIDES_METHOD_RETURN_TYPE = "@Provides methods must either return a primitive, an array or a declared type."; static final String PRODUCES_METHOD_RETURN_TYPE = "@Produces methods must either return a primitive, an array or a declared type, or a" + " ListenableFuture of one of those types."; static final String PRODUCES_METHOD_RAW_FUTURE = "@Produces methods cannot return a raw ListenableFuture."; static final String BINDING_METHOD_SET_VALUES_RAW_SET = "@%s methods of type set values cannot return a raw Set"; static final String PROVIDES_METHOD_SET_VALUES_RETURN_SET = "@Provides methods of type set values must return a Set"; static final String PRODUCES_METHOD_SET_VALUES_RETURN_SET = "@Produces methods of type set values must return a Set or ListenableFuture of Set"; static final String BINDING_METHOD_MUST_RETURN_A_VALUE = "@%s methods must return a value (not void)."; static final String BINDING_METHOD_ABSTRACT = "@%s methods cannot be abstract"; static final String BINDING_METHOD_PRIVATE = "@%s methods cannot be private"; static final String BINDING_METHOD_TYPE_PARAMETER = "@%s methods may not have type parameters."; static final String BINDING_METHOD_NOT_IN_MODULE = "@%s methods can only be present within a @%s"; static final String BINDING_METHOD_NOT_MAP_HAS_MAP_KEY = "@%s methods of non map type cannot declare a map key"; static final String BINDING_METHOD_WITH_NO_MAP_KEY = "@%s methods of type map must declare a map key"; static final String BINDING_METHOD_WITH_MULTIPLE_MAP_KEY = "@%s methods may not have more than one @MapKey-marked annotation"; static final String BINDING_METHOD_WITH_SAME_NAME = "Cannot have more than one @%s method with the same name in a single module"; static final String MODULES_WITH_TYPE_PARAMS_MUST_BE_ABSTRACT = "Modules with type parameters must be abstract"; static final String REFERENCED_MODULES_MUST_NOT_BE_ABSTRACT = "%s is listed as a module, but is an abstract class or interface"; static final String REFERENCED_MODULE_NOT_ANNOTATED = "%s is listed as a module, but is not annotated with %s"; static final String REFERENCED_MODULE_MUST_NOT_HAVE_TYPE_PARAMS = "%s is listed as a module, but has type parameters"; static final String PROVIDES_METHOD_OVERRIDES_ANOTHER = "@%s methods may not override another method. Overrides: %s"; static final String METHOD_OVERRIDES_PROVIDES_METHOD = "@%s methods may not be overridden in modules. Overrides: %s"; static final String PROVIDES_OR_PRODUCES_METHOD_MULTIPLE_QUALIFIERS = "Cannot use more than one @Qualifier on a @Provides or @Produces method"; /* mapKey errors*/ static final String MAPKEY_WITHOUT_MEMBERS = "Map key annotations must have members"; static final String UNWRAPPED_MAP_KEY_WITH_TOO_MANY_MEMBERS= "Map key annotations with unwrapped values must have exactly one member"; static final String UNWRAPPED_MAP_KEY_WITH_ARRAY_MEMBER = "Map key annotations with unwrapped values cannot use arrays"; /* collection binding errors */ static final String MULTIPLE_CONTRIBUTION_TYPES_FORMAT = "More than one binding present of different types %s"; static final String MULTIPLE_BINDING_TYPES_FOR_KEY_FORMAT = "%s has incompatible bindings:\n"; static final String PROVIDER_ENTRY_POINT_MAY_NOT_DEPEND_ON_PRODUCER_FORMAT = "%s is a provision entry-point, which cannot depend on a production."; static final String PROVIDER_MAY_NOT_DEPEND_ON_PRODUCER_FORMAT = "%s is a provision, which cannot depend on a production."; static final String REQUIRES_AT_INJECT_CONSTRUCTOR_OR_PROVIDER_FORMAT = "%s cannot be provided without an @Inject constructor or from an @Provides-annotated method."; static final String REQUIRES_PROVIDER_FORMAT = "%s cannot be provided without an @Provides-annotated method."; static final String REQUIRES_AT_INJECT_CONSTRUCTOR_OR_PROVIDER_OR_PRODUCER_FORMAT = "%s cannot be provided without an @Inject constructor or from an @Provides- or " + "@Produces-annotated method."; static final String REQUIRES_PROVIDER_OR_PRODUCER_FORMAT = "%s cannot be provided without an @Provides- or @Produces-annotated method."; static final String MEMBERS_INJECTION_DOES_NOT_IMPLY_PROVISION = "This type supports members injection but cannot be implicitly provided."; static final String MEMBERS_INJECTION_WITH_RAW_TYPE = "%s has type parameters, cannot members inject the raw type. via:\n%s"; static final String MEMBERS_INJECTION_WITH_UNBOUNDED_TYPE = "Type parameters must be bounded for members injection. %s required by %s, via:\n%s"; static final String CONTAINS_DEPENDENCY_CYCLE_FORMAT = "%s.%s() contains a dependency cycle:\n%s"; static final String MALFORMED_MODULE_METHOD_FORMAT = "Cannot generated a graph because method %s on module %s was malformed"; static String nullableToNonNullable(String typeName, String bindingString) { return String.format( "%s is not nullable, but is being provided by %s", typeName, bindingString); } static final String CANNOT_RETURN_NULL_FROM_NON_NULLABLE_COMPONENT_METHOD = "Cannot return null from a non-@Nullable component method"; static final String CANNOT_RETURN_NULL_FROM_NON_NULLABLE_PROVIDES_METHOD = "Cannot return null from a non-@Nullable @Provides method"; static ComponentBuilderMessages builderMsgsFor(ComponentDescriptor.Kind kind) { switch(kind) { case COMPONENT: return ComponentBuilderMessages.INSTANCE; case SUBCOMPONENT: return SubcomponentBuilderMessages.INSTANCE; case PRODUCTION_COMPONENT: return ProductionComponentBuilderMessages.INSTANCE; default: throw new IllegalStateException(kind.toString()); } } static class ComponentBuilderMessages { static final ComponentBuilderMessages INSTANCE = new ComponentBuilderMessages(); protected String process(String s) { return s; } /** Errors for component builders. */ final String moreThanOne() { return process("@Component has more than one @Component.Builder: %s"); } final String cxtorOnlyOneAndNoArgs() { return process("@Component.Builder classes must have exactly one constructor," + " and it must not have any parameters"); } final String generics() { return process("@Component.Builder types must not have any generic types"); } final String mustBeInComponent() { return process("@Component.Builder types must be nested within a @Component"); } final String mustBeClassOrInterface() { return process("@Component.Builder types must be abstract classes or interfaces"); } final String isPrivate() { return process("@Component.Builder types must not be private"); } final String mustBeStatic() { return process("@Component.Builder types must be static"); } final String mustBeAbstract() { return process("@Component.Builder types must be abstract"); } final String missingBuildMethod() { return process("@Component.Builder types must have exactly one no-args method that " + " returns the @Component type"); } final String manyMethodsForType() { return process("@Component.Builder types must not have more than one setter method per type," + " but %s is set by %s"); } final String extraSetters() { return process( "@Component.Builder has setters for modules or components that aren't required: %s"); } final String missingSetters() { return process( "@Component.Builder is missing setters for required modules or components: %s"); } final String twoBuildMethods() { return process("@Component.Builder types must have exactly one zero-arg method, and that" + " method must return the @Component type. Already found: %s"); } final String inheritedTwoBuildMethods() { return process("@Component.Builder types must have exactly one zero-arg method, and that" + " method must return the @Component type. Found %s and %s"); } final String buildMustReturnComponentType() { return process( "@Component.Builder methods that have no arguments must return the @Component type"); } final String inheritedBuildMustReturnComponentType() { return process( "@Component.Builder methods that have no arguments must return the @Component type" + " Inherited method: %s"); } final String methodsMustTakeOneArg() { return process("@Component.Builder methods must not have more than one argument"); } final String inheritedMethodsMustTakeOneArg() { return process( "@Component.Builder methods must not have more than one argument. Inherited method: %s"); } final String methodsMustReturnVoidOrBuilder() { return process("@Component.Builder setter methods must return void, the builder," + " or a supertype of the builder"); } final String inheritedMethodsMustReturnVoidOrBuilder() { return process("@Component.Builder setter methods must return void, the builder," + "or a supertype of the builder. Inherited method: %s"); } final String methodsMayNotHaveTypeParameters() { return process("@Component.Builder methods must not have type parameters"); } final String inheritedMethodsMayNotHaveTypeParameters() { return process( "@Component.Builder methods must not have type parameters. Inherited method: %s"); } } static final class SubcomponentBuilderMessages extends ComponentBuilderMessages { @SuppressWarnings("hiding") static final SubcomponentBuilderMessages INSTANCE = new SubcomponentBuilderMessages(); @Override protected String process(String s) { return s.replaceAll("component", "subcomponent").replaceAll("Component", "Subcomponent"); } String builderMethodRequiresNoArgs() { return "Methods returning a @Subcomponent.Builder must have no arguments"; } String moreThanOneRefToSubcomponent() { return "Only one method can create a given subcomponent. %s is created by: %s"; } } static final class ProductionComponentBuilderMessages extends ComponentBuilderMessages { @SuppressWarnings("hiding") static final ProductionComponentBuilderMessages INSTANCE = new ProductionComponentBuilderMessages(); @Override protected String process(String s) { return s.replaceAll("component", "production component") .replaceAll("Component", "ProductionComponent"); } } /** * A regular expression to match a small list of specific packages deemed to * be unhelpful to display in fully qualified types in error messages. * * Note: This should never be applied to messages themselves. */ private static final Pattern COMMON_PACKAGE_PATTERN = Pattern.compile( "(?:^|[^.a-z_])" // What we want to match on but not capture. + "((?:" // Start a group with a non-capturing or part + "java[.]lang" + "|java[.]util" + "|javax[.]inject" + "|dagger" + "|com[.]google[.]common[.]base" + "|com[.]google[.]common[.]collect" + ")[.])" // Always end with a literal . + "[A-Z]"); // What we want to match on but not capture. /** * A method to strip out common packages and a few rare type prefixes * from types' string representation before being used in error messages. * * This type assumes a String value that is a valid fully qualified * (and possibly parameterized) type, and should NOT be used with * arbitrary text, especially prose error messages. * * TODO(cgruber): Tighten these to take type representations (mirrors * and elements) to avoid accidental mis-use by running errors * through this method. */ static String stripCommonTypePrefixes(String type) { // Special case this enum's constants since they will be incredibly common. type = type.replace(Provides.Type.class.getCanonicalName() + ".", ""); // Do regex magic to remove common packages we care to shorten. Matcher matcher = COMMON_PACKAGE_PATTERN.matcher(type); StringBuilder result = new StringBuilder(); int index = 0; while (matcher.find()) { result.append(type.subSequence(index, matcher.start(1))); index = matcher.end(1); // Skip the matched pattern content. } result.append(type.subSequence(index, type.length())); return result.toString(); } //TODO(cgruber): Extract Formatter and do something less stringy. static String format(AnnotationMirror annotation) { return stripCommonTypePrefixes(annotation.toString()); } private ErrorMessages() {} }