Skip to content

Commit 8ef216a

Browse files
authored
Merge pull request #346 from domaframework/data-type
Add the DataType annotation
2 parents fd67294 + 9e3ffbe commit 8ef216a

File tree

17 files changed

+532
-121
lines changed

17 files changed

+532
-121
lines changed

docs/domain.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,23 @@ The value ``new`` means that the object of annotated class is created with a con
5959
6060
.. _records: https://openjdk.java.net/jeps/359
6161

62+
.. warning::
63+
64+
To annotate records with ``@Domain`` is a little redundant,
65+
because you must specify some properties to ``@Domain`` such as ``valueType``.
66+
Instead of ``@Domain``, you can annotate records with ``@DataType``:
67+
68+
.. code-block:: java
69+
70+
@DataType
71+
public record PhoneNumber(String value) {
72+
public String getAreaCode() {
73+
...
74+
}
75+
}
76+
77+
But note that ``@DataType`` is an experimental feature.
78+
6279

6380
Instantiation with a static factory method
6481
------------------------------------------
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.seasar.doma.experimental;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* Indicates a data type (aka domain class).
10+
*
11+
* <p>The data type is the user defined type that wraps a basic value. It can be mapped to a
12+
* database column.
13+
*
14+
* <p>This annotation is applied for only record types.
15+
*
16+
* <pre>
17+
* &#064;DataType
18+
* public record PhoneNumber(String value) {
19+
* }
20+
* </pre>
21+
*/
22+
@Target(ElementType.TYPE)
23+
@Retention(RetentionPolicy.RUNTIME)
24+
public @interface DataType {}

src/main/java/org/seasar/doma/internal/apt/MoreElements.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,34 @@
55

66
import java.io.Writer;
77
import java.lang.annotation.Annotation;
8-
import java.util.*;
8+
import java.util.Arrays;
9+
import java.util.Collections;
10+
import java.util.HashMap;
11+
import java.util.Iterator;
12+
import java.util.LinkedHashMap;
13+
import java.util.List;
14+
import java.util.Map;
915
import java.util.function.Predicate;
1016
import javax.annotation.processing.ProcessingEnvironment;
11-
import javax.lang.model.element.*;
17+
import javax.lang.model.element.AnnotationMirror;
18+
import javax.lang.model.element.AnnotationValue;
19+
import javax.lang.model.element.Element;
20+
import javax.lang.model.element.ExecutableElement;
21+
import javax.lang.model.element.Modifier;
22+
import javax.lang.model.element.Name;
23+
import javax.lang.model.element.PackageElement;
24+
import javax.lang.model.element.Parameterizable;
25+
import javax.lang.model.element.TypeElement;
26+
import javax.lang.model.element.TypeParameterElement;
27+
import javax.lang.model.element.VariableElement;
1228
import javax.lang.model.type.DeclaredType;
1329
import javax.lang.model.type.TypeMirror;
1430
import javax.lang.model.util.ElementFilter;
1531
import javax.lang.model.util.Elements;
1632
import javax.lang.model.util.SimpleElementVisitor8;
1733
import org.seasar.doma.ParameterName;
1834
import org.seasar.doma.internal.apt.def.TypeParametersDef;
35+
import org.seasar.doma.internal.apt.util.ElementKindUtil;
1936

2037
public class MoreElements implements Elements {
2138

@@ -267,4 +284,16 @@ List<String> getTypeParameterNames(List<? extends TypeParameterElement> typePara
267284
typeParameterElements.stream().map(TypeParameterElement::asType).collect(toList());
268285
return ctx.getMoreTypes().getTypeParameterNames(typeMirrors);
269286
}
287+
288+
public VariableElement getSingleParameterOfRecordConstructor(TypeElement record) {
289+
if (!ElementKindUtil.isRecord(record.getKind())) {
290+
throw new AptIllegalStateException(record.getQualifiedName() + " must be a record type.");
291+
}
292+
return ElementFilter.constructorsIn(record.getEnclosedElements()).stream()
293+
.filter(c -> c.getModifiers().contains(Modifier.PUBLIC))
294+
.filter(c -> c.getParameters().size() == 1)
295+
.flatMap(c -> c.getParameters().stream())
296+
.findFirst()
297+
.orElse(null);
298+
}
270299
}

