Skip to contentMethod: generateClassFile(MetamodelClass)
1: /*
2: * JOPA
3: * Copyright (C) 2024 Czech Technical University in Prague
4: *
5: * This library is free software; you can redistribute it and/or
6: * modify it under the terms of the GNU Lesser General Public
7: * License as published by the Free Software Foundation; either
8: * version 3.0 of the License, or (at your option) any later version.
9: *
10: * This library is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13: * Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public
16: * License along with this library.
17: */
18: package cz.cvut.kbss.jopa.modelgen;
19:
20: import cz.cvut.kbss.jopa.modelgen.classmodel.Field;
21: import cz.cvut.kbss.jopa.modelgen.classmodel.MappingAnnotations;
22: import cz.cvut.kbss.jopa.modelgen.classmodel.MetamodelClass;
23: import cz.cvut.kbss.jopa.modelgen.classmodel.Type;
24: import cz.cvut.kbss.jopa.modelgen.exception.ModelGenException;
25:
26: import javax.annotation.processing.Messager;
27: import javax.tools.Diagnostic;
28: import java.io.File;
29: import java.io.IOException;
30: import java.nio.file.Files;
31: import java.nio.file.StandardOpenOption;
32: import java.util.Collection;
33: import java.util.List;
34: import java.util.Map;
35: import java.util.Objects;
36: import java.util.Set;
37:
38: /**
39: * Class for generating output files
40: */
41: public class OutputFilesGenerator {
42:
43: private final String targetDir;
44: private final boolean debugMode;
45:
46: private final Messager messager;
47:
48: public OutputFilesGenerator(String targetDir, boolean debugMode, Messager messager) {
49: this.targetDir = targetDir;
50: this.debugMode = debugMode;
51: this.messager = messager;
52: }
53:
54: /**
55: * Generates files containing Java classes representing the specified static metamodel objects.
56: *
57: * @param classes Metamodel objects
58: */
59: public void generateOutputFiles(Collection<MetamodelClass> classes) {
60: for (MetamodelClass cls : classes) {
61: generateClassFile(cls);
62: }
63: }
64:
65: private void debug(String msg) {
66: if (debugMode) {
67: messager.printMessage(Diagnostic.Kind.NOTE, msg);
68: }
69: }
70:
71: private void generateClassFile(MetamodelClass cls) {
72: final File targetFile = createTargetFile(cls);
73: final StringBuilder content = new StringBuilder(generateClassPreamble(cls));
74: content.append(generateAttributes(cls));
75: content.append(generateClassSuffix());
76: try {
77: Files.writeString(targetFile.toPath(), content.toString(), StandardOpenOption.TRUNCATE_EXISTING);
78: } catch (IOException e) {
79: throw new ModelGenException("Unable to output content to target file '" + targetFile + "'!", e);
80: }
81: debug("\t - File '" + targetFile.getName() + "' created.");
82: }
83:
84: private File createTargetFile(MetamodelClass cls) {
85: final StringBuilder fileName = new StringBuilder(targetDir);
86: fileName.append("/");
87: String pack = cls.getPckg();
88: while (pack.contains(".")) {
89: int index = pack.indexOf(".");
90: fileName.append(pack, 0, index).append("/");
91: pack = pack.substring(index + 1);
92: }
93: fileName.append(pack)
94: .append("/")
95: .append(cls.getName())
96: .append("_.java");
97: try {
98: File file = new File(fileName.toString());
99: file.getParentFile().mkdirs();
100: if (!file.exists()) {
101: boolean result = file.createNewFile();
102: if (!result) {
103: throw new ModelGenException("Unable to create target file '" + file + "'!");
104: }
105: }
106: return file;
107: } catch (IOException e) {
108: throw new ModelGenException("Unable to create target file '" + fileName + "'!", e);
109: }
110: }
111:
112: public String generateClassPreamble(MetamodelClass cls) {
113: String extend = "";
114: if (!cls.getExtend().isEmpty()) {
115: extend = "extends ";
116: if (cls.getExtend().contains(".")) {
117: extend += cls.getExtend().substring(cls.getExtend().lastIndexOf(".") + 1) + "_ ";
118: } else {
119: extend += cls.getExtend();
120: }
121: }
122: StringBuilder sbOut = new StringBuilder();
123:
124: if (!cls.getPckg().isEmpty()) {
125: sbOut.append("package ")
126: .append(cls.getPckg())
127: .append(";\n\n");
128: }
129: cls.getImports().forEach(imp -> sbOut.append("import ")
130: .append(imp)
131: .append(";\n"));
132: if (!cls.getExtend().isEmpty()) {
133: sbOut.append("import ")
134: .append(cls.getExtend())
135: .append("_;\n");
136: }
137: sbOut.append("\n@Generated(value = \"")
138: .append("cz.cvut.kbss.jopa.modelgen.ModelGenProcessor\")")
139: .append("\n@StaticMetamodel(")
140: .append(cls.getName())
141: .append(".class)\n")
142: .append("public ")
143: .append("abstract ")
144: .append("class ")
145: .append(cls.getName())
146: .append("_ ")
147: .append(extend)
148: .append("{\n\n");
149: return sbOut.toString();
150: }
151:
152: private static String generateAttributes(MetamodelClass cls) {
153: StringBuilder attributes = new StringBuilder();
154: for (Field field : cls.getFields()) {
155: final String declaringClass = field.getParentName().substring(field.getParentName().lastIndexOf('.') + 1);
156: attributes.append(" public static volatile ");
157: //@Id
158: if (isAnnotatedWith(field, MappingAnnotations.ID)) {
159: attributes.append("Identifier<")
160: .append(declaringClass)
161: .append(", ")
162: .append(field.getType().getTypeName()
163: .substring(field.getType().getTypeName().lastIndexOf(".") + 1));
164: //@Types
165: } else if (isAnnotatedWith(field, MappingAnnotations.TYPES)) {
166: attributes.append("TypesSpecification<")
167: .append(declaringClass)
168: .append(", ");
169: if (field.getType().getIsSimple()) {
170: attributes.append(field.getType().getSimpleName());
171: } else {
172: attributes.append(field.getType().getTypes().get(0).getSimpleName());
173: }
174: //@Properties
175: } else if (isAnnotatedWith(field, MappingAnnotations.PROPERTIES)) {
176: attributes.append("PropertiesSpecification<")
177: .append(declaringClass)
178: .append(", ");
179: Type type = field.getType();
180: if (!Objects.equals(type.getTypeName(), Map.class.getName())) {
181: attributes
182: .append(type.getTypes().get(0).getTypeName()
183: .substring(type.getTypes().get(0).getTypeName().lastIndexOf(".") + 1));
184: } else {
185: attributes.append("Map, ")
186: .append(type.getTypes().get(0).getSimpleName())
187: .append(", ")
188: .append(type.getTypes().get(1).getTypes().get(0).getSimpleName());
189: }
190: } else if (isAnnotatedWith(field, MappingAnnotations.DATA_PROPERTY)
191: || isAnnotatedWith(field, MappingAnnotations.OBJECT_PROPERTY)
192: || isAnnotatedWith(field, MappingAnnotations.ANNOTATION_PROPERTY)) {
193: Type type = field.getType();
194: if (type.getIsSimple()) {
195: attributes.append("SingularAttribute<")
196: .append(declaringClass)
197: .append(", ")
198: .append(type.getSimpleName());
199: } else {
200: if (type.getTypeName().equals(List.class.getName())) {
201: attributes.append("ListAttribute<");
202: } else if (type.getTypeName().equals(Set.class.getName())) {
203: attributes.append("SetAttribute<");
204: }
205: attributes.append(declaringClass)
206: .append(", ")
207: .append(type.getTypes().get(0).getSimpleName());
208: }
209: }
210: attributes
211: .append("> ")
212: .append(field.getName())
213: .append(";\n");
214: }
215: return attributes.toString();
216: }
217:
218: private static String generateClassSuffix() {
219: return "}";
220: }
221:
222: /**
223: * Checking method whether Field has at least one of the given annotations.
224: *
225: * @param field Field to check
226: * @param mappingAnnotations Annotations for which to check
227: * @return {@code true} if the field is annotated with at least one of the mapping annotations, {@code false}
228: * otherwise
229: */
230: static boolean isAnnotatedWith(Field field, MappingAnnotations mappingAnnotations) {
231: List<MappingAnnotations> annotations = field.getAnnotatedWith();
232: if (annotations.isEmpty()) {
233: return false;
234: }
235: for (MappingAnnotations an : annotations) {
236: if (an.equals(mappingAnnotations)) {
237: return true;
238: }
239: }
240: return false;
241: }
242: }