root/trunk/src/java/org/jcoderz/phoenix/cmpgen2/CmpGenerator.java

Revision 1011, 18.0 kB (checked in by amandel, 4 years ago)

Aligned svn keyword settings.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 *
4 * Copyright 2006, The jCoderZ.org Project. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 *    * Redistributions of source code must retain the above copyright
11 *      notice, this list of conditions and the following disclaimer.
12 *    * Redistributions in binary form must reproduce the above
13 *      copyright notice, this list of conditions and the following
14 *      disclaimer in the documentation and/or other materials
15 *      provided with the distribution.
16 *    * Neither the name of the jCoderZ.org Project nor the names of
17 *      its contributors may be used to endorse or promote products
18 *      derived from this software without specific prior written
19 *      permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33 package org.jcoderz.phoenix.cmpgen2;
34
35import java.io.File;
36import java.io.FileInputStream;
37import java.io.FileWriter;
38import java.io.IOException;
39import java.io.StringReader;
40import java.io.StringWriter;
41import java.util.Iterator;
42import java.util.List;
43import java.util.Properties;
44import java.util.Set;
45import java.util.StringTokenizer;
46import java.util.TreeSet;
47import java.util.logging.Level;
48import java.util.logging.Logger;
49
50import org.apache.velocity.Template;
51import org.apache.velocity.VelocityContext;
52import org.apache.velocity.app.Velocity;
53import org.apache.velocity.runtime.RuntimeConstants;
54import org.jcoderz.commons.util.IoUtil;
55import org.jcoderz.phoenix.sqlparser.ColumnSpec;
56import org.jcoderz.phoenix.sqlparser.CreateTableStatement;
57import org.jcoderz.phoenix.sqlparser.SqlParser;
58import org.jcoderz.phoenix.sqlparser.SqlScanner;
59import org.jcoderz.phoenix.sqlparser.SqlStatement;
60
61
62/**
63 * This is a velocity based version of the CMP generator.
64 * @author Albrecht Messner
65 */
66public class CmpGenerator
67{
68   /** The full qualified name of this class. */
69   private static final String CLASSNAME = CmpGenerator.class.getName();
70   /** The logger to use. */
71   private static final Logger logger = Logger.getLogger(CLASSNAME);
72
73   private static final String ARRAY_MAGIC = "[]";
74   private static final int ARRAY_MAGIC_LENGTH = ARRAY_MAGIC.length();
75
76   private final String mOutputBaseDirectory;
77   private final String mPackagePrefix;
78   private final String mDataSource;
79   private File mOutputDirectory;
80   private final String mTemplateDir;
81   private final boolean mOverwrite;
82
83   /**
84    * Construct CMP bean generator.
85    *
86    * @param outputDir the output directory
87    * @param pkgPrefix the package prefix
88    * @param dataSource the jndi name of the data source
89    */
90   public CmpGenerator (String outputDir, String pkgPrefix, String dataSource,
91         String templateDir, boolean overwrite)
92   {
93      mOutputBaseDirectory = outputDir;
94      mPackagePrefix = pkgPrefix;
95      mDataSource = dataSource;
96      mTemplateDir = templateDir;
97      mOverwrite = overwrite;
98   }
99
100   private static void usage (String errorText)
101   {
102      if (errorText != null)
103      {
104         System.err.println(errorText);
105      }
106      System.err.println("Usage: CmpGenerator ");
107      System.err.println(
108            "   -i <inputfile>        Input SQL file");
109      System.err.println("   -d <outputdir>        Base output directory");
110      System.err.println(
111            "                         Package subdirs will be created here");
112      System.err.println(
113            "   -p <package>          Package of generated classes");
114      System.err.println("                         Defaults to 'org.jcoderz'");
115      System.err.println(
116            "   -ds <datasource>      JNDI lookup name of bean's datasource");
117      System.err.println(
118            "   -t <templatedir>      Directory where bean "
119            + "templates can be found");
120      System.err.println("   -o                    Overwrite existing files");
121      System.exit(1);
122   }
123
124   /**
125    * Main method.
126    * @param args command line args
127    */
128   public static void main (String[] args)
129         throws Exception
130   {
131      String outputDir = ".";
132      String pkgPrefix = "org.jcoderz";
133      String dataSource = "jdbc/default";
134      String inputFile = null;
135      String templateDir = null;
136
137      boolean overwrite = false;
138
139      String currentArg = null;
140
141      try
142      {
143         for (int i = 0; i < args.length; i++)
144         {
145            currentArg = args[i];
146            if (currentArg.equals("-d"))
147            {
148               outputDir = args[++i];
149            }
150            else if (currentArg.equals("-i"))
151            {
152               inputFile = args[++i];
153            }
154            else if (currentArg.equals("-p"))
155            {
156               pkgPrefix = args[++i];
157            }
158            else if (currentArg.equals("-ds"))
159            {
160               dataSource = args[++i];
161            }
162            else if (currentArg.equals("-t"))
163            {
164               templateDir = args[++i];
165            }
166            else if (currentArg.equals("-o"))
167            {
168               overwrite = true;
169            }
170            else
171            {
172               usage("Unknown command line option " + currentArg);
173            }
174         }
175      }
176      catch (ArrayIndexOutOfBoundsException e)
177      {
178         usage("Command line option '" + currentArg + "' requires an option");
179      }
180
181      if (inputFile == null || templateDir == null)
182      {
183         usage("Mandatory parameter missing");
184      }
185
186      final CmpGenerator generator =
187         new CmpGenerator(
188               outputDir, pkgPrefix, dataSource, templateDir, overwrite);
189      generator.generateCmpBeans(inputFile);
190   }
191
192   /**
193    * Entry method for CMP bean generator.
194    *
195    * @param inputFile the input file
196    */
197   public final void generateCmpBeans (String inputFile)
198         throws Exception
199   {
200      final String methodName = "generateCmpBeans(String)";
201      if (logger.isLoggable(Level.FINER))
202      {
203         logger.entering(CLASSNAME, methodName, new Object[] {inputFile});
204      }
205
206      try
207      {
208         setUp();
209
210         final FileInputStream fin = new FileInputStream(inputFile);
211         final SqlScanner sqlScanner = new SqlScanner(fin);
212         final SqlParser sqlParser = new SqlParser(sqlScanner);
213         final List sqlStatements = sqlParser.parse();
214         for (final Iterator it = sqlStatements.iterator(); it.hasNext();)
215         {
216            final SqlStatement statement = (SqlStatement) it.next();
217            if (statement instanceof CreateTableStatement)
218            {
219               // mBeanImportList.clear();
220               // mHelperImportList.clear();
221               generateCmpBean((CreateTableStatement) statement);
222            }
223            else
224            {
225               logger.info("Skipping statement " + statement);
226            }
227         }
228      }
229      catch (Exception x)
230      {
231         logger.log(Level.SEVERE, "Error during CMP generation", x);
232         throw x;
233      }
234
235      if (logger.isLoggable(Level.FINER))
236      {
237         logger.exiting(CLASSNAME, methodName);
238      }
239   }
240
241   public String sqlNameToJavaName (String sqlName)
242   {
243      final StringReader rdr = new StringReader(sqlName);
244      int c;
245      boolean capitalizeNext = true;
246      final StringBuffer result = new StringBuffer();
247      try
248      {
249         while ((c = rdr.read()) != -1)
250         {
251            final char chr = (char) c;
252            switch (chr)
253            {
254               case '_':
255                  capitalizeNext = true;
256                  break;
257               default:
258                  if (capitalizeNext)
259                  {
260                     result.append(Character.toUpperCase(chr));
261                     capitalizeNext = false;
262                  }
263                  else
264                  {
265                     result.append(chr);
266                  }
267                  break;
268            }
269         }
270      }
271      catch (IOException e)
272      {
273         throw new RuntimeException("Huh???", e);
274      }
275      return result.toString();
276   }
277
278   /**
279    * This method capitalizes the first character of the given string and,
280    * assuming that the argument is usually a java type, also takes care
281    * of arrays by replacing "[]" with "Array".
282    * @param s input to be modified.
283    * @return adapted String.
284    */
285   public String capitalize (String s)
286   {
287      final String str;
288      if (s.endsWith(ARRAY_MAGIC))
289      {
290         str = s.substring(0, s.length() - ARRAY_MAGIC_LENGTH) + "Array";
291      }
292      else
293      {
294         str = s;
295      }
296      final char c = str.charAt(0);
297      final String result;
298      if (Character.isUpperCase(c))
299      {
300          result = str;
301      }
302      else
303      {
304         result = String.valueOf(Character.toUpperCase(c)) + str.substring(1);
305      }
306      return result;
307   }
308
309   public String getUnqualifiedJavaType (ColumnSpec column)
310         throws CmpGeneratorException
311   {
312      final String result = TypeMapping.getJavaType(column, false);
313      if (result == null)
314      {
315         throw new RuntimeException("TypeMapping returned null for " + column);
316      }
317      return result;
318   }
319
320   public String getQualifiedJavaType (ColumnSpec column)
321         throws CmpGeneratorException
322   {
323      return TypeMapping.getJavaType(column, true);
324   }
325
326   public String unqualifyType (String type)
327   {
328      return TypeMapping.unqualifyType(type);
329   }
330
331   private void generateCmpBean (CreateTableStatement stmt)
332         throws Exception
333   {
334      if (! hasPrimaryKey(stmt))
335      {
336         logger.info("*** No PK Field available for "
337               + stmt.getTableName() + ", skipping bean creation");
338         return;
339      }
340
341      String baseName = stmt.getBeanName() + "Entity";
342      if (baseName == null)
343      {
344         baseName = sqlNameToJavaName(stmt.getTableName());
345      }
346      logger.info("*** Start creation of bean " + baseName);
347
348      final String valueInterface = mergeTemplate(
349            getVelocityContext(stmt, baseName), "GenerateValue.vtl");
350      final File valueIfOutputFile = new File(mOutputDirectory,
351            baseName + "Value.java");
352      writeFile(valueIfOutputFile, valueInterface);
353
354      final String valueImpl = mergeTemplate(
355            getVelocityContext(stmt, baseName), "GenerateValueImpl.vtl");
356      final File valueImplOutputFile = new File(mOutputDirectory,
357            baseName + "ValueImpl.java");
358      writeFile(valueImplOutputFile, valueImpl);
359
360      final String bean = mergeTemplate(
361            getVelocityContext(stmt, baseName), "GenerateBean.vtl");
362      final File beanOutputFile = new File(mOutputDirectory,
363            baseName + "Bean.java");
364      writeFile(beanOutputFile, bean);
365
366      if (checkIfHelperRequired(stmt))
367      {
368         final String helper = mergeTemplate(
369               getVelocityContext(stmt, baseName), "GenerateHelper.vtl");
370         final File helperOutputFile = new File(mOutputDirectory,
371               baseName + "TypeConverter.java");
372         writeFile(helperOutputFile, helper);
373      }
374   }
375
376   private String mergeTemplate (VelocityContext ctx, String templateFile)
377         throws Exception
378   {
379      final StringWriter sw = new StringWriter();
380      final Template template = Velocity.getTemplate(templateFile);
381      template.merge(ctx, sw);
382      return sw.getBuffer().toString();
383   }
384
385   private void writeFile (File outputFile, final String data)
386         throws IOException
387   {
388      if (outputFile.exists() && !mOverwrite)
389      {
390         throw new RuntimeException(
391               "Output file " + outputFile + " already exists");
392      }
393      final FileWriter fout = new FileWriter(outputFile);
394      try
395      {
396          fout.write(data);
397      }
398      finally
399      {
400          IoUtil.close(fout);
401      }
402      logger.info("Wrote file " + outputFile + " successfully");
403   }
404
405   /**
406    * @param stmt
407    * @param baseName
408    * @return
409    * @throws Exception
410    */
411   private VelocityContext getVelocityContext (CreateTableStatement stmt,
412         String baseName)
413         throws Exception
414   {
415      final Properties velocityProps = new Properties();
416      velocityProps.put(RuntimeConstants.RESOURCE_LOADER, "file");
417      velocityProps.put("file.resource.loader.class",
418            "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
419      velocityProps.put(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, 
420              mTemplateDir);
421      velocityProps.put(RuntimeConstants.VM_LIBRARY, "macros.vm");
422      // velocityProps.put("runtime.log", "/tmp/velocity.log");
423      velocityProps.put(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
424              logger.isLoggable(Level.FINER)
425                  ? "org.apache.velocity.runtime.log.SimpleLog4JLogSystem"
426                  : "org.apache.velocity.runtime.log.NullLogSystem");
427      Velocity.init(velocityProps);
428      final VelocityContext ctx = new VelocityContext();
429      ctx.put("stmt", stmt);
430      ctx.put("baseName", baseName);
431      ctx.put("package", mPackagePrefix);
432      ctx.put("datasource", mDataSource);
433      ctx.put("cmpgen", this);
434      return ctx;
435   }
436
437   /**
438    *
439    * @throws CmpGeneratorException
440    */
441   private void setUp () throws CmpGeneratorException
442   {
443      File outputDir = new File(mOutputBaseDirectory);
444      if (!outputDir.exists())
445      {
446         throw new CmpGeneratorException("Output directory does not exist");
447      }
448      if (!outputDir.isDirectory())
449      {
450         throw new CmpGeneratorException(
451            mOutputBaseDirectory + " is not a directory");
452      }
453
454      final StringTokenizer tok = new StringTokenizer(mPackagePrefix, ".");
455
456      while (tok.hasMoreTokens())
457      {
458         outputDir = new File(outputDir, tok.nextToken());
459      }
460      logger.finer("Generating package subdirectories for " + outputDir);
461      outputDir.mkdirs();
462      mOutputDirectory = outputDir;
463   }
464
465
466   private boolean hasPrimaryKey (CreateTableStatement statement)
467   {
468      boolean hasPrimaryKey = false;
469      for (final Iterator it = statement.getColumns().iterator(); 
470          it.hasNext(); )
471      {
472         final ColumnSpec column = (ColumnSpec) it.next();
473         if (column.isPrimaryKey())
474         {
475            hasPrimaryKey = true;
476            break;
477         }
478      }
479      return hasPrimaryKey;
480   }
481
482   /**
483    * Checks if a helper class is required for this bean
484    *
485    * a helper class is required as soon as a custom java type is used
486    * with a load method and a store method.
487    *
488    * @param statement the parsed SQL create statement
489    * @return true if a helper class is required, false otherwise
490    */
491   public boolean checkIfHelperRequired (CreateTableStatement statement)
492   {
493      boolean needHelper = false;
494
495      for (final Iterator it = statement.getColumns().iterator(); 
496          it.hasNext(); )
497      {
498         final ColumnSpec column = (ColumnSpec) it.next();
499         if (column.getJavaType() != null)
500         {
501            if (column.getStoreMethod() != null)
502            {
503               needHelper = true;
504               break;
505            }
506         }
507      }
508      return needHelper;
509   }
510
511   public String getVersion ()
512   {
513      return "$Revision: 1.9 $";
514   }
515
516   public Set buildBeanImportList (CreateTableStatement statement)
517         throws CmpGeneratorException
518   {
519      final Set beanImportList = new TreeSet();
520      for (final Iterator it = statement.getColumns().iterator(); 
521          it.hasNext(); )
522      {
523         final ColumnSpec column = (ColumnSpec) it.next();
524         final String javaType = TypeMapping.getJavaType(column, true);
525         if (javaType != null
526               && !javaType.startsWith("java.lang")
527               && !TypeMapping.isPrimitiveType(javaType))
528         {
529            beanImportList.add(javaType);
530         }
531      }
532
533      beanImportList.add("org.jcoderz.commons.types.Period");
534      beanImportList.add("javax.ejb.CreateException");
535      return beanImportList;
536   }
537
538   public Set buildHelperImportList (CreateTableStatement statement)
539         throws CmpGeneratorException
540   {
541      final Set helperImportList = new TreeSet();
542      for (final Iterator it = statement.getColumns().iterator(); 
543          it.hasNext(); )
544      {
545         final ColumnSpec column = (ColumnSpec) it.next();
546         final String javaType = TypeMapping.getJavaType(column, true);
547         if (javaType != null
548               && !javaType.equals("java.sql.Date") // don't import date since
549                                                    // refs to date are always
550                                                    // fully qualified.
551               && !javaType.startsWith("java.lang")
552               && !TypeMapping.isPrimitiveType(javaType))
553         {
554            helperImportList.add(javaType);
555         }
556         final String complexType = column.getJavaType();
557         if (complexType != null
558               && !complexType.startsWith("java.lang")
559               && !TypeMapping.isPrimitiveType(complexType))
560         {
561            helperImportList.add(complexType);
562         }
563      }
564
565      // extra imports:
566      helperImportList.add("org.jcoderz.commons.InconsistentDatabaseException");
567      helperImportList.add("org.jcoderz.commons.types.Date");
568      helperImportList.add("org.jcoderz.commons.types.Period");
569      helperImportList.add("org.jcoderz.commons.util.Assert");
570
571      return helperImportList;
572   }
573
574   /** {@inheritDoc} */
575   public String toString ()
576   {
577      return "[Phoenix CmpGenerator " + getVersion() + "]";
578   }
579
580   public boolean isPrimitiveType (String type)
581   {
582      return TypeMapping.isPrimitiveType(type);
583   }
584}
Note: See TracBrowser for help on using the browser.