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 org.codehaus.modello.ModelloException;
26  import org.codehaus.modello.ModelloParameterConstants;
27  import org.codehaus.modello.ModelloRuntimeException;
28  import org.codehaus.modello.model.Model;
29  import org.codehaus.modello.model.ModelAssociation;
30  import org.codehaus.modello.model.ModelClass;
31  import org.codehaus.modello.model.ModelDefault;
32  import org.codehaus.modello.model.ModelField;
33  import org.codehaus.modello.model.Version;
34  import org.codehaus.modello.model.VersionDefinition;
35  import org.codehaus.modello.plugin.java.AbstractJavaModelloGenerator;
36  import org.codehaus.modello.plugin.java.javasource.JClass;
37  import org.codehaus.modello.plugin.java.javasource.JInterface;
38  import org.codehaus.modello.plugin.java.javasource.JMethod;
39  import org.codehaus.modello.plugin.java.javasource.JMethodSignature;
40  import org.codehaus.modello.plugin.java.javasource.JParameter;
41  import org.codehaus.modello.plugin.java.javasource.JSourceCode;
42  import org.codehaus.modello.plugin.java.javasource.JSourceWriter;
43  import org.codehaus.modello.plugin.java.javasource.JType;
44  import org.codehaus.modello.plugin.java.metadata.JavaClassMetadata;
45  import org.codehaus.modello.plugin.java.metadata.JavaFieldMetadata;
46  import org.codehaus.plexus.util.IOUtil;
47  
48  import java.io.IOException;
49  import java.util.ArrayList;
50  import java.util.Collections;
51  import java.util.List;
52  import java.util.Properties;
53  
54  /**
55   * Generate a basic conversion class between two versions of a model.
56   */
57  public class ConverterGenerator
58      extends AbstractJavaModelloGenerator
59  {
60      public void generate( Model model, Properties parameters )
61          throws ModelloException
62      {
63          initialize( model, parameters );
64  
65          String[] versions = parameters.getProperty( ModelloParameterConstants.ALL_VERSIONS ).split( "," );
66  
67          List<Version> allVersions = new ArrayList<Version>( versions.length );
68          for ( String version : versions )
69          {
70              allVersions.add( new Version( version ) );
71          }
72          Collections.sort( allVersions );
73  
74          Version nextVersion = null;
75          for ( Version v : allVersions )
76          {
77              if ( v.greaterThan( getGeneratedVersion() ) )
78              {
79                  nextVersion = v;
80                  break;
81              }
82          }
83  
84          try
85          {
86              // if nextVersion remains null, there is none greater so we are converting back to the unpackaged version
87  
88              generateConverters( nextVersion );
89  
90              if ( nextVersion == null )
91              {
92                  generateConverterTool( allVersions );
93              }
94          }
95          catch ( IOException ex )
96          {
97              throw new ModelloException( "Exception while generating model converters.", ex );
98          }
99      }
100 
101     private void generateConverters( Version toVersion )
102         throws ModelloException, IOException
103     {
104         Model objectModel = getModel();
105 
106         Version fromVersion = getGeneratedVersion();
107         String packageName = objectModel.getDefaultPackageName( true, fromVersion ) + ".convert";
108 
109         Version effectiveToVersion = ( toVersion == null ) ? fromVersion : toVersion;
110         String jDoc = "Converts from version " + fromVersion + " (with version in package name) to version "
111             + effectiveToVersion + " (with" + ( toVersion != null ? "" : "out" )
112             + " version in package name) of the model.";
113 
114         JInterface conversionInterface = new JInterface( packageName + ".VersionConverter" );
115         initHeader( conversionInterface );
116         suppressAllWarnings( objectModel, conversionInterface );
117         conversionInterface.getJDocComment().setComment( jDoc );
118 
119         JClass basicConverterClass = new JClass( packageName + ".BasicVersionConverter" );
120         initHeader( basicConverterClass );
121         suppressAllWarnings( objectModel, basicConverterClass );
122         basicConverterClass.getJDocComment().setComment( jDoc );
123         basicConverterClass.addInterface( conversionInterface );
124 
125         VersionDefinition versionDefinition = objectModel.getVersionDefinition();
126 
127         for ( ModelClass modelClass : objectModel.getClasses( fromVersion ) )
128         {
129             JavaClassMetadata javaClassMetadata = (JavaClassMetadata) modelClass.getMetadata( JavaClassMetadata.ID );
130 
131             if ( !javaClassMetadata.isEnabled() )
132             {
133                 // Skip generation of those classes that are not enabled for the java plugin.
134                 continue;
135             }
136 
137             // check if it's present in the next version
138             if ( toVersion != null && !toVersion.inside( modelClass.getVersionRange() ) )
139             {
140                 // Don't convert - it's not there in the next one
141                 continue;
142             }
143 
144             String methodName = "convert" + modelClass.getName();
145             String parameterName = uncapitalise( modelClass.getName() );
146             String sourceClass = getSourceClassName( modelClass, fromVersion );
147             String targetClass =
148                 modelClass.getPackageName( toVersion != null, toVersion ) + "." + modelClass.getName();
149 
150             if ( !javaClassMetadata.isAbstract() )
151             {
152                 // Don't generate converter for abstract classes.
153 
154                 JMethodSignature methodSig = new JMethodSignature( methodName, new JType( targetClass ) );
155                 methodSig.addParameter( new JParameter( new JType( sourceClass ), parameterName ) );
156                 conversionInterface.addMethod( methodSig );
157 
158                 // Method from interface, delegates to converter with the given implementation of the target class
159                 JMethod jMethod = new JMethod( methodName, new JType( targetClass ), null );
160                 jMethod.addParameter( new JParameter( new JType( sourceClass ), parameterName ) );
161                 basicConverterClass.addMethod( jMethod );
162 
163                 JSourceCode sc = jMethod.getSourceCode();
164 
165                 sc.add( "return " + methodName + "( " + parameterName + ", new " + targetClass + "() );" );
166             }
167 
168             // Actual conversion method, takes implementation as a parameter to facilitate being called as a superclass
169             JMethod jMethod = new JMethod( methodName, new JType( targetClass ), null );
170             jMethod.addParameter( new JParameter( new JType( sourceClass ), parameterName ) );
171             jMethod.addParameter( new JParameter( new JType( targetClass ), "value" ) );
172             basicConverterClass.addMethod( jMethod );
173 
174             JSourceCode sc = jMethod.getSourceCode();
175 
176             sc.add( "if ( " + parameterName + " == null )" );
177 
178             sc.add( "{" );
179             sc.indent();
180 
181             sc.add( "return null;" );
182 
183             sc.unindent();
184             sc.add( "}" );
185 
186             if ( modelClass.getSuperClass() != null )
187             {
188                 sc.add( "// Convert super class" );
189 
190                 sc.add( "value = (" + targetClass + ") convert" + modelClass.getSuperClass() + "( " + parameterName
191                     + ", value );" );
192 
193                 sc.add( "" );
194             }
195 
196             for ( ModelField modelField : modelClass.getFields( fromVersion ) )
197             {
198                 String name = capitalise( modelField.getName() );
199 
200                 if ( toVersion != null )
201                 {
202                     if ( versionDefinition != null && versionDefinition.isFieldType() )
203                     {
204                         if ( versionDefinition.getValue().equals( modelField.getName() )
205                             || versionDefinition.getValue().equals( modelField.getAlias() ) )
206                         {
207                             sc.add( "value.set" + name + "( \"" + toVersion + "\" );" );
208                             continue;
209                         }
210                     }
211                 }
212 
213                 // check if it's present in the next version
214                 if ( toVersion != null && !toVersion.inside( modelField.getVersionRange() ) )
215                 {
216                     // check if it is present in a new definition instead
217                     ModelField newField = null;
218                     try
219                     {
220                         newField = modelClass.getField( modelField.getName(), toVersion );
221                     }
222                     catch ( ModelloRuntimeException e )
223                     {
224                         // Don't convert - it's not there in the next one
225                         continue;
226                     }
227 
228                     if ( !newField.getType().equals( modelField.getType() ) )
229                     {
230                         // Don't convert - it's a different type in the next one
231                         continue;
232                     }
233                 }
234 
235                 if ( modelField instanceof ModelAssociation )
236                 {
237                     ModelAssociation assoc = (ModelAssociation) modelField;
238 
239                     if ( assoc.isManyMultiplicity() )
240                     {
241                         String type = assoc.getType();
242                         if ( ModelDefault.LIST.equals( type ) || ModelDefault.SET.equals( type ) )
243                         {
244                             sc.add( "{" );
245 
246                             sc.indent();
247 
248                             sc.add( assoc.getType() + " list = " + assoc.getDefaultValue() + ";" );
249 
250                             sc.add( "for ( java.util.Iterator i = " + parameterName + ".get" + name
251                                 + "().iterator(); i.hasNext(); )" );
252 
253                             sc.add( "{" );
254 
255                             sc.indent();
256 
257                             if ( isClassInModel( assoc.getTo(), modelClass.getModel() ) )
258                             {
259                                 String className = getSourceClassName( assoc.getToClass(), fromVersion );
260                                 sc.add( className + " v = (" + className + ") i.next();" );
261                             }
262                             else
263                             {
264                                 sc.add( assoc.getTo() + " v = (" + assoc.getTo() + ") i.next();" );
265                             }
266 
267                             if ( isClassInModel( assoc.getTo(), objectModel ) )
268                             {
269                                 sc.add( "list.add( convert" + assoc.getTo() + "( v ) );" );
270                             }
271                             else
272                             {
273                                 sc.add( "list.add( v );" );
274                             }
275 
276                             sc.unindent();
277 
278                             sc.add( "}" );
279 
280                             sc.add( "value.set" + name + "( list );" );
281 
282                             sc.unindent();
283 
284                             sc.add( "}" );
285                         }
286                         else
287                         {
288                             sc.add( "{" );
289 
290                             sc.indent();
291 
292                             // Map or Properties
293                             sc.add( assoc.getType() + " map = " + assoc.getDefaultValue() + ";" );
294 
295                             sc.add( "for ( java.util.Iterator i = " + parameterName + ".get" + name
296                                 + "().entrySet().iterator(); i.hasNext(); )" );
297 
298                             sc.add( "{" );
299 
300                             sc.indent();
301 
302                             sc.add( "java.util.Map.Entry entry = (java.util.Map.Entry) i.next();" );
303 
304                             if ( isClassInModel( assoc.getTo(), modelClass.getModel() ) )
305                             {
306                                 String className = getSourceClassName( assoc.getToClass(), fromVersion );
307                                 sc.add( className + " v = (" + className + ") entry.getValue();" );
308                             }
309                             else
310                             {
311                                 sc.add( assoc.getTo() + " v = (" + assoc.getTo() + ") entry.getValue();" );
312                             }
313 
314                             if ( isClassInModel( assoc.getTo(), objectModel ) )
315                             {
316                                 sc.add( "map.put( entry.getKey(), convert" + assoc.getTo() + "( v ) );" );
317                             }
318                             else
319                             {
320                                 sc.add( "map.put( entry.getKey(), v );" );
321                             }
322 
323                             sc.unindent();
324 
325                             sc.add( "}" );
326 
327                             sc.add( "value.set" + name + "( map );" );
328 
329                             sc.unindent();
330 
331                             sc.add( "}" );
332                         }
333                     }
334                     else
335                     {
336                         sc.add( "value.set" + name + "( convert" + assoc.getTo() + "( " + parameterName + ".get" + name
337                             + "() ) );" );
338                     }
339                 }
340                 else
341                 {
342                     sc.add( "// Convert field " + modelField.getName() );
343 
344                     JavaFieldMetadata javaFieldMetadata =
345                         (JavaFieldMetadata) modelField.getMetadata( JavaFieldMetadata.ID );
346                     String value = parameterName + "." + getPrefix( javaFieldMetadata ) + name + "()";
347                     sc.add( "value.set" + name + "( " + value + " );" );
348                 }
349             }
350 
351             sc.add( "" );
352 
353             sc.add( "return value;" );
354         }
355 
356         JSourceWriter interfaceWriter = null;
357         JSourceWriter classWriter = null;
358 
359         try
360         {
361             interfaceWriter = newJSourceWriter( packageName, conversionInterface.getName( true ) );
362             classWriter = newJSourceWriter( packageName, basicConverterClass.getName( true ) );
363 
364             conversionInterface.print( interfaceWriter );
365             basicConverterClass.print( classWriter );
366         }
367         finally
368         {
369             IOUtil.close( classWriter );
370             IOUtil.close( interfaceWriter );
371         }
372     }
373 
374     private void generateConverterTool( List<Version> allVersions )
375         throws ModelloException, IOException
376     {
377         Model objectModel = getModel();
378         String root = objectModel.getRoot( getGeneratedVersion() );
379 
380         ModelClass rootClass = objectModel.getClass( root, getGeneratedVersion() );
381 
382 
383         String basePackage = objectModel.getDefaultPackageName( false, null );
384         String packageName = basePackage + ".convert";
385 
386         String jDoc = "Converts between the available versions of the model.";
387 
388         JClass converterClass = new JClass( packageName + ".ConverterTool" );
389         initHeader( converterClass );
390         suppressAllWarnings( objectModel, converterClass );
391         converterClass.getJDocComment().setComment( jDoc );
392 
393         converterClass.addImport( "java.io.File" );
394         converterClass.addImport( "java.io.IOException" );
395 
396         converterClass.addImport( "javax.xml.stream.*" );
397 
398         for ( Version v : allVersions )
399         {
400             writeConvertMethod( converterClass, objectModel, basePackage, allVersions, v, rootClass );
401         }
402         writeConvertMethod( converterClass, objectModel, basePackage, allVersions, null, rootClass );
403 
404         JSourceWriter classWriter = null;
405         try
406         {
407             classWriter = newJSourceWriter( packageName, converterClass.getName( true ) );
408             converterClass.print( new JSourceWriter( classWriter ) );
409         }
410         finally
411         {
412             IOUtil.close( classWriter );
413         }
414     }
415 
416     private static void writeConvertMethod( JClass converterClass, Model objectModel, String basePackage,
417                                             List<Version> allVersions, Version v, ModelClass rootClass )
418     {
419         String modelName = objectModel.getName();
420         String rootClassName = rootClass.getName();
421 
422         String targetPackage = objectModel.getDefaultPackageName( v != null, v );
423         String targetClass = targetPackage + "." + rootClassName;
424 
425         String methodName = "convertFromFile";
426         if ( v != null )
427         {
428             methodName += "_" + v.toString( "v", "_" );
429         }
430         JMethod method = new JMethod( methodName, new JType( targetClass ), null );
431         method.addParameter( new JParameter( new JType( "File" ), "f" ) );
432         method.addException( new JClass( "IOException" ) );
433         method.addException( new JClass( "XMLStreamException" ) );
434         converterClass.addMethod( method );
435 
436         JSourceCode sc = method.getSourceCode();
437 
438         sc.add( basePackage + ".io.stax." + modelName + "StaxReaderDelegate reader = new " + basePackage + ".io.stax."
439             + modelName + "StaxReaderDelegate();" );
440 
441         sc.add( "Object value = reader.read( f );" );
442 
443         String prefix = "";
444         for ( Version sourceVersion : allVersions )
445         {
446             String sourcePackage = objectModel.getDefaultPackageName( true, sourceVersion );
447             String sourceClass = sourcePackage + "." + rootClassName;
448             sc.add( prefix + "if ( value instanceof " + sourceClass + " )" );
449             sc.add( "{" );
450             sc.indent();
451 
452             boolean foundFirst = false;
453             for ( Version targetVersion : allVersions )
454             {
455                 if ( !foundFirst )
456                 {
457                     if ( targetVersion.equals( sourceVersion ) )
458                     {
459                         foundFirst = true;
460                     }
461                     else
462                     {
463                         continue;
464                     }
465                 }
466 
467                 if ( targetVersion.equals( v ) )
468                 {
469                     break;
470                 }
471 
472                 // TODO: need to be able to specify converter class implementation
473                 String p = objectModel.getDefaultPackageName( true, targetVersion );
474                 String c = p + "." + rootClassName;
475                 sc.add( "value = new " + p + ".convert.BasicVersionConverter().convert" + rootClassName
476                     + "( (" + c + ") value );" );
477             }
478 
479             sc.unindent();
480             sc.add( "}" );
481 
482             prefix = "else ";
483 
484             if ( sourceVersion.equals( v ) )
485             {
486                 break;
487             }
488         }
489         sc.add( "else" );
490         sc.add( "{" );
491         sc.indent();
492 
493         sc.add( "throw new IllegalStateException( \"Can't find converter for class '\" + value.getClass() + \"'\" );" );
494 
495         sc.unindent();
496         sc.add( "}" );
497 
498         sc.add( "return (" + targetClass + ") value;" );
499     }
500 
501     private static String getSourceClassName( ModelClass modelClass, Version generatedVersion )
502     {
503         return modelClass.getPackageName( true, generatedVersion ) + "." + modelClass.getName();
504     }
505 }