View Javadoc

1   package org.codehaus.modello.plugin.dom4j;
2   
3   /*
4    * Copyright (c) 2006, Codehaus.
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.model.Model;
27  import org.codehaus.modello.model.ModelAssociation;
28  import org.codehaus.modello.model.ModelClass;
29  import org.codehaus.modello.model.ModelDefault;
30  import org.codehaus.modello.model.ModelField;
31  import org.codehaus.modello.plugin.java.javasource.JClass;
32  import org.codehaus.modello.plugin.java.javasource.JMethod;
33  import org.codehaus.modello.plugin.java.javasource.JParameter;
34  import org.codehaus.modello.plugin.java.javasource.JSourceCode;
35  import org.codehaus.modello.plugin.java.javasource.JSourceWriter;
36  import org.codehaus.modello.plugin.java.metadata.JavaFieldMetadata;
37  import org.codehaus.modello.plugin.model.ModelClassMetadata;
38  import org.codehaus.modello.plugins.xml.AbstractXmlJavaGenerator;
39  import org.codehaus.modello.plugins.xml.metadata.XmlAssociationMetadata;
40  import org.codehaus.modello.plugins.xml.metadata.XmlFieldMetadata;
41  import org.codehaus.modello.plugins.xml.metadata.XmlModelMetadata;
42  
43  import java.io.IOException;
44  import java.util.List;
45  import java.util.Properties;
46  
47  /**
48   * <p>
49   * Generate a writer that uses Dom4J.
50   * </p>
51   * TODO: chunks are lifted from xpp3, including the tests. Can we abstract it in some way?
52   *
53   * @author <a href="mailto:brett@codehaus.org">Brett Porter</a>
54   */
55  public class Dom4jWriterGenerator
56      extends AbstractXmlJavaGenerator
57  {
58  
59      private boolean requiresDomSupport;
60  
61      public void generate( Model model, Properties parameters )
62          throws ModelloException
63      {
64          initialize( model, parameters );
65  
66          requiresDomSupport = false;
67  
68          try
69          {
70              generateDom4jWriter();
71          }
72          catch ( IOException ex )
73          {
74              throw new ModelloException( "Exception while generating Dom4j Writer.", ex );
75          }
76      }
77  
78      private void generateDom4jWriter()
79          throws ModelloException, IOException
80      {
81          Model objectModel = getModel();
82  
83          String packageName = objectModel.getDefaultPackageName( isPackageWithVersion(), getGeneratedVersion() )
84              + ".io.dom4j";
85  
86          String marshallerName = getFileName( "Dom4jWriter" );
87  
88          JSourceWriter sourceWriter = newJSourceWriter( packageName, marshallerName );
89  
90          JClass jClass = new JClass( packageName + '.' + marshallerName );
91          initHeader( jClass );
92          suppressAllWarnings( objectModel, jClass );
93  
94          jClass.addImport( "java.io.OutputStream" );
95          jClass.addImport( "java.io.Writer" );
96          jClass.addImport( "java.util.Arrays" );
97          jClass.addImport( "java.util.Iterator" );
98          jClass.addImport( "java.util.Locale" );
99          jClass.addImport( "java.text.DateFormat" );
100         jClass.addImport( "org.dom4j.Document" );
101         jClass.addImport( "org.dom4j.DocumentException" );
102         jClass.addImport( "org.dom4j.DocumentFactory" );
103         jClass.addImport( "org.dom4j.Element" );
104         jClass.addImport( "org.dom4j.io.OutputFormat" );
105         jClass.addImport( "org.dom4j.io.XMLWriter" );
106 
107         addModelImports( jClass, null );
108 
109         String root = objectModel.getRoot( getGeneratedVersion() );
110 
111         ModelClass rootClass = objectModel.getClass( root, getGeneratedVersion() );
112 
113         String rootElement = resolveTagName( rootClass );
114         String variableName = uncapitalise( root );
115 
116         // ----------------------------------------------------------------------
117         // Write the write( Reader, Model ) method which will do the unmarshalling.
118         // ----------------------------------------------------------------------
119 
120         JMethod marshall = new JMethod( "write" );
121 
122         marshall.addParameter( new JParameter( new JClass( "Writer" ), "writer" ) );
123         marshall.addParameter( new JParameter( new JClass( root ), variableName ) );
124 
125         marshall.addException( new JClass( "java.io.IOException" ) );
126 
127         JSourceCode sc = marshall.getSourceCode();
128 
129         sc.add( "Document document = new DocumentFactory().createDocument();" );
130 
131         sc.add( "write" + root + "( " + variableName + ", \"" + rootElement + "\", document );" );
132 
133         // TODO: pretty printing optional
134         sc.add( "OutputFormat format = OutputFormat.createPrettyPrint();" );
135         sc.add( "format.setLineSeparator( System.getProperty( \"line.separator\" ) );" );
136         sc.add( "XMLWriter serializer = new XMLWriter( writer, format );" );
137 
138         sc.add( "serializer.write( document );" );
139 
140         jClass.addMethod( marshall );
141 
142         // ----------------------------------------------------------------------
143         // Write the write( OutputStream, Model ) method which will do the unmarshalling.
144         // ----------------------------------------------------------------------
145 
146         marshall = new JMethod( "write" );
147 
148         marshall.addParameter( new JParameter( new JClass( "OutputStream" ), "stream" ) );
149         marshall.addParameter( new JParameter( new JClass( root ), variableName ) );
150 
151         marshall.addException( new JClass( "java.io.IOException" ) );
152 
153         sc = marshall.getSourceCode();
154 
155         sc.add( "Document document = new DocumentFactory().createDocument();" );
156 
157         sc.add( "write" + root + "( " + variableName + ", \"" + rootElement + "\", document );" );
158 
159         // TODO: pretty printing optional
160         sc.add( "OutputFormat format = OutputFormat.createPrettyPrint();" );
161         sc.add( "format.setLineSeparator( System.getProperty( \"line.separator\" ) );" );
162         sc.add( "format.setEncoding( " + variableName + ".getModelEncoding() );" );
163         sc.add( "XMLWriter serializer = new XMLWriter( stream, format );" );
164 
165         sc.add( "serializer.write( document );" );
166 
167         jClass.addMethod( marshall );
168 
169         writeAllClasses( objectModel, jClass );
170 
171         if ( requiresDomSupport )
172         {
173             jClass.addImport( "org.codehaus.plexus.util.xml.Xpp3Dom" );
174             writeDomHelpers( jClass );
175         }
176 
177         jClass.print( sourceWriter );
178 
179         sourceWriter.close();
180     }
181 
182     private void writeAllClasses( Model objectModel, JClass jClass )
183         throws ModelloException
184     {
185         for ( ModelClass clazz : getClasses( objectModel ) )
186         {
187             writeClass( clazz, jClass );
188         }
189     }
190 
191     private void writeClass( ModelClass modelClass, JClass jClass )
192         throws ModelloException
193     {
194         String className = modelClass.getName();
195 
196         String uncapClassName = uncapitalise( className );
197 
198         JMethod marshall = new JMethod( "write" + className );
199         marshall.getModifiers().makePrivate();
200 
201         marshall.addParameter( new JParameter( new JClass( className ), uncapClassName ) );
202         marshall.addParameter( new JParameter( new JClass( "String" ), "tagName" ) );
203 
204         ModelClassMetadata classMetadata = (ModelClassMetadata) modelClass.getMetadata( ModelClassMetadata.ID );
205 
206         marshall.addParameter( new JParameter( new JClass( "org.dom4j.Branch" ), "parentElement" ) );
207 
208         marshall.addException( new JClass( "java.io.IOException" ) );
209 
210         JSourceCode sc = marshall.getSourceCode();
211 
212         sc.add( "if ( " + uncapClassName + " != null )" );
213 
214         sc.add( "{" );
215         sc.indent();
216 
217         XmlModelMetadata xmlModelMetadata = (XmlModelMetadata) modelClass.getModel().getMetadata( XmlModelMetadata.ID );
218 
219         // add namespace information for root element only
220         if ( classMetadata.isRootElement() && ( xmlModelMetadata.getNamespace() != null ) )
221         {
222             String namespace = xmlModelMetadata.getNamespace( getGeneratedVersion() );
223             sc.add( "Element element = parentElement.addElement( tagName, \"" + namespace + "\" );" );
224 
225             if ( xmlModelMetadata.getSchemaLocation() != null )
226             {
227                 String url = xmlModelMetadata.getSchemaLocation( getGeneratedVersion() );
228 
229                 sc.add( "element.addAttribute( \"xmlns:xsi\", \"http://www.w3.org/2001/XMLSchema-instance\" );" );
230                 sc.add(
231                     "element.addAttribute( \"xsi:schemaLocation\", \"" + namespace + " " + url + "\" );" );
232             }
233         }
234         else
235         {
236             sc.add( "Element element = parentElement.addElement( tagName );" );
237         }
238 
239         ModelField contentField = null;
240 
241         String contentValue = null;
242 
243         List<ModelField> modelFields = getFieldsForXml( modelClass, getGeneratedVersion() );
244 
245         // XML attributes
246         for ( ModelField field : modelFields )
247         {
248             XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata( XmlFieldMetadata.ID );
249 
250             JavaFieldMetadata javaFieldMetadata = (JavaFieldMetadata) field.getMetadata( JavaFieldMetadata.ID );
251 
252             String fieldTagName = resolveTagName( field, xmlFieldMetadata );
253 
254             String type = field.getType();
255 
256             String value = uncapClassName + "." + getPrefix( javaFieldMetadata ) + capitalise( field.getName() ) + "()";
257 
258             if ( xmlFieldMetadata.isContent() )
259             {
260                 contentField = field;
261                 contentValue = value;
262                 continue;
263             }
264 
265             if ( xmlFieldMetadata.isAttribute() )
266             {
267                 sc.add( getValueChecker( type, value, field ) );
268 
269                 sc.add( "{" );
270                 sc.addIndented( "element.addAttribute( \"" + fieldTagName + "\", "
271                                 + getValue( field.getType(), value, xmlFieldMetadata ) + " );" );
272                 sc.add( "}" );
273             }
274         }
275 
276         if ( contentField != null )
277         {
278             XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) contentField.getMetadata( XmlFieldMetadata.ID );
279             sc.add( "element.setText( " + getValue( contentField.getType(), contentValue, xmlFieldMetadata ) + " );" );
280         }
281 
282         // XML tags
283         for ( ModelField field : modelFields )
284         {
285             XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata( XmlFieldMetadata.ID );
286 
287             if ( xmlFieldMetadata.isContent() )
288             {
289                 // skip field with type Content
290                 continue;
291             }
292 
293             if ( !xmlFieldMetadata.isAttribute() )
294             {
295                 processField( field, xmlFieldMetadata, uncapClassName, sc, modelClass, jClass );
296             }
297         }
298 
299         sc.unindent();
300         sc.add( "}" );
301 
302         jClass.addMethod( marshall );
303     }
304 
305     private void processField( ModelField field, XmlFieldMetadata xmlFieldMetadata, String uncapClassName,
306                                JSourceCode sc, ModelClass modelClass, JClass jClass )
307         throws ModelloException
308     {
309         JavaFieldMetadata javaFieldMetadata = (JavaFieldMetadata) field.getMetadata( JavaFieldMetadata.ID );
310 
311         String fieldTagName = resolveTagName( field, xmlFieldMetadata );
312 
313         String type = field.getType();
314 
315         String value = uncapClassName + "." + getPrefix( javaFieldMetadata ) + capitalise( field.getName() ) + "()";
316 
317         if ( field instanceof ModelAssociation )
318         {
319             ModelAssociation association = (ModelAssociation) field;
320 
321             String associationName = association.getName();
322 
323             if ( association.isOneMultiplicity() )
324             {
325                 sc.add( getValueChecker( type, value, association ) );
326 
327                 sc.add( "{" );
328                 sc.addIndented( "write" + association.getTo() + "( " + value + ", \"" + fieldTagName + "\", element );" );
329                 sc.add( "}" );
330             }
331             else
332             {
333                 //MANY_MULTIPLICITY
334 
335                 XmlAssociationMetadata xmlAssociationMetadata =
336                     (XmlAssociationMetadata) association.getAssociationMetadata( XmlAssociationMetadata.ID );
337 
338                 String valuesTagName = resolveTagName( fieldTagName, xmlAssociationMetadata );
339 
340                 type = association.getType();
341                 String toType = association.getTo();
342 
343                 boolean wrappedItems = xmlAssociationMetadata.isWrappedItems();
344 
345                 if ( ModelDefault.LIST.equals( type ) || ModelDefault.SET.equals( type ) )
346                 {
347                     sc.add( getValueChecker( type, value, association ) );
348 
349                     sc.add( "{" );
350                     sc.indent();
351 
352                     sc.add( "Element listElement = element;" );
353 
354                     if ( wrappedItems )
355                     {
356                         sc.add( "listElement = element.addElement( \"" + fieldTagName + "\" );" );
357                     }
358 
359                     sc.add( "for ( Iterator iter = " + value + ".iterator(); iter.hasNext(); )" );
360 
361                     sc.add( "{" );
362                     sc.indent();
363 
364                     if ( isClassInModel( association.getTo(), modelClass.getModel() ) )
365                     {
366                         sc.add( toType + " o = (" + toType + ") iter.next();" );
367 
368                         sc.add( "write" + toType + "( o, \"" + valuesTagName + "\", listElement );" );
369                     }
370                     else
371                     {
372                         sc.add( toType + " " + singular( uncapitalise( field.getName() ) ) + " = (" + toType
373                             + ") iter.next();" );
374 
375                         sc.add( "listElement.addElement( \"" + valuesTagName + "\" ).setText( "
376                             + singular( uncapitalise( field.getName() ) ) + " );" );
377                     }
378 
379                     sc.unindent();
380                     sc.add( "}" );
381 
382                     sc.unindent();
383                     sc.add( "}" );
384                 }
385                 else
386                 {
387                     //Map or Properties
388 
389                     sc.add( getValueChecker( type, value, field ) );
390 
391                     sc.add( "{" );
392                     sc.indent();
393 
394                     sc.add( "Element listElement = element;" );
395 
396                     if ( wrappedItems )
397                     {
398                         sc.add( "listElement = element.addElement( \"" + fieldTagName + "\" );" );
399                     }
400 
401                     sc.add( "for ( Iterator iter = " + value + ".keySet().iterator(); iter.hasNext(); )" );
402 
403                     sc.add( "{" );
404                     sc.indent();
405 
406                     sc.add( "String key = (String) iter.next();" );
407 
408                     sc.add( "String value = (String) " + value + ".get( key );" );
409 
410                     if ( xmlAssociationMetadata.isMapExplode() )
411                     {
412                         sc.add( "Element assocElement = listElement.addElement( \"" + singular( associationName )
413                             + "\" );" );
414                         sc.add( "assocElement.addElement( \"key\" ).setText( key );" );
415                         sc.add( "assocElement.addElement( \"value\" ).setText( value );" );
416                     }
417                     else
418                     {
419                         sc.add( "listElement.addElement( key ).setText( value );" );
420                     }
421 
422                     sc.unindent();
423                     sc.add( "}" );
424 
425                     sc.unindent();
426                     sc.add( "}" );
427                 }
428             }
429         }
430         else
431         {
432             sc.add( getValueChecker( type, value, field ) );
433 
434             sc.add( "{" );
435             sc.indent();
436 
437             if ( "DOM".equals( field.getType() ) )
438             {
439                 sc.add( "writeXpp3DomToElement( (Xpp3Dom) " + value + ", element );" );
440 
441                 requiresDomSupport = true;
442             }
443             else
444             {
445                 sc.add( "element.addElement( \"" + fieldTagName + "\" ).setText( "
446                     + getValue( field.getType(), value, xmlFieldMetadata ) + " );" );
447             }
448 
449             sc.unindent();
450             sc.add( "}" );
451         }
452     }
453 
454     private void writeDomHelpers( JClass jClass )
455     {
456         JMethod method = new JMethod( "writeXpp3DomToElement" );
457         method.getModifiers().makePrivate();
458 
459         method.addParameter( new JParameter( new JClass( "Xpp3Dom" ), "xpp3Dom" ) );
460         method.addParameter( new JParameter( new JClass( "Element" ), "parentElement" ) );
461 
462         JSourceCode sc = method.getSourceCode();
463 
464         sc.add( "Element element = parentElement.addElement( xpp3Dom.getName() );" );
465 
466         sc.add( "if ( xpp3Dom.getValue() != null )" );
467         sc.add( "{" );
468         sc.addIndented( "element.setText( xpp3Dom.getValue() );" );
469         sc.add( "}" );
470 
471         sc.add( "for ( Iterator i = Arrays.asList( xpp3Dom.getAttributeNames() ).iterator(); i.hasNext(); )" );
472         sc.add( "{" );
473         sc.indent();
474 
475         sc.add( "String name = (String) i.next();" );
476         sc.add( "element.addAttribute( name, xpp3Dom.getAttribute( name ) );" );
477 
478         sc.unindent();
479         sc.add( "}" );
480 
481         sc.add( "for ( Iterator i = Arrays.asList( xpp3Dom.getChildren() ).iterator(); i.hasNext(); )" );
482         sc.add( "{" );
483         sc.indent();
484 
485         sc.add( "Xpp3Dom child = (Xpp3Dom) i.next();" );
486         sc.add( "writeXpp3DomToElement( child, element );" );
487 
488         sc.unindent();
489         sc.add( "}" );
490 
491         jClass.addMethod( method );
492     }
493 }