View Javadoc
1   /**
2    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3    *
4    * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
5    * Portions Copyright 2013-2018 Philip Helger + contributors
6    *
7    * The contents of this file are subject to the terms of either the GNU
8    * General Public License Version 2 only ("GPL") or the Common Development
9    * and Distribution License("CDDL") (collectively, the "License").  You
10   * may not use this file except in compliance with the License.  You can
11   * obtain a copy of the License at
12   * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
13   * or packager/legal/LICENSE.txt.  See the License for the specific
14   * language governing permissions and limitations under the License.
15   *
16   * When distributing the software, include this License Header Notice in each
17   * file and include the License file at packager/legal/LICENSE.txt.
18   *
19   * GPL Classpath Exception:
20   * Oracle designates this particular file as subject to the "Classpath"
21   * exception as provided by Oracle in the GPL Version 2 section of the License
22   * file that accompanied this code.
23   *
24   * Modifications:
25   * If applicable, add the following below the License Header, with the fields
26   * enclosed by brackets [] replaced by your own identifying information:
27   * "Portions Copyright [year] [name of copyright owner]"
28   *
29   * Contributor(s):
30   * If you wish your version of this file to be governed by only the CDDL or
31   * only the GPL Version 2, indicate your decision by adding "[Contributor]
32   * elects to include this software in this distribution under the [CDDL or GPL
33   * Version 2] license."  If you don't indicate a single choice of license, a
34   * recipient has the option to distribute your version of this file under
35   * either the CDDL, the GPL Version 2 or to extend the choice of license to
36   * its licensees as provided above.  However, if you add GPL Version 2 code
37   * and therefore, elected the GPL Version 2 license, then the option applies
38   * only if the new code is made subject to such option by the copyright
39   * holder.
40   */
41  package com.helger.jcodemodel.writer;
42  
43  import java.io.BufferedOutputStream;
44  import java.io.File;
45  import java.io.IOException;
46  import java.io.OutputStream;
47  import java.nio.charset.Charset;
48  import java.nio.charset.StandardCharsets;
49  import java.util.Collection;
50  import java.util.List;
51  
52  import javax.annotation.Nonnull;
53  import javax.annotation.Nullable;
54  
55  import com.helger.jcodemodel.IJFormatter;
56  import com.helger.jcodemodel.JAnnotationUse;
57  import com.helger.jcodemodel.JCodeModel;
58  import com.helger.jcodemodel.JDefinedClass;
59  import com.helger.jcodemodel.JDocComment;
60  import com.helger.jcodemodel.JPackage;
61  import com.helger.jcodemodel.SourcePrintWriter;
62  import com.helger.jcodemodel.fmt.AbstractJResourceFile;
63  import com.helger.jcodemodel.util.JCValueEnforcer;
64  import com.helger.jcodemodel.writer.ProgressCodeWriter.IProgressTracker;
65  
66  /**
67   * Java Code Model Builder
68   *
69   * @author Philip Helger
70   * @since 3.2.0
71   */
72  public class JCMWriter
73  {
74    /** default is 4 spaces */
75    public static final String DEFAULT_INDENT_STRING = "    ";
76  
77    /** Cached default new line */
78    private static String s_sDefaultNewLine;
79  
80    @Nonnull
81    public static String getDefaultNewLine ()
82    {
83      String ret = s_sDefaultNewLine;
84      if (ret == null)
85      {
86        try
87        {
88          ret = s_sDefaultNewLine = System.getProperty ("line.separator");
89        }
90        catch (final Exception ex)
91        {
92          // Fall through
93        }
94  
95        // Fall back
96        if (ret == null || ret.length () == 0)
97          ret = s_sDefaultNewLine = "\n";
98      }
99      return ret;
100   }
101 
102   private final JCodeModel m_aCM;
103 
104   /** The charset used for building the output - null means system default */
105   private Charset m_aCharset = StandardCharsets.UTF_8;
106 
107   /** The newline string to be used. Defaults to system default */
108   private String m_sNewLine = getDefaultNewLine ();
109 
110   /**
111    * String to be used for each indentation. Defaults to four spaces.
112    */
113   private String m_sIndentString = DEFAULT_INDENT_STRING;
114 
115   public JCMWriter (@Nonnull final JCodeModel aCM)
116   {
117     m_aCM = aCM;
118   }
119 
120   /**
121    * @return The default charset used for building. <code>null</code> means
122    *         system default.
123    */
124   @Nullable
125   public Charset getCharset ()
126   {
127     return m_aCharset;
128   }
129 
130   /**
131    * Set the charset to be used for emitting files.
132    *
133    * @param aCharset
134    *        The charset to be used. May be <code>null</code> to indicate the use
135    *        of the system default.
136    * @return this for chaining
137    */
138   @Nonnull
139   public JCMWriter setCharset (@Nullable final Charset aCharset)
140   {
141     m_aCharset = aCharset;
142     return this;
143   }
144 
145   /**
146    * @return The newline string to be used. Defaults to system default
147    */
148   @Nonnull
149   public String getNewLine ()
150   {
151     return m_sNewLine;
152   }
153 
154   /**
155    * Set the new line string to be used for emitting source files.
156    *
157    * @param sNewLine
158    *        The new line string to be used. May neither be <code>null</code> nor
159    *        empty.
160    * @return this for chaining
161    */
162   @Nonnull
163   public JCMWriter setNewLine (@Nonnull final String sNewLine)
164   {
165     JCValueEnforcer.notEmpty (sNewLine, "NewLine");
166     m_sNewLine = sNewLine;
167     return this;
168   }
169 
170   @Nonnull
171   public String getIndentString ()
172   {
173     return m_sIndentString;
174   }
175 
176   @Nonnull
177   public JCMWriter setIndentString (@Nonnull final String sIndentString)
178   {
179     JCValueEnforcer.notNull (sIndentString, "IndentString");
180     m_sIndentString = sIndentString;
181     return this;
182   }
183 
184   /**
185    * Generates Java source code. A convenience method for
186    * <code>build(destDir,destDir,status)</code>.
187    *
188    * @param aDestDir
189    *        source files and resources are generated into this directory.
190    * @param aStatusPT
191    *        if non-<code>null</code>, progress indication will be sent to this
192    *        stream.
193    * @throws IOException
194    *         on IO error
195    */
196   public void build (@Nonnull final File aDestDir, @Nullable final IProgressTracker aStatusPT) throws IOException
197   {
198     build (aDestDir, aDestDir, aStatusPT);
199   }
200 
201   /**
202    * Generates Java source code. A convenience method that calls
203    * {@link #build(AbstractCodeWriter,AbstractCodeWriter)}.
204    *
205    * @param aSrcDir
206    *        Java source files are generated into this directory.
207    * @param aResourceDir
208    *        Other resource files are generated into this directory.
209    * @param aStatusPT
210    *        Progress tracker. May be <code>null</code>.
211    * @throws IOException
212    *         on IO error if non-null, progress indication will be sent to this
213    *         stream.
214    */
215   public void build (@Nonnull final File aSrcDir,
216                      @Nonnull final File aResourceDir,
217                      @Nullable final IProgressTracker aStatusPT) throws IOException
218   {
219     AbstractCodeWriter aSrcWriter = new FileCodeWriter (aSrcDir, m_aCharset, m_sNewLine);
220     AbstractCodeWriter aResWriter = new FileCodeWriter (aResourceDir, m_aCharset, m_sNewLine);
221     if (aStatusPT != null)
222     {
223       aSrcWriter = new ProgressCodeWriter (aSrcWriter, aStatusPT);
224       aResWriter = new ProgressCodeWriter (aResWriter, aStatusPT);
225     }
226     build (aSrcWriter, aResWriter);
227   }
228 
229   /**
230    * A convenience method for <code>build(destDir,System.out)</code>.
231    *
232    * @param aDestDir
233    *        source files and resources are generated into this directory.
234    * @throws IOException
235    *         on IO error
236    */
237   public void build (@Nonnull final File aDestDir) throws IOException
238   {
239     build (aDestDir, System.out::println);
240   }
241 
242   /**
243    * A convenience method for <code>build(srcDir,resourceDir,System.out)</code>.
244    *
245    * @param aSrcDir
246    *        Java source files are generated into this directory.
247    * @param aResourceDir
248    *        Other resource files are generated into this directory.
249    * @throws IOException
250    *         on IO error
251    */
252   public void build (@Nonnull final File aSrcDir, @Nonnull final File aResourceDir) throws IOException
253   {
254     build (aSrcDir, aResourceDir, System.out::println);
255   }
256 
257   /**
258    * A convenience method for <code>build(out,out)</code>.
259    *
260    * @param aWriter
261    *        Source code and resource writer
262    * @throws IOException
263    *         on IO error
264    */
265   public void build (@Nonnull final AbstractCodeWriter aWriter) throws IOException
266   {
267     build (aWriter, aWriter);
268   }
269 
270   /**
271    * Generates Java source code.
272    *
273    * @param aSourceWriter
274    *        Source code writer
275    * @param aResourceWriter
276    *        Resource writer
277    * @throws IOException
278    *         on IO error
279    */
280   public void build (@Nonnull final AbstractCodeWriter aSourceWriter,
281                      @Nonnull final AbstractCodeWriter aResourceWriter) throws IOException
282   {
283     try
284     {
285       // Copy to avoid concurrent modification exception
286       final List <JPackage> aPackages = m_aCM.getAllPackages ();
287       for (final JPackage aPackage : aPackages)
288         buildPackage (aSourceWriter, aResourceWriter, aPackage);
289     }
290     finally
291     {
292       aSourceWriter.close ();
293       aResourceWriter.close ();
294     }
295   }
296 
297   @Nonnull
298   private JFormatter _createJavaSourceFileWriter (@Nonnull final AbstractCodeWriter aSrcWriter,
299                                                   @Nonnull final JPackage aPackage,
300                                                   @Nonnull final String sClassName) throws IOException
301   {
302     final SourcePrintWriter aWriter = aSrcWriter.openSource (aPackage, sClassName + ".java");
303     final JFormatterJFormatter.html#JFormatter">JFormatter ret = new JFormatter (aWriter, m_sIndentString);
304     // Add all classes to not be imported (may be empty)
305     ret.addDontImportClasses (m_aCM.getAllDontImportClasses ());
306     return ret;
307   }
308 
309   public void buildPackage (@Nonnull final AbstractCodeWriter aSrcWriter,
310                             @Nonnull final AbstractCodeWriter aResWriter,
311                             @Nonnull final JPackage aPackage) throws IOException
312   {
313     // write classes
314     for (final JDefinedClass c : aPackage.classes ())
315     {
316       if (c.isHidden ())
317       {
318         // don't generate this file
319         continue;
320       }
321 
322       try (final JFormatter f = _createJavaSourceFileWriter (aSrcWriter, aPackage, c.name ()))
323       {
324         f.writeClassFull (c);
325       }
326     }
327 
328     // write package annotations
329     final Collection <JAnnotationUse> aAnnotations = aPackage.annotations ();
330     final JDocComment aJavaDoc = aPackage.javadoc ();
331     if (!aAnnotations.isEmpty () || !aJavaDoc.isEmpty ())
332     {
333       try (final IJFormatter f = _createJavaSourceFileWriter (aSrcWriter, aPackage, "package-info"))
334       {
335         if (!aJavaDoc.isEmpty ())
336           f.generable (aJavaDoc);
337 
338         // TODO: think about importing
339         for (final JAnnotationUse a : aAnnotations)
340           f.generable (a).newline ();
341 
342         f.declaration (aPackage);
343       }
344     }
345 
346     // write resources
347     for (final AbstractJResourceFile rsrc : aPackage.getAllResourceFiles ())
348     {
349       final AbstractCodeWriter cw = rsrc.isResource () ? aResWriter : aSrcWriter;
350       try (final OutputStream os = new BufferedOutputStream (cw.openBinary (aPackage, rsrc.name ())))
351       {
352         rsrc.build (os);
353       }
354     }
355   }
356 }