From 8850798a04e7f44925ed16b3450623b02f5b6295 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 13 Aug 2012 00:11:02 +0200 Subject: Documentation for @Value, @FieldDefaults, @Wither --- buildScripts/website.ant.xml | 9 ++ doc/changelog.markdown | 1 + .../experimental/FieldDefaultsExample_post.jpage | 12 ++ .../experimental/FieldDefaultsExample_pre.jpage | 18 +++ .../experimental/ValueExample_post.jpage | 140 +++++++++++++++++++++ usage_examples/experimental/ValueExample_pre.jpage | 19 +++ .../experimental/WitherExample_post.jpage | 21 ++++ .../experimental/WitherExample_pre.jpage | 14 +++ website/features/experimental/ExtensionMethod.html | 2 +- website/features/experimental/FieldDefaults.html | 84 +++++++++++++ website/features/experimental/Value.html | 87 +++++++++++++ website/features/experimental/Wither.html | 100 +++++++++++++++ website/features/experimental/index.html | 6 + 13 files changed, 512 insertions(+), 1 deletion(-) create mode 100644 usage_examples/experimental/FieldDefaultsExample_post.jpage create mode 100644 usage_examples/experimental/FieldDefaultsExample_pre.jpage create mode 100644 usage_examples/experimental/ValueExample_post.jpage create mode 100644 usage_examples/experimental/ValueExample_pre.jpage create mode 100644 usage_examples/experimental/WitherExample_post.jpage create mode 100644 usage_examples/experimental/WitherExample_pre.jpage create mode 100644 website/features/experimental/FieldDefaults.html create mode 100644 website/features/experimental/Value.html create mode 100644 website/features/experimental/Wither.html diff --git a/buildScripts/website.ant.xml b/buildScripts/website.ant.xml index 0531129d..12cae011 100644 --- a/buildScripts/website.ant.xml +++ b/buildScripts/website.ant.xml @@ -151,6 +151,15 @@ such as converting the changelog into HTML, and creating javadoc. + + + + + + + + + diff --git a/doc/changelog.markdown b/doc/changelog.markdown index bbda9dfe..4ff9b39f 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v0.11.3 (Edgy Guinea Pig) +* FEATURE: {Experimental} `@Value`, `@Wither` and `@FieldDefaults` are now available. These are a lot like `@Data` but geared towards immutable classes. [Documentation on @Value](http://projectlombok.org/features/experimental/Value.html), [Documentation on @Wither](http://projectlombok.org/features/experimental/Wither.html) and [Documentation on @FieldDefaults](http://projectlombok.org/features/experimental/FieldDefaults.html). * BUGFIX: Eclipse would throw an OOME if using `@ExtensionMethod`. [Issue #390](http://code.google.com/p/projectlombok/issues/detail?id=390) * BUGFIX: {Netbeans} `@Cleanup` and `@Synchronized` cause far fewer issues in the netbeans editor. [Issue #393](http://code.google.com/p/projectlombok/issues/detail?id=393) * BUGFIX: {Installer} Erroneous messages about the installer complaining about needing root access when installing or removing lombok from eclipse installs has been fixed. The installer edge of this problem was actually already fixed in v0.11.2. [Issue #363](http://code.google.com/p/projectlombok/issues/detail?id=363) diff --git a/usage_examples/experimental/FieldDefaultsExample_post.jpage b/usage_examples/experimental/FieldDefaultsExample_post.jpage new file mode 100644 index 00000000..95c17a4b --- /dev/null +++ b/usage_examples/experimental/FieldDefaultsExample_post.jpage @@ -0,0 +1,12 @@ +public class FieldDefaultsExample { + public final int a; + private final int b; + private int c; + final int d; + + FieldDefaultsExample() { + a = 0; + b = 0; + d = 0; + } +} diff --git a/usage_examples/experimental/FieldDefaultsExample_pre.jpage b/usage_examples/experimental/FieldDefaultsExample_pre.jpage new file mode 100644 index 00000000..a0a38f7a --- /dev/null +++ b/usage_examples/experimental/FieldDefaultsExample_pre.jpage @@ -0,0 +1,18 @@ +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; +import lombok.experimental.NonFinal; +import lombok.experimental.PackagePrivate; + +@FieldDefaults(makeFinal = true, level=AccessLevel.PRIVATE) +public class FieldDefaultsExample { + public final int a; + int b; + @NonFinal int c; + @PackagePrivate int d; + + FieldDefaultsExample() { + a = 0; + b = 0; + d = 0; + } +} diff --git a/usage_examples/experimental/ValueExample_post.jpage b/usage_examples/experimental/ValueExample_post.jpage new file mode 100644 index 00000000..47de72a5 --- /dev/null +++ b/usage_examples/experimental/ValueExample_post.jpage @@ -0,0 +1,140 @@ +import java.util.Arrays; + +public final class ValueExample { + private final String name; + private int age; + private final double score; + protected final String[] tags; + + @java.beans.ConstructorProperties({"name", "age", "score", "tags"}) + public ValueExample(String name, int age, double score, String[] tags) { + this.name = name; + this.age = age; + this.score = score; + this.tags = tags; + } + + public String getName() { + return this.name; + } + + public int getAge() { + return this.age; + } + + public double getScore() { + return this.score; + } + + public String[] getTags() { + return this.tags; + } + + public ValueExample withName(String name) { + return this.name == name ? this : new ValueExample(name, age, score, tags); + } + + public ValueExample withScore(double score) { + return this.score == score ? this : new ValueExample(name, age, score, tags); + } + + public ValueExample withTags(String[] tags) { + return this.tags == tags ? this : new ValueExample(name, age, score, tags); + } + + @java.lang.Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof ValueExample)) return false; + final ValueExample other = (ValueExample)o; + final Object this$name = this.getName(); + final Object other$name = other.getName(); + if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; + if (this.getAge() != other.getAge()) return false; + if (Double.compare(this.getScore(), other.getScore()) != 0) return false; + if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $name = this.getName(); + result = result * PRIME + ($name == null ? 0 : $name.hashCode()); + result = result * PRIME + this.getAge(); + final long $score = Double.doubleToLongBits(this.getScore()); + result = result * PRIME + (int)($score >>> 32 ^ $score); + result = result * PRIME + Arrays.deepHashCode(this.getTags()); + return result; + } + + @java.lang.Override + public String toString() { + return "ValueExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")"; + } + + ValueExample withAge(int age) { + return this.age == age ? this : new ValueExample(name, age, score, tags); + } + + public static final class Exercise { + private final String name; + private final T value; + + private Exercise(String name, T value) { + this.name = name; + this.value = value; + } + + public static Exercise of(String name, T value) { + return new Exercise(name, value); + } + + public String getName() { + return this.name; + } + + public T getValue() { + return this.value; + } + + public Exercise withName(String name) { + return this.name == name ? this : new Exercise(name, value); + } + + public Exercise withValue(T value) { + return this.value == value ? this : new Exercise(name, value); + } + + @java.lang.Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof ValueExample.Exercise)) return false; + final Exercise other = (Exercise)o; + final Object this$name = this.getName(); + final Object other$name = other.getName(); + if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; + final Object this$value = this.getValue(); + final Object other$value = other.getValue(); + if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $name = this.getName(); + result = result * PRIME + ($name == null ? 0 : $name.hashCode()); + final Object $value = this.getValue(); + result = result * PRIME + ($value == null ? 0 : $value.hashCode()); + return result; + } + + @java.lang.Override + public String toString() { + return "ValueExample.Exercise(name=" + getName() + ", value=" + getValue() + ")"; + } + } +} \ No newline at end of file diff --git a/usage_examples/experimental/ValueExample_pre.jpage b/usage_examples/experimental/ValueExample_pre.jpage new file mode 100644 index 00000000..d9550c25 --- /dev/null +++ b/usage_examples/experimental/ValueExample_pre.jpage @@ -0,0 +1,19 @@ +import lombok.AccessLevel; +import lombok.experimental.NonFinal; +import lombok.experimental.Value; +import lombok.experimental.Wither; +import lombok.ToString; + +@Value public class ValueExample { + String name; + @Wither(AccessLevel.PACKAGE) @NonFinal int age; + double score; + protected String[] tags; + + @ToString(includeFieldNames=true) + @Value(staticConstructor="of") + public static class Exercise { + String name; + T value; + } +} diff --git a/usage_examples/experimental/WitherExample_post.jpage b/usage_examples/experimental/WitherExample_post.jpage new file mode 100644 index 00000000..bb5952af --- /dev/null +++ b/usage_examples/experimental/WitherExample_post.jpage @@ -0,0 +1,21 @@ +import lombok.NonNull; + +public class WitherExample { + private final int age; + private @NonNull final String name; + + public WitherExample(String name, int age) { + if (name == null) throw new NullPointerException(); + this.name = name; + this.age = age; + } + + public WitherExample withAge(int age) { + return this.age == age ? this : new WitherExample(age, name); + } + + protected WitherExample withName(@NonNull String name) { + if (name == null) throw new java.lang.NullPointerException("name"); + return this.name == name ? this : new WitherExample(age, name); + } +} \ No newline at end of file diff --git a/usage_examples/experimental/WitherExample_pre.jpage b/usage_examples/experimental/WitherExample_pre.jpage new file mode 100644 index 00000000..5db799fc --- /dev/null +++ b/usage_examples/experimental/WitherExample_pre.jpage @@ -0,0 +1,14 @@ +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.experimental.Wither; + +public class WitherExample { + @Wither private final int age; + @Wither(AccessLevel.PROTECTED) @NonNull private final String name; + + public WitherExample(String name, int age) { + if (name == null) throw new NullPointerException(); + this.name = name; + this.age = age; + } +} diff --git a/website/features/experimental/ExtensionMethod.html b/website/features/experimental/ExtensionMethod.html index 773904bb..0025d795 100644 --- a/website/features/experimental/ExtensionMethod.html +++ b/website/features/experimental/ExtensionMethod.html @@ -90,7 +90,7 @@ System.out.println(x.or("Hello, World!"));
diff --git a/website/features/experimental/FieldDefaults.html b/website/features/experimental/FieldDefaults.html new file mode 100644 index 00000000..94c23a27 --- /dev/null +++ b/website/features/experimental/FieldDefaults.html @@ -0,0 +1,84 @@ + + + + + + + + EXPERIMENTAL - @FieldDefaults +
+
+
+ +

@FieldDefaults

+ +
+

Since

+

+ @FieldDefaults was introduced as experimental feature in lombok v0.11.4. +

+
+
+

Experimental

+

+ Experimental because: +

    +
  • New feature; unsure if this busts enough boilerplate
  • +
  • Would be nice if you could stick this on the package-info.java package to set the default for all classes in that package
  • +
  • Part of the work on @Value, which is experimental
  • +
+ Current status: positive - Currently we feel this feature may move out of experimental status with no or minor changes soon. +
+
+

Overview

+

+ The @FieldDefaults annotation can add an access modifier (public, private, or protected) + to each field in the annotated class or enum. It can also add final to each field in the annotated class or enum. +

+

+ To add final to each field, use @FieldDefaults(makeFinal=true). Any non-final field which must remain nonfinal + can be annotated with @NonFinal (also in the lombok.experimental package). +

+ To add an access modifier to each field, use @FieldDefaults(level=AccessLevel.PRIVATE). Any field that does not already have an + access modifier (i.e. any field that looks like package private access) is changed to have the appropriate access modifier. Any package private + field which must remain package private can be annotated with @PackagePrivate (also in the lombok.experimental package). +

+
+
+
+

With Lombok

+
@HTML_PRE@
+
+
+
+

Vanilla Java

+
@HTML_POST@
+
+
+
+
+

Small print

+

+ Like other lombok handlers that touch fields, any field whose name starts with a dollar ($) symbol is skipped entirely. + Such a field will not be modified at all. +

+
+
+ +
+
+
+ + + diff --git a/website/features/experimental/Value.html b/website/features/experimental/Value.html new file mode 100644 index 00000000..464f0966 --- /dev/null +++ b/website/features/experimental/Value.html @@ -0,0 +1,87 @@ + + + + + + + + EXPERIMENTAL - @ExtensionMethod +
+
+
+ +

@Value

+ +
+

Since

+

+ @Value was introduced as experimental feature in lombok v0.11.4. +

+
+
+

Experimental

+

+ Experimental because: +

    +
  • Various choices still have to be vetted as being the correct 'least surprise' choice: Should the class be made final by default, etc.
  • +
  • Dependent on @Wither which is experimental.
  • +
+ Current status: positive - Currently we feel this feature may move out of experimental status with no or minor changes soon. +
+
+

Overview

+

+ @Value is the immutable variant of @Data; all fields are made private and final by default, and instead of setters, each field gets a so-called 'wither', + which is a method that produces a clone with each field having the same value, except for the field you want a new value for. The class itself is also made final by default, because immutability is not something that can + be forced onto a subclass. Like @Data, useful toString(), equals() and hashCode() methods are also generated, each field gets a getter method, and a constructor that covers every + argument (except final fields that are initialized in the field declaration) is also generated. +

+ In practice, @Value is shorthand for: final @ToString @EqualsAndHashCode @AllArgsConstructor @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Getter @Wither. +

+ It is possible to override the final-by-default and private-by-default behaviour using either an explicit access level on a field, or by using the @NonFinal or @PackagePrivate annotations.
+ It is possible to override any default behaviour for any of the 'parts' that make up @Value by explicitly using that annotation. +

+
+
+
+

With Lombok

+
@HTML_PRE@
+
+
+
+

Vanilla Java

+
@HTML_POST@
+
+
+
+
+

Small print

+

+ Look for the documentation on the 'parts' of @Value: @ToString, @EqualsAndHashCode, + @AllArgsConstructor, @FieldDefaults, @Getter, + @Wither. +

+ For classes with generics, it's useful to have a static method which serves as a constructor, because inference of generic parameters via static methods works in java6 and avoids having to use the diamond operator. + While you can force this by applying an explicit @AllArgsConstructor annotation, there's also the @Value(staticConstructor="of") feature, which will make the generated all-arguments constructor + private, and generates a public static method named of which is a wrapper around this private constructor. +

+
+
+ +
+
+
+ + + diff --git a/website/features/experimental/Wither.html b/website/features/experimental/Wither.html new file mode 100644 index 00000000..34d6d0d9 --- /dev/null +++ b/website/features/experimental/Wither.html @@ -0,0 +1,100 @@ + + + + + + + + EXPERIMENTAL - @ExtensionMethod +
+
+
+ +

@Wither

+ +
+

Since

+

+ @Wither was introduced as experimental feature in lombok v0.11.4. +

+
+
+

Experimental

+

+ Experimental because: +

    +
  • Still not sure that @Wither is an appropriate name for this feature.
  • +
  • Should there be an option to supply a way of cloning the input somehow?
  • +
  • Should the way that the clone is created by configurable?
  • +
  • Should we replace @Wither entirely with a builder class?
  • +
+ Current status: neutral - More feedback requires on the items in the above list before promotion to the main package is warranted. +
+
+

Overview

+

+ The next best alternative to a setter for an immutable property is to construct a clone of the object, but with a new value for this one field. + A method to generate this clone is precisely what @Wither generates: a withFieldName(newValue) method which produces a clone + except for the new value for the associated field. +

+ For example, if you create public class Point { private final int x, y; }, setters make no sense because the fields + are final. @Wither can generate a withX(int newXValue) method for you which will return a new point with the supplied + value for x and the same value for y. +

+ Like @Setter, you can specify an access level in case you want the generated wither to be something other than public:
+ @Wither(level = AccessLevel.PROTECTED). Also like @Setter, you can also put a @Wither annotation on a type, which means + a 'wither' is generated for each field (even non-final fields). +

+
+
+
+

With Lombok

+
@HTML_PRE@
+
+
+
+

Vanilla Java

+
@HTML_POST@
+
+
+
+
+

Small print

+

+ Withers cannot be generated for static fields because that makes no sense. +

+ When applying @Wither to a type, static fields and fields whose name start with a $ are skipped. +

+ For generating the method names, the first character of the field, if it is a lowercase character, is title-cased, otherwise, it is left unmodified. + Then, with is prefixed. +

+ No method is generated if any method already exists with the same name (case insensitive) and same parameter count. For example, withX(int x) + will not be generated if there's already a method withX(String... x) even though it is technically possible to make the method. This caveat + exists to prevent confusion. If the generation of a method is skipped for this reason, a warning is emitted instead. Varargs count as 0 to N parameters. +

+ For boolean fields that start with is immediately followed by a title-case letter, nothing is prefixed to generate the wither name. +

+ Any annotations named @NonNull (case insensitive) on the field are interpreted as: This field must not ever hold + null. Therefore, these annotations result in an explicit null check in the generated wither. Also, these + annotations (as well as any annotation named @Nullable or @CheckForNull) are copied to wither parameter. +

+
+
+ +
+
+
+ + + diff --git a/website/features/experimental/index.html b/website/features/experimental/index.html index 9b25d710..6f180065 100644 --- a/website/features/experimental/index.html +++ b/website/features/experimental/index.html @@ -26,6 +26,12 @@
A more fluent API for getters and setters.
@ExtensionMethod
Annoying API? Fix it yourself: Add new methods to existing types!
+
@FieldDefaults
+
New default field modifiers for the 21st century.
+
@Wither
+
Immutable 'setters' - methods that create a clone but with one changed field.
+
@Value
+
Immutable classes made very easy.