Skip to content

Commit 3d903ef

Browse files
committed
Add trait annotation
1 parent df5554e commit 3d903ef

15 files changed

Lines changed: 244 additions & 28 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Add trait annotation [#351](https://github.com/nbbrd/java-design-util/issues/351)
13+
1014
### Changed
1115

1216
- Migrate OSSRH to Central Portal
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package nbbrd.design;
2+
3+
import java.lang.annotation.*;
4+
5+
/**
6+
* This annotation indicates that the class is a trait.
7+
* Traits are used to define reusable behaviors that can be mixed into classes.
8+
* They are typically used in a design context to enhance the functionality of classes without using inheritance.
9+
*
10+
* @see <a href="https://en.wikipedia.org/wiki/Trait_(computer_programming)">Trait (computer programming)</a>
11+
*/
12+
@Target({ElementType.TYPE})
13+
@Retention(RetentionPolicy.SOURCE)
14+
@Documented
15+
public @interface Trait {
16+
}

java-design-processor/src/main/java/internal/nbbrd/design/ClassNameConstantProcessor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import java.util.Set;
3131

3232
import static internal.nbbrd.design.proc.Rule.is;
33-
import static internal.nbbrd.design.proc.Rule.of;
33+
import static internal.nbbrd.design.proc.Rule.it;
3434
import static javax.lang.model.element.Modifier.FINAL;
3535
import static javax.lang.model.element.Modifier.STATIC;
3636

@@ -61,5 +61,5 @@ private static boolean hasClassName(VariableElement field) {
6161
.and(is(STATIC))
6262
.and(is(FINAL))
6363
.and(is(String.class))
64-
.and(of(ClassNameConstantProcessor::hasClassName, "'%s' should represent the full name of its enclosing class"));
64+
.and(it(ClassNameConstantProcessor::hasClassName, "'%s' should represent the full name of its enclosing class"));
6565
}

java-design-processor/src/main/java/internal/nbbrd/design/DirectImplProcessor.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
import static internal.nbbrd.design.proc.Elements2.fieldsIn;
3434
import static internal.nbbrd.design.proc.Rule.is;
35-
import static internal.nbbrd.design.proc.Rule.of;
35+
import static internal.nbbrd.design.proc.Rule.it;
3636
import static javax.lang.model.element.Modifier.FINAL;
3737

3838
/**
@@ -52,15 +52,15 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
5252
return Processing.of(IS_DIRECT_IMPL).process(annotations, roundEnv, processingEnv);
5353
}
5454

55-
private static boolean extendAtLeastOneInterface(TypeElement type) {
55+
private static boolean extendsAtLeastOneInterface(TypeElement type) {
5656
return !type.getInterfaces().isEmpty();
5757
}
5858

59-
private static boolean doNotExtendClass(TypeElement type) {
59+
private static boolean doesNotExtendClass(TypeElement type) {
6060
return type.getSuperclass().toString().equals(Object.class.getName());
6161
}
6262

63-
private static boolean doNotContainPublicVars(TypeElement type) {
63+
private static boolean doesNotContainPublicVars(TypeElement type) {
6464
return fieldsIn(type).noneMatch(DirectImplProcessor::isVariableNotStaticButPublic);
6565
}
6666

@@ -72,7 +72,7 @@ private static boolean isVariableNotStaticButPublic(VariableElement e) {
7272

7373
private static final Rule<TypeElement> IS_DIRECT_IMPL = Rule.on(TypeElement.class)
7474
.and(is(FINAL))
75-
.and(of(DirectImplProcessor::doNotExtendClass, "'%s' may not extend another class"))
76-
.and(of(DirectImplProcessor::doNotContainPublicVars, "'%s' may not contain public vars"))
77-
.and(of(DirectImplProcessor::extendAtLeastOneInterface, "'%s' must extend at least one interface"));
75+
.and(it(DirectImplProcessor::doesNotExtendClass, "'%s' may not extend another class"))
76+
.and(it(DirectImplProcessor::doesNotContainPublicVars, "'%s' may not contain public vars"))
77+
.and(it(DirectImplProcessor::extendsAtLeastOneInterface, "'%s' must extend at least one interface"));
7878
}

java-design-processor/src/main/java/internal/nbbrd/design/ImmutableProcessor.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import static internal.nbbrd.design.proc.Elements2.fieldsIn;
3535
import static internal.nbbrd.design.proc.Rule.is;
36+
import static internal.nbbrd.design.proc.Rule.it;
3637
import static javax.lang.model.element.Modifier.*;
3738

3839
/**
@@ -52,12 +53,12 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
5253
return Processing.of(IS_IMMUTABLE).process(annotations, roundEnv, processingEnv);
5354
}
5455

55-
private static boolean areFieldsFinalOrLazy(TypeElement type) {
56+
private static boolean hasFieldsFinalOrLazy(TypeElement type) {
5657
boolean lazy = type.getAnnotation(Immutable.class).lazy();
5758
return getNonStaticFields(type).allMatch(field -> field.getModifiers().contains(FINAL) || (lazy && field.getModifiers().contains(VOLATILE)));
5859
}
5960

60-
private static boolean areFieldsPrivate(TypeElement type) {
61+
private static boolean hasFieldsPrivate(TypeElement type) {
6162
return getNonStaticFields(type).allMatch(field -> field.getModifiers().contains(PRIVATE));
6263
}
6364

@@ -70,13 +71,9 @@ private static Stream<VariableElement> getNonStaticFields(TypeElement type) {
7071
return fieldsIn(type).filter(field -> !field.getModifiers().contains(STATIC));
7172
}
7273

73-
private static final Rule<TypeElement> ARE_FIELDS_FINAL_OR_LAZY = Rule.of(ImmutableProcessor::areFieldsFinalOrLazy, "Fields of '%s' must be final or lazy");
74-
private static final Rule<TypeElement> ARE_FIELDS_PRIVATE = Rule.of(ImmutableProcessor::areFieldsPrivate, "Fields of '%s' must be private");
75-
private static final Rule<TypeElement> HAS_LAZY_FIELD_IF_LAZY = Rule.of(ImmutableProcessor::hasLazyFieldIfLazy, "'%s' must have at least one lazy field");
76-
7774
private static final Rule<TypeElement> IS_IMMUTABLE = Rule.on(TypeElement.class)
7875
.and(is(FINAL))
79-
.and(ARE_FIELDS_FINAL_OR_LAZY)
80-
.and(ARE_FIELDS_PRIVATE)
81-
.and(HAS_LAZY_FIELD_IF_LAZY);
76+
.and(it(ImmutableProcessor::hasFieldsFinalOrLazy, "Fields of '%s' must be final or lazy"))
77+
.and(it(ImmutableProcessor::hasFieldsPrivate, "Fields of '%s' must be private"))
78+
.and(it(ImmutableProcessor::hasLazyFieldIfLazy, "'%s' must have at least one lazy field"));
8279
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2018 National Bank of Belgium
3+
*
4+
* Licensed under the EUPL, Version 1.1 or - as soon they will be approved
5+
* by the European Commission - subsequent versions of the EUPL (the "Licence");
6+
* You may not use this work except in compliance with the Licence.
7+
* You may obtain a copy of the Licence at:
8+
*
9+
* http://ec.europa.eu/idabc/eupl
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the Licence is distributed on an "AS IS" basis,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the Licence for the specific language governing permissions and
15+
* limitations under the Licence.
16+
*/
17+
package internal.nbbrd.design;
18+
19+
import internal.nbbrd.design.proc.Elements2;
20+
import internal.nbbrd.design.proc.Processing;
21+
import internal.nbbrd.design.proc.Rule;
22+
import nbbrd.service.ServiceProvider;
23+
24+
import javax.annotation.processing.AbstractProcessor;
25+
import javax.annotation.processing.Processor;
26+
import javax.annotation.processing.RoundEnvironment;
27+
import javax.annotation.processing.SupportedAnnotationTypes;
28+
import javax.lang.model.SourceVersion;
29+
import javax.lang.model.element.TypeElement;
30+
import java.util.Set;
31+
32+
import static internal.nbbrd.design.proc.Rule.*;
33+
import static javax.lang.model.element.ElementKind.INTERFACE;
34+
35+
/**
36+
* @author Philippe Charles
37+
*/
38+
@ServiceProvider(Processor.class)
39+
@SupportedAnnotationTypes("nbbrd.design.Trait")
40+
public final class TraitProcessor extends AbstractProcessor {
41+
42+
@Override
43+
public SourceVersion getSupportedSourceVersion() {
44+
return SourceVersion.latestSupported();
45+
}
46+
47+
@Override
48+
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
49+
return Processing.of(IS_TRAIT).process(annotations, roundEnv, processingEnv);
50+
}
51+
52+
private static boolean hasAtLeastOneMethod(TypeElement type) {
53+
return Elements2.methodsIn(type).findAny().isPresent();
54+
}
55+
56+
static boolean isValidName(String name) {
57+
return name.endsWith("ble") || name.startsWith("Has");
58+
}
59+
60+
private static final Rule<TypeElement> IS_TRAIT = Rule.on(TypeElement.class)
61+
.and(is(INTERFACE))
62+
.and(isNamedTesting(TraitProcessor::isValidName, "must end with 'ble' or start with 'Has'"))
63+
.and(it(TraitProcessor::hasAtLeastOneMethod, "'%s' must have at least one method"));
64+
}

