View Javadoc
1   package org.codehaus.modello.plugin.xpp3;
2   
3   /*
4    * Copyright (c) 2004, 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.List;
29  import java.util.Properties;
30  
31  import org.codehaus.modello.ModelloException;
32  import org.codehaus.modello.ModelloParameterConstants;
33  import org.codehaus.modello.model.Model;
34  import org.codehaus.modello.model.ModelAssociation;
35  import org.codehaus.modello.model.ModelClass;
36  import org.codehaus.modello.model.ModelDefault;
37  import org.codehaus.modello.model.ModelField;
38  import org.codehaus.modello.plugin.java.javasource.JClass;
39  import org.codehaus.modello.plugin.java.javasource.JField;
40  import org.codehaus.modello.plugin.java.javasource.JMethod;
41  import org.codehaus.modello.plugin.java.javasource.JParameter;
42  import org.codehaus.modello.plugin.java.javasource.JSourceCode;
43  import org.codehaus.modello.plugin.java.javasource.JSourceWriter;
44  import org.codehaus.modello.plugin.java.javasource.JType;
45  import org.codehaus.modello.plugin.java.metadata.JavaFieldMetadata;
46  import org.codehaus.modello.plugin.model.ModelClassMetadata;
47  import org.codehaus.modello.plugins.xml.metadata.XmlAssociationMetadata;
48  import org.codehaus.modello.plugins.xml.metadata.XmlFieldMetadata;
49  import org.codehaus.modello.plugins.xml.metadata.XmlModelMetadata;
50  
51  /**
52   * @author <a href="mailto:jason@modello.org">Jason van Zyl </a>
53   * @author <a href="mailto:evenisse@codehaus.org">Emmanuel Venisse </a>
54   */
55  @Named("xpp3-writer")
56  public class Xpp3WriterGenerator extends AbstractXpp3Generator {
57      private String extendedClassnameSuffix;
58  
59      protected void prepareLocationTracking(JClass jClass) {
60          // NO OP
61      }
62  
63      protected void writeLocationTracking(JSourceCode sc, String name, String key) {
64          if (isLocationTracking()) {
65              sc.add("writeLocationTracking( " + name + ", " + key + ", serializer );");
66          }
67      }
68  
69      @Override
70      protected void initialize(Model model, Properties parameters) throws ModelloException {
71          super.initialize(model, parameters);
72  
73          extendedClassnameSuffix = "Ex";
74          if (isLocationTracking()) {
75              extendedClassnameSuffix = parameters.getProperty(ModelloParameterConstants.EXTENDED_CLASSNAME_SUFFIX);
76          }
77      }
78  
79      public void generate(Model model, Properties parameters) throws ModelloException {
80          initialize(model, parameters);
81  
82          try {
83              generateXpp3Writer();
84          } catch (IOException ex) {
85              throw new ModelloException("Exception while generating XPP3 Writer.", ex);
86          }
87      }
88  
89      private void generateXpp3Writer() throws ModelloException, IOException {
90          Model objectModel = getModel();
91  
92          String packageName =
93                  objectModel.getDefaultPackageName(isPackageWithVersion(), getGeneratedVersion()) + ".io.xpp3";
94  
95          String marshallerName = getFileName("Xpp3Writer" + (isLocationTracking() ? extendedClassnameSuffix : ""));
96  
97          JSourceWriter sourceWriter = newJSourceWriter(packageName, marshallerName);
98  
99          JClass jClass = new JClass(packageName + '.' + marshallerName);
100         initHeader(jClass);
101         suppressAllWarnings(objectModel, jClass);
102 
103         jClass.addImport("org.codehaus.plexus.util.xml.pull.XmlSerializer");
104         jClass.addImport("org.codehaus.plexus.util.xml.pull.MXSerializer");
105         jClass.addImport("java.io.OutputStream");
106         jClass.addImport("java.io.Writer");
107         jClass.addImport("java.util.Iterator");
108 
109         JField namespaceField = new JField(new JClass("String"), "NAMESPACE");
110         namespaceField.getModifiers().setFinal(true);
111         namespaceField.getModifiers().setStatic(true);
112         namespaceField.setInitString("null");
113         jClass.addField(namespaceField);
114 
115         JField commentField = new JField(new JClass("String"), "fileComment");
116         commentField.setInitString("null");
117         jClass.addField(commentField);
118 
119         // Add setComment method
120         JMethod setComment = new JMethod("setFileComment");
121 
122         setComment.addParameter(new JParameter(new JClass("String"), "fileComment"));
123         JSourceCode setCommentSourceCode = setComment.getSourceCode();
124         setCommentSourceCode.add("this.fileComment = fileComment;");
125         jClass.addMethod(setComment);
126 
127         addModelImports(jClass, null);
128 
129         String root = objectModel.getRoot(getGeneratedVersion());
130 
131         ModelClass rootClass = objectModel.getClass(root, getGeneratedVersion());
132 
133         String rootElement = resolveTagName(rootClass);
134 
135         // ----------------------------------------------------------------------
136         // Write the write( Writer, Model ) method which will do the unmarshalling.
137         // ----------------------------------------------------------------------
138 
139         JMethod marshall = new JMethod("write");
140 
141         String rootElementParameterName = uncapitalise(root);
142         marshall.addParameter(new JParameter(new JClass("Writer"), "writer"));
143         marshall.addParameter(new JParameter(new JClass(root), rootElementParameterName));
144 
145         marshall.addException(new JClass("java.io.IOException"));
146 
147         JSourceCode sc = marshall.getSourceCode();
148 
149         sc.add("XmlSerializer serializer = new MXSerializer();");
150 
151         sc.add(
152                 "serializer.setProperty( \"http://xmlpull.org/v1/doc/properties.html#serializer-indentation\", \"  \" );");
153 
154         sc.add(
155                 "serializer.setProperty( \"http://xmlpull.org/v1/doc/properties.html#serializer-line-separator\", \"\\n\" );");
156 
157         sc.add("serializer.setOutput( writer );");
158 
159         sc.add("serializer.startDocument( " + rootElementParameterName + ".getModelEncoding(), null );");
160 
161         sc.add("write" + root + "( " + rootElementParameterName + ", \"" + rootElement + "\", serializer );");
162 
163         sc.add("serializer.endDocument();");
164 
165         jClass.addMethod(marshall);
166 
167         // ----------------------------------------------------------------------
168         // Write the write( OutputStream, Model ) method which will do the unmarshalling.
169         // ----------------------------------------------------------------------
170 
171         marshall = new JMethod("write");
172 
173         marshall.addParameter(new JParameter(new JClass("OutputStream"), "stream"));
174         marshall.addParameter(new JParameter(new JClass(root), rootElementParameterName));
175 
176         marshall.addException(new JClass("java.io.IOException"));
177 
178         sc = marshall.getSourceCode();
179 
180         sc.add("XmlSerializer serializer = new MXSerializer();");
181 
182         sc.add(
183                 "serializer.setProperty( \"http://xmlpull.org/v1/doc/properties.html#serializer-indentation\", \"  \" );");
184 
185         sc.add(
186                 "serializer.setProperty( \"http://xmlpull.org/v1/doc/properties.html#serializer-line-separator\", \"\\n\" );");
187 
188         sc.add("serializer.setOutput( stream, " + rootElementParameterName + ".getModelEncoding() );");
189 
190         sc.add("serializer.startDocument( " + rootElementParameterName + ".getModelEncoding(), null );");
191 
192         sc.add("write" + root + "( " + rootElementParameterName + ", \"" + rootElement + "\", serializer );");
193 
194         sc.add("serializer.endDocument();");
195 
196         jClass.addMethod(marshall);
197 
198         writeAllClasses(objectModel, jClass);
199 
200         if (isLocationTracking()) {
201             prepareLocationTracking(jClass);
202         }
203 
204         if (requiresDomSupport) {
205             createWriteDomMethod(jClass);
206         }
207 
208         jClass.print(sourceWriter);
209 
210         sourceWriter.close();
211     }
212 
213     private void writeAllClasses(Model objectModel, JClass jClass) throws ModelloException {
214         for (ModelClass clazz : getClasses(objectModel)) {
215             writeClass(clazz, jClass);
216         }
217     }
218 
219     private void writeClass(ModelClass modelClass, JClass jClass) throws ModelloException {
220         String className = modelClass.getName();
221 
222         String uncapClassName = uncapitalise(className);
223 
224         JMethod marshall = new JMethod("write" + className);
225 
226         marshall.addParameter(new JParameter(new JClass(className), uncapClassName));
227         marshall.addParameter(new JParameter(new JClass("String"), "tagName"));
228         marshall.addParameter(new JParameter(new JClass("XmlSerializer"), "serializer"));
229 
230         marshall.addException(new JClass("java.io.IOException"));
231 
232         marshall.getModifiers().makePrivate();
233 
234         JSourceCode sc = marshall.getSourceCode();
235 
236         ModelClassMetadata classMetadata = (ModelClassMetadata) modelClass.getMetadata(ModelClassMetadata.ID);
237 
238         String namespace = null;
239         XmlModelMetadata xmlModelMetadata =
240                 (XmlModelMetadata) modelClass.getModel().getMetadata(XmlModelMetadata.ID);
241 
242         // add namespace information for root element only
243         if (classMetadata.isRootElement() && (xmlModelMetadata.getNamespace() != null)) {
244             sc.add("if ( this.fileComment != null )");
245             sc.add("{");
246             sc.add("serializer.comment(this.fileComment);");
247             sc.add("}");
248 
249             namespace = xmlModelMetadata.getNamespace(getGeneratedVersion());
250             sc.add("serializer.setPrefix( \"\", \"" + namespace + "\" );");
251         }
252 
253         if ((namespace != null) && (xmlModelMetadata.getSchemaLocation() != null)) {
254             String url = xmlModelMetadata.getSchemaLocation(getGeneratedVersion());
255 
256             sc.add("serializer.setPrefix( \"xsi\", \"http://www.w3.org/2001/XMLSchema-instance\" );");
257 
258             sc.add("serializer.startTag( NAMESPACE, tagName );");
259 
260             sc.add("serializer.attribute( \"\", \"xsi:schemaLocation\", \"" + namespace + " " + url + "\" );");
261         } else {
262             sc.add("serializer.startTag( NAMESPACE, tagName );");
263         }
264 
265         ModelField contentField = null;
266 
267         String contentValue = null;
268 
269         List<ModelField> modelFields = getFieldsForXml(modelClass, getGeneratedVersion());
270 
271         // XML attributes
272         for (ModelField field : modelFields) {
273             XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata(XmlFieldMetadata.ID);
274 
275             JavaFieldMetadata javaFieldMetadata = (JavaFieldMetadata) field.getMetadata(JavaFieldMetadata.ID);
276 
277             String fieldTagName = resolveTagName(field, xmlFieldMetadata);
278 
279             String type = field.getType();
280 
281             String value = uncapClassName + "." + getPrefix(javaFieldMetadata) + capitalise(field.getName()) + "()";
282 
283             if (xmlFieldMetadata.isContent()) {
284                 contentField = field;
285                 contentValue = value;
286                 continue;
287             }
288 
289             if (xmlFieldMetadata.isAttribute()) {
290                 sc.add(getValueChecker(type, value, field));
291 
292                 sc.add("{");
293                 sc.addIndented("serializer.attribute( NAMESPACE, \"" + fieldTagName + "\", "
294                         + getValue(field.getType(), value, xmlFieldMetadata) + " );");
295                 sc.add("}");
296             }
297         }
298 
299         if (contentField != null) {
300             XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) contentField.getMetadata(XmlFieldMetadata.ID);
301             sc.add("serializer.text( " + getValue(contentField.getType(), contentValue, xmlFieldMetadata) + " );");
302         }
303 
304         // XML tags
305         for (ModelField field : modelFields) {
306             XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata(XmlFieldMetadata.ID);
307 
308             if (xmlFieldMetadata.isContent()) {
309                 // skip field with type Content
310                 continue;
311             }
312 
313             JavaFieldMetadata javaFieldMetadata = (JavaFieldMetadata) field.getMetadata(JavaFieldMetadata.ID);
314 
315             String fieldTagName = resolveTagName(field, xmlFieldMetadata);
316 
317             String type = field.getType();
318 
319             String value = uncapClassName + "." + getPrefix(javaFieldMetadata) + capitalise(field.getName()) + "()";
320 
321             if (xmlFieldMetadata.isAttribute()) {
322                 continue;
323             }
324 
325             if (field instanceof ModelAssociation) {
326                 ModelAssociation association = (ModelAssociation) field;
327 
328                 String associationName = association.getName();
329 
330                 if (association.isOneMultiplicity()) {
331                     sc.add(getValueChecker(type, value, association));
332 
333                     sc.add("{");
334                     sc.addIndented("write" + association.getTo() + "( (" + association.getTo() + ") " + value + ", \""
335                             + fieldTagName + "\", serializer );");
336                     sc.add("}");
337                 } else {
338                     // MANY_MULTIPLICITY
339 
340                     XmlAssociationMetadata xmlAssociationMetadata =
341                             (XmlAssociationMetadata) association.getAssociationMetadata(XmlAssociationMetadata.ID);
342 
343                     String valuesTagName = resolveTagName(fieldTagName, xmlAssociationMetadata);
344 
345                     type = association.getType();
346                     String toType = association.getTo();
347 
348                     boolean wrappedItems = xmlAssociationMetadata.isWrappedItems();
349 
350                     if (ModelDefault.LIST.equals(type) || ModelDefault.SET.equals(type)) {
351                         boolean isList = ModelDefault.LIST.equals(type);
352 
353                         sc.add(getValueChecker(type, value, association));
354 
355                         sc.add("{");
356                         sc.indent();
357 
358                         if (wrappedItems) {
359                             sc.add("serializer.startTag( NAMESPACE, " + "\"" + fieldTagName + "\" );");
360                         }
361 
362                         if (isLocationTracking() && !isClassInModel(association.getTo(), modelClass.getModel())) {
363                             sc.add(locationTracker.getName() + " location = " + uncapClassName + ".getLocation( \""
364                                     + fieldTagName + "\" );");
365                             if (isList) {
366                                 sc.add("int n = 0;");
367                             }
368                         }
369 
370                         sc.add("for ( Iterator iter = " + value + ".iterator(); iter.hasNext(); )");
371 
372                         sc.add("{");
373                         sc.indent();
374 
375                         if (isClassInModel(association.getTo(), modelClass.getModel())) {
376                             sc.add(toType + " o = (" + toType + ") iter.next();");
377 
378                             sc.add("write" + toType + "( o, \"" + valuesTagName + "\", serializer );");
379                         } else {
380                             String variable = singular(uncapitalise(field.getName()));
381 
382                             sc.add(toType + " " + variable + " = (" + toType + ") iter.next();");
383 
384                             sc.add("serializer.startTag( NAMESPACE, \"" + valuesTagName + "\" ).text( " + variable
385                                     + " ).endTag( NAMESPACE, \"" + valuesTagName + "\" );");
386 
387                             writeLocationTracking(sc, "location", isList ? "Integer.valueOf( n++ )" : variable);
388                         }
389 
390                         sc.unindent();
391                         sc.add("}");
392 
393                         if (wrappedItems) {
394                             sc.add("serializer.endTag( NAMESPACE, \"" + fieldTagName + "\" );");
395                         }
396 
397                         sc.unindent();
398                         sc.add("}");
399                     } else {
400                         // Map or Properties
401 
402                         sc.add(getValueChecker(type, value, field));
403 
404                         sc.add("{");
405                         sc.indent();
406 
407                         if (wrappedItems) {
408                             sc.add("serializer.startTag( NAMESPACE, \"" + fieldTagName + "\" );");
409                         }
410 
411                         if (isLocationTracking()) {
412                             sc.add(locationTracker.getName() + " location = " + uncapClassName + ".getLocation( \""
413                                     + fieldTagName + "\" );");
414                         }
415 
416                         sc.add("for ( Iterator iter = " + value + ".keySet().iterator(); iter.hasNext(); )");
417 
418                         sc.add("{");
419                         sc.indent();
420 
421                         sc.add("String key = (String) iter.next();");
422 
423                         sc.add("String value = (String) " + value + ".get( key );");
424 
425                         if (xmlAssociationMetadata.isMapExplode()) {
426                             sc.add("serializer.startTag( NAMESPACE, \"" + singular(associationName) + "\" );");
427                             sc.add(
428                                     "serializer.startTag( NAMESPACE, \"key\" ).text( key ).endTag( NAMESPACE, \"key\" );");
429                             sc.add(
430                                     "serializer.startTag( NAMESPACE, \"value\" ).text( value ).endTag( NAMESPACE, \"value\" );");
431                             sc.add("serializer.endTag( NAMESPACE, \"" + singular(associationName) + "\" );");
432                         } else {
433                             sc.add("serializer.startTag( NAMESPACE, key ).text( value ).endTag( NAMESPACE, key );");
434                         }
435 
436                         writeLocationTracking(sc, "location", "key");
437 
438                         sc.unindent();
439                         sc.add("}");
440 
441                         if (wrappedItems) {
442                             sc.add("serializer.endTag( NAMESPACE, \"" + fieldTagName + "\" );");
443                         }
444 
445                         sc.unindent();
446                         sc.add("}");
447                     }
448                 }
449             } else {
450                 sc.add(getValueChecker(type, value, field));
451 
452                 sc.add("{");
453                 if ("DOM".equals(field.getType())) {
454                     if (domAsXpp3) {
455                         jClass.addImport("org.codehaus.plexus.util.xml.Xpp3Dom");
456 
457                         if (isLocationTracking()) {
458                             sc.addIndented("writeXpp3DomToSerializer( (Xpp3Dom) " + value + ", serializer );");
459                         } else {
460                             sc.addIndented("((Xpp3Dom) " + value + ").writeToSerializer( NAMESPACE, serializer );");
461                         }
462                     } else {
463                         sc.addIndented("writeDom( (org.w3c.dom.Element) " + value + ", serializer );");
464                     }
465 
466                     requiresDomSupport = true;
467                 } else {
468                     sc.addIndented("serializer.startTag( NAMESPACE, " + "\"" + fieldTagName + "\" ).text( "
469                             + getValue(field.getType(), value, xmlFieldMetadata) + " ).endTag( NAMESPACE, " + "\""
470                             + fieldTagName + "\" );");
471                     sc.indent();
472                     writeLocationTracking(sc, uncapClassName, '"' + fieldTagName + '"');
473                     sc.unindent();
474                 }
475                 sc.add("}");
476             }
477         }
478 
479         sc.add("serializer.endTag( NAMESPACE, tagName );");
480 
481         jClass.addMethod(marshall);
482     }
483 
484     private void createWriteDomMethod(JClass jClass) {
485         if (domAsXpp3) {
486             return;
487         }
488         String type = "org.w3c.dom.Element";
489         JMethod method = new JMethod("writeDom");
490         method.getModifiers().makePrivate();
491 
492         method.addParameter(new JParameter(new JType(type), "dom"));
493         method.addParameter(new JParameter(new JClass("XmlSerializer"), "serializer"));
494 
495         method.addException(new JClass("java.io.IOException"));
496 
497         JSourceCode sc = method.getSourceCode();
498 
499         // start element
500         sc.add("serializer.startTag( NAMESPACE, dom.getTagName() );");
501 
502         // attributes
503         sc.add("org.w3c.dom.NamedNodeMap attributes = dom.getAttributes();");
504         sc.add("for ( int i = 0; i < attributes.getLength(); i++ )");
505         sc.add("{");
506 
507         sc.indent();
508         sc.add("org.w3c.dom.Node attribute = attributes.item( i );");
509         sc.add("serializer.attribute( NAMESPACE, attribute.getNodeName(), attribute.getNodeValue() );");
510         sc.unindent();
511 
512         sc.add("}");
513 
514         // child nodes & text
515         sc.add("org.w3c.dom.NodeList children = dom.getChildNodes();");
516         sc.add("for ( int i = 0; i < children.getLength(); i++ )");
517         sc.add("{");
518         sc.indent();
519         sc.add("org.w3c.dom.Node node = children.item( i );");
520         sc.add("if ( node instanceof org.w3c.dom.Element)");
521         sc.add("{");
522         sc.addIndented("writeDom( (org.w3c.dom.Element) children.item( i ), serializer );");
523         sc.add("}");
524         sc.add("else");
525         sc.add("{");
526         sc.addIndented("serializer.text( node.getTextContent() );");
527         sc.add("}");
528         sc.unindent();
529         sc.add("}");
530 
531         sc.add("serializer.endTag( NAMESPACE, dom.getTagName() );");
532 
533         jClass.addMethod(method);
534     }
535 }