diff options
Diffstat (limited to 'src')
63 files changed, 1944 insertions, 622 deletions
diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index ade86e27..f5134bbd 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -126,6 +126,13 @@ public class ConfigurationKeys { public static final ConfigurationKey<FlagUsageType> NO_ARGS_CONSTRUCTOR_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.noArgsConstructor.flagUsage", "Emit a warning or error if @NoArgsConstructor is used.") {}; /** + * lombok configuration: {@code lombok.noArgsConstructor.extraPrivate} = {@code true} | {@code false}. + * + * If {@code true} (default), @Data and @Value will also generate a private no-args constructor, if there isn't already one, setting all fields to their default values. + */ + public static final ConfigurationKey<Boolean> NO_ARGS_CONSTRUCTOR_EXTRA_PRIVATE = new ConfigurationKey<Boolean>("lombok.noArgsConstructor.extraPrivate", "Generate a private no-ars constructor for @Data and @Value (default: true).") {}; + + /** * lombok configuration: {@code lombok.requiredArgsConstructor.flagUsage} = {@code WARNING} | {@code ERROR}. * * If set, <em>any</em> usage of {@code @RequiredArgsConstructor} results in a warning / error. @@ -383,6 +390,13 @@ public class ConfigurationKeys { public static final ConfigurationKey<FlagUsageType> LOG_JBOSSLOG_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.log.jbosslog.flagUsage", "Emit a warning or error if @JBossLog is used.") {}; /** + * lombok configuration: {@code lombok.log.flogger.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, <em>any</em> usage of {@code @Flogger} results in a warning / error. + */ + public static final ConfigurationKey<FlagUsageType> LOG_FLOGGER_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.log.flogger.flagUsage", "Emit a warning or error if @Flogger is used.") {}; + + /** * lombok configuration: {@code lombok.log.fieldName} = <String: aJavaIdentifier> (Default: {@code log}). * * If set the various log annotations (which make a log field) will use the stated identifier instead of {@code log} as a name. @@ -499,6 +513,28 @@ public class ConfigurationKeys { */ public static final ConfigurationKey<FlagUsageType> UTILITY_CLASS_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.utilityClass.flagUsage", "Emit a warning or error if @UtilityClass is used.") {}; + // ----- FieldNameConstants ----- + /** + * lombok configuration: {@code lombok.fieldNameConstants.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, <em>any</em> usage of {@code @FieldNameConstants} results in a warning / error. + */ + public static final ConfigurationKey<FlagUsageType> FIELD_NAME_CONSTANTS_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.fieldNameConstants.flagUsage", "Emit a warning or error if @FieldNameConstants is used.") {}; + + /** + * lombok configuration: {@code lombok.fieldNameConstants.prefix} = <String: aJavaIdentifierPrefix> (Default: {@code PREFIX_}). + * + * The names of the constants generated by {@code @FieldNameConstants} will be prefixed with this value. + */ + public static final ConfigurationKey<String> FIELD_NAME_CONSTANTS_PREFIX = new ConfigurationKey<String>("lombok.fieldNameConstants.prefix", "names of constants generated by @FieldNameConstants will be prefixed with this value. (default: 'PREFIX_').") {}; + + /** + * lombok configuration: {@code lombok.fieldNameConstants.suffix} = <String: aJavaIdentifierPrefix> (Default: nothing). + * + * The names of the constants generated by {@code @FieldNameConstants} will be suffixed with this value. + */ + public static final ConfigurationKey<String> FIELD_NAME_CONSTANTS_SUFFIX = new ConfigurationKey<String>("lombok.fieldNameConstants.suffix", "names of constants generated by @FieldNameConstants will be suffixed with this value. (default: nothing).") {}; + // ----- Wither ----- /** diff --git a/src/core/lombok/EqualsAndHashCode.java b/src/core/lombok/EqualsAndHashCode.java index 2f88ac50..b2fc672d 100644 --- a/src/core/lombok/EqualsAndHashCode.java +++ b/src/core/lombok/EqualsAndHashCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2017 The Project Lombok Authors. + * Copyright (C) 2009-2018 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 @@ -37,6 +37,8 @@ public @interface EqualsAndHashCode { /** * Any fields listed here will not be taken into account in the generated {@code equals} and {@code hashCode} implementations. * Mutually exclusive with {@link #of()}. + * <p> + * Will soon be marked {@code @Deprecated}; use the {@code @EqualsAndHashCode.Exclude} annotation instead. * * @return A list of fields to exclude. */ @@ -47,6 +49,8 @@ public @interface EqualsAndHashCode { * Normally, all non-static, non-transient fields are used for identity. * <p> * Mutually exclusive with {@link #exclude()}. + * <p> + * Will soon be marked {@code @Deprecated}; use the {@code @EqualsAndHashCode.Include} annotation together with {@code @EqualsAndHashCode(onlyExplicitlyIncluded = true)}. * * @return A list of fields to use (<em>default</em>: all of them). */ @@ -89,4 +93,27 @@ public @interface EqualsAndHashCode { @Retention(RetentionPolicy.SOURCE) @Target({}) @interface AnyAnnotation {} + + /** + * Only include fields and methods explicitly marked with {@code @EqualsAndHashCode.Include}. + * Normally, all (non-static, non-transient) fields are included by default. + */ + boolean onlyExplicitlyIncluded() default false; + + /** + * If present, do not include this field in the generated {@code equals} and {@code hashCode} methods. + */ + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.SOURCE) + public @interface Exclude {} + + /** + * Configure the behaviour of how this member is treated in the {@code equals} and {@code hashCode} implementation; if on a method, include the method's return value as part of calculating hashCode/equality. + */ + @Target({ElementType.FIELD, ElementType.METHOD}) + @Retention(RetentionPolicy.SOURCE) + public @interface Include { + /** Defaults to the method name of the annotated member. If on a method and the name equals the name of a default-included field, this member takes its place. */ + String replaces() default ""; + } } diff --git a/src/core/lombok/ToString.java b/src/core/lombok/ToString.java index 0c43c40b..ae3f071f 100644 --- a/src/core/lombok/ToString.java +++ b/src/core/lombok/ToString.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2017 The Project Lombok Authors. + * Copyright (C) 2009-2018 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 @@ -45,6 +45,8 @@ public @interface ToString { /** * Any fields listed here will not be printed in the generated {@code toString} implementation. * Mutually exclusive with {@link #of()}. + * <p> + * Will soon be marked {@code @Deprecated}; use the {@code @ToString.Exclude} annotation instead. * * @return A list of fields to exclude. */ @@ -55,6 +57,8 @@ public @interface ToString { * Normally, all non-static fields are printed. * <p> * Mutually exclusive with {@link #exclude()}. + * <p> + * Will soon be marked {@code @Deprecated}; use the {@code @ToString.Include} annotation together with {@code @ToString(onlyExplicitlyIncluded = true)}. * * @return A list of fields to use (<em>default</em>: all of them). */ @@ -75,4 +79,33 @@ public @interface ToString { * @return If {@code true}, always use direct field access instead of calling the getter method. */ boolean doNotUseGetters() default false; + + /** + * Only include fields and methods explicitly marked with {@code @ToString.Include}. + * Normally, all (non-static) fields are included by default. + */ + boolean onlyExplicitlyIncluded() default false; + + /** + * If present, do not include this field in the generated {@code toString}. + */ + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.SOURCE) + public @interface Exclude {} + + /** + * Configure the behaviour of how this member is rendered in the {@code toString}; if on a method, include the method's return value in the output. + */ + @Target({ElementType.FIELD, ElementType.METHOD}) + @Retention(RetentionPolicy.SOURCE) + public @interface Include { +// /** If true and the return value is {@code null}, omit this member entirely from the {@code toString} output. */ +// boolean skipNull() default false; // -- We'll add it later, it requires a complete rework on the toString code we generate. + + /** Higher ranks are printed first. Members of the same rank are printed in the order they appear in the source file. */ + int rank() default 0; + + /** Defaults to the field / method name of the annotated member. If the name equals the name of a default-included field, this member takes its place. */ + String name() default ""; + } } diff --git a/src/core/lombok/core/AnnotationProcessor.java b/src/core/lombok/core/AnnotationProcessor.java index 5b4ef393..04448ecb 100644 --- a/src/core/lombok/core/AnnotationProcessor.java +++ b/src/core/lombok/core/AnnotationProcessor.java @@ -26,6 +26,7 @@ import static lombok.core.Augments.ClassLoader_lombokAlreadyAddedTo; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; @@ -48,6 +49,7 @@ import lombok.patcher.ClassRootFinder; @SupportedAnnotationTypes("*") public class AnnotationProcessor extends AbstractProcessor { + private static String trace(Throwable t) { StringWriter w = new StringWriter(); t.printStackTrace(new PrintWriter(w, true)); @@ -63,6 +65,42 @@ public class AnnotationProcessor extends AbstractProcessor { private final List<ProcessorDescriptor> registered = Arrays.asList(new JavacDescriptor(), new EcjDescriptor()); private final List<ProcessorDescriptor> active = new ArrayList<ProcessorDescriptor>(); private final List<String> delayedWarnings = new ArrayList<String>(); + + /** + * This method is a simplified version of {@link lombok.javac.apt.LombokProcessor.getJavacProcessingEnvironment} + * It simply returns the processing environment, but in case of gradle incremental compilation, + * the delegate ProcessingEnvironment of the gradle wrapper is returned. + */ + public static ProcessingEnvironment getJavacProcessingEnvironment(ProcessingEnvironment procEnv, List<String> delayedWarnings) { + ProcessingEnvironment javacProcEnv = tryRecursivelyObtainJavacProcessingEnvironment(procEnv); + + if (javacProcEnv == null) { + delayedWarnings.add("Can't get the delegate of the gradle IncrementalProcessingEnvironment."); + } + + return javacProcEnv; + } + + private static ProcessingEnvironment tryRecursivelyObtainJavacProcessingEnvironment(ProcessingEnvironment procEnv) { + if (procEnv.getClass().getName().equals("com.sun.tools.javac.processing.JavacProcessingEnvironment")) { + return procEnv; + } + + for (Class<?> procEnvClass = procEnv.getClass(); procEnvClass != null; procEnvClass = procEnvClass.getSuperclass()) { + try { + Field field = procEnvClass.getDeclaredField("delegate"); + field.setAccessible(true); + Object delegate = field.get(procEnv); + + return tryRecursivelyObtainJavacProcessingEnvironment((ProcessingEnvironment) delegate); + } catch (final Exception e) { + // no valid delegate, try superclass + } + } + + return null; + } + static class JavacDescriptor extends ProcessorDescriptor { private Processor processor; @@ -72,10 +110,12 @@ public class AnnotationProcessor extends AbstractProcessor { } @Override boolean want(ProcessingEnvironment procEnv, List<String> delayedWarnings) { - if (!procEnv.getClass().getName().equals("com.sun.tools.javac.processing.JavacProcessingEnvironment")) return false; - + ProcessingEnvironment javacProcEnv = getJavacProcessingEnvironment(procEnv, delayedWarnings); + + if (javacProcEnv == null) return false; + try { - ClassLoader classLoader = findAndPatchClassLoader(procEnv); + ClassLoader classLoader = findAndPatchClassLoader(javacProcEnv); processor = (Processor) Class.forName("lombok.javac.apt.LombokProcessor", false, classLoader).newInstance(); } catch (Exception e) { delayedWarnings.add("You found a bug in lombok; lombok.javac.apt.LombokProcessor is not available. Lombok will not run during this compilation: " + trace(e)); diff --git a/src/core/lombok/core/AnnotationValues.java b/src/core/lombok/core/AnnotationValues.java index d04797cb..a24330fa 100644 --- a/src/core/lombok/core/AnnotationValues.java +++ b/src/core/lombok/core/AnnotationValues.java @@ -27,6 +27,7 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -42,7 +43,7 @@ public class AnnotationValues<A extends Annotation> { private final Class<A> type; private final Map<String, AnnotationValue> values; private final LombokNode<?, ?, ?> ast; - + /** * Represents a single method on the annotation class. For example, the value() method on the Getter annotation. */ @@ -50,8 +51,7 @@ public class AnnotationValues<A extends Annotation> { /** A list of the raw expressions. List is size 1 unless an array is provided. */ public final List<String> raws; - /** Guesses for each raw expression. If the raw expression is a literal expression, the guess will - * likely be right. If not, it'll be wrong. */ + /** Guesses for each raw expression. It's 'primitive' (String or primitive), an AV.ClassLiteral, an AV.FieldSelect, or an array of one of those. */ public final List<Object> valueGuesses; /** A list of the actual expressions. List is size 1 unless an array is provided. */ @@ -160,6 +160,62 @@ public class AnnotationValues<A extends Annotation> { private A cachedInstance = null; + public List<String> getAsStringList(String methodName) { + AnnotationValue v = values.get(methodName); + + if (v == null) { + String[] s = getDefaultIf(methodName, String[].class, new String[0]); + return Collections.unmodifiableList(Arrays.asList(s)); + } + + List<String> out = new ArrayList<String>(v.valueGuesses.size()); + int idx = 0; + for (Object guess : v.valueGuesses) { + Object result = guess == null ? null : guessToType(guess, String.class, v, idx); + if (result == null) { + if (v.valueGuesses.size() == 1) { + String[] s = getDefaultIf(methodName, String[].class, new String[0]); + return Collections.unmodifiableList(Arrays.asList(s)); + } + throw new AnnotationValueDecodeFail(v, + "I can't make sense of this annotation value. Try using a fully qualified literal.", idx); + } + out.add((String) result); + } + + return Collections.unmodifiableList(out); + } + + public String getAsString(String methodName) { + AnnotationValue v = values.get(methodName); + if (v == null || v.valueGuesses.size() != 1) { + return getDefaultIf(methodName, String.class, ""); + } + + Object guess = guessToType(v.valueGuesses.get(0), String.class, v, 0); + if (guess instanceof String) return (String) guess; + return getDefaultIf(methodName, String.class, ""); + } + + public boolean getAsBoolean(String methodName) { + AnnotationValue v = values.get(methodName); + if (v == null || v.valueGuesses.size() != 1) { + return getDefaultIf(methodName, boolean.class, false); + } + + Object guess = guessToType(v.valueGuesses.get(0), boolean.class, v, 0); + if (guess instanceof Boolean) return ((Boolean) guess).booleanValue(); + return getDefaultIf(methodName, boolean.class, false); + } + + public <T> T getDefaultIf(String methodName, Class<T> type, T defaultValue) { + try { + return type.cast(type.getMethod(methodName).getDefaultValue()); + } catch (Exception e) { + return defaultValue; + } + } + /** * Creates an actual annotation instance. You can use this to query any annotation methods, except for * those annotation methods with class literals, as those can most likely not be turned into Class objects. @@ -190,7 +246,7 @@ public class AnnotationValues<A extends Annotation> { if (!isArray && v.valueGuesses.size() > 1) { throw new AnnotationValueDecodeFail(v, - "Expected a single value, but " + method.getName() + " has an array of values", -1); + "Expected a single value, but " + method.getName() + " has an array of values", -1); } if (v.valueGuesses.size() == 0 && !isArray) { @@ -217,7 +273,7 @@ public class AnnotationValues<A extends Annotation> { return defaultValue; } throw new AnnotationValueDecodeFail(v, - "I can't make sense of this annotation value. Try using a fully qualified literal.", idx); + "I can't make sense of this annotation value. Try using a fully qualified literal.", idx); } Array.set(array, idx++, result); } @@ -230,48 +286,48 @@ public class AnnotationValues<A extends Annotation> { } private Object guessToType(Object guess, Class<?> expected, AnnotationValue v, int pos) { - if (expected == int.class) { + if (expected == int.class || expected == Integer.class) { if (guess instanceof Integer || guess instanceof Short || guess instanceof Byte) { - return ((Number)guess).intValue(); + return ((Number) guess).intValue(); } } - if (expected == long.class) { + if (expected == long.class || expected == Long.class) { if (guess instanceof Long || guess instanceof Integer || guess instanceof Short || guess instanceof Byte) { - return ((Number)guess).longValue(); + return ((Number) guess).longValue(); } } - if (expected == short.class) { + if (expected == short.clas |