java-design-processor/src/main/java/internal/nbbrd/design/proc/Elements2.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33
import javax.lang.model.element.*;
44
import javax.lang.model.util.ElementFilter;
55
import java.lang.annotation.Annotation;
6+
import java.util.function.Predicate;
67
import java.util.stream.Stream;
78

89
@lombok.experimental.UtilityClass
910
public class Elements2 {
1011

1112
public static boolean isNamed(Element e, String name) {
12-
return e.getSimpleName().toString().equals(name);
13+
return isNamedTesting(e, name::equals);
14+
}
15+
16+
public static boolean isNamedTesting(Element e, Predicate<? super String> namePredicate) {
17+
return namePredicate.test(e.getSimpleName().toString());
1318
}
1419

1520
public static boolean is(Element e, Modifier modifier) {

java-design-processor/src/main/java/internal/nbbrd/design/proc/ExecutableRules.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
public class ExecutableRules {
1717

1818
public static Rule<ExecutableElement> hasNoParameter() {
19-
return of(m -> m.getParameters().isEmpty(), "'%s' must not have parameters");
19+
return Rule.it(m -> m.getParameters().isEmpty(), "'%s' must not have parameters");
2020
}
2121

2222
public static Rule<ExecutableElement> hasParametersThat(Rule<? super VariableElement>... rules) {

java-design-processor/src/main/java/internal/nbbrd/design/proc/Rule.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ static <T extends Element> Rule<T> on(Class<T> ignore) {
7070
return (env, e) -> NO_ERROR;
7171
}
7272

73-
static <T extends Element> Rule<T> of(Predicate<? super T> condition, String formattedMessage) {
73+
static <T extends Element> Rule<T> it(Predicate<? super T> condition, String formattedMessage) {
7474
return (env, e) -> !condition.test(e) ? String.format(Locale.ROOT, formattedMessage, e) : NO_ERROR;
7575
}
7676

@@ -83,19 +83,23 @@ static <T> Rule<T> ifThenElse(BiPredicate<ProcessingEnvironment, ? super T> cond
8383
}
8484

8585
static <T extends Element> Rule<T> is(Modifier modifier) {
86-
return of(e -> Elements2.is(e, modifier), "'%s' must be " + modifier);
86+
return it(e -> Elements2.is(e, modifier), "'%s' must be " + modifier);
8787
}
8888

8989
static <T extends Element> Rule<T> isNot(Modifier modifier) {
90-
return of(e -> !Elements2.is(e, modifier), "'%s' must not be " + modifier);
90+
return it(e -> !Elements2.is(e, modifier), "'%s' must not be " + modifier);
9191
}
9292

9393
static <T extends Element> Rule<T> is(ElementKind kind) {
94-
return of(e -> Elements2.is(e, kind), "'%s' must be " + kind);
94+
return it(e -> Elements2.is(e, kind), "'%s' must be " + kind);
9595
}
9696

9797
static <T extends Element> Rule<T> isNamed(String name) {
98-
return of(e -> Elements2.isNamed(e, name), "'%s' must be named " + name);
98+
return it(e -> Elements2.isNamed(e, name), "'%s' must be named " + name);
99+
}
100+
101+
static <T extends Element> Rule<T> isNamedTesting(Predicate<? super String> namePredicate, String predicateDescription) {
102+
return it(e -> Elements2.isNamedTesting(e, namePredicate), "'%s' must " + predicateDescription);
99103
}
100104

101105
static <T extends Element> Rule<T> is(Element expected) {

java-design-processor/src/main/java/internal/nbbrd/design/proc/TypeRules.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,16 @@
44

55
import static internal.nbbrd.design.proc.Elements2.constructorsIn;
66
import static internal.nbbrd.design.proc.Elements2.is;
7-
import static internal.nbbrd.design.proc.Rule.of;
87
import static javax.lang.model.element.Modifier.PUBLIC;
98

109
@lombok.experimental.UtilityClass
1110
public class TypeRules {
1211

1312
public static Rule<TypeElement> hasNoInterface() {
14-
return of(type -> type.getInterfaces().isEmpty(), "'%s' must not implement interfaces");
13+
return Rule.it(type -> type.getInterfaces().isEmpty(), "'%s' must not implement interfaces");
1514
}
1615

1716
public static Rule<TypeElement> hasNoPublicConstructor() {
18-
return of(type -> constructorsIn(type).noneMatch(method -> is(method, PUBLIC)), "'%s' cannot have public constructors");
17+
return Rule.it(type -> constructorsIn(type).noneMatch(method -> is(method, PUBLIC)), "'%s' cannot have public constructors");
1918
}
2019
}

0 commit comments

Comments
 (0)