View Javadoc
1   package org.codehaus.modello.plugin.jackson;
2   
3   /*
4    * Copyright (c) 2004-2013, 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.model.Model;
33  import org.codehaus.modello.model.ModelAssociation;
34  import org.codehaus.modello.model.ModelClass;
35  import org.codehaus.modello.model.ModelDefault;
36  import org.codehaus.modello.model.ModelField;
37  import org.codehaus.modello.plugin.java.javasource.JClass;
38  import org.codehaus.modello.plugin.java.javasource.JConstructor;
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.metadata.JavaFieldMetadata;
45  import org.codehaus.modello.plugins.xml.metadata.XmlAssociationMetadata;
46  import org.codehaus.modello.plugins.xml.metadata.XmlFieldMetadata;
47  
48  /**
49   * @author <a href="mailto:simonetripodi@apache.org">Simone Tripodi</a>
50   */
51  @Named("jackson-writer")
52  public class JacksonWriterGenerator extends AbstractJacksonGenerator {
53  
54      private boolean requiresDomSupport;
55  
56      public void generate(Model model, Properties parameters) throws ModelloException {
57          initialize(model, parameters);
58  
59          requiresDomSupport = true;
60  
61          try {
62              generateJacksonWriter();
63          } catch (IOException ex) {
64              throw new ModelloException("Exception while generating JSON Jackson Writer.", ex);
65          }
66      }
67  
68      private void generateJacksonWriter() throws ModelloException, IOException {
69          Model objectModel = getModel();
70  
71          String packageName =
72                  objectModel.getDefaultPackageName(isPackageWithVersion(), getGeneratedVersion()) + ".io.jackson";
73  
74          String marshallerName = getFileName("JacksonWriter");
75  
76          JSourceWriter sourceWriter = newJSourceWriter(packageName, marshallerName);
77  
78          JClass jClass = new JClass(packageName + '.' + marshallerName);
79          initHeader(jClass);
80  
81          jClass.addImport("com.fasterxml.jackson.core.JsonFactory");
82          jClass.addImport("com.fasterxml.jackson.core.JsonGenerator");
83          jClass.addImport("com.fasterxml.jackson.core.JsonGenerator.Feature");
84          jClass.addImport("java.io.IOException");
85          jClass.addImport("java.io.OutputStream");
86          jClass.addImport("java.io.OutputStreamWriter");
87          jClass.addImport("java.io.Writer");
88  
89          addModelImports(jClass, null);
90  
91          JField factoryField = new JField(new JClass("JsonFactory"), "factory");
92          factoryField.getModifiers().setFinal(true);
93          factoryField.setInitString("new JsonFactory()");
94          jClass.addField(factoryField);
95  
96          String root = objectModel.getRoot(getGeneratedVersion());
97  
98          JConstructor jacksonWriterConstructor = new JConstructor(jClass);
99          JSourceCode sc = jacksonWriterConstructor.getSourceCode();
100         sc.add("factory.enable( Feature.AUTO_CLOSE_JSON_CONTENT );");
101         sc.add("factory.enable( Feature.AUTO_CLOSE_TARGET );");
102         sc.add("factory.enable( Feature.ESCAPE_NON_ASCII );");
103         sc.add("factory.enable( Feature.FLUSH_PASSED_TO_STREAM );");
104         sc.add("factory.enable( Feature.QUOTE_FIELD_NAMES );");
105         sc.add("factory.enable( Feature.QUOTE_NON_NUMERIC_NUMBERS );");
106         sc.add("factory.disable( Feature.WRITE_NUMBERS_AS_STRINGS );");
107 
108         jClass.addConstructor(jacksonWriterConstructor);
109 
110         writeAllClasses(objectModel, jClass);
111 
112         // ----------------------------------------------------------------------
113         // DOM support
114         // ----------------------------------------------------------------------
115 
116         if (requiresDomSupport) {
117             jClass.addImport("com.fasterxml.jackson.databind.ObjectMapper");
118 
119             sc.add("factory.setCodec( new ObjectMapper() );");
120         }
121 
122         // ----------------------------------------------------------------------
123         // Write the write( Writer, Model ) method which will do the unmarshalling.
124         // ----------------------------------------------------------------------
125 
126         JMethod marshall = new JMethod("write");
127 
128         String rootElementParameterName = uncapitalise(root);
129         marshall.addParameter(new JParameter(new JClass("Writer"), "writer"));
130         marshall.addParameter(new JParameter(new JClass(root), rootElementParameterName));
131 
132         marshall.addException(new JClass("IOException"));
133 
134         sc = marshall.getSourceCode();
135 
136         sc.add("JsonGenerator generator = factory.createGenerator( writer );");
137 
138         sc.add("generator.useDefaultPrettyPrinter();");
139 
140         sc.add("write" + root + "( " + rootElementParameterName + ", generator );");
141 
142         sc.add("generator.close();");
143 
144         jClass.addMethod(marshall);
145 
146         // ----------------------------------------------------------------------
147         // Write the write( OutputStream, Model ) method which will do the unmarshalling.
148         // ----------------------------------------------------------------------
149 
150         marshall = new JMethod("write");
151 
152         marshall.addParameter(new JParameter(new JClass("OutputStream"), "stream"));
153         marshall.addParameter(new JParameter(new JClass(root), rootElementParameterName));
154 
155         marshall.addException(new JClass("IOException"));
156 
157         sc = marshall.getSourceCode();
158 
159         sc.add("write( new OutputStreamWriter( stream, "
160                 + rootElementParameterName
161                 + ".getModelEncoding() ), "
162                 + rootElementParameterName
163                 + " );");
164 
165         jClass.addMethod(marshall);
166 
167         jClass.print(sourceWriter);
168 
169         sourceWriter.close();
170     }
171 
172     private void writeAllClasses(Model objectModel, JClass jClass) throws ModelloException {
173         for (ModelClass clazz : getClasses(objectModel)) {
174             writeClass(clazz, jClass);
175         }
176     }
177 
178     private void writeClass(ModelClass modelClass, JClass jClass) throws ModelloException {
179         String className = modelClass.getName();
180 
181         String uncapClassName = uncapitalise(className);
182 
183         JMethod marshall = new JMethod("write" + className);
184 
185         marshall.addParameter(new JParameter(new JClass(className), uncapClassName));
186         marshall.addParameter(new JParameter(new JClass("JsonGenerator"), "generator"));
187 
188         marshall.addException(new JClass("IOException"));
189 
190         marshall.getModifiers().makePrivate();
191 
192         JSourceCode sc = marshall.getSourceCode();
193 
194         sc.add("generator.writeStartObject();");
195 
196         List<ModelField> modelFields = getFieldsForXml(modelClass, getGeneratedVersion());
197 
198         final boolean useJava5 = hasJavaSourceSupport(5);
199 
200         // XML tags
201         for (ModelField field : modelFields) {
202             XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata(XmlFieldMetadata.ID);
203 
204             JavaFieldMetadata javaFieldMetadata = (JavaFieldMetadata) field.getMetadata(JavaFieldMetadata.ID);
205 
206             String fieldTagName = resolveTagName(field, xmlFieldMetadata);
207 
208             String type = field.getType();
209 
210             String value = uncapClassName + "." + getPrefix(javaFieldMetadata) + capitalise(field.getName()) + "()";
211 
212             if (field instanceof ModelAssociation) {
213                 ModelAssociation association = (ModelAssociation) field;
214 
215                 if (association.isOneMultiplicity()) {
216                     sc.add(getValueChecker(type, value, association));
217 
218                     sc.add("{");
219                     sc.addIndented("generator.writeFieldName( \"" + fieldTagName + "\" );");
220                     sc.addIndented("write" + association.getTo() + "( (" + association.getTo() + ") " + value
221                             + ", generator );");
222                     sc.add("}");
223                 } else {
224                     // MANY_MULTIPLICITY
225 
226                     XmlAssociationMetadata xmlAssociationMetadata =
227                             (XmlAssociationMetadata) association.getAssociationMetadata(XmlAssociationMetadata.ID);
228 
229                     type = association.getType();
230                     String toType = association.getTo();
231 
232                     if (ModelDefault.LIST.equals(type) || ModelDefault.SET.equals(type)) {
233                         sc.add(getValueChecker(type, value, association));
234 
235                         sc.add("{");
236                         sc.indent();
237 
238                         sc.add("generator.writeArrayFieldStart( \"" + fieldTagName + "\" );");
239 
240                         if (useJava5) {
241                             sc.add("for ( " + toType + " o : " + value + " )");
242                         } else {
243                             sc.add("for ( java.util.Iterator it = " + value + ".iterator(); it.hasNext(); )");
244                         }
245 
246                         sc.add("{");
247                         sc.indent();
248 
249                         if (!useJava5) {
250                             sc.add(toType + " o = (" + toType + " ) it.next();");
251                         }
252 
253                         if (isClassInModel(association.getTo(), modelClass.getModel())) {
254                             sc.add("write" + toType + "( o, generator );");
255                         } else {
256                             sc.add("generator.writeObject( o ); ");
257                         }
258 
259                         sc.unindent();
260                         sc.add("}");
261 
262                         sc.add("generator.writeEndArray();");
263 
264                         sc.unindent();
265                         sc.add("}");
266                     } else {
267                         // Map or Properties
268 
269                         sc.add(getValueChecker(type, value, field));
270 
271                         sc.add("{");
272                         sc.indent();
273 
274                         if (xmlAssociationMetadata.isMapExplode()) {
275                             sc.add("generator.writeArrayFieldStart( \"" + fieldTagName + "\" );");
276                         } else {
277                             sc.add("generator.writeObjectFieldStart( \"" + fieldTagName + "\" );");
278                         }
279 
280                         StringBuilder entryTypeBuilder = new StringBuilder("java.util.Map.Entry");
281 
282                         if (useJava5) {
283                             entryTypeBuilder.append('<');
284 
285                             if (association.getType().equals(ModelDefault.PROPERTIES)) {
286                                 entryTypeBuilder.append("Object, Object");
287                             } else {
288                                 entryTypeBuilder.append("String, ").append(association.getTo());
289                             }
290 
291                             entryTypeBuilder.append('>');
292                         }
293 
294                         if (useJava5) {
295                             sc.add("for ( " + entryTypeBuilder + " entry : " + value + ".entrySet() )");
296                         } else {
297                             sc.add("for ( java.util.Iterator it = " + value
298                                     + ".entrySet().iterator(); it.hasNext(); )");
299                         }
300 
301                         sc.add("{");
302                         sc.indent();
303 
304                         if (!useJava5) {
305                             sc.add(entryTypeBuilder + " entry = (" + entryTypeBuilder + ") it.next();");
306                         }
307 
308                         sc.add("final String key = String.valueOf( entry.getKey() );");
309                         sc.add("final String value = String.valueOf( entry.getValue() );");
310 
311                         if (xmlAssociationMetadata.isMapExplode()) {
312                             sc.add("generator.writeStartObject();");
313                             sc.add("generator.writeStringField( \"key\", key );");
314                             sc.add("generator.writeStringField( \"value\", value );");
315                             sc.add("generator.writeEndObject();");
316                         } else {
317                             sc.add("generator.writeStringField( key, value );");
318                         }
319 
320                         sc.unindent();
321                         sc.add("}");
322 
323                         if (xmlAssociationMetadata.isMapExplode()) {
324                             sc.add("generator.writeEndArray();");
325                         } else {
326                             sc.add("generator.writeEndObject();");
327                         }
328 
329                         sc.unindent();
330                         sc.add("}");
331                     }
332                 }
333             } else {
334                 sc.add(getValueChecker(type, value, field));
335 
336                 sc.add("{");
337                 if ("DOM".equals(field.getType())) {
338                     if (domAsXpp3) {
339                         getLogger()
340                                 .warn("Xpp3Dom not supported for "
341                                         + modelClass.getName()
342                                         + "#"
343                                         + field.getName()
344                                         + ", it will be treated as a regular Java Object.");
345                     }
346 
347                     requiresDomSupport = true;
348 
349                     sc.addIndented("generator.writeObjectField( \"" + fieldTagName + "\", " + value + " );");
350                 } else {
351                     sc.addIndented("generator.writeObjectField( \"" + fieldTagName + "\", " + value + " );");
352                 }
353                 sc.add("}");
354             }
355         }
356 
357         sc.add("generator.writeEndObject();");
358 
359         jClass.addMethod(marshall);
360     }
361 }