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