View Javadoc

1   /*******************************************************************************
2    * Copyright (c) 2015 LegSem.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the GNU Lesser Public License v2.1
5    * which accompanies this distribution, and is available at
6    * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
7    * 
8    * Contributors:
9    *     LegSem - initial API and implementation
10   ******************************************************************************/
11  package com.legstar.codegen;
12  
13  import java.io.BufferedWriter;
14  import java.io.File;
15  import java.io.FileOutputStream;
16  import java.io.IOException;
17  import java.io.OutputStreamWriter;
18  import java.io.StringWriter;
19  import java.io.Writer;
20  import java.net.InetAddress;
21  import java.net.URI;
22  import java.net.URISyntaxException;
23  import java.net.UnknownHostException;
24  import java.nio.charset.Charset;
25  import java.text.SimpleDateFormat;
26  import java.util.Calendar;
27  import java.util.Map;
28  import java.util.Random;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.velocity.VelocityContext;
33  import org.apache.velocity.app.Velocity;
34  import org.apache.velocity.exception.MethodInvocationException;
35  import org.apache.velocity.exception.ParseErrorException;
36  import org.apache.velocity.exception.ResourceNotFoundException;
37  
38  /**
39   * Various utility methods which are mostly useful for code generation using
40   * velocity templates.
41   */
42  public final class CodeGenUtil {
43  
44      /** Generated code has reference to generation date following this format. */
45      public static final String DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss";
46  
47      /** Used to generate random serial version IDs. */
48      private static Random mRandom = new Random();
49  
50      /** Suffix used for JAXB type variable names. */
51      public static final String JAXB_TYPE_SUFFIX = "Type";
52  
53      /** Get the platform specific line separator. */
54      public static final String CRLF = System.getProperty("line.separator");
55  
56      /** Logger. */
57      private static final Log LOG = LogFactory.getLog(CodeGenUtil.class);
58  
59      /**
60       * Defeats instantiation. Utility class.
61       */
62      private CodeGenUtil() {
63      }
64  
65      /**
66       * Check that a directory is valid.
67       * 
68       * @param dir the directory name to check
69       * @param create true if directory should be created when not found
70       * @param errorDirName name to refer to if an error occurs
71       */
72      public static void checkDirectory(final String dir, final boolean create,
73              final String errorDirName) {
74          try {
75              checkDirectory(dir, create);
76          } catch (IllegalArgumentException e) {
77              throw new IllegalArgumentException(errorDirName + ": "
78                      + e.getMessage());
79          }
80      }
81  
82      /**
83       * Check that a directory is valid.
84       * 
85       * @param fdir the directory name to check
86       * @param create true if directory should be created when not found
87       * @param errorDirName name to refer to if an error occurs
88       */
89      public static void checkDirectory(final File fdir, final boolean create,
90              final String errorDirName) {
91          try {
92              checkDirectory(fdir, create);
93          } catch (IllegalArgumentException e) {
94              throw new IllegalArgumentException(errorDirName + ": "
95                      + e.getMessage());
96          }
97      }
98  
99      /**
100      * Check that a directory is valid.
101      * 
102      * @param dir the directory name to check
103      * @param create true if directory should be created when not found
104      */
105     public static void checkDirectory(final String dir, final boolean create) {
106 
107         if (dir == null || dir.length() == 0) {
108             throw (new IllegalArgumentException(
109                     "No directory name was specified"));
110         }
111 
112         checkDirectory(new File(dir), create);
113     }
114 
115     /**
116      * Check that a directory is valid.
117      * 
118      * @param fdir the directory to check
119      * @param create true if directory should be created when not found
120      */
121     public static void checkDirectory(final File fdir, final boolean create) {
122 
123         if (fdir == null) {
124             throw (new IllegalArgumentException(
125                     "No directory name was specified"));
126         }
127 
128         if (!fdir.exists()) {
129             if (!create) {
130                 throw (new IllegalArgumentException(fdir.getName()
131                         + " does not exist"));
132             } else {
133                 if (!fdir.mkdirs()) {
134                     throw (new IllegalArgumentException(
135                             "Could not create directory " + fdir.getName()));
136                 } else {
137                     return;
138                 }
139             }
140         }
141         if (!fdir.isDirectory()) {
142             throw (new IllegalArgumentException(fdir.getName()
143                     + " is not a directory"));
144         }
145         if (!fdir.canWrite()) {
146             throw (new IllegalArgumentException("Directory " + fdir.getName()
147                     + " is not writable"));
148         }
149     }
150 
151     /**
152      * Retrieve a file. Given a directory name and a filename, this creates a
153      * File according to the following rules:
154      * <ul>
155      * <li>If the filename is absolute, the directory name is ignored</li>
156      * <li>If the directory is not null, it is assumed to exist</li>
157      * <li>If the directory is not null and the filename is not absolute, then
158      * filename is appended to directory</li>
159      * </ul>
160      * 
161      * @param dir parent directory
162      * @param filename absolute or relative file name
163      * @return a File
164      */
165     public static File getFile(final String dir, final String filename) {
166         File file = new File(filename);
167         if (file.isAbsolute()) {
168             return file;
169         }
170         if (dir == null || dir.length() == 0) {
171             return new File(filename);
172         }
173         return new File(dir, filename);
174     }
175 
176     /**
177      * Retrieve a file. Given a directory and a filename, this creates a File
178      * according to the following rules:
179      * <ul>
180      * <li>If the filename is absolute, the directory name is ignored</li>
181      * <li>Otherwise, filename is appended to directory</li>
182      * </ul>
183      * 
184      * @param fdir parent directory
185      * @param filename absolute or relative file name
186      * @return a File
187      */
188     public static File getFile(final File fdir, final String filename) {
189         File file = new File(filename);
190         if (file.isAbsolute()) {
191             return file;
192         }
193         return new File(fdir, filename);
194     }
195 
196     /**
197      * @deprecated use com.legstar.coxb.util.Utils#toClassName instead. Create a
198      *             valid Java class name from a given noun.
199      * 
200      * @param noun the characters to turn into a java class name
201      * @return the Java class name
202      */
203     public static String classNormalize(final String noun) {
204         String className = null;
205         if (noun != null && noun.length() > 0) {
206             className = noun.substring(0, 1).toUpperCase();
207             if (noun.length() > 1) {
208                 className += noun.substring(1, noun.length());
209             }
210         }
211         return className;
212     }
213 
214     /**
215      * Given a package name, this method returns the relative path location of
216      * the java files. A package like seg1.seg2.seg3 becomes /seg1/seg2/seg3/
217      * 
218      * @param packageName the package name
219      * @return the relative location of java files
220      */
221     public static String relativeLocation(final String packageName) {
222         if (packageName == null || packageName.length() == 0) {
223             return "";
224         }
225         String loc = packageName.replace('.', '/');
226         if (loc.charAt(0) != '/') {
227             loc = '/' + loc;
228         }
229         if (loc.charAt(loc.length() - 1) != '/') {
230             loc += '/';
231         }
232         return loc;
233     }
234 
235     /**
236      * Given a root directory name and a package name, returns the location for
237      * class files. Optionally the location can be physically created.
238      * 
239      * @param rootDirName the root directory name.
240      * @param packageName the package name or null if none
241      * @param create true if directory should be created when not found
242      * @return an existing location to store class files
243      */
244     public static String classFilesLocation(final String rootDirName,
245             final String packageName, final boolean create) {
246         if (rootDirName == null || rootDirName.length() == 0) {
247             throw (new IllegalArgumentException(
248                     "No root directory name was specified"));
249         }
250         String dir;
251         if (packageName != null && packageName.length() > 0) {
252             dir = rootDirName + '/' + CodeGenUtil.relativeLocation(packageName);
253         } else {
254             dir = rootDirName;
255         }
256         if (create) {
257             CodeGenUtil.checkDirectory(dir, true);
258         }
259         return dir;
260     }
261 
262     /**
263      * Concatenates the path derived from a package name to a root directory.
264      * 
265      * @param rootDir the root directory. Optionally the location can be
266      *            physically created.
267      * @param packageName the package name
268      * @param create true if directory should be created when not found
269      * @return the file derived from concatenating the root directory with the
270      *         package path.
271      */
272     public static File classFilesLocation(final File rootDir,
273             final String packageName, final boolean create) {
274         File dir = rootDir;
275         if (packageName != null && packageName.length() > 0) {
276             dir = new File(rootDir, CodeGenUtil.relativeLocation(packageName));
277         }
278         if (create) {
279             CodeGenUtil.checkDirectory(dir, true);
280         }
281         return dir;
282     }
283 
284     /**
285      * Setup Velocity so that it searches for templates in the classpath.
286      * <p/>
287      * In order to work around issue 158 that arises when velocity dynamically
288      * loaded classes are already in the context classloader parent, we
289      * temporarily switch to a new context classloader that sees our plugin
290      * classes and dependencies only.
291      * 
292      * @throws CodeGenVelocityException if setup fails
293      */
294     public static void initVelocity() throws CodeGenVelocityException {
295         ClassLoader loader = Thread.currentThread().getContextClassLoader();
296         try {
297             Velocity.addProperty("resource.loader", "classpath");
298             Velocity.addProperty("classpath.resource.loader.description",
299                     "Velocity Classpath Resource Loader");
300             Velocity.addProperty("classpath.resource.loader.class",
301                     "org.apache.velocity.runtime.resource.loader."
302                             + "ClasspathResourceLoader");
303             Velocity.addProperty("classpath.resource.loader.cache", true);
304             Thread.currentThread().setContextClassLoader(
305                     Velocity.class.getClassLoader());
306             Velocity.init();
307         } catch (Exception e) {
308             throw new CodeGenVelocityException(e);
309         } finally {
310             Thread.currentThread().setContextClassLoader(loader);
311         }
312     }
313 
314     /**
315      * A simple context to use by generation templates.
316      * 
317      * @param generatorName generator name
318      * @return a velocity context
319      */
320     public static VelocityContext getContext(final String generatorName) {
321         VelocityContext context = new VelocityContext();
322         context.put("formattedDate", now());
323         context.put("generatorName", generatorName);
324         return context;
325     }
326 
327     /**
328      * Apply a velocity template taken from a code generation make xml.
329      * 
330      * @param generatorName the generator name
331      * @param templateName the velocity template to apply
332      * @param modelName the model name
333      * @param model the model providing data for velocity templates
334      * @param parameters additional parameters to pass to template
335      * @param targetFile the file to generate using default charset
336      * @throws CodeGenMakeException if processing fails
337      */
338     public static void processTemplate(final String generatorName,
339             final String templateName, final String modelName,
340             final Object model, final Map < String, Object > parameters,
341             final File targetFile) throws CodeGenMakeException {
342 
343         processTemplate(generatorName, templateName, modelName, model,
344                 parameters, targetFile, null);
345     }
346 
347     /**
348      * Apply a velocity template taken from a code generation make xml.
349      * 
350      * @param generatorName the generator name
351      * @param templateName the velocity template to apply
352      * @param modelName the model name
353      * @param model the model providing data for velocity templates
354      * @param parameters additional parameters to pass to template
355      * @param targetFile the file to generate
356      * @param targetCharsetName the target character set. null is interpreted as
357      *            the default encoding
358      * @throws CodeGenMakeException if processing fails
359      */
360     public static void processTemplate(final String generatorName,
361             final String templateName, final String modelName,
362             final Object model, final Map < String, Object > parameters,
363             final File targetFile, final String targetCharsetName)
364             throws CodeGenMakeException {
365 
366         if (LOG.isDebugEnabled()) {
367             LOG.debug("Processing template");
368             LOG.debug("Template name       = " + templateName);
369             LOG.debug("Target file         = " + targetFile);
370             LOG.debug("Target charset name = " + targetCharsetName);
371             if (parameters != null) {
372                 for (String key : parameters.keySet()) {
373                     Object value = parameters.get(key);
374                     LOG.debug("Parameter " + key + " = " + value);
375                 }
376             }
377         }
378         VelocityContext context = CodeGenUtil.getContext(generatorName);
379         context.put(modelName, model);
380         context.put("serialVersionID", Long.toString(mRandom.nextLong()) + 'L');
381         if (parameters != null) {
382             for (String key : parameters.keySet()) {
383                 context.put(key, parameters.get(key));
384             }
385         }
386         StringWriter w = new StringWriter();
387 
388         try {
389             Velocity.mergeTemplate(templateName, "UTF-8", context, w);
390             Writer out = null;
391             try {
392                 FileOutputStream fos = new FileOutputStream(targetFile);
393                 OutputStreamWriter osw;
394                 if (targetCharsetName == null) {
395                     osw = new OutputStreamWriter(fos);
396                 } else {
397                     osw = new OutputStreamWriter(fos, targetCharsetName);
398                 }
399                 out = new BufferedWriter(osw);
400                 out.write(w.toString());
401             } catch (IOException e) {
402                 throw new CodeGenMakeException(e);
403             } finally {
404                 if (out != null) {
405                     out.close();
406                 }
407             }
408         } catch (ResourceNotFoundException e) {
409             throw new CodeGenMakeException(e);
410         } catch (ParseErrorException e) {
411             throw new CodeGenMakeException(e);
412         } catch (MethodInvocationException e) {
413             throw new CodeGenMakeException(e);
414         } catch (Exception e) {
415             throw new CodeGenMakeException(e);
416         }
417     }
418 
419     /**
420      * Formats todays date and time.
421      * 
422      * @return a formatted date
423      */
424     public static String now() {
425         Calendar cal = Calendar.getInstance();
426         SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_NOW);
427         return sdf.format(cal.getTime());
428     }
429 
430     /**
431      * Checks that a URI is valid and HTTP scheme.
432      * 
433      * @param httpUri the URI to check
434      * @throws CodeGenMakeException if URI has wrong syntax
435      */
436     public static void checkHttpURI(final String httpUri)
437             throws CodeGenMakeException {
438         try {
439             if (httpUri == null || httpUri.length() == 0) {
440                 throw new CodeGenMakeException("You must specify a valid URI");
441             }
442             URI uri = new URI(httpUri);
443             if (uri.getScheme() == null
444                     || uri.getScheme().compareToIgnoreCase("http") != 0) {
445                 throw new CodeGenMakeException("URI " + uri
446                         + " must have http scheme");
447             }
448         } catch (URISyntaxException e) {
449             throw new CodeGenMakeException(e);
450         }
451 
452     }
453 
454     /**
455      * Checks that a character set is valid.
456      * 
457      * @param charset the character set
458      * @see java.nio.charset.Charset
459      * @throws CodeGenMakeException if character set not supported
460      */
461     public static void checkCharset(final String charset)
462             throws CodeGenMakeException {
463         if (charset == null || charset.length() == 0) {
464             throw new CodeGenMakeException(
465                     "You must specify a valid character set");
466         }
467         if (!Charset.isSupported(charset)) {
468             throw new CodeGenMakeException("Character set " + charset
469                     + " is not supported");
470         }
471     }
472 
473     /**
474      * Field names are derived from property names by lower casing the first
475      * character.
476      * 
477      * @param propertyName the property name
478      * @return a valid field name or null if property name is empty
479      */
480     public static String fieldNameFromPropertyName(final String propertyName) {
481         String fieldName = null;
482         if (propertyName != null && propertyName.length() > 0) {
483             fieldName = propertyName.substring(0, 1).toLowerCase();
484             if (propertyName.length() > 1) {
485                 fieldName += propertyName.substring(1, propertyName.length());
486             }
487         }
488         return fieldName;
489     }
490 
491     /**
492      * Property names are derived from field names by upper casing the first
493      * character.
494      * 
495      * @param fieldName the field name
496      * @return a valid property name or null if field name is empty
497      */
498     public static String propertyNameFromFieldName(final String fieldName) {
499         String propertyName = null;
500         if (fieldName != null && fieldName.length() > 0) {
501             propertyName = fieldName.substring(0, 1).toUpperCase();
502             if (fieldName.length() > 1) {
503                 propertyName += fieldName.substring(1, fieldName.length());
504             }
505         }
506         return propertyName;
507     }
508 
509     /**
510      * Property names are derived from jaxb type names by stripping the type
511      * suffix (if any).
512      * 
513      * @param jaxbType the jaxb type name
514      * @return a valid property name or null if jaxb type name is empty
515      */
516     public static String propertyNameFromJaxbType(final String jaxbType) {
517         String propertyName = null;
518         if (jaxbType != null && jaxbType.length() > 0) {
519             propertyName = jaxbType;
520             if (propertyName.endsWith(JAXB_TYPE_SUFFIX)) {
521                 propertyName = propertyName.substring(0, propertyName.length()
522                         - JAXB_TYPE_SUFFIX.length());
523             }
524         }
525         return propertyName;
526     }
527 
528     /**
529      * Retrieve the IP address of the generation machine .
530      * 
531      * @return the local machine IP address
532      */
533     public static String getLocalIPAddress() {
534         try {
535             InetAddress addr = InetAddress.getLocalHost();
536             byte[] ipAddr = addr.getAddress();
537             String ipAddrStr = "";
538             for (int i = 0; i < ipAddr.length; i++) {
539                 if (i > 0) {
540                     ipAddrStr += ".";
541                 }
542                 ipAddrStr += ipAddr[i] & 0xFF;
543             }
544             return ipAddrStr;
545         } catch (UnknownHostException e) {
546             return "";
547         }
548 
549     }
550 }