Skip to content

Commit bc0df8e

Browse files
committed
Add DecoratorPattern annotation
1 parent 5a4aa4a commit bc0df8e

10 files changed

Lines changed: 294 additions & 0 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 DecoratorPattern annotation [#338](https://github.com/nbbrd/java-design-util/issues/338)
13+
1014
### Fixed
1115

1216
- Fix package pattern in MightBePromoted [#337](https://github.com/nbbrd/java-design-util/issues/337)

java-design-annotation/src/main/java/nbbrd/design/BuilderPattern.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,28 @@
1919
import java.lang.annotation.*;
2020

2121
/**
22+
* The builder pattern is a design pattern that provides a flexible solution to various object creation problems in object-oriented programming.
23+
* The builder pattern separates the construction of a complex object from its representation.
24+
*
2225
* @author Philippe Charles
26+
* @see <a href="https://en.wikipedia.org/wiki/Builder_pattern">Wikipedia</a>
2327
*/
2428
@Target({ElementType.TYPE})
2529
@Retention(RetentionPolicy.SOURCE)
2630
@Documented
2731
public @interface BuilderPattern {
2832

33+
/**
34+
* The type of the object that will be built by the builder.
35+
*
36+
* @return a non-null type
37+
*/
2938
Class<?> value();
3039

40+
/**
41+
* The name of the method that builds the object.
42+
*
43+
* @return a non-null method name
44+
*/
3145
String buildMethodName() default "build";
3246
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package nbbrd.design;
2+
3+
import java.lang.annotation.*;
4+
5+
/**
6+
* In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object,
7+
* dynamically, without affecting the behavior of other instances of the same class.
8+
*
9+
* @see <a href="https://en.wikipedia.org/wiki/Decorator_pattern">Wikipedia</a>
10+
*/
11+
@Target({ElementType.TYPE})
12+
@Retention(RetentionPolicy.SOURCE)
13+
@Documented
14+
public @interface DecoratorPattern {
15+
16+
/**
17+
* The interface (or class) to decorate.<br>This
18+
* value is optional if the decorator implements/extends exactly one
19+
* interface/class.
20+
*
21+
* @return a non-null type
22+
*/
23+
Class<?> value() default Void.class;
24+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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.Processors;
22+
import internal.nbbrd.design.proc.Rule;
23+
import nbbrd.design.DecoratorPattern;
24+
import nbbrd.service.ServiceProvider;
25+
26+
import javax.annotation.processing.*;
27+
import javax.lang.model.SourceVersion;
28+
import javax.lang.model.element.ElementKind;
29+
import javax.lang.model.element.TypeElement;
30+
import javax.lang.model.type.NoType;
31+
import javax.lang.model.type.TypeKind;
32+
import javax.lang.model.type.TypeMirror;
33+
import java.util.List;
34+
import java.util.Optional;
35+
import java.util.Set;
36+
37+
import static internal.nbbrd.design.proc.Processors.extractResultType;
38+
39+
/**
40+
* @author Philippe Charles
41+
*/
42+
@ServiceProvider(Processor.class)
43+
@SupportedAnnotationTypes("nbbrd.design.DecoratorPattern")
44+
public final class DecoratorPatternProcessor extends AbstractProcessor {
45+
46+
@Override
47+
public SourceVersion getSupportedSourceVersion() {
48+
return SourceVersion.latestSupported();
49+
}
50+
51+
@Override
52+
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
53+
return Processing.of(IS_DECORATOR_PATTERN).process(annotations, roundEnv, processingEnv);
54+
}
55+
56+
private static final Rule<TypeElement> IS_DECORATOR_PATTERN =
57+
Rule.on(TypeElement.class)
58+
.and(Rule.is(ElementKind.CLASS))
59+
.and(Rule.of(DecoratorPatternProcessor::isExtendingDecoratedType, "'%s' doesn't extend a class"))
60+
.and(Rule.of(DecoratorPatternProcessor::hasConstructorWithDecoratedType, "'%s' doesn't have a constructor with the decorated type"));
61+
62+
private static boolean isExtendingDecoratedType(ProcessingEnvironment env, TypeElement type) {
63+
return getDecoratedType(env, type)
64+
.filter(decoratedType -> isExtendingDecoratedType(env, type, decoratedType))
65+
.isPresent();
66+
}
67+
68+
private static boolean isExtendingDecoratedType(ProcessingEnvironment env, TypeElement type, TypeMirror decoratedType) {
69+
return env.getTypeUtils().isAssignable(type.getSuperclass(), decoratedType) ||
70+
type.getInterfaces().stream().anyMatch(x -> env.getTypeUtils().isAssignable(x, decoratedType));
71+
}
72+
73+
private static boolean hasConstructorWithDecoratedType(ProcessingEnvironment env, TypeElement type) {
74+
return getDecoratedType(env, type)
75+
.filter(decoratedType -> hasConstructorWithDecoratedType(env, type, decoratedType))
76+
.isPresent();
77+
}
78+
79+
private static boolean hasConstructorWithDecoratedType(ProcessingEnvironment env, TypeElement type, TypeMirror decoratedType) {
80+
return Elements2.constructorsIn(type)
81+
.flatMap(method -> method.getParameters().stream())
82+
.anyMatch(var -> env.getTypeUtils().isSameType(decoratedType, var.asType()));
83+
}
84+
85+
private static boolean isNullType(ProcessingEnvironment env, TypeMirror expected) {
86+
return env.getTypeUtils().isSameType(Processors.getTypeMirror(env, Void.class), expected);
87+
}
88+
89+
private static boolean isDirectSuperClass(TypeMirror found) {
90+
return !(found instanceof NoType && found.getKind() == TypeKind.NONE) && !(found.toString().equals("java.lang.Object"));
91+
}
92+
93+
private static Optional<TypeMirror> inferDecoratedType(TypeElement type) {
94+
TypeMirror superclass = type.getSuperclass();
95+
List<? extends TypeMirror> parents = type.getInterfaces();
96+
return isDirectSuperClass(superclass) && parents.isEmpty()
97+
? Optional.of(superclass)
98+
: (!isDirectSuperClass(superclass) && parents.size() == 1 ? Optional.of(parents.get(0)) : Optional.empty());
99+
}
100+
101+
private static Optional<TypeMirror> getDecoratedType(ProcessingEnvironment env, TypeElement type) {
102+
TypeMirror result = extractResultType(type.getAnnotation(DecoratorPattern.class)::value);
103+
return isNullType(env, result) ? inferDecoratedType(type) : Optional.of(result);
104+
}
105+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package internal.nbbrd.design;
2+
3+
4+
import com.google.testing.compile.Compilation;
5+
import com.google.testing.compile.JavaFileObjects;
6+
import org.junit.jupiter.api.Test;
7+
8+
import javax.tools.JavaFileObject;
9+
10+
import static com.google.testing.compile.CompilationSubject.assertThat;
11+
12+
class DecoratorPatternProcessorTest {
13+
14+
@Test
15+
public void testValid() {
16+
JavaFileObject file = JavaFileObjects.forResource("internal/nbbrd/design/TestDecoratorValid.java");
17+
Compilation compilation = compile(file);
18+
19+
assertThat(compilation)
20+
.succeededWithoutWarnings();
21+
}
22+
23+
@Test
24+
public void testConstructorMissing() {
25+
JavaFileObject file = JavaFileObjects.forResource("internal/nbbrd/design/TestDecoratorConstructorMissing.java");
26+
Compilation compilation = compile(file);
27+
28+
assertThat(compilation)
29+
.failed();
30+
}
31+
32+
@Test
33+
public void testConstructorNoArg() {
34+
JavaFileObject file = JavaFileObjects.forResource("internal/nbbrd/design/TestDecoratorConstructorNoArg.java");
35+
Compilation compilation = compile(file);
36+
37+
assertThat(compilation)
38+
.failed();
39+
}
40+
41+
@Test
42+
public void testConstructorNoDecorated() {
43+
JavaFileObject file = JavaFileObjects.forResource("internal/nbbrd/design/TestDecoratorConstructorNoDecorated.java");
44+
Compilation compilation = compile(file);
45+
46+
assertThat(compilation)
47+
.failed();
48+
}
49+
50+
@Test
51+
public void testConstructorSuper() {
52+
JavaFileObject file = JavaFileObjects.forResource("internal/nbbrd/design/TestDecoratorConstructorSuper.java");
53+
Compilation compilation = compile(file);
54+
55+
assertThat(compilation)
56+
.failed();
57+
}
58+
59+
private Compilation compile(JavaFileObject file) {
60+
return com.google.testing.compile.Compiler.javac()
61+
.withProcessors(new DecoratorPatternProcessor())
62+
.compile(file);
63+
}
64+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package internal.nbbrd.design;
2+
3+
import nbbrd.design.DecoratorPattern;
4+
5+
import java.io.Reader;
6+
7+
@DecoratorPattern
8+
public abstract class TestDecoratorConstructorMissing extends Reader {
9+
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package internal.nbbrd.design;
2+
3+
import nbbrd.design.DecoratorPattern;
4+
5+
import java.io.Reader;
6+
7+
@DecoratorPattern
8+
public abstract class TestDecoratorConstructorNoArg extends Reader {
9+
10+
TestDecoratorConstructorNoArg() {}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package internal.nbbrd.design;
2+
3+
import nbbrd.design.DecoratorPattern;
4+
5+
import java.io.Reader;
6+
7+
@DecoratorPattern
8+
public abstract class TestDecoratorConstructorNoDecorated extends Reader {
9+
10+
TestDecoratorConstructorNoDecorated(boolean stuff) {}
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package internal.nbbrd.design;
2+
3+
import nbbrd.design.DecoratorPattern;
4+
5+
import java.io.FilterInputStream;
6+
import java.io.InputStream;
7+
import java.io.Reader;
8+
9+
@DecoratorPattern
10+
public abstract class TestDecoratorConstructorSuper extends FilterInputStream {
11+
12+
TestDecoratorConstructorSuper(InputStream delegate) { super(delegate); }
13+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package internal.nbbrd.design;
2+
3+
import nbbrd.design.DecoratorPattern;
4+
5+
import java.io.*;
6+
7+
public class TestDecoratorValid {
8+
9+
@DecoratorPattern
10+
public abstract static class V1 extends Reader {
11+
12+
V1(Reader delegate) {}
13+
}
14+
15+
@DecoratorPattern
16+
public abstract static class V2 implements Closeable {
17+
18+
V2(Closeable delegate) {}
19+
}
20+
21+
@DecoratorPattern(Closeable.class)
22+
public abstract static class V3 extends Reader implements Closeable, Flushable {
23+
24+
V3(Closeable delegate) {}
25+
}
26+
27+
@DecoratorPattern
28+
public abstract static class V4 implements Closeable {
29+
30+
V4(boolean stuff, Closeable delegate) {}
31+
}
32+
33+
@DecoratorPattern(InputStream.class)
34+
public abstract static class V5 extends FilterInputStream {
35+
36+
V5(InputStream delegate) { super(delegate); }
37+
}
38+
}

0 commit comments

Comments
 (0)