src/main/java/org/seasar/doma/internal/apt/annot/Annotations.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.function.Function;
99
import javax.lang.model.element.*;
1010
import org.seasar.doma.*;
11+
import org.seasar.doma.experimental.DataType;
1112
import org.seasar.doma.experimental.Sql;
1213
import org.seasar.doma.internal.apt.Context;
1314
import org.seasar.doma.internal.apt.util.AnnotationValueUtil;
@@ -100,6 +101,11 @@ public DaoAnnot newDaoAnnot(TypeElement typeElement) {
100101
return newInstance(typeElement, Dao.class, DaoAnnot::new);
101102
}
102103

104+
public DataTypeAnnot newDataTypeAnnot(TypeElement typeElement) {
105+
assertNotNull(typeElement);
106+
return newInstance(typeElement, DataType.class, DataTypeAnnot::new);
107+
}
108+
103109
public DomainAnnot newDomainAnnot(TypeElement typeElement) {
104110
assertNotNull(typeElement);
105111
return newInstance(typeElement, Domain.class, DomainAnnot::new);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.seasar.doma.internal.apt.annot;
2+
3+
import java.util.Map;
4+
import javax.lang.model.element.AnnotationMirror;
5+
import javax.lang.model.element.AnnotationValue;
6+
7+
public class DataTypeAnnot extends AbstractAnnot {
8+
9+
DataTypeAnnot(AnnotationMirror annotationMirror, Map<String, AnnotationValue> values) {
10+
super(annotationMirror);
11+
}
12+
}

src/main/java/org/seasar/doma/internal/apt/cttype/CtTypes.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import javax.lang.model.element.ElementKind;
3636
import javax.lang.model.element.Name;
3737
import javax.lang.model.element.TypeElement;
38+
import javax.lang.model.element.VariableElement;
3839
import javax.lang.model.type.ArrayType;
3940
import javax.lang.model.type.DeclaredType;
4041
import javax.lang.model.type.MirroredTypeException;
@@ -45,6 +46,7 @@
4546
import org.seasar.doma.Domain;
4647
import org.seasar.doma.Embeddable;
4748
import org.seasar.doma.Entity;
49+
import org.seasar.doma.experimental.DataType;
4850
import org.seasar.doma.internal.ClassName;
4951
import org.seasar.doma.internal.ClassNames;
5052
import org.seasar.doma.internal.apt.AptException;
@@ -229,6 +231,10 @@ private DomainInfo getDomainInfo(TypeElement typeElement) {
229231
if (domain != null) {
230232
return getDomainInfo(domain);
231233
}
234+
DataType dataType = typeElement.getAnnotation(DataType.class);
235+
if (dataType != null) {
236+
return getDomainInfo(typeElement, dataType);
237+
}
232238
return getExternalDomainInfo(typeElement.asType());
233239
}
234240

@@ -241,6 +247,15 @@ private DomainInfo getDomainInfo(Domain domain) {
241247
throw new AptIllegalStateException("unreachable.");
242248
}
243249

250+
private DomainInfo getDomainInfo(TypeElement typeElement, DataType dataType) {
251+
VariableElement param =
252+
ctx.getMoreElements().getSingleParameterOfRecordConstructor(typeElement);
253+
if (param == null) {
254+
throw new AptIllegalStateException(typeElement.getQualifiedName().toString());
255+
}
256+
return new DomainInfo(param.asType(), false);
257+
}
258+
244259
private DomainInfo getExternalDomainInfo(TypeMirror domainType) {
245260
String csv = ctx.getOptions().getDomainConverters();
246261
if (csv != null) {
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package org.seasar.doma.internal.apt.meta.domain;
2+
3+
import static org.seasar.doma.internal.util.AssertionUtil.assertNotNull;
4+
5+
import java.util.List;
6+
import javax.lang.model.element.TypeElement;
7+
import javax.lang.model.type.TypeMirror;
8+
import org.seasar.doma.internal.apt.annot.DataTypeAnnot;
9+
import org.seasar.doma.internal.apt.cttype.BasicCtType;
10+
import org.seasar.doma.internal.apt.def.TypeParametersDef;
11+
12+
public class DataTypeMeta implements DomainMeta {
13+
14+
private final TypeElement typeElement;
15+
16+
private final TypeMirror type;
17+
18+
private final DataTypeAnnot dataTypeAnnot;
19+
20+
private TypeParametersDef typeParametersDef;
21+
22+
private BasicCtType basicCtType;
23+
24+
private String accessorMethod;
25+
26+
public DataTypeMeta(TypeElement typeElement, TypeMirror type, DataTypeAnnot dataTypeAnnot) {
27+
assertNotNull(typeElement, type);
28+
this.typeElement = typeElement;
29+
this.type = type;
30+
this.dataTypeAnnot = dataTypeAnnot;
31+
}
32+
33+
@Override
34+
public TypeMirror getType() {
35+
return type;
36+
}
37+
38+
@Override
39+
public TypeElement getTypeElement() {
40+
return typeElement;
41+
}
42+
43+
public DataTypeAnnot getDataTypeAnnot() {
44+
return dataTypeAnnot;
45+
}
46+
47+
@Override
48+
public String getFactoryMethod() {
49+
return "new";
50+
}
51+
52+
@Override
53+
public String getAccessorMethod() {
54+
return accessorMethod;
55+
}
56+
57+
public void setAccessorMethod(String accessorMethod) {
58+
this.accessorMethod = accessorMethod;
59+
}
60+
61+
@Override
62+
public boolean getAcceptNull() {
63+
return false;
64+
}
65+
66+
@Override
67+
public boolean providesConstructor() {
68+
return true;
69+
}
70+
71+
@Override
72+
public List<String> getTypeParameters() {
73+
return typeParametersDef.getTypeParameters();
74+
}
75+
76+
public void setTypeParametersDef(TypeParametersDef typeParametersDef) {
77+
this.typeParametersDef = typeParametersDef;
78+
}
79+
80+
@Override
81+
public List<String> getTypeVariables() {
82+
return typeParametersDef.getTypeVariables();
83+
}
84+
85+
@Override
86+
public BasicCtType getBasicCtType() {
87+
return basicCtType;
88+
}
89+
90+
public void setBasicCtType(BasicCtType basicCtType) {
91+
this.basicCtType = basicCtType;
92+
}
93+
94+
@Override
95+
public TypeMirror getValueType() {
96+
return basicCtType.getType();
97+
}
98+
99+
@Override
100+
public boolean isParameterized() {
101+
return !typeElement.getTypeParameters().isEmpty();
102+
}
103+
104+
@Override
105+
public boolean isError() {
106+
return false;
107+
}
108+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package org.seasar.doma.internal.apt.meta.domain;
2+
3+
import static org.seasar.doma.internal.util.AssertionUtil.assertNotNull;
4+
5+
import java.util.Arrays;
6+
import java.util.Set;
7+
import javax.lang.model.element.Element;
8+
import javax.lang.model.element.Modifier;
9+
import javax.lang.model.element.NestingKind;
10+
import javax.lang.model.element.TypeElement;
11+
import javax.lang.model.element.VariableElement;
12+
import javax.lang.model.type.TypeMirror;
13+
import org.seasar.doma.internal.Constants;
14+
import org.seasar.doma.internal.apt.AptException;
15+
import org.seasar.doma.internal.apt.AptIllegalStateException;
16+
import org.seasar.doma.internal.apt.Context;
17+
import org.seasar.doma.internal.apt.annot.DataTypeAnnot;
18+
import org.seasar.doma.internal.apt.cttype.BasicCtType;
19+
import org.seasar.doma.internal.apt.def.TypeParametersDef;
20+
import org.seasar.doma.internal.apt.meta.TypeElementMetaFactory;
21+
import org.seasar.doma.internal.apt.util.ElementKindUtil;
22+
import org.seasar.doma.message.Message;
23+
24+
public class DataTypeMetaFactory implements TypeElementMetaFactory<DataTypeMeta> {
25+
26+
private final Context ctx;
27+
28+
public DataTypeMetaFactory(Context ctx) {
29+
assertNotNull(ctx);
30+
this.ctx = ctx;
31+
}
32+
33+
@Override
34+
public DataTypeMeta createTypeElementMeta(TypeElement typeElement) {
35+
assertNotNull(typeElement);
36+
DataTypeAnnot dataTypeAnnot = ctx.getAnnotations().newDataTypeAnnot(typeElement);
37+
if (dataTypeAnnot == null) {
38+
throw new AptIllegalStateException("dataTypeAnnot");
39+
}
40+
DataTypeMeta dataTypeMeta = new DataTypeMeta(typeElement, typeElement.asType(), dataTypeAnnot);
41+
validateTypeElement(typeElement, dataTypeMeta);
42+
doTypeParameters(typeElement, dataTypeMeta);
43+
doValueType(typeElement, dataTypeMeta);
44+
return dataTypeMeta;
45+
}
46+
47+
private void validateTypeElement(TypeElement typeElement, DataTypeMeta dataTypeMeta) {
48+
if (!ElementKindUtil.isRecord(typeElement.getKind())) {
49+
throw new AptException(Message.DOMA4449, typeElement, new Object[] {});
50+
}
51+
if (typeElement.getNestingKind().isNested()) {
52+
validateEnclosingElement(typeElement);
53+
}
54+
}
55+
56+
void validateEnclosingElement(Element element) {
57+
TypeElement typeElement = ctx.getMoreElements().toTypeElement(element);
58+
if (typeElement == null) {
59+
return;
60+
}
61+
String simpleName = typeElement.getSimpleName().toString();
62+
if (simpleName.contains(Constants.BINARY_NAME_DELIMITER)
63+
|| simpleName.contains(Constants.DESC_NAME_DELIMITER)) {
64+
throw new AptException(
65+
Message.DOMA4450, typeElement, new Object[] {typeElement.getQualifiedName()});
66+
}
67+
NestingKind nestingKind = typeElement.getNestingKind();
68+
if (nestingKind == NestingKind.TOP_LEVEL) {
69+
return;
70+
} else if (nestingKind == NestingKind.MEMBER) {
71+
Set<Modifier> modifiers = typeElement.getModifiers();
72+
if (modifiers.containsAll(Arrays.asList(Modifier.STATIC, Modifier.PUBLIC))) {
73+
validateEnclosingElement(typeElement.getEnclosingElement());
74+
} else {
75+
throw new AptException(
76+
Message.DOMA4451, typeElement, new Object[] {typeElement.getQualifiedName()});
77+
}
78+
} else {
79+
throw new AptException(
80+
Message.DOMA4452, typeElement, new Object[] {typeElement.getQualifiedName()});
81+
}
82+
}
83+
84+
public void doTypeParameters(TypeElement typeElement, DataTypeMeta dataTypeMeta) {
85+
TypeParametersDef typeParametersDef = ctx.getMoreElements().getTypeParametersDef(typeElement);
86+
dataTypeMeta.setTypeParametersDef(typeParametersDef);
87+
}
88+
89+
private void doValueType(TypeElement typeElement, DataTypeMeta dataTypeMeta) {
90+
VariableElement param =
91+
ctx.getMoreElements().getSingleParameterOfRecordConstructor(typeElement);
92+
if (param == null) {
93+
throw new AptException(Message.DOMA4453, typeElement, new Object[] {});
94+
}
95+
TypeMirror type = param.asType();
96+
BasicCtType basicCtType = ctx.getCtTypes().newBasicCtType(type);
97+
if (basicCtType == null) {
98+
throw new AptException(Message.DOMA4454, param, new Object[] {type});
99+
}
100+
dataTypeMeta.setBasicCtType(basicCtType);
101+
dataTypeMeta.setAccessorMethod(param.getSimpleName().toString());
102+
}
103+
}

0 commit comments

Comments
 (0)