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