View Javadoc
1   package org.codehaus.modello.plugin.converters;
2   
3   /*
4    * Copyright (c) 2006, Codehaus.org
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining a copy of
7    * this software and associated documentation files (the "Software"), to deal in
8    * the Software without restriction, including without limitation the rights to
9    * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10   * of the Software, and to permit persons to whom the Software is furnished to do
11   * so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in all
14   * copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22   * SOFTWARE.
23   */
24  
25  import javax.inject.Named;
26  
27  import java.io.IOException;
28  import java.util.ArrayList;
29  import java.util.Collections;
30  import java.util.List;
31  import java.util.Properties;
32  
33  import org.codehaus.modello.ModelloException;
34  import org.codehaus.modello.ModelloParameterConstants;
35  import org.codehaus.modello.ModelloRuntimeException;
36  import org.codehaus.modello.model.Model;
37  import org.codehaus.modello.model.ModelAssociation;
38  import org.codehaus.modello.model.ModelClass;
39  import org.codehaus.modello.model.ModelDefault;
40  import org.codehaus.modello.model.ModelField;
41  import org.codehaus.modello.model.Version;
42  import org.codehaus.modello.model.VersionDefinition;
43  import org.codehaus.modello.plugin.java.AbstractJavaModelloGenerator;
44  import org.codehaus.modello.plugin.java.javasource.JClass;
45  import org.codehaus.modello.plugin.java.javasource.JInterface;
46  import org.codehaus.modello.plugin.java.javasource.JMethod;
47  import org.codehaus.modello.plugin.java.javasource.JMethodSignature;
48  import org.codehaus.modello.plugin.java.javasource.JParameter;
49  import org.codehaus.modello.plugin.java.javasource.JSourceCode;
50  import org.codehaus.modello.plugin.java.javasource.JSourceWriter;
51  import org.codehaus.modello.plugin.java.javasource.JType;
52  import org.codehaus.modello.plugin.java.metadata.JavaClassMetadata;
53  import org.codehaus.modello.plugin.java.metadata.JavaFieldMetadata;
54  
55  /**
56   * Generate a basic conversion class between two versions of a model.
57   */
58  @Named("converters")
59  public class ConverterGenerator extends AbstractJavaModelloGenerator {
60      public void generate(Model model, Properties parameters) throws ModelloException {
61          initialize(model, parameters);
62  
63          String[] versions =
64                  parameters.getProperty(ModelloParameterConstants.ALL_VERSIONS).split(",");
65  
66          List<Version> allVersions = new ArrayList<Version>(versions.length);
67          for (String version : versions) {
68              allVersions.add(new Version(version));
69          }
70          Collections.sort(allVersions);
71  
72          Version nextVersion = null;
73          for (Version v : allVersions) {
74              if (v.greaterThan(getGeneratedVersion())) {
75                  nextVersion = v;
76                  break;
77              }
78          }
79  
80          try {
81              // if nextVersion remains null, there is none greater so we are converting back to the unpackaged version
82  
83              generateConverters(nextVersion);
84  
85              if (nextVersion == null) {
86                  generateConverterTool(allVersions);
87              }
88          } catch (IOException ex) {
89              throw new ModelloException("Exception while generating model converters.", ex);
90          }
91      }
92  
93      private void generateConverters(Version toVersion) throws ModelloException, IOException {
94          Model objectModel = getModel();
95  
96          Version fromVersion = getGeneratedVersion();
97          String packageName = objectModel.getDefaultPackageName(true, fromVersion) + ".convert";
98  
99          Version effectiveToVersion = (toVersion == null) ? fromVersion : toVersion;
100         String jDoc = "Converts from version " + fromVersion + " (with version in package name) to version "
101                 + effectiveToVersion + " (with" + (toVersion != null ? "" : "out")
102                 + " version in package name) of the model.";
103 
104         JInterface conversionInterface = new JInterface(packageName + ".VersionConverter");
105         initHeader(conversionInterface);
106         suppressAllWarnings(objectModel, conversionInterface);
107         conversionInterface.getJDocComment().setComment(jDoc);
108 
109         JClass basicConverterClass = new JClass(packageName + ".BasicVersionConverter");
110         initHeader(basicConverterClass);
111         suppressAllWarnings(objectModel, basicConverterClass);
112         basicConverterClass.getJDocComment().setComment(jDoc);
113         basicConverterClass.addInterface(conversionInterface);
114 
115         VersionDefinition versionDefinition = objectModel.getVersionDefinition();
116 
117         for (ModelClass modelClass : objectModel.getClasses(fromVersion)) {
118             JavaClassMetadata javaClassMetadata = (JavaClassMetadata) modelClass.getMetadata(JavaClassMetadata.ID);
119 
120             if (!javaClassMetadata.isEnabled()) {
121                 // Skip generation of those classes that are not enabled for the java plugin.
122                 continue;
123             }
124 
125             // check if it's present in the next version
126             if (toVersion != null && !toVersion.inside(modelClass.getVersionRange())) {
127                 // Don't convert - it's not there in the next one
128                 continue;
129             }
130 
131             String methodName = "convert" + modelClass.getName();
132             String parameterName = uncapitalise(modelClass.getName());
133             String sourceClass = getSourceClassName(modelClass, fromVersion);
134             String targetClass = modelClass.getPackageName(toVersion != null, toVersion) + "." + modelClass.getName();
135 
136             if (!javaClassMetadata.isAbstract()) {
137                 // Don't generate converter for abstract classes.
138 
139                 JMethodSignature methodSig = new JMethodSignature(methodName, new JType(targetClass));
140                 methodSig.addParameter(new JParameter(new JType(sourceClass), parameterName));
141                 conversionInterface.addMethod(methodSig);
142 
143                 // Method from interface, delegates to converter with the given implementation of the target class
144                 JMethod jMethod = new JMethod(methodName, new JType(targetClass), null);
145                 jMethod.addParameter(new JParameter(new JType(sourceClass), parameterName));
146                 basicConverterClass.addMethod(jMethod);
147 
148                 JSourceCode sc = jMethod.getSourceCode();
149 
150                 sc.add("return " + methodName + "( " + parameterName + ", new " + targetClass + "() );");
151             }
152 
153             // Actual conversion method, takes implementation as a parameter to facilitate being called as a superclass
154             JMethod jMethod = new JMethod(methodName, new JType(targetClass), null);
155             jMethod.addParameter(new JParameter(new JType(sourceClass), parameterName));
156             jMethod.addParameter(new JParameter(new JType(targetClass), "value"));
157             basicConverterClass.addMethod(jMethod);
158 
159             JSourceCode sc = jMethod.getSourceCode();
160 
161             sc.add("if ( " + parameterName + " == null )");
162 
163             sc.add("{");
164             sc.indent();
165 
166             sc.add("return null;");
167 
168             sc.unindent();
169             sc.add("}");
170 
171             if (modelClass.getSuperClass() != null) {
172                 sc.add("// Convert super class");
173 
174                 sc.add("value = (" + targetClass + ") convert" + modelClass.getSuperClass() + "( " + parameterName
175                         + ", value );");
176 
177                 sc.add("");
178             }
179 
180             for (ModelField modelField : modelClass.getFields(fromVersion)) {
181                 String name = capitalise(modelField.getName());
182 
183                 if (toVersion != null) {
184                     if (versionDefinition != null && versionDefinition.isFieldType()) {
185                         if (versionDefinition.getValue().equals(modelField.getName())
186                                 || versionDefinition.getValue().equals(modelField.getAlias())) {
187                             sc.add("value.set" + name + "( \"" + toVersion + "\" );");
188                             continue;
189                         }
190                     }
191                 }
192 
193                 // check if it's present in the next version
194                 if (toVersion != null && !toVersion.inside(modelField.getVersionRange())) {
195                     // check if it is present in a new definition instead
196                     ModelField newField = null;
197                     try {
198                         newField = modelClass.getField(modelField.getName(), toVersion);
199                     } catch (ModelloRuntimeException e) {
200                         // Don't convert - it's not there in the next one
201                         continue;
202                     }
203 
204                     if (!newField.getType().equals(modelField.getType())) {
205                         // Don't convert - it's a different type in the next one
206                         continue;
207                     }
208                 }
209 
210                 if (modelField instanceof ModelAssociation) {
211                     ModelAssociation assoc = (ModelAssociation) modelField;
212 
213                     if (assoc.isManyMultiplicity()) {
214                         String type = assoc.getType();
215                         if (ModelDefault.LIST.equals(type) || ModelDefault.SET.equals(type)) {
216                             sc.add("{");
217 
218                             sc.indent();
219 
220                             sc.add(assoc.getType() + " list = " + assoc.getDefaultValue() + ";");
221 
222                             sc.add("for ( java.util.Iterator i = " + parameterName + ".get" + name
223                                     + "().iterator(); i.hasNext(); )");
224 
225                             sc.add("{");
226 
227                             sc.indent();
228 
229                             if (isClassInModel(assoc.getTo(), modelClass.getModel())) {
230                                 String className = getSourceClassName(assoc.getToClass(), fromVersion);
231                                 sc.add(className + " v = (" + className + ") i.next();");
232                             } else {
233                                 sc.add(assoc.getTo() + " v = (" + assoc.getTo() + ") i.next();");
234                             }
235 
236                             if (isClassInModel(assoc.getTo(), objectModel)) {
237                                 sc.add("list.add( convert" + assoc.getTo() + "( v ) );");
238                             } else {
239                                 sc.add("list.add( v );");
240                             }
241 
242                             sc.unindent();
243 
244                             sc.add("}");
245 
246                             sc.add("value.set" + name + "( list );");
247 
248                             sc.unindent();
249 
250                             sc.add("}");
251                         } else {
252                             sc.add("{");
253 
254                             sc.indent();
255 
256                             // Map or Properties
257                             sc.add(assoc.getType() + " map = " + assoc.getDefaultValue() + ";");
258 
259                             sc.add("for ( java.util.Iterator i = " + parameterName + ".get" + name
260                                     + "().entrySet().iterator(); i.hasNext(); )");
261 
262                             sc.add("{");
263 
264                             sc.indent();
265 
266                             sc.add("java.util.Map.Entry entry = (java.util.Map.Entry) i.next();");
267 
268                             if (isClassInModel(assoc.getTo(), modelClass.getModel())) {
269                                 String className = getSourceClassName(assoc.getToClass(), fromVersion);
270                                 sc.add(className + " v = (" + className + ") entry.getValue();");
271                             } else {
272                                 sc.add(assoc.getTo() + " v = (" + assoc.getTo() + ") entry.getValue();");
273                             }
274 
275                             if (isClassInModel(assoc.getTo(), objectModel)) {
276                                 sc.add("map.put( entry.getKey(), convert" + assoc.getTo() + "( v ) );");
277                             } else {
278                                 sc.add("map.put( entry.getKey(), v );");
279                             }
280 
281                             sc.unindent();
282 
283                             sc.add("}");
284 
285                             sc.add("value.set" + name + "( map );");
286 
287                             sc.unindent();
288 
289                             sc.add("}");
290                         }
291                     } else {
292                         sc.add("value.set" + name + "( convert" + assoc.getTo() + "( " + parameterName + ".get" + name
293                                 + "() ) );");
294                     }
295                 } else {
296                     sc.add("// Convert field " + modelField.getName());
297 
298                     JavaFieldMetadata javaFieldMetadata =
299                             (JavaFieldMetadata) modelField.getMetadata(JavaFieldMetadata.ID);
300                     String value = parameterName + "." + getPrefix(javaFieldMetadata) + name + "()";
301                     sc.add("value.set" + name + "( " + value + " );");
302                 }
303             }
304 
305             sc.add("");
306 
307             sc.add("return value;");
308         }
309 
310         try (JSourceWriter interfaceWriter = newJSourceWriter(packageName, conversionInterface.getName(true));
311                 JSourceWriter classWriter = newJSourceWriter(packageName, basicConverterClass.getName(true))) {
312             conversionInterface.print(interfaceWriter);
313             basicConverterClass.print(classWriter);
314         }
315     }
316 
317     private void generateConverterTool(List<Version> allVersions) throws ModelloException, IOException {
318         Model objectModel = getModel();
319         String root = objectModel.getRoot(getGeneratedVersion());
320 
321         ModelClass rootClass = objectModel.getClass(root, getGeneratedVersion());
322 
323         String basePackage = objectModel.getDefaultPackageName(false, null);
324         String packageName = basePackage + ".convert";
325 
326         String jDoc = "Converts between the available versions of the model.";
327 
328         JClass converterClass = new JClass(packageName + ".ConverterTool");
329         initHeader(converterClass);
330         suppressAllWarnings(objectModel, converterClass);
331         converterClass.getJDocComment().setComment(jDoc);
332 
333         converterClass.addImport("java.io.File");
334         converterClass.addImport("java.io.IOException");
335 
336         converterClass.addImport("javax.xml.stream.*");
337 
338         for (Version v : allVersions) {
339             writeConvertMethod(converterClass, objectModel, basePackage, allVersions, v, rootClass);
340         }
341         writeConvertMethod(converterClass, objectModel, basePackage, allVersions, null, rootClass);
342 
343         try (JSourceWriter classWriter = newJSourceWriter(packageName, converterClass.getName(true))) {
344             converterClass.print(new JSourceWriter(classWriter));
345         }
346     }
347 
348     private static void writeConvertMethod(
349             JClass converterClass,
350             Model objectModel,
351             String basePackage,
352             List<Version> allVersions,
353             Version v,
354             ModelClass rootClass) {
355         String modelName = objectModel.getName();
356         String rootClassName = rootClass.getName();
357 
358         String targetPackage = objectModel.getDefaultPackageName(v != null, v);
359         String targetClass = targetPackage + "." + rootClassName;
360 
361         String methodName = "convertFromFile";
362         if (v != null) {
363             methodName += "_" + v.toString("v", "_");
364         }
365         JMethod method = new JMethod(methodName, new JType(targetClass), null);
366         method.addParameter(new JParameter(new JType("File"), "f"));
367         method.addException(new JClass("IOException"));
368         method.addException(new JClass("XMLStreamException"));
369         converterClass.addMethod(method);
370 
371         JSourceCode sc = method.getSourceCode();
372 
373         sc.add(basePackage + ".io.stax." + modelName + "StaxReaderDelegate reader = new " + basePackage + ".io.stax."
374                 + modelName + "StaxReaderDelegate();");
375 
376         sc.add("Object value = reader.read( f );");
377 
378         String prefix = "";
379         for (Version sourceVersion : allVersions) {
380             String sourcePackage = objectModel.getDefaultPackageName(true, sourceVersion);
381             String sourceClass = sourcePackage + "." + rootClassName;
382             sc.add(prefix + "if ( value instanceof " + sourceClass + " )");
383             sc.add("{");
384             sc.indent();
385 
386             boolean foundFirst = false;
387             for (Version targetVersion : allVersions) {
388                 if (!foundFirst) {
389                     if (targetVersion.equals(sourceVersion)) {
390                         foundFirst = true;
391                     } else {
392                         continue;
393                     }
394                 }
395 
396                 if (targetVersion.equals(v)) {
397                     break;
398                 }
399 
400                 // TODO: need to be able to specify converter class implementation
401                 String p = objectModel.getDefaultPackageName(true, targetVersion);
402                 String c = p + "." + rootClassName;
403                 sc.add("value = new " + p + ".convert.BasicVersionConverter().convert" + rootClassName + "( (" + c
404                         + ") value );");
405             }
406 
407             sc.unindent();
408             sc.add("}");
409 
410             prefix = "else ";
411 
412             if (sourceVersion.equals(v)) {
413                 break;
414             }
415         }
416         sc.add("else");
417         sc.add("{");
418         sc.indent();
419 
420         sc.add("throw new IllegalStateException( \"Can't find converter for class '\" + value.getClass() + \"'\" );");
421 
422         sc.unindent();
423         sc.add("}");
424 
425         sc.add("return (" + targetClass + ") value;");
426     }
427 
428     private static String getSourceClassName(ModelClass modelClass, Version generatedVersion) {
429         return modelClass.getPackageName(true, generatedVersion) + "." + modelClass.getName();
430     }
431 }