diff options
author | Reinier Zwitserloot <r.zwitserloot@projectlombok.org> | 2019-07-09 00:46:03 +0200 |
---|---|---|
committer | Reinier Zwitserloot <r.zwitserloot@projectlombok.org> | 2019-07-09 00:46:03 +0200 |
commit | b7e67345b998e2a40ff63fabc893393cc9327596 (patch) | |
tree | 3e468ae52735d856b44821602001aeefd0c640b2 | |
parent | 0984d14826c62c5b99a2887aa198766ef08fea16 (diff) | |
parent | 5ad6796b182c8071fe747493ed2f9ad2443dc212 (diff) | |
download | lombok-b7e67345b998e2a40ff63fabc893393cc9327596.tar.gz lombok-b7e67345b998e2a40ff63fabc893393cc9327596.tar.bz2 lombok-b7e67345b998e2a40ff63fabc893393cc9327596.zip |
Merge branch 'customlog'
36 files changed, 974 insertions, 278 deletions
@@ -1,5 +1,6 @@ Lombok contributors in alphabetical order: +Adam Juraszek <juriad@gmail.com> Bulgakov Alexander <buls@yandex.ru> Christian Nüssgens <christian@nuessgens.com> Christian Sterzl <christian.sterzl@gmail.com> diff --git a/doc/changelog.markdown b/doc/changelog.markdown index afb07aac..e486a713 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,12 +2,15 @@ Lombok Changelog ---------------- ### v1.18.9 "Edgy Guinea Pig" +* FEATURE: You can now configure a custom logger framework using the new `@CustomLog` annotation in combination with the `lombok.log.custom.declaration` configuration key. See the [log documentation](https://projectlombok.org/features/Log) for more information. [Pullrequest #2086](https://github.com/rzwitserloot/lombok/pull/2086) with thanks to Adam Juraszek. * ENHANCEMENT: Thanks to Mark Haynes, the `staticConstructor` will now also be generated if a (private) constructor already exists. [Issue #2100](https://github.com/rzwitserloot/lombok/issues/2100) * ENHANCEMENT: `val` is now capable of decoding the type of convoluted expressions (particularly if the right hand side involves lambdas and conditional (ternary) expressions). [Pull Request #2109](https://github.com/rzwitserloot/lombok/pull/2109) and [Pull Request #2138](https://github.com/rzwitserloot/lombok/pull/2138) with thanks to Alexander Bulgakov. * ENHANCEMENT: You can now configure the generated builder class name via the config system, using key `lombok.builder.className`. See the [Builder documentation](https://projectlombok.org/features/Builder) and [SuperBuilder documentation](https://projectlombok.org/features/experimental/SuperBuilder) * ENHANCEMENT: If you mix up eclipse's non-null support, such as `@NonNullByDefault`, with lombok's `@NonNull`, you get a bunch of warnings about dead code that are inappropriate. These warnings are now suppressed, thanks to a contribution from Till Brychcy! [Pull Request #2155](https://github.com/rzwitserloot/lombok/pull/2155) * BUGFIX: Delombok would turn something like `List<byte[]>...` in a method parameter to `List<byte...>...` [Issue #2140](https://github.com/rzwitserloot/lombok/issues/2140) * BUGFIX: Javac would generate the wrong equals and hashCode if a type-use annotation was put on an array type field [Issue #2165](https://github.com/rzwitserloot/lombok/issues/2165) +* IMPROBABLE BREAKING CHANGE: Stricter validation of configuration keys dealing with identifiers and types (`lombok.log.fieldName`, `lombok.fieldNameConstants.innerTypeName`, `lombok.copyableAnnotations`). +>>>>>>> customlog ### v1.18.8 (May 7th, 2019) * FEATURE: You can now configure `@FieldNameConstants` to `CONSTANT_CASE` the generated constants, using a `lombok.config` option. See the [FieldNameConstants documentation](https://projectlombok.org/features/experimental/FieldNameConstants). [Issue #2092](https://github.com/rzwitserloot/lombok/issues/2092). diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 975cb72e..dda0b54b 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -25,7 +25,9 @@ import java.util.List; import lombok.core.configuration.CallSuperType; import lombok.core.configuration.ConfigurationKey; +import lombok.core.configuration.LogDeclaration; import lombok.core.configuration.FlagUsageType; +import lombok.core.configuration.IdentifierName; import lombok.core.configuration.NullCheckExceptionType; import lombok.core.configuration.TypeName; @@ -309,17 +311,16 @@ public class ConfigurationKeys { // ----- NonNull ----- /** - * lombok configuration: {@code lombok.nonNull.exceptionType} = <String: <em>a java exception type</em>; either [{@code IllegalArgumentException} or: {@code NullPointerException}]. + * lombok configuration: {@code lombok.nonNull.exceptionType} = one of: [{@code IllegalArgumentException}, {@code NullPointerException}, or {@code Assertion}]. * - * Sets the exception to throw if {@code @NonNull} is applied to a method parameter, and a caller passes in {@code null}. + * Sets the exception to throw if {@code @NonNull} is applied to a method parameter, and a caller passes in {@code null}. If the chosen configuration is {@code Assertion}, an assertion is generated instead, + * which would mean your code throws an {@code AssertionError} if assertions are enabled, and does nothing if assertions are not enabled. */ public static final ConfigurationKey<NullCheckExceptionType> NON_NULL_EXCEPTION_TYPE = new ConfigurationKey<NullCheckExceptionType>("lombok.nonNull.exceptionType", "The type of the exception to throw if a passed-in argument is null (Default: NullPointerException).") {}; /** * lombok configuration: {@code lombok.nonNull.flagUsage} = {@code WARNING} | {@code ERROR}. * - * <em>Implementation note: This field is supposed to be lombok.NonNull itself, but jdk6 and 7 have bugs where fields in annotations don't work well.</em> - * * If set, <em>any</em> usage of {@code @NonNull} results in a warning / error. */ public static final ConfigurationKey<FlagUsageType> NON_NULL_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.nonNull.flagUsage", "Emit a warning or error if @NonNull is used.") {}; @@ -423,7 +424,7 @@ public class ConfigurationKeys { * * If set the various log annotations (which make a log field) will use the stated identifier instead of {@code log} as a name. */ - public static final ConfigurationKey<String> LOG_ANY_FIELD_NAME = new ConfigurationKey<String>("lombok.log.fieldName", "Use this name for the generated logger fields (default: 'log').") {}; + public static final ConfigurationKey<IdentifierName> LOG_ANY_FIELD_NAME = new ConfigurationKey<IdentifierName>("lombok.log.fieldName", "Use this name for the generated logger fields (default: 'log').") {}; /** * lombok configuration: {@code lombok.log.fieldIsStatic} = {@code true} | {@code false}. @@ -434,6 +435,40 @@ public class ConfigurationKeys { */ public static final ConfigurationKey<Boolean> LOG_ANY_FIELD_IS_STATIC = new ConfigurationKey<Boolean>("lombok.log.fieldIsStatic", "Make the generated logger fields static (default: true).") {}; + // ----- Custom Logging ----- + + /** + * lombok configuration: {@code lombok.log.custom.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, <em>any</em> usage of {@code @CustomLog} results in a warning / error. + */ + public static final ConfigurationKey<FlagUsageType> LOG_CUSTOM_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.log.custom.flagUsage", "Emit a warning or error if @CustomLog is used.") {}; + + /** + * lombok configuration: {@code lombok.log.custom.declaration} = <logDeclaration string>. + * + * The log declaration must follow the pattern: + * <br> + * {@code [LoggerType ]LoggerFactoryType.loggerFactoryMethod(loggerFactoryMethodParams)[(loggerFactoryMethodParams)]} + * <br> + * It consists of: + * <ul> + * <li>Optional fully qualified logger type, e.g. {@code my.cool.Logger}, followed by space. If not specified, it defaults to the <em>LoggerFactoryType</em>. + * <li>Fully qualified logger factory type, e.g. {@code my.cool.LoggerFactory}, followed by dot. + * <li>Factory method, e.g. {@code createLogger}. This must be a {@code public static} method in the <em>LoggerFactoryType</em>. + * <li>At least one definition of factory method parameters, e.g. {@code ()} or {@code (TOPIC,TYPE)}. The format inside the parentheses is a comma-separated list of parameter kinds.<br> + * The allowed parameters are: {@code TYPE} | {@code NAME} | {@code TOPIC} | {@code NULL}.<br> + * There can be at most one parameter definition with {@code TOPIC} and at most one without {@code TOPIC}. You can specify both. + * </ul> + * + * An example: {@code my.cool.Logger my.cool.LoggerFactory.createLogger(TYPE)(TYPE,TOPIC)}<br> + * If no topic is provided in the usage of {@code @CustomLog}, the above will invoke {@code LoggerFactory}'s {@code createLogger} method, passing in the type as a {@code java.lang.Class} variable.<br> + * If a topic is provided, the overload of that method is invoked with 2 parameters: First the type (as {@code Class}), then the topic (as {@code String}). + * <p> + * If this configuration key is not set, any usage of {@code @CustomLog} will result in an error. + */ + public static final ConfigurationKey<LogDeclaration> LOG_CUSTOM_DECLARATION = new ConfigurationKey<LogDeclaration>("lombok.log.custom.declaration", "Define the generated custom logger field.") {}; + // ##### Experimental ##### /** @@ -548,7 +583,7 @@ public class ConfigurationKeys { * * The names of the constants generated by {@code @FieldNameConstants} will be prefixed with this value. */ - public static final ConfigurationKey<String> FIELD_NAME_CONSTANTS_INNER_TYPE_NAME = new ConfigurationKey<String>("lombok.fieldNameConstants.innerTypeName", "The default name of the inner type generated by @FieldNameConstants. (default: 'Fields').") {}; + public static final ConfigurationKey<IdentifierName> FIELD_NAME_CONSTANTS_INNER_TYPE_NAME = new ConfigurationKey<IdentifierName>("lombok.fieldNameConstants.innerTypeName", "The default name of the inner type generated by @FieldNameConstants. (default: 'Fields').") {}; /** * lombok configuration: {@code lombok.fieldNameConstants.uppercase} = {@code true} | {@code false}. diff --git a/src/core/lombok/CustomLog.java b/src/core/lombok/CustomLog.java new file mode 100644 index 00000000..d1f45f7c --- /dev/null +++ b/src/core/lombok/CustomLog.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2019 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Causes lombok to generate a logger field based on a custom logger implementation. + * <p> + * Complete documentation is found at <a href="https://projectlombok.org/features/Log">the project lombok features page for lombok log annotations</a>. + * <p> + * Example: + * <pre> + * @CustomLog + * public class LogExample { + * } + * </pre> + * With configuration: + * <pre> + * lombok.log.custom.declaration=my.cool.Logger my.cool.LoggerFactory.getLogger(NAME) + * </pre> + * + * will generate: + * + * <pre> + * public class LogExample { + * private static final my.cool.Logger log = my.cool.LoggerFactory.getLogger(LogExample.class.getName()); + * } + * </pre> + * <p> + * Configuration must be provided in lombok.config, otherwise any usage of this annotation will result in a compile-time error. + * + * This annotation is valid for classes and enumerations.<br> + * @see lombok.extern.java.Log @Log + * @see lombok.extern.apachecommons.CommonsLog @CommonsLog + * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.log4j.Log4j2 @Log4j2 + * @see lombok.extern.slf4j.Slf4j @Slf4j + * @see lombok.extern.slf4j.XSlf4j @XSlf4j + * @see lombok.extern.jbosslog.JBossLog @JBossLog + * @see lombok.extern.flogger.Flogger @Flogger + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface CustomLog { + /** + * + * Sets a custom topic/category. Note that this requires you to specify a parameter configuration for your custom logger that includes {@code TOPIC}. + * + * @return The topic/category of the constructed Logger. By default (or for the empty string as topic), the parameter configuration without {@code TOPIC} is invoked. + */ + String topic() default ""; +} diff --git a/src/core/lombok/core/configuration/ConfigurationDataType.java b/src/core/lombok/core/configuration/ConfigurationDataType.java index 7512d2e6..50b47a2e 100644 --- a/src/core/lombok/core/configuration/ConfigurationDataType.java +++ b/src/core/lombok/core/configuration/ConfigurationDataType.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 The Project Lombok Authors. + * Copyright (C) 2013-2019 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,6 +21,8 @@ */ package lombok.core.configuration; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Arrays; @@ -97,23 +99,10 @@ public final class ConfigurationDataType { return "[false | true]"; } }); - map.put(TypeName.class, new ConfigurationValueParser() { - @Override public Object parse(String value) { - return TypeName.valueOf(value); - } - - @Override public String description() { - return "type-name"; - } - - @Override public String exampleValue() { - return "<fully.qualified.Type>"; - } - }); SIMPLE_TYPES = map; } - private static ConfigurationValueParser enumParser(Object enumType) { + private static ConfigurationValueParser enumParser(Type enumType) { final Class<?> type = (Class<?>) enumType; @SuppressWarnings("rawtypes") final Class rawType = type; @@ -145,6 +134,38 @@ public final class ConfigurationDataType { }; } + private static ConfigurationValueParser valueTypeParser(Type argumentType) { + final Class<?> type = (Class<?>) argumentType; + final Method valueOfMethod = getMethod(type, "valueOf", String.class); + final Method descriptionMethod = getMethod(type, "description"); + final Method exampleValueMethod = getMethod(type, "exampleValue"); + return new ConfigurationValueParser() { + @Override public Object parse(String value) { + return invokeStaticMethod(valueOfMethod, value); + } + + @Override public String description() { + return invokeStaticMethod(descriptionMethod); + } + + @Override public String exampleValue() { + return invokeStaticMethod(exampleValueMethod); + } + + @SuppressWarnings("unchecked") + private <R> R invokeStaticMethod(Method method, Object... arguments) { + try { + return (R) method.invoke(null, arguments); + } catch (IllegalAccessException e) { + throw new IllegalStateException("The method " + method.getName() + " ", e); + } catch (InvocationTargetException e) { + // There shouldn't be any checked Exception, only IllegalArgumentException is expected + throw (RuntimeException) e.getTargetException(); + } + } + }; + } + private final boolean isList; private final ConfigurationValueParser parser; @@ -155,7 +176,7 @@ public final class ConfigurationDataType { Type type = keyClass.getGenericSuperclass(); if (!(type instanceof ParameterizedType)) { - throw new IllegalArgumentException("Missing type parameter in "+ type); + throw new IllegalArgumentException("Missing type parameter in " + type); } ParameterizedType parameterized = (ParameterizedType) type; @@ -178,6 +199,10 @@ public final class ConfigurationDataType { return new ConfigurationDataType(isList, enumParser(argumentType)); } + if (isConfigurationValueType(argumentType)) { + return new ConfigurationDataType(isList, valueTypeParser(argumentType)); + } + throw new IllegalArgumentException("Unsupported type parameter in " + type); } @@ -203,4 +228,18 @@ public final class ConfigurationDataType { private static boolean isEnum(Type argumentType) { return argumentType instanceof Class && ((Class<?>) argumentType).isEnum(); } + + private static boolean isConfigurationValueType(Type argumentType) { + return argumentType instanceof Class && ConfigurationValueType.class.isAssignableFrom((Class<?>) argumentType); + } + + private static Method getMethod(Class<?> argumentType, String name, Class<?>... parameterTypes) { + try { + return argumentType.getMethod(name, parameterTypes); + } catch (NoSuchMethodException e) { + throw new IllegalStateException("Method " + name + " with parameters " + Arrays.toString(parameterTypes) + " was not found.", e); + } catch (SecurityException e) { + throw new IllegalStateException("Cannot inspect methods of type " + argumentType, e); + } + } }
\ No newline at end of file diff --git a/src/core/lombok/core/configuration/ConfigurationValueType.java b/src/core/lombok/core/configuration/ConfigurationValueType.java new file mode 100644 index 00000000..a44a5a83 --- /dev/null +++ b/src/core/lombok/core/configuration/ConfigurationValueType.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2019 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core.configuration; + +/** + * If a type used in {@link ConfigurationKey} type argument implements this interface, + * it is expected to provide the following three static methods: + * <ul> + * <li><code>public static SELF valueOf(String value)</code> + * <li><code>public static String description()</code> + * <li><code>public static String exampleValue()</code> + * </ul> + * None of them should throw checked exceptions. + * Based on these methods, an instance of {@link ConfigurationValueParser} is created + * and used by the configuration system. + */ +public interface ConfigurationValueType { +} diff --git a/src/core/lombok/core/configuration/IdentifierName.java b/src/core/lombok/core/configuration/IdentifierName.java new file mode 100644 index 00000000..db83c2cc --- /dev/null +++ b/src/core/lombok/core/configuration/IdentifierName.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core.configuration; + +import lombok.core.JavaIdentifiers; + +public final class IdentifierName implements ConfigurationValueType { + private final String name; + + private IdentifierName(String name) { + this.name = name; + } + + public static IdentifierName valueOf(String name) { + if (name == null || name.trim().isEmpty()) return null; + + String trimmedName = name.trim(); + if (!JavaIdentifiers.isValidJavaIdentifier(trimmedName)) throw new IllegalArgumentException("Invalid identifier " + trimmedName); + return new IdentifierName(trimmedName); + } + + public static String description() { + return "identifier-name"; + } + + public static String exampleValue() { + return "<javaIdentifier>"; + } + + @Override public boolean equals(Object obj) { + if (!(obj instanceof IdentifierName)) return false; + return name.equals(((IdentifierName) obj).name); + } + + @Override public int hashCode() { + return name.hashCode(); + } + + @Override public String toString() { + return name; + } + + public String getName() { + return name; + } + + public char[] getCharArray() { + return name.toCharArray(); + } +} diff --git a/src/core/lombok/core/configuration/LogDeclaration.java b/src/core/lombok/core/configuration/LogDeclaration.java new file mode 100644 index 00000000..ad4102d4 --- /dev/null +++ b/src/core/lombok/core/configuration/LogDeclaration.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2019 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core.configuration; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class LogDeclaration implements ConfigurationValueType { + private static final Pattern PARAMETERS_PATTERN = Pattern.compile("(?:\\(([A-Z,]*)\\))"); + private static final Pattern DECLARATION_PATTERN = Pattern.compile("^(?:([^ ]+) )?([^(]+)\\.([^(]+)(" + PARAMETERS_PATTERN.pattern() + "+)$"); + + public enum LogFactoryParameter { + TYPE, NAME, TOPIC, NULL; + } + + private final TypeName loggerType; + private final TypeName loggerFactoryType; + private final IdentifierName loggerFactoryMethod; + private final List<LogFactoryParameter> parametersWithoutTopic; + private final List<LogFactoryParameter> parametersWithTopic; + + private LogDeclaration(TypeName loggerType, TypeName loggerFactoryType, IdentifierName loggerFactoryMethod, List<LogFactoryParameter> parametersWithoutTopic, List<LogFactoryParameter> parametersWithTopic) { + this.loggerType = loggerType; + this.loggerFactoryType = loggerFactoryType; + this.loggerFactoryMethod = loggerFactoryMethod; + this.parametersWithoutTopic = parametersWithoutTopic; + this.parametersWithTopic = parametersWithTopic; + } + + public static LogDeclaration valueOf(String declaration) { + if (declaration == null) return null; + + Matcher matcher = DECLARATION_PATTERN.matcher(declaration); + if (!matcher.matches()) throw new IllegalArgumentException("The declaration must follow the pattern: [LoggerType ]LoggerFactoryType.loggerFactoryMethod(loggerFactoryMethodParams)[(loggerFactoryMethodParams)]"); + + TypeName loggerFactoryType = TypeName.valueOf(matcher.group(2)); + TypeName loggerType = TypeName.valueOf(matcher.group(1)); + if (loggerType == null) loggerType = loggerFactoryType; + IdentifierName loggerFactoryMethod = IdentifierName.valueOf(matcher.group(3)); + List<List<LogFactoryParameter>> allParameters = parseParameters(matcher.group(4)); + + List<LogFactoryParameter> parametersWithoutTopic = null; + List<LogFactoryParameter> parametersWithTopic = null; + for (List<LogFactoryParameter> parameters: allParameters) { + if (parameters.contains(LogFactoryParameter.TOPIC)) { + if (parametersWithTopic != null) throw new IllegalArgumentException("There is more than one parameter definition that includes TOPIC: " + parametersWithTopic + " and " + parameters); + parametersWithTopic = parameters; + } else { + if (parametersWithoutTopic != null) throw new IllegalArgumentException("There is more than one parmaeter definition that does not include TOPIC: " + parametersWithoutTopic + " and " + parameters); + parametersWithoutTopic = parameters; + } + } + + // sanity check (the pattern should disallow this situation + if (parametersWithoutTopic == null && parametersWithTopic == null) throw new IllegalArgumentException("No logger factory method parameters specified."); + + return new LogDeclaration(loggerType, loggerFactoryType, loggerFactoryMethod, parametersWithoutTopic, parametersWithTopic); + } + + private static List<List<LogFactoryParameter>> parseParameters(String parametersDefinitions) { + List<List<LogFactoryParameter>> allParameters = new ArrayList<List<LogFactoryParameter>>(); + Matcher matcher = PARAMETERS_PATTERN.matcher(parametersDefinitions); + while (matcher.find()) { + String parametersDefinition = matcher.group(1); + List<LogFactoryParameter> parameters = new ArrayList<LogFactoryParameter>(); + if (!parametersDefinition.isEmpty()) { + for (String parameter : parametersDefinition.split(",")) { + parameters.add(LogFactoryParameter.valueOf(parameter)); + } + } + allParameters.add(parameters); + } + return allParameters; + } + + public static String description() { + return "custom-log-declaration"; + } + + public static String exampleValue() { + return "my.cool.Logger my.cool.LoggerFactory.createLogger()(TOPIC,TYPE)"; + } + + @Override public boolean equals(Object obj) { + if (!(obj instanceof LogDeclaration)) return false; + return loggerType.equals(((LogDeclaration) obj).loggerType) + && loggerFactoryType.equals(((LogDeclaration) obj).loggerFactoryType) + && loggerFactoryMethod.equals(((LogDeclaration) obj).loggerFactoryMethod) + && parametersWithoutTopic == ((LogDeclaration) obj).parametersWithoutTopic || parametersWithoutTopic.equals(((LogDeclaration) obj).parametersWithoutTopic) + && parametersWithTopic == ((LogDeclaration) obj).parametersWithTopic || parametersWithTopic.equals(((LogDeclaration) obj).parametersWithTopic); + } + + @Override public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + loggerType.hashCode(); + result = prime * result + loggerFactoryType.hashCode(); + result = prime * result + loggerFactoryMethod.hashCode(); + result = prime * result + ((parametersWithTopic == null) ? 0 : parametersWithTopic.hashCode()); + result = prime * result + ((parametersWithoutTopic == null) ? 0 : parametersWithoutTopic.hashCode()); + return result; + } + + @Override public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(loggerType); + sb.append(" "); + sb.append(loggerFactoryType); + sb.append("."); + sb.append(loggerFactoryMethod); + appendParams(sb, parametersWithoutTopic); + appendParams(sb, parametersWithTopic); + return sb.toString(); + } + + private static void appendParams(StringBuilder sb, List<LogFactoryParameter> params) { + if (params != null) { + sb.append("("); + boolean first = true; + for (LogFactoryParameter param : params) { + if (!first) { + sb.append(","); + } + first = false; + sb.append(param); + } + sb.append(")"); + } + } + + public TypeName getLoggerType() { + return loggerType; + } + + public TypeName getLoggerFactoryType() { + return loggerFactoryType; + } + + public IdentifierName getLoggerFactoryMethod() { + return loggerFactoryMethod; + } + + public List<LogFactoryParameter> getParametersWithoutTopic() { + return parametersWithoutTopic; + } + + public List<LogFactoryParameter> getParametersWithTopic() { + return parametersWithTopic; + } +} diff --git a/src/core/lombok/core/configuration/TypeName.java b/src/core/lombok/core/configuration/TypeName.java index 989e1b97..a1eac7bd 100644 --- a/src/core/lombok/core/configuration/TypeName.java +++ b/src/core/lombok/core/configuration/TypeName.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Project Lombok Authors. + * Copyright (C) 2013-2019 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,9 @@ */ package lombok.core.configuration; -public final class TypeName { +import lombok.core.JavaIdentifiers; + +public final class TypeName implements ConfigurationValueType { private final String name; private TypeName(String name) { @@ -29,7 +31,21 @@ public final class TypeName { } public static TypeName valueOf(String name) { - return new TypeName(name); + if (name == null || name.trim().isEmpty()) return null; + + String trimmedName = name.trim(); + for (String identifier : trimmedName.split("\\.")) { + if (!JavaIdentifiers.isValidJavaIdentifier(identifier)) throw new IllegalArgumentException("Invalid type name " + trimmedName + " (part " + identifier + ")"); + } + return new TypeName(trimmedName); + } + + public static String description() { + return "type-name"; + } + + public static String exampleValue() { + return "<fully.qualified.Type>"; } @Override public boolean equals(Object obj) { @@ -44,4 +60,12 @@ public final class TypeName { @Override public String toString() { return name; } + + public String getName() { + return name; + } + + public char[] getCharArray() { + return name.toCharArray(); + } } diff --git a/src/core/lombok/core/handlers/LoggingFramework.java b/src/core/lombok/core/handlers/LoggingFramework.java new file mode 100644 index 00000000..88c0317b --- /dev/null +++ b/src/core/lombok/core/handlers/LoggingFramework.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2019 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core.handlers; + +import java.lang.annotation.Annotation; + +import lombok.core.configuration.LogDeclaration; + +public class LoggingFramework { + // private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(TargetType.class); + public static final LoggingFramework COMMONS = new LoggingFramework( + lombok.extern.apachecommons.CommonsLog.class, + LogDeclaration.valueOf("org.apache.commons.logging.Log org.apache.commons.logging.LogFactory.getLog(TYPE)(TOPIC)") + ); + + // private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(TargetType.class.getName()); + public static final LoggingFramework JUL = new LoggingFramework( + lombok.extern.java.Log.class, + LogDeclaration.valueOf("java.util.logging.Logger java.util.logging.Logger.getLogger(NAME)(TOPIC)") + ); + + // private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TargetType.class); + public static final LoggingFramework LOG4J = new LoggingFramework( + lombok.extern.log4j.Log4j.class, + LogDeclaration.valueOf("org.apache.log4j.Logger org.apache.log4j.Logger.getLogger(TYPE)(TOPIC)") + ); + + // private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(TargetType.class); + public static final LoggingFramework LOG4J2 = new LoggingFramework( + lombok.extern.log4j.Log4j2.class, + LogDeclaration.valueOf("org.apache.logging.log4j.Logger org.apache.logging.log4j.LogManager.getLogger(TYPE)(TOPIC)") + ); + + // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class); + public static final LoggingFramework SLF4J = new LoggingFramework( + lombok.extern.slf4j.Slf4j.class, + LogDeclaration.valueOf("org.slf4j.Logger org.slf4j.LoggerFactory.getLogger(TYPE)(TOPIC)") + ); + + // private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(TargetType.class); + public static final LoggingFramework XSLF4J = new LoggingFramework( + lombok.extern.slf4j.XSlf4j.class, + LogDeclaration.valueOf("org.slf4j.ext.XLogger org.slf4j.ext.XLoggerFactory.getXLogger(TYPE)(TOPIC)") + ); + + // private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(TargetType.class); + public static final LoggingFramework JBOSSLOG = new LoggingFramework( + lombok.extern.jbosslog.JBossLog.class, + LogDeclaration.valueOf("org.jboss.logging.Logger org.jboss.logging.Logger.getLogger(TYPE)(TOPIC)") + ); + + // private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass(); + public static final LoggingFramework FLOGGER = new LoggingFramework( + lombok.extern.flogger.Flogger.class, + LogDeclaration.valueOf("com.google.common.flogger.FluentLogger com.google.common.flogger.FluentLogger.forEnclosingClass()") + ); + + private final Class<? extends Annotation> annotationClass; + private final String annotationAsString; + private final LogDeclaration declaration; + + public LoggingFramework(Class<? extends Annotation> annotationClass, LogDeclaration declaration) { + this.annotationClass = annotationClass; + this.annotationAsString = "@" + annotationClass.getSimpleName(); + this.declaration = declaration; + } + + public Class<? extends Annotation> getAnnotationClass() { + return annotationClass; + } + + public String getAnnotationAsString() { + return annotationAsString; + } + + public LogDeclaration getDeclaration() { + return declaration; + } +} diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 463990d1..1a0633bf 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -761,7 +761,7 @@ public class EclipseHandlerUtil { TypeReference typeRef = annotation.type; boolean match = false; if (typeRef != null && typeRef.getTypeName() != null) { - for (TypeName cn : configuredCopyable) if (typeMatches(cn.toString(), node, typeRef)) { + for (TypeName cn : configuredCopyable) if (cn != null && typeMatches(cn.toString(), node, typeRef)) { result.add(annotation); match = true; break; diff --git a/src/core/lombok/eclipse/handlers/HandleFieldNameConstants.java b/src/core/lombok/eclipse/handlers/HandleFieldNameConstants.java index 574c1f41..2db7591c 100644 --- a/src/core/lombok/eclipse/handlers/HandleFieldNameConstants.java +++ b/src/core/lombok/eclipse/handlers/HandleFieldNameConstants.java @@ -47,6 +47,7 @@ import lombok.AccessLevel; import lombok.ConfigurationKeys; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; +import lombok.core.configuration.IdentifierName; import lombok.core.handlers.HandlerUtil; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; @@ -56,7 +57,9 @@ import lombok.experimental.FieldNameConstants; @ProviderFor(EclipseAnnotationHandler.class) public class HandleFieldNameConstants extends EclipseAnnotationHandler<FieldNameConstants> { - public void generateFieldNameConstantsForType(EclipseNode typeNode, EclipseNode errorNode, AccessLevel level, boolean asEnum, String innerTypeName, boolean onlyExplicit, boolean uppercase) { + private static final IdentifierName FIELDS = IdentifierName.valueOf("Fields"); + + public void generateFieldNameConstantsForType(EclipseNode typeNode, EclipseNode errorNode, AccessLevel level, boolean asEnum, IdentifierName innerTypeName, boolean onlyExplicit, boolean uppercase) { TypeDeclaration typeDecl = null; if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get(); @@ -110,23 +113,29 @@ public class HandleFieldNameConstants extends EclipseAnnotationHandler<FieldName return; } - String innerTypeName = annotationInstance.innerTypeName(); - if (innerTypeName.isEmpty()) innerTypeName = annotationNode.getAst().readConfiguration(ConfigurationKeys.FIELD_NAME_CONSTANTS_INNER_TYPE_NAME); - if (innerTypeName == null || innerTypeName.isEmpty()) innerTypeName = "Fields"; + IdentifierName innerTypeName; + try { + innerTypeName = IdentifierName.valueOf(annotationInstance.innerTypeName()); + } catch(IllegalArgumentException e) { + annotationNode.addError("InnerTypeName " + annotationInstance.innerTypeName() + " is not a valid Java identifier."); + return; + } + if (innerTypeName == null) innerTypeName = annotationNode.getAst().readConfiguration(ConfigurationKeys.FIELD_NAME_CONSTANTS_INNER_TYPE_NAME); + if (innerTypeName == null) innerTypeName = FIELDS; Boolean uppercase = annotationNode.getAst().readConfiguration(ConfigurationKeys.FIELD_NAME_CONSTANTS_UPPERCASE); if (uppercase == null) uppercase = false; generateFieldNameConstantsForType(node, annotationNode, level, asEnum, innerTypeName, annotationInstance.onlyExplicitlyIncluded(), uppercase); } - private void createInnerTypeFieldNameConstants(EclipseNode typeNode, EclipseNode errorNode, ASTNode source, AccessLevel level, List<EclipseNode> fields, boolean asEnum, String innerTypeName, boolean uppercase) { + private void createInnerTypeFieldNameConstants(EclipseNode typeNode, EclipseNode errorNode, ASTNode source, AccessLevel level, List<EclipseNode> fields, boolean asEnum, IdentifierName innerTypeName, boolean uppercase) { if (fields.isEmpty()) return; ASTVisitor generatedByVisitor = new SetGeneratedByVisitor(source); TypeDeclaration parent = (TypeDeclaration) typeNode.get(); - EclipseNode fieldsType = findInnerClass(typeNode, innerTypeName); + EclipseNode fieldsType = findInnerClass(typeNode, innerTypeName.getName()); boolean genConstr = false, genClinit = false; - char[] name = innerTypeName.toCharArray(); + char[] name = innerTypeName.getCharArray(); TypeDeclaration generatedInnerType = null; if (fieldsType == null) { generatedInnerType = new TypeDeclaration(parent.compilationResult); diff --git a/src/core/lombok/eclipse/handlers/HandleLog.java b/src/core/lombok/eclipse/handlers/HandleLog.java index 8c7f7971..7a140193 100644 --- a/src/core/lombok/eclipse/handlers/HandleLog.java +++ b/src/core/lombok/eclipse/handlers/HandleLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 The Project Lombok Authors. + * Copyright (C) 2010-2019 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,24 +21,20 @@ */ package lombok.eclipse.handlers; -import static lombok.core.handlers.HandlerUtil.*; +import static lombok.core.handlers.HandlerUtil.handleFlagUsage; import static lombok.eclipse.Eclipse.fromQualifiedName; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; import java.lang.reflect.Modifier; import java.util.Arrays; - -import lombok.ConfigurationKeys; -import lombok.core.AnnotationValues; -import lombok.eclipse.EclipseAnnotationHandler; -import lombok.eclipse.EclipseNode; -import lombok.eclipse.handlers.EclipseHandlerUtil.MemberExistsResult; +import java.util.List; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.StringLiteral; @@ -47,7 +43,18 @@ import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.mangosdk.spi.ProviderFor; +import lombok.ConfigurationKeys; +import lombok.core.AnnotationValues; +import lombok.core.configuration.IdentifierName; +import lombok.core.configuration.LogDeclaration; +import lombok.core.configuration.LogDeclaration.LogFactoryParameter; +import lombok.core.handlers.LoggingFramework; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; + public class HandleLog { + private static final IdentifierName LOG = IdentifierName.valueOf("log"); + private HandleLog() { throw new UnsupportedOperationException(); } @@ -57,8 +64,8 @@ public class HandleLog { switch (owner.getKind()) { case TYPE: - String logFieldName = annotationNode.getAst().readConfiguration(ConfigurationKeys.LOG_ANY_FIELD_NAME); - if (logFieldName == null) logFieldName = "log"; + IdentifierName logFieldName = annotationNode.getAst().readConfiguration(ConfigurationKeys.LOG_ANY_FIELD_NAME); + if (logFieldName == null) logFieldName = LOG; boolean useStatic = !Boolean.FALSE.equals(annotationNode.getAst().readConfiguration(ConfigurationKeys.LOG_ANY_FIELD_IS_STATIC)); @@ -66,24 +73,32 @@ public class HandleLog { if (owner.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) owner.get(); int modifiers = typeDecl == null ? 0 : typeDecl.modifiers; - boolean notAClass = (modifiers & - (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation)) != 0; + boolean notAClass = (modifiers & (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation)) != 0; if (typeDecl == null || notAClass) { annotationNode.addError(framework.getAnnotationAsString() + " is legal only on classes and enums."); return; } - if (fieldExists(logFieldName, owner) != MemberExistsResult.NOT_EXISTS) { + if (fieldExists(logFieldName.getName(), owner) != MemberExistsResult.NOT_EXISTS) { annotationNode.addWarning("Field '" + logFieldName + "' already exists."); return; } - ClassLiteralAccess loggingType = selfType(owner, source); + if (loggerTopic != null && loggerTopic.trim().isEmpty()) loggerTopic = null; + if (framework.getDeclaration().getParametersWithTopic() == null && loggerTopic != null) { + annotationNode.addError(framework.getAnnotationAsString() + " does not allow a topic."); + loggerTopic = null; + } + if (framework.getDeclaration().getParametersWithoutTopic() == null && loggerTopic == null) { + annotationNode.addError(framework.getAnnotationAsString() + " requires a topic."); + loggerTopic = ""; + } - FieldDeclaration fieldDeclaration = createField(framework, source, loggingType, logFieldName, useStatic, loggerTopic); + ClassLiteralAccess loggingType = selfType(owner, source); + FieldDeclaration fieldDeclaration = createField(framework, source, loggingType, logFieldName.getName(), useStatic, loggerTopic); fieldDeclaration.traverse(new SetGeneratedByVisitor(source), typeDecl.staticInitializerScope); - // TODO temporary workaround for issue 217. http://code.google.com/p/projectlombok/issues/detail?id=217 + // TODO temporary workaround for issue 290. https://github.com/rzwitserloot/lombok/issues/290 // injectFieldSuppressWarnings(owner, fieldDeclaration); injectField(owner, fieldDeclaration); owner.rebuild(); @@ -95,12 +110,12 @@ public class HandleLog { public static ClassLiteralAccess selfType(EclipseNode type, Annotation source) { int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long)pS << 32 | pE; - + long p = (long) pS << 32 | pE; + TypeDeclaration typeDeclaration = (TypeDeclaration)type.get(); TypeReference typeReference = new SingleTypeReference(typeDeclaration.name, p); setGeneratedBy(typeReference, source); - + ClassLiteralAccess result = new ClassLiteralAccess(source.sourceEnd, typeReference); setGeneratedBy(result, source); @@ -111,30 +126,23 @@ public class HandleLog { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long) pS << 32 | pE; - // private static final <loggerType> log = <factoryMethod>(<parameter>); + // private static final <loggerType> log = <factoryMethod>(<parameter>); FieldDeclaration fieldDecl = new FieldDeclaration(logFieldName.toCharArray(), 0, -1); setGeneratedBy(fieldDecl, source); fieldDecl.declarationSourceEnd = -1; fieldDecl.modifiers = Modifier.PRIVATE | (useStatic ? Modifier.STATIC : 0) | Modifier.FINAL; - fieldDecl.type = createTypeReference(framework.getLoggerTypeName(), source); + LogDeclaration logDeclaration = framework.getDeclaration(); + fieldDecl.type = createTypeReference(logDeclaration.getLoggerType().getName(), source); MessageSend factoryMethodCall = new MessageSend(); setGeneratedBy(factoryMethodCall, source); - - factoryMethodCall.receiver = createNameReference(framework.getLoggerFactoryTypeName(), source); - factoryMethodCall.selector = framework.getLoggerFactoryMethodName().toCharArray(); - Expression parameter; - if (!framework.passTypeName) { - parameter = null; - } else if (loggerTopic == null || loggerTopic.trim().length() == 0) { - parameter = framework.createFactoryParameter(loggingType, source); - } else { - parameter = new StringLiteral(loggerTopic.toCharArray(), pS, pE, 0); - } + factoryMethodCall.receiver = createNameReference(logDeclaration.getLoggerFactoryType().getName(), source); + factoryMethodCall.selector = logDeclaration.getLoggerFactoryMethod().getCharArray(); - factoryMethodCall.arguments = parameter != null ? new Expression[] { parameter } : null; + List<LogFactoryParameter> parameters = loggerTopic != null ? logDeclaration.getParametersWithTopic() : logDeclaration.getParametersWithoutTopic(); + factoryMethodCall.arguments = createFactoryParameters(loggingType, source, parameters, loggerTopic); factoryMethodCall.nameSourcePosition = p; factoryMethodCall.sourceStart = pS; factoryMethodCall.sourceEnd = factoryMethodCall.statementEnd = pE; @@ -146,25 +154,64 @@ public class HandleLog { public static TypeReference createTypeReference(String typeName, Annotation source) { int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long)pS << 32 | pE; + long p = (long) pS << 32 | pE; - TypeReference typeReference; - if (typeName.contains(".")) { - - char[][] typeNameTokens = fromQualifiedName(typeName); - long[] pos = new long[typeNameTokens.length]; - Arrays.fill(pos, p); - - typeReference = new QualifiedTypeReference(typeNameTokens, pos); - } - else { - typeReference = null; - } + char[][] typeNameTokens = fromQualifiedName(typeName); + long[] pos = new long[typeNameTokens.length]; + Arrays.fill(pos, p); + TypeReference typeReference = new QualifiedTypeReference(typeNameTokens, pos); setGeneratedBy(typeReference, source); return typeReference; } + private static final Expression[] createFactoryParameters(ClassLiteralAccess loggingType, Annotation source, List<LogFactoryParameter> parameters, String loggerTopic) { + Expression[] expressions = new Expression[parameters.size()]; + int pS = source.sourceStart, pE = source.sourceEnd; + + for (int i = 0; i < parameters.size(); i++) { + LogFactoryParameter parameter = parameters.get(i); + + switch(parameter) { + case TYPE: + expressions[i] = createFactoryTypeParameter(loggingType, source); + break; + case NAME: + long p = (long) pS << 32 | pE; + + MessageSend factoryParameterCall = new MessageSend(); + setGeneratedBy(factoryParameterCall, source); + + factoryParameterCall.receiver = createFactoryTypeParameter(loggingType, source); + factoryParameterCall.selector = "getName".toCharArray(); + + factoryParameterCall.nameSourcePosition = p; + factoryParameterCall.sourceStart = pS; + factoryParameterCall.sourceEnd = factoryParameterCall.statementEnd = pE; + + expressions[i] = factoryParameterCall; + break; + case TOPIC: + expressions[i] = new StringLiteral(loggerTopic.toCharArray(), pS, pE, 0); + break; + case NULL: + expressions[i] = new NullLiteral(pS, pE); + break; + default: + throw new IllegalStateException("Unknown logger factory parameter type: " + parameter); + } + } + + return expressions; + } + + private static final Expression createFactoryTypeParameter(ClassLiteralAccess loggingType, Annotation source) { + TypeReference copy = copyType(loggingType.type, source); + ClassLiteralAccess result = new ClassLiteralAccess(source.sourceEnd, copy); + setGeneratedBy(result, source); + return result; + } + /** * Handles the {@link lombok.extern.apachecommons.CommonsLog} annotation for Eclipse. */ @@ -253,92 +300,20 @@ public class HandleLog { } } - enum LoggingFramework { - // private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(TargetType.class); - COMMONS("org.apache.commons.logging.Log", "org.apache.commons.logging.LogFactory", "getLog", "@CommonsLog"), - - // private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(TargetType.class.getName()); - JUL("java.util.logging.Logger", "java.util.logging.Logger", "getLogger", "@Log") { - @Override public Expression createFactoryParameter(ClassLiteralAccess type, Annotation source) { - int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long)pS << 32 | pE; - - MessageSend factoryParameterCall = new MessageSend(); - setGeneratedBy(factoryParameterCall, source); - - factoryParameterCall.receiver = super.createFactoryParameter(type, source); - factoryParameterCall.selector = "getName".toCharArray(); - - factoryParameterCall.nameSourcePosition = p; - factoryParameterCall.sourceStart = pS; - factoryParameterCall.sourceEnd = factoryParameterCall.statementEnd = pE; - - return factoryParameterCall; + /** + * Handles the {@link lombok.CustomLog} annotation for Eclipse. + */ + @ProviderFor(EclipseAnnotationHandler.class) + public static class HandleCustomLog extends EclipseAnnotationHandler<lombok.CustomLog> { + @Override public void handle(AnnotationValues<lombok.CustomLog> annotation, Annotation source, EclipseNode annotationNode) { + handleFlagUsage(annotationNode, ConfigurationKeys.LOG_CUSTOM_FLAG_USAGE, "@CustomLog", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); + LogDeclaration logDeclaration = annotationNode.getAst().readConfiguration(ConfigurationKeys.LOG_CUSTOM_DECLARATION); + if (logDeclaration == null) { + annotationNode.addError("The @CustomLog annotation is not configured; please set log.custom.declaration in lombok.config."); + return; } - }, - - // private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TargetType.class); - LOG4J("org.apache.log4j.Logger", "org.apache.log4j.Logger", "getLogger", "@Log4j"), - - // private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(TargetType.class); - LOG4J2("org.apache.logging.log4j.Logger", "org.apache.logging.log4j.LogManager", "getLogger", "@Log4j2"), - - // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class); - SLF4J("org.slf4j.Logger", "org.slf4j.LoggerFactory", "getLogger", "@Slf4j"), - - // private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(TargetType.class); - XSLF4J("org.slf4j.ext.XLogger", "org.slf4j.ext.XLoggerFactory", "getXLogger", "@XSlf4j"), - - // private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(TargetType.class); - JBOSSLOG("org.jboss.logging.Logger", "org.jboss.logging.Logger", "getLogger", "@JBossLog"), - - // private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass(); - FLOGGER("com.google.common.flogger.FluentLogger", "com.google.common.flogger.FluentLogger", "forEnclosingClass", "@Flogger", false), - ; - - private final String loggerTypeName; - private final String loggerFactoryTypeName; - private final String loggerFactoryMethodName; - private final String annotationAsString; - private final boolean passTypeName; - - LoggingFramework(String loggerTypeName, String loggerFactoryTypeName, String loggerFactoryMethodName, String annotationAsString, boolean passTypeName) { - this.loggerTypeName = loggerTypeName; - this.loggerFactoryTypeName = loggerFactoryTypeName; - this.loggerFactoryMethodName = loggerFactoryMethodName; - this.annotationAsString = annotationAsString; - this.passTypeName = passTypeName; - } - - LoggingFramework(String loggerTypeName, String loggerFactoryTypeName, String loggerFactoryMethodName, String annotationAsString) { - this.loggerTypeName = loggerTypeName; - this.loggerFactoryTypeName = loggerFactoryTypeName; - this.loggerFactoryMethodName = loggerFactoryMethodName; - this.annotationAsString = annotationAsString; - this.passTypeName = true; - } - - final String getAnnotationAsString() { - return annotationAsString; - } - - final String getLoggerTypeName() { - return loggerTypeName; + LoggingFramework framework = new LoggingFramework(lombok.CustomLog.class, logDeclaration); + processAnnotation(framework, annotation, source, annotationNode, annotation.getInstance().topic()); } - - final String getLoggerFactoryTypeName() { - return loggerFactoryTypeName; - } - - final String getLoggerFactoryMethodName() { - return loggerFactoryMethodName; - } - - Expression createFactoryParameter(ClassLiteralAccess loggingType, Annotation source) { - TypeReference copy = copyType(loggingType.type, source); - ClassLiteralAccess result = new ClassLiteralAccess(source.sourceEnd, copy); - setGeneratedBy(result, source); - return result; - }; } } diff --git a/src/core/lombok/eclipse/handlers/HandleSynchronized.java b/src/core/lombok/eclipse/handlers/HandleSynchronized.java index a45a499c..22f7f967 100644 --- a/src/core/lombok/eclipse/handlers/HandleSynchronized.java +++ b/src/core/lombok/eclipse/handlers/HandleSynchronized.java @@ -98,7 +98,7 @@ public class HandleSynchronized extends EclipseAnnotationHandler<Synchronized> { fieldDecl.type = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, new long[] { 0, 0, 0 }); setGeneratedBy(fieldDecl.type, source); fieldDecl.initialization = arrayAlloc; - // TODO temporary workaround for issue 217. http://code.google.com/p/projectlombok/issues/detail?id=217 + // TODO temporary workaround for issue 290. https://github.com/rzwitserloot/lombok/issues/290 // injectFieldSuppressWarnings(annotationNode.up().up(), fieldDecl); injectField(annotationNode.up().up(), fieldDecl); } diff --git a/src/core/lombok/extern/apachecommons/CommonsLog.java b/src/core/lombok/extern/apachecommons/CommonsLog.java index fa3d6f09..ca808329 100644 --- a/src/core/lombok/extern/apachecommons/CommonsLog.java +++ b/src/core/lombok/extern/apachecommons/CommonsLog.java @@ -57,6 +57,7 @@ import java.lang.annotation.Target; * @see lombok.extern.slf4j.XSlf4j @XSlf4j * @see lombok.extern.jbosslog.JBossLog @JBossLog * @see lombok.extern.flogger.Flogger @Flogger + * @see lombok.CustomLog @CustomLog */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) diff --git a/src/core/lombok/extern/flogger/Flogger.java b/src/core/lombok/extern/flogger/Flogger.java index ecbfd28c..3446e949 100644 --- a/src/core/lombok/extern/flogger/Flogger.java +++ b/src/core/lombok/extern/flogger/Flogger.java @@ -55,6 +55,7 @@ import java.lang.annotation.Target; * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.slf4j.XSlf4j @XSlf4j * @see lombok.extern.jbosslog.JBossLog @JBossLog + * @see lombok.CustomLog @CustomLog */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) diff --git a/src/core/lombok/extern/java/Log.java b/src/core/lombok/extern/java/Log.java index 9a1ee412..f2b5024f 100644 --- a/src/core/lombok/extern/java/Log.java +++ b/src/core/lombok/extern/java/Log.java @@ -56,6 +56,7 @@ import java.lang.annotation.Target; * @see lombok.extern.slf4j.XSlf4j @XSlf4j * @see lombok.extern.jbosslog.JBossLog @JBossLog * @see lombok.extern.flogger.Flogger @Flogger + * @see lombok.CustomLog @CustomLog */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) diff --git a/src/core/lombok/extern/jbosslog/JBossLog.java b/src/core/lombok/extern/jbosslog/JBossLog.java index 684585e0..960a111b 100644 --- a/src/core/lombok/extern/jbosslog/JBossLog.java +++ b/src/core/lombok/extern/jbosslog/JBossLog.java @@ -56,7 +56,8 @@ import java.lang.annotation.Target; * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.slf4j.XSlf4j @XSlf4j * @see lombok.extern.flogger.Flogger @Flogger - * */ + * @see lombok.CustomLog @CustomLog + */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface JBossLog { diff --git a/src/core/lombok/extern/log4j/Log4j.java b/src/core/lombok/extern/log4j/Log4j.java index 249ef71a..1b4a973b 100644 --- a/src/core/lombok/extern/log4j/Log4j.java +++ b/src/core/lombok/extern/log4j/Log4j.java @@ -57,6 +57,7 @@ import java.lang.annotation.Target; * @see lombok.extern.slf4j.XSlf4j @XSlf4j * @see lombok.extern.jbosslog.JBossLog @JBossLog * @see lombok.extern.flogger.Flogger @Flogger + * @see lombok.CustomLog @CustomLog */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) diff --git a/src/core/lombok/extern/log4j/Log4j2.java b/src/core/lombok/extern/log4j/Log4j2.java index a6aa90c0..571b0563 100644 --- a/src/core/lombok/extern/log4j/Log4j2.java +++ b/src/core/lombok/extern/log4j/Log4j2.java @@ -57,6 +57,7 @@ import java.lang.annotation.Target; * @see lombok.extern.slf4j.XSlf4j @XSlf4j * @see lombok.extern.jbosslog.JBossLog @JBossLog * @see lombok.extern.flogger.Flogger @Flogger + * @see lombok.CustomLog @CustomLog */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) diff --git a/src/core/lombok/extern/slf4j/Slf4j.java b/src/core/lombok/extern/slf4j/Slf4j.java index 347d81d2..c4aded24 100644 --- a/src/core/lombok/extern/slf4j/Slf4j.java +++ b/src/core/lombok/extern/slf4j/Slf4j.java @@ -56,6 +56,7 @@ import java.lang.annotation.Target; * @see lombok.extern.slf4j.XSlf4j @XSlf4j * @see lombok.extern.jbosslog.JBossLog @JBossLog * @see lombok.extern.flogger.Flogger @Flogger + * @see lombok.CustomLog @CustomLog */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) diff --git a/src/core/lombok/extern/slf4j/XSlf4j.java b/src/core/lombok/extern/slf4j/XSlf4j.java index 4d53a1eb..b99554a5 100644 --- a/src/core/lombok/extern/slf4j/XSlf4j.java +++ b/src/core/lombok/extern/slf4j/XSlf4j.java @@ -56,6 +56,7 @@ import java.lang.annotation.Target; * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.jbosslog.JBossLog @JBossLog * @see lombok.extern.flogger.Flogger @Flogger + * @see lombok.CustomLog @CustomLog */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) diff --git a/src/core/lombok/javac/handlers/HandleFieldNameConstants.java b/src/core/lombok/javac/handlers/HandleFieldNameConstants.java index 628e83dc..ec4015c7 100644 --- a/src/core/lombok/javac/handlers/HandleFieldNameConstants.java +++ b/src/core/lombok/javac/handlers/HandleFieldNameConstants.java @@ -30,6 +30,7 @@ import lombok.AccessLevel; import lombok.ConfigurationKeys; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; +import lombok.core.configuration.IdentifierName; import lombok.core.handlers.HandlerUtil; import lombok.experimental.FieldNameConstants; import lombok.javac.JavacAnnotationHandler; @@ -54,7 +55,9 @@ import com.sun.tools.javac.util.Name; @ProviderFor(JavacAnnotationHandler.class) public class HandleFieldNameConstants extends JavacAnnotationHandler<FieldNameConstants> { - public void generateFieldNameConstantsForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean asEnum, String innerTypeName, boolean onlyExplicit, boolean uppercase) { + private static final IdentifierName FIELDS = IdentifierName.valueOf("Fields"); + + public void generateFieldNameConstantsForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean asEnum, IdentifierName innerTypeName, boolean onlyExplicit, boolean uppercase) { JCClassDecl typeDecl = null; if (typeNode.get() instanceof JCClassDecl) typeDecl = (JCClassDecl) typeNode.get(); @@ -115,24 +118,30 @@ public class HandleFieldNameConstants extends JavacAnnotationHandler<FieldNameCo return; } - String innerTypeName = annotationInstance.innerTypeName(); - if (innerTypeName.isEmpty()) innerTypeName = annotationNode.getAst().readConfiguration(ConfigurationKeys.FIELD_NAME_CONSTANTS_INNER_TYPE_NAME); - if (innerTypeName == null || innerTypeName.isEmpty()) innerTypeName = "Fields"; + IdentifierName innerTypeName; + try { + innerTypeName = IdentifierName.valueOf(annotationInstance.innerTypeName()); + } catch(IllegalArgumentException e) { + annotationNode.addError("InnerTypeName " + annotationInstance.innerTypeName() + " is not a valid Java identifier."); + return; + } + if (innerTypeName == null) innerTypeName = annotationNode.getAst().readConfiguration(ConfigurationKeys.FIELD_NAME_CONSTANTS_INNER_TYPE_NAME); + if (innerTypeName == null) innerTypeName = FIELDS; Boolean uppercase = annotationNode.getAst().readConfiguration(ConfigurationKeys.FIELD_NAME_CONSTANTS_UPPERCASE); if (uppercase == null) uppercase = false; generateFieldNameConstantsForType(node, annotationNode, level, asEnum, innerTypeName, annotationInstance.onlyExplicitlyIncluded(), uppercase); } - private void createInnerTypeFieldNameConstants(JavacNode typeNode, JavacNode errorNode, JCTree pos, AccessLevel level, java.util.List<JavacNode> fields, boolean asEnum, String innerTypeName, boolean uppercase) { + private void createInnerTypeFieldNameConstants(JavacNode typeNode, JavacNode errorNode, JCTree pos, AccessLevel level, java.util.List<JavacNode> fields, boolean asEnum, IdentifierName innerTypeName, boolean uppercase) { if (fields.isEmpty()) return; JavacTreeMaker maker = typeNode.getTreeMaker(); JCModifiers mods = maker.Modifiers(toJavacModifier(level) | (asEnum ? Flags.ENUM : Flags.STATIC | Flags.FINAL)); - Name fieldsName = typeNode.toName(innerTypeName); + Name fieldsName = typeNode.toName(innerTypeName.getName()); - JavacNode fieldsType = findInnerClass(typeNode, innerTypeName); + JavacNode fieldsType = findInnerClass(typeNode, innerTypeName.getName()); boolean genConstr = false; if (fieldsType == null) { JCClassDecl innerType = maker.ClassDef(mods, fieldsName, List.<JCTypeParameter>nil(), null, List.<JCExpression>nil(), List.<JCTree>nil()); diff --git a/src/core/lombok/javac/handlers/HandleLog.java b/src/core/lombok/javac/handlers/HandleLog.java index 6d742e76..522f8576 100644 --- a/src/core/lombok/javac/handlers/HandleLog.java +++ b/src/core/lombok/javac/handlers/HandleLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 The Project Lombok Authors. + * Copyright (C) 2010-2019 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,15 +22,19 @@ package lombok.javac.handlers; import static lombok.core.handlers.HandlerUtil.*; +import static lombok.javac.Javac.CTC_BOT; import static lombok.javac.handlers.JavacHandlerUtil.*; -import java.lang.annotation.Annotation; - import lombok.ConfigurationKeys; import lombok.core.AnnotationValues; +import lombok.core.configuration.IdentifierName; +import lombok.core.configuration.LogDeclaration; +import lombok.core.configuration.LogDeclaration.LogFactoryParameter; +import lombok.core.handlers.LoggingFramework; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacHandlerUtil.MemberExistsResult; import org.mangosdk.spi.ProviderFor; @@ -46,32 +50,44 @@ import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Name; public class HandleLog { + private static final IdentifierName LOG = IdentifierName.valueOf("log"); + private HandleLog() { throw new UnsupportedOperationException(); } - + public static void processAnnotation(LoggingFramework framework, AnnotationValues<?> annotation, JavacNode annotationNode, String loggerTopic) { deleteAnnotationIfNeccessary(annotationNode, framework.getAnnotationClass()); - + JavacNode typeNode = annotationNode.up(); switch (typeNode.getKind()) { case TYPE: - String logFieldName = annotationNode.getAst().readConfiguration(ConfigurationKeys.LOG_ANY_FIELD_NAME); - if (logFieldName == null) logFieldName = "log"; + IdentifierName logFieldName = annotationNode.getAst().readConfiguration(ConfigurationKeys.LOG_ANY_FIELD_NAME); + if (logFieldName == null) logFieldName = LOG; boolean useStatic = !Boolean.FALSE.equals(annotationNode.getAst().readConfiguration(ConfigurationKeys.LOG_ANY_FIELD_IS_STATIC)); - if ((((JCClassDecl)typeNode.get()).mods.flags & Flags.INTERFACE) != 0) { - annotationNode.addError("@Log is legal only on classes and enums."); + if ((((JCClassDecl) typeNode.get()).mods.flags & Flags.INTERFACE) != 0) { + annotationNode.addError(framework.getAnnotationAsString() + " is legal only on classes and enums."); return; } - if (fieldExists(logFieldName, typeNode) != MemberExistsResult.NOT_EXISTS) { + if (fieldExists(logFieldName.getName(), typeNode) != MemberExistsResult.NOT_EXISTS) { annotationNode.addWarning("Field '" + logFieldName + "' already exists."); return; } - + + if (loggerTopic != null && loggerTopic.trim().isEmpty()) loggerTopic = null; + if (framework.getDeclaration().getParametersWithTopic() == null && loggerTopic != null) { + annotationNode.addError(framework.getAnnotationAsString() + " does not allow a topic."); + loggerTopic = null; + } + if (framework.getDeclaration().getParametersWithoutTopic() == null && loggerTopic == null) { + annotationNode.addError(framework.getAnnotationAsString() + " requires a topic."); + loggerTopic = ""; + } + JCFieldAccess loggingType = selfType(typeNode); - createField(framework, typeNode, loggingType, annotationNode.get(), logFieldName, useStatic, loggerTopic); + createField(framework, typeNode, loggingType, annotationNode.get(), logFieldName.getName(), useStatic, loggerTopic); break; default: annotationNode.addError("@Log is legal only on types."); @@ -88,29 +104,51 @@ public class HandleLog { private static boolean createField(LoggingFramework framework, JavacNode typeNode, JCFieldAccess loggingType, JCTree source, String logFieldName, boolean useStatic, String loggerTopic) { JavacTreeMaker maker = typeNode.getTreeMaker(); + LogDeclaration logDeclaration = framework.getDeclaration(); // private static final <loggerType> log = <factoryMethod>(<parameter>); - JCExpression loggerType = chainDotsString(typeNode, framework.getLoggerTypeName()); - JCExpression factoryMethod = chainDotsString(typeNode, framework.getLoggerFactoryMethodName()); + JCExpression loggerType = chainDotsString(typeNode, logDeclaration.getLoggerType().getName()); + JCExpression factoryMethod = chainDotsString(typeNode, logDeclaration.getLoggerFactoryType().getName() + "." + logDeclaration.getLoggerFactoryMethod().getName()); - JCExpression loggerName; - if (!framework.passTypeName) { - loggerName = null; - } else if (loggerTopic == null || loggerTopic.trim().length() == 0) { - loggerName = framework.createFactoryParameter(typeNode, loggingType); - } else { - loggerName = maker.Literal(loggerTopic); - } - - JCMethodInvocation factoryMethodCall = maker.Apply(List.<JCExpression>nil(), factoryMethod, loggerName != null ? List.<JCExpression>of(loggerName) : List.<JCExpression>nil()); + java.util.List<LogFactoryParameter> parameters = loggerTopic != null ? logDeclaration.getParametersWithTopic() : logDeclaration.getParametersWithoutTopic(); + JCExpression[] factoryParameters = createFactoryParameters(typeNode, loggingType, parameters, loggerTopic); + JCMethodInvocation factoryMethodCall = maker.Apply(List.<JCExpression>nil(), factoryMethod, List.<JCExpression>from(factoryParameters)); JCVariableDecl fieldDecl = recursiveSetGeneratedBy(maker.VarDef( - maker.Modifiers(Flags.PRIVATE | Flags.FINAL | (useStatic ? Flags.STATIC : 0)), - typeNode.toName(logFieldName), loggerType, factoryMethodCall), source, typeNode.getContext()); + maker.Modifiers(Flags.PRIVATE | Flags.FINAL | (useStatic ? Flags.STATIC : 0)), + typeNode.toName(logFieldName), loggerType, factoryMethodCall), source, typeNode.getContext()); injectFieldAndMarkGenerated(typeNode, fieldDecl); return true; } + private static JCExpression[] createFactoryParameters(JavacNode typeNode, JCFieldAccess loggingType, java.util.List<LogFactoryParameter> parameters, String loggerTopic) { + JCExpression[] expressions = new JCExpression[parameters.size()]; + JavacTreeMaker maker = typeNode.getTreeMaker(); + + for (int i = 0; i < parameters.size(); i++) { + LogFactoryParameter parameter = parameters.get(i); + switch (parameter) { + case TYPE: + expressions[i] = loggingType; + break; + case NAME: + JCExpression method = maker.Select(loggingType, typeNode.toName("getName")); + expressions[i] = maker.Apply(List.<JCExpression>nil(), method, List.<JCExpression>nil()); + break; + case TOPIC: + expressions[i] = maker.Literal(loggerTopic); + break; + case NULL: + expressions[i] = maker.Literal(CTC_BOT, null); + break; + default: + throw new IllegalStateException("Unknown logger factory parameter type: " + parameter); + } + } + + return expressions; + } + /** * Handles the {@link lombok.extern.apachecommons.CommonsLog} annotation for javac. */ @@ -199,71 +237,20 @@ public class HandleLog { } } - enum LoggingFramework { - // private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(TargetType.class); - COMMONS(lombok.extern.apachecommons.CommonsLog.class, "org.apache.commons.logging.Log", "org.apache.commons.logging.LogFactory.getLog"), - - // private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(TargetType.class.getName()); - JUL(lombok.extern.java.Log.class, "java.util.logging.Logger", "java.util.logging.Logger.getLogger") { - @Override public JCExpression createFactoryParameter(JavacNode typeNode, JCFieldAccess loggingType) { - JavacTreeMaker maker = typeNode.getTreeMaker(); - JCExpression method = maker.Select(loggingType, typeNode.toName("getName")); - return maker.Apply(List.<JCExpression>nil(), method, List.<JCExpression>nil()); + /** + * Handles the {@link lombok.CustomLog} annotation for javac. + */ + @ProviderFor(JavacAnnotationHandler.class) + public static class HandleCustomLog extends JavacAnnotationHandler<lombok.CustomLog> { + @Override public void handle(AnnotationValues<lombok.CustomLog> annotation, JCAnnotation ast, JavacNode annotationNode) { + handleFlagUsage(annotationNode, ConfigurationKeys.LOG_CUSTOM_FLAG_USAGE, "@CustomLog", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); + LogDeclaration logDeclaration = annotationNode.getAst().readConfiguration(ConfigurationKeys.LOG_CUSTOM_DECLARATION); + if (logDeclaration == null) { + annotationNode.addError("The @CustomLog is not configured; please set log.custom.declaration in lombok.config."); + return; } - }, - - // private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TargetType.class); - LOG4J(lombok.extern.log4j.Log4j.class, "org.apache.log4j.Logger", "org.apache.log4j.Logger.getLogger"), - - // private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(TargetType.class); - LOG4J2(lombok.extern.log4j.Log4j2.class, "org.apache.logging.log4j.Logger", "org.apache.logging.log4j.LogManager.getLogger"), - - // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class); - SLF4J(lombok.extern.slf4j.Slf4j.class, "org.slf4j.Logger", "org.slf4j.LoggerFactory.getLogger"), - - // private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(TargetType.class); - XSLF4J(lombok.extern.slf4j.XSlf4j.class, "org.slf4j.ext.XLogger", "org.slf4j.ext.XLoggerFactory.getXLogger"), - - // private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(TargetType.class); - JBOSSLOG(lombok.extern.jbosslog.JBossLog.class, "org.jboss.logging.Logger", "org.jboss.logging.Logger.getLogger"), - - // private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass(); - FLOGGER(lombok.extern.flogger.Flogger.class, "com.google.common.flogger.FluentLogger", "com.google.common.flogger.FluentLogger.forEnclosingClass", false), - ; - - private final Class<? extends Annotation> annotationClass; - private final String loggerTypeName; - private final String loggerFactoryName; - private final boolean passTypeName; - - LoggingFramework(Class<? extends Annotation> annotationClass, String loggerTypeName, String loggerFactoryName, boolean passTypeName) { - this.annotationClass = annotationClass; - this.loggerTypeName = loggerTypeName; - this.loggerFactoryName = loggerFactoryName; - this.passTypeName = passTypeName; - } - - LoggingFramework(Class<? extends Annotation> annotationClass, String loggerTypeName, String loggerFactoryName) { - this.annotationClass = annotationClass; - this.loggerTypeName = loggerTypeName; - this.loggerFactoryName = loggerFactoryName; - this.passTypeName = true; - } - - final Class<? extends Annotation> getAnnotationClass() { - return annotationClass; - } - - final String getLoggerTypeName() { - return loggerTypeName; - } - - final String getLoggerFactoryMethodName() { - return loggerFactoryName; - } - - JCExpression createFactoryParameter(JavacNode typeNode, JCFieldAccess loggingType) { - return loggingType; + LoggingFramework framework = new LoggingFramework(lombok.CustomLog.class, logDeclaration); + processAnnotation(framework, annotation, annotationNode, annotation.getInstance().topic()); } } } diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index ee012d2b..6fa70ff3 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -1450,7 +1450,7 @@ public class JavacHandlerUtil { java.util.List<TypeName> configuredCopyable = node.getAst().readConfiguration(ConfigurationKeys.COPYABLE_ANNOTATIONS); if (!annoName.isEmpty()) { - for (TypeName cn : configuredCopyable) if (typeMatches(cn.toString(), node, anno.annotationType)) return List.of(anno); + for (TypeName cn : configuredCopyable) if (cn != null && typeMatches(cn.toString(), node, anno.annotationType)) return List.of(anno); for (String bn : BASE_COPYABLE_ANNOTATIONS) if (typeMatches(bn, node, anno.annotationType)) return List.of(anno); } @@ -1459,7 +1459,7 @@ public class JavacHandlerUtil { if (child.getKind() == Kind.ANNOTATION) { JCAnnotation annotation = (JCAnnotation) child.get(); boolean match = false; - for (TypeName cn : configuredCopyable) if (typeMatches(cn.toString(), node, annotation.annotationType)) { + for (TypeName cn : configuredCopyable) if (cn != null && typeMatches(cn.toString(), node, annotation.annotationType)) { result.append(annotation); match = true; break; diff --git a/test/transform/resource/after-delombok/LoggerCustom.java b/test/transform/resource/after-delombok/LoggerCustom.java new file mode 100644 index 00000000..4a33e8a9 --- /dev/null +++ b/test/transform/resource/after-delombok/LoggerCustom.java @@ -0,0 +1,13 @@ +class LoggerCustomLog { + @java.lang.SuppressWarnings("all") + private static final MyLogger log = MyLoggerFactory.create(LoggerCustomLog.class); +} + +class MyLoggerFactory { + static MyLogger create(Class<?> clazz) { + return null; + } +} + +class MyLogger { +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/LoggerCustomWithPackage.java b/test/transform/resource/after-delombok/LoggerCustomWithPackage.java new file mode 100644 index 00000000..45e741c2 --- /dev/null +++ b/test/transform/resource/after-delombok/LoggerCustomWithPackage.java @@ -0,0 +1,14 @@ +package before; +class LoggerCustomLog { + @java.lang.SuppressWarnings("all") + private static final before.MyLogger log = before.MyLoggerFactory.create(LoggerCustomLog.class); +} + +class MyLoggerFactory { + static MyLogger create(Class<?> clazz) { + return null; + } +} + +class MyLogger { +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/LoggerCustomWithTopicAndName.java b/test/transform/resource/after-delombok/LoggerCustomWithTopicAndName.java new file mode 100644 index 00000000..abb785b9 --- /dev/null +++ b/test/transform/resource/after-delombok/LoggerCustomWithTopicAndName.java @@ -0,0 +1,10 @@ +class LoggerCustomLog { + @java.lang.SuppressWarnings("all") + private static final MyLoggerFactory log = MyLoggerFactory.create(LoggerCustomLog.class.getName(), "t", null, LoggerCustomLog.class, "t"); +} + +class MyLoggerFactory { + static MyLoggerFactory create(String name, String t1, Object o, Class<?> clazz, String t2) { + return null; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/LoggerCustom.java b/test/transform/resource/after-ecj/LoggerCustom.java new file mode 100644 index 00000000..2f60b7b6 --- /dev/null +++ b/test/transform/resource/after-ecj/LoggerCustom.java @@ -0,0 +1,21 @@ +@lombok.CustomLog class LoggerCustomLog { + private static final MyLogger log = MyLoggerFactory.create(LoggerCustomLog.class); + <clinit>() { + } + LoggerCustomLog() { + super(); + } +} +class MyLoggerFactory { + MyLoggerFactory() { + super(); + } + static MyLogger create(Class<?> clazz) { + return null; + } +} +class MyLogger { + MyLogger() { + super(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/LoggerCustomWithPackage.java b/test/transform/resource/after-ecj/LoggerCustomWithPackage.java new file mode 100644 index 00000000..d1bc1704 --- /dev/null +++ b/test/transform/resource/after-ecj/LoggerCustomWithPackage.java @@ -0,0 +1,22 @@ +package before; +@lombok.CustomLog class LoggerCustomLog { + private static final before.MyLogger log = before.MyLoggerFactory.create(LoggerCustomLog.class); + <clinit>() { + } + LoggerCustomLog() { + super(); + } +} +class MyLoggerFactory { + MyLoggerFactory() { + super(); + } + static MyLogger create(Class<?> clazz) { + return null; + } +} +class MyLogger { + MyLogger() { + super(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/LoggerCustomWithTopicAndName.java b/test/transform/resource/after-ecj/LoggerCustomWithTopicAndName.java new file mode 100644 index 00000000..91cf6d8d --- /dev/null +++ b/test/transform/resource/after-ecj/LoggerCustomWithTopicAndName.java @@ -0,0 +1,16 @@ +@lombok.CustomLog(topic = "t") class LoggerCustomLog { + private static final MyLoggerFactory log = MyLoggerFactory.create(LoggerCustomLog.class.getName(), "t", null, LoggerCustomLog.class, "t"); + <clinit>() { + } + LoggerCustomLog() { + super(); + } +} +class MyLoggerFactory { + MyLoggerFactory() { + super(); + } + static MyLoggerFactory create(String name, String t1, Object o, Class<?> clazz, String t2) { + return null; + } +} diff --git a/test/transform/resource/before/LoggerCustom.java b/test/transform/resource/before/LoggerCustom.java new file mode 100644 index 00000000..c0435a29 --- /dev/null +++ b/test/transform/resource/before/LoggerCustom.java @@ -0,0 +1,13 @@ +//CONF: lombok.log.custom.declaration = MyLogger MyLoggerFactory.create(TYPE) +@lombok.CustomLog +class LoggerCustomLog { +} + +class MyLoggerFactory { + static MyLogger create(Class<?> clazz) { + return null; + } +} + +class MyLogger { +}
\ No newline at end of file diff --git a/test/transform/resource/before/LoggerCustomWithPackage.java b/test/transform/resource/before/LoggerCustomWithPackage.java new file mode 100644 index 00000000..77c4498d --- /dev/null +++ b/test/transform/resource/before/LoggerCustomWithPackage.java @@ -0,0 +1,14 @@ +//CONF: lombok.log.custom.declaration = before.MyLogger before.MyLoggerFactory.create(TYPE) +package before; +@lombok.CustomLog +class LoggerCustomLog { +} + +class MyLoggerFactory { + static MyLogger create(Class<?> clazz) { + return null; + } +} + +class MyLogger { +}
\ No newline at end of file diff --git a/test/transform/resource/before/LoggerCustomWithTopicAndName.java b/test/transform/resource/before/LoggerCustomWithTopicAndName.java new file mode 100644 index 00000000..b40ed86a --- /dev/null +++ b/test/transform/resource/before/LoggerCustomWithTopicAndName.java @@ -0,0 +1,10 @@ +//CONF: lombok.log.custom.declaration = MyLoggerFactory.create(NAME,TOPIC,NULL,TYPE,TOPIC) +@lombok.CustomLog(topic="t") +class LoggerCustomLog { +} + +class MyLoggerFactory { + static MyLoggerFactory create(String name, String t1, Object o, Class<?> clazz, String t2) { + return null; + } +} diff --git a/test/transform/resource/messages-delombok/LoggerSlf4jTypes.java.messages b/test/transform/resource/messages-delombok/LoggerSlf4jTypes.java.messages index 08f6bb7b..9910685c 100644 --- a/test/transform/resource/messages-delombok/LoggerSlf4jTypes.java.messages +++ b/test/transform/resource/messages-delombok/LoggerSlf4jTypes.java.messages @@ -1,2 +1,2 @@ -1 @Log is legal only on classes and enums. -4 @Log is legal only on classes and enums.
\ No newline at end of file +1 @Slf4j is legal only on classes and enums. +4 @Slf4j is legal only on classes and enums.
\ No newline at end of file diff --git a/website/templates/features/log.html b/website/templates/features/log.html index 1de26836..9436d338 100644 --- a/website/templates/features/log.html +++ b/website/templates/features/log.html @@ -7,52 +7,67 @@ <em>NEW in lombok 0.10: </em>You can annotate any class with a log annotation to let lombok generate a logger field.<br/> The logger is named <code>log</code> and the field's type depends on which logger you have selected. </p><p> - <em>NEW in lombok v1.16.24: </em>Addition of google's FluentLogger (flogger). + <em>NEW in lombok v1.16.24: </em>Addition of google's FluentLogger (via <a href="#Flogger"><code>@Flogger</code></a>). + </p><p> + <em>NEW in lombok v1.18.10: </em>Addition of <a href="#CustomLog"><code>@CustomLog</code></a> which lets you add any logger by configuring how to create them with a config key. </p> </@f.history> <@f.overview> <p> - You put the variant of <code>@Log</code> on your class (whichever one applies to the logging system you use); you then have a static final <code>log</code> field, initialized to the name of your class, which you can then use to write log statements. + You put the variant of <code>@Log</code> on your class (whichever one applies to the logging system you use); you then have a static final <code>log</code> field, initialized as is the commonly prescribed way for the logging framework you use, which you can then use to write log statements. </p><p> There are several choices available:<br /> <dl> - <dt> + <dt id="CommonsLog"> <code>@CommonsLog</code> </dt><dd> Creates <code><span class="keyword">private static final </span><a href="https://commons.apache.org/logging/apidocs/org/apache/commons/logging/Log.html">org.apache.commons.logging.Log</a> <span class="staticfield">log</span> = <a href="https://commons.apache.org/logging/apidocs/org/apache/commons/logging/LogFactory.html#getLog(java.lang.Class)">org.apache.commons.logging.LogFactory.getLog</a>(LogExample.<span class="keyword">class</span>);</code> - </dd><dt> + </dd><dt id="Flogger"> <code>@Flogger</code> </dt><dd> Creates <code><span class="keyword">private static final </span><a href="https://google.github.io/flogger/">com.google.common.flogger.FluentLogger</a> <span class="staticfield">log</span> = com.google.common.flogger.FluentLogger.forEnclosingClass();</code> - </dd><dt> + </dd><dt id="JBossLog"> <code>@JBossLog</code> </dt><dd> Creates <code><span class="keyword">private static final </span><a href="https://docs.jboss.org/jbosslogging/latest/org/jboss/logging/Logger.html">org.jboss.logging.Logger</a> <span class="staticfield">log</span> = <a href="https://docs.jboss.org/jbosslogging/latest/org/jboss/logging/Logger.html#getLogger(java.lang.Class)">org.jboss.logging.Logger.getLogger</a>(LogExample.<span class="keyword">class</span>);</code> - </dd><dt> + </dd><dt id="javaLog"> <code>@Log</code> </dt><dd> Creates <code><span class="keyword">private static final </span><a href="https://docs.oracle.com/javase/6/docs/api/java/util/logging/Logger.html">java.util.logging.Logger</a> <span class="staticfield">log</span> = <a href="https://docs.oracle.com/javase/6/docs/api/java/util/logging/Logger.html#getLogger(java.lang.String)">java.util.logging.Logger.getLogger</a>(LogExample.<span class="keyword">class</span>.getName());</code> - </dd><dt> + </dd><dt id="Log4j"> <code>@Log4j</code> </dt><dd> Creates <code><span class="keyword">private static final </span><a href="https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Logger.html">org.apache.log4j.Logger</a> <span class="staticfield">log</span> = <a href="https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Logger.html#getLogger(java.lang.Class)">org.apache.log4j.Logger.getLogger</a>(LogExample.<span class="keyword">class</span>);</code> - </dd><dt> + </dd><dt id="Log4j2"> <code>@Log4j2</code> </dt><dd> Creates <code><span class="keyword">private static final </span><a href="https://logging.apache.org/log4j/2.0/log4j-api/apidocs/org/apache/logging/log4j/Logger.html">org.apache.logging.log4j.Logger</a> <span class="staticfield">log</span> = <a href="https://logging.apache.org/log4j/2.0/log4j-api/apidocs/org/apache/logging/log4j/LogManager.html#getLogger(java.lang.Class)">org.apache.logging.log4j.LogManager.getLogger</a>(LogExample.<span class="keyword">class</span>);</code> - </dd><dt> + </dd><dt id="Slf4j"> <code>@Slf4j</code> </dt><dd> Creates <code><span class="keyword">private static final </span><a href="https://www.slf4j.org/api/org/slf4j/Logger.html">org.slf4j.Logger</a> <span class="staticfield">log</span> = <a href="https://www.slf4j.org/api/org/slf4j/LoggerFactory.html#getLogger(java.lang.Class)">org.slf4j.LoggerFactory.getLogger</a>(LogExample.<span class="keyword">class</span>);</code> - </dd><dt> + </dd><dt id="Xslf4j"> <code>@XSlf4j</code> </dt><dd> Creates <code><span class="keyword">private static final </span><a href="https://www.slf4j.org/api/org/slf4j/ext/XLogger.html">org.slf4j.ext.XLogger</a> <span class="staticfield">log</span> = <a href="https://www.slf4j.org/api/org/slf4j/ext/XLoggerFactory.html#getXLogger(java.lang.Class)">org.slf4j.ext.XLoggerFactory.getXLogger</a>(LogExample.<span class="keyword">class</span>);</code> + </dd><dt id="CustomLog"> + <code>@CustomLog</code> + </dt><dd> + Creates <code><span class="keyword">private static final </span><em>com.foo.your.Logger</em> <span class="staticfield">log</span> = <em>com.foo.your.LoggerFactory.createYourLogger</em>(LogExample.<span class="keyword">class</span>);</code> + <p> + This option <em>requires</em> that you add a configuration to your <a href="/features/configuration"><code>lombok.config</code></a> file to specify what <code>@CustomLog</code> should do. + </p><p> + For example:<code>lombok.log.custom.declaration = com.foo.your.Logger com.foo.your.LoggerFactory.createYourLog(TYPE)(TOPIC)</code> which would produce the above statement. First comes a type which is the type of your logger, then a space, then the type of your logger factory, then a dot, then the name of the logger factory method, and then 1 or 2 parameter definitions; at most one definition with <code>TOPIC</code> and at most one without <code>TOPIC</code>. Each parameter definition is specified as a parenthesised comma-separated list of parameter kinds. The options are: <code>TYPE</code> (passes this <code>@Log</code> decorated type, as a class), <code>NAME</code> (passes this <code>@Log</code> decorated type's fully qualified name), <code>TOPIC</code> (passes the explicitly chosen topic string set on the <code>@CustomLog</code> annotation), and <code>NULL</code> (passes <code>null</code>). + </p><p> + The logger type is optional; if it is omitted, the logger factory type is used. (So, if your logger class has a static method that creates loggers, you can shorten your logger definition). + </p><p> + Please contact us if there is a public, open source, somewhat commonly used logging framework that we don't yet have an explicit annotation for. The primary purpose of <code>@CustomLog</code> is to support your in-house, private logging frameworks. + </p> </dd> </dl> </p><p> - By default, the topic (or name) of the logger will be the class name of the class annotated with the <code>@Log</code> annotation. This can be customised by specifying the <code>topic</code> parameter. For example: <code>@XSlf4j(topic="reporting")</code>. + By default, the topic (or name) of the logger will be the (name of) the class annotated with the <code>@Log</code> annotation. This can be customised by specifying the <code>topic</code> parameter. For example: <code>@XSlf4j(topic="reporting")</code>. </p> </@f.overview> @@ -68,10 +83,18 @@ </dt><dd> Normally the generated logger is a <code>static</code> field. By setting this key to <code>false</code>, the generated field will be an instance field instead. </dd><dt> + <code>lombok.log.custom.declaration</code> = <em>LoggerType </em> LoggerFactoryType.loggerFactoryMethod(loggerFactoryMethodParams)<em>(loggerFactoryMethodParams)</em> + </dt><dd> + Configures what to generate when <code>@CustomLog</code> is used. (The italicized parts are optional). loggerFactoryMethodParams is a comma-separated list of zero to any number of parameter kinds to pass. Valid kinds: TYPE, NAME, TOPIC, and NULL. You can include a parameter definition for the case where no explicit topic is set (do not include the TOPIC in the parameter list), and for when an explicit topic is set (do include the TOPIC parameter in the list). + </dd><dt> <code>lombok.log.flagUsage</code> = [<code>warning</code> | <code>error</code>] (default: not set) </dt><dd> Lombok will flag any usage of any of the various log annotations as a warning or error if configured. </dd><dt> + <code>lombok.log.custom.flagUsage</code> = [<code>warning</code> | <code>error</code>] (default: not set) + </dt><dd> + Lombok will flag any usage of <code>@lombok.CustomLog</code> as a warning or error if configured. + </dd><dt> <code>lombok.log.apacheCommons.flagUsage</code> = [<code>warning</code> | <code>error</code>] (default: not set) </dt><dd> Lombok will flag any usage of <code>@lombok.extern.apachecommons.CommonsLog</code> as a warning or error if configured. |