/* * $Id$ * * Copyright 2006, The jCoderZ.org Project. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the jCoderZ.org Project nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jcoderz.phoenix.cmpgen2; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; import org.apache.velocity.runtime.RuntimeConstants; import org.jcoderz.commons.util.IoUtil; import org.jcoderz.phoenix.sqlparser.ColumnSpec; import org.jcoderz.phoenix.sqlparser.CreateTableStatement; import org.jcoderz.phoenix.sqlparser.SqlParser; import org.jcoderz.phoenix.sqlparser.SqlScanner; import org.jcoderz.phoenix.sqlparser.SqlStatement; /** * This is a velocity based version of the CMP generator. * @author Albrecht Messner */ public class CmpGenerator { /** The full qualified name of this class. */ private static final String CLASSNAME = CmpGenerator.class.getName(); /** The logger to use. */ private static final Logger logger = Logger.getLogger(CLASSNAME); private static final String ARRAY_MAGIC = "[]"; private static final int ARRAY_MAGIC_LENGTH = ARRAY_MAGIC.length(); private final String mOutputBaseDirectory; private final String mPackagePrefix; private final String mDataSource; private File mOutputDirectory; private final String mTemplateDir; private final boolean mOverwrite; /** * Construct CMP bean generator. * * @param outputDir the output directory * @param pkgPrefix the package prefix * @param dataSource the jndi name of the data source */ public CmpGenerator (String outputDir, String pkgPrefix, String dataSource, String templateDir, boolean overwrite) { mOutputBaseDirectory = outputDir; mPackagePrefix = pkgPrefix; mDataSource = dataSource; mTemplateDir = templateDir; mOverwrite = overwrite; } private static void usage (String errorText) { if (errorText != null) { System.err.println(errorText); } System.err.println("Usage: CmpGenerator "); System.err.println( " -i <inputfile> Input SQL file"); System.err.println(" -d <outputdir> Base output directory"); System.err.println( " Package subdirs will be created here"); System.err.println( " -p <package> Package of generated classes"); System.err.println(" Defaults to 'org.jcoderz'"); System.err.println( " -ds <datasource> JNDI lookup name of bean's datasource"); System.err.println( " -t <templatedir> Directory where bean " + "templates can be found"); System.err.println(" -o Overwrite existing files"); System.exit(1); } /** * Main method. * @param args command line args */ public static void main (String[] args) throws Exception { String outputDir = "."; String pkgPrefix = "org.jcoderz"; String dataSource = "jdbc/default"; String inputFile = null; String templateDir = null; boolean overwrite = false; String currentArg = null; try { for (int i = 0; i < args.length; i++) { currentArg = args[i]; if (currentArg.equals("-d")) { outputDir = args[++i]; } else if (currentArg.equals("-i")) { inputFile = args[++i]; } else if (currentArg.equals("-p")) { pkgPrefix = args[++i]; } else if (currentArg.equals("-ds")) { dataSource = args[++i]; } else if (currentArg.equals("-t")) { templateDir = args[++i]; } else if (currentArg.equals("-o")) { overwrite = true; } else { usage("Unknown command line option " + currentArg); } } } catch (ArrayIndexOutOfBoundsException e) { usage("Command line option '" + currentArg + "' requires an option"); } if (inputFile == null || templateDir == null) { usage("Mandatory parameter missing"); } final CmpGenerator generator = new CmpGenerator( outputDir, pkgPrefix, dataSource, templateDir, overwrite); generator.generateCmpBeans(inputFile); } /** * Entry method for CMP bean generator. * * @param inputFile the input file */ public final void generateCmpBeans (String inputFile) throws Exception { final String methodName = "generateCmpBeans(String)"; if (logger.isLoggable(Level.FINER)) { logger.entering(CLASSNAME, methodName, new Object[] {inputFile}); } try { setUp(); final FileInputStream fin = new FileInputStream(inputFile); final SqlScanner sqlScanner = new SqlScanner(fin); final SqlParser sqlParser = new SqlParser(sqlScanner); final List sqlStatements = sqlParser.parse(); for (final Iterator it = sqlStatements.iterator(); it.hasNext();) { final SqlStatement statement = (SqlStatement) it.next(); if (statement instanceof CreateTableStatement) { // mBeanImportList.clear(); // mHelperImportList.clear(); generateCmpBean((CreateTableStatement) statement); } else { logger.info("Skipping statement " + statement); } } } catch (Exception x) { logger.log(Level.SEVERE, "Error during CMP generation", x); throw x; } if (logger.isLoggable(Level.FINER)) { logger.exiting(CLASSNAME, methodName); } } public String sqlNameToJavaName (String sqlName) { final StringReader rdr = new StringReader(sqlName); int c; boolean capitalizeNext = true; final StringBuffer result = new StringBuffer(); try { while ((c = rdr.read()) != -1) { final char chr = (char) c; switch (chr) { case '_': capitalizeNext = true; break; default: if (capitalizeNext) { result.append(Character.toUpperCase(chr)); capitalizeNext = false; } else { result.append(chr); } break; } } } catch (IOException e) { throw new RuntimeException("Huh???", e); } return result.toString(); } /** * This method capitalizes the first character of the given string and, * assuming that the argument is usually a java type, also takes care * of arrays by replacing "[]" with "Array". * @param s input to be modified. * @return adapted String. */ public String capitalize (String s) { final String str; if (s.endsWith(ARRAY_MAGIC)) { str = s.substring(0, s.length() - ARRAY_MAGIC_LENGTH) + "Array"; } else { str = s; } final char c = str.charAt(0); final String result; if (Character.isUpperCase(c)) { result = str; } else { result = String.valueOf(Character.toUpperCase(c)) + str.substring(1); } return result; } public String getUnqualifiedJavaType (ColumnSpec column) throws CmpGeneratorException { final String result = TypeMapping.getJavaType(column, false); if (result == null) { throw new RuntimeException("TypeMapping returned null for " + column); } return result; } public String getQualifiedJavaType (ColumnSpec column) throws CmpGeneratorException { return TypeMapping.getJavaType(column, true); } public String unqualifyType (String type) { return TypeMapping.unqualifyType(type); } private void generateCmpBean (CreateTableStatement stmt) throws Exception { if (! hasPrimaryKey(stmt)) { logger.info("*** No PK Field available for " + stmt.getTableName() + ", skipping bean creation"); return; } String baseName = stmt.getBeanName() + "Entity"; if (baseName == null) { baseName = sqlNameToJavaName(stmt.getTableName()); } logger.info("*** Start creation of bean " + baseName); final String valueInterface = mergeTemplate( getVelocityContext(stmt, baseName), "GenerateValue.vtl"); final File valueIfOutputFile = new File(mOutputDirectory, baseName + "Value.java"); writeFile(valueIfOutputFile, valueInterface); final String valueImpl = mergeTemplate( getVelocityContext(stmt, baseName), "GenerateValueImpl.vtl"); final File valueImplOutputFile = new File(mOutputDirectory, baseName + "ValueImpl.java"); writeFile(valueImplOutputFile, valueImpl); final String bean = mergeTemplate( getVelocityContext(stmt, baseName), "GenerateBean.vtl"); final File beanOutputFile = new File(mOutputDirectory, baseName + "Bean.java"); writeFile(beanOutputFile, bean); if (checkIfHelperRequired(stmt)) { final String helper = mergeTemplate( getVelocityContext(stmt, baseName), "GenerateHelper.vtl"); final File helperOutputFile = new File(mOutputDirectory, baseName + "TypeConverter.java"); writeFile(helperOutputFile, helper); } } private String mergeTemplate (VelocityContext ctx, String templateFile) throws Exception { final StringWriter sw = new StringWriter(); final Template template = Velocity.getTemplate(templateFile); template.merge(ctx, sw); return sw.getBuffer().toString(); } private void writeFile (File outputFile, final String data) throws IOException { if (outputFile.exists() && !mOverwrite) { throw new RuntimeException( "Output file " + outputFile + " already exists"); } final FileWriter fout = new FileWriter(outputFile); try { fout.write(data); } finally { IoUtil.close(fout); } logger.info("Wrote file " + outputFile + " successfully"); } /** * @param stmt * @param baseName * @return * @throws Exception */ private VelocityContext getVelocityContext (CreateTableStatement stmt, String baseName) throws Exception { final Properties velocityProps = new Properties(); velocityProps.put(RuntimeConstants.RESOURCE_LOADER, "file"); velocityProps.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader"); velocityProps.put(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, mTemplateDir); velocityProps.put(RuntimeConstants.VM_LIBRARY, "macros.vm"); // velocityProps.put("runtime.log", "/tmp/velocity.log"); velocityProps.put(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, logger.isLoggable(Level.FINER) ? "org.apache.velocity.runtime.log.SimpleLog4JLogSystem" : "org.apache.velocity.runtime.log.NullLogSystem"); Velocity.init(velocityProps); final VelocityContext ctx = new VelocityContext(); ctx.put("stmt", stmt); ctx.put("baseName", baseName); ctx.put("package", mPackagePrefix); ctx.put("datasource", mDataSource); ctx.put("cmpgen", this); return ctx; } /** * * @throws CmpGeneratorException */ private void setUp () throws CmpGeneratorException { File outputDir = new File(mOutputBaseDirectory); if (!outputDir.exists()) { throw new CmpGeneratorException("Output directory does not exist"); } if (!outputDir.isDirectory()) { throw new CmpGeneratorException( mOutputBaseDirectory + " is not a directory"); } final StringTokenizer tok = new StringTokenizer(mPackagePrefix, "."); while (tok.hasMoreTokens()) { outputDir = new File(outputDir, tok.nextToken()); } logger.finer("Generating package subdirectories for " + outputDir); outputDir.mkdirs(); mOutputDirectory = outputDir; } private boolean hasPrimaryKey (CreateTableStatement statement) { boolean hasPrimaryKey = false; for (final Iterator it = statement.getColumns().iterator(); it.hasNext(); ) { final ColumnSpec column = (ColumnSpec) it.next(); if (column.isPrimaryKey()) { hasPrimaryKey = true; break; } } return hasPrimaryKey; } /** * Checks if a helper class is required for this bean * * a helper class is required as soon as a custom java type is used * with a load method and a store method. * * @param statement the parsed SQL create statement * @return true if a helper class is required, false otherwise */ public boolean checkIfHelperRequired (CreateTableStatement statement) { boolean needHelper = false; for (final Iterator it = statement.getColumns().iterator(); it.hasNext(); ) { final ColumnSpec column = (ColumnSpec) it.next(); if (column.getJavaType() != null) { if (column.getStoreMethod() != null) { needHelper = true; break; } } } return needHelper; } public String getVersion () { return "$Revision: 1.9 $"; } public Set buildBeanImportList (CreateTableStatement statement) throws CmpGeneratorException { final Set beanImportList = new TreeSet(); for (final Iterator it = statement.getColumns().iterator(); it.hasNext(); ) { final ColumnSpec column = (ColumnSpec) it.next(); final String javaType = TypeMapping.getJavaType(column, true); if (javaType != null && !javaType.startsWith("java.lang") && !TypeMapping.isPrimitiveType(javaType)) { beanImportList.add(javaType); } } beanImportList.add("org.jcoderz.commons.types.Period"); beanImportList.add("javax.ejb.CreateException"); return beanImportList; } public Set buildHelperImportList (CreateTableStatement statement) throws CmpGeneratorException { final Set helperImportList = new TreeSet(); for (final Iterator it = statement.getColumns().iterator(); it.hasNext(); ) { final ColumnSpec column = (ColumnSpec) it.next(); final String javaType = TypeMapping.getJavaType(column, true); if (javaType != null && !javaType.equals("java.sql.Date") // don't import date since // refs to date are always // fully qualified. && !javaType.startsWith("java.lang") && !TypeMapping.isPrimitiveType(javaType)) { helperImportList.add(javaType); } final String complexType = column.getJavaType(); if (complexType != null && !complexType.startsWith("java.lang") && !TypeMapping.isPrimitiveType(complexType)) { helperImportList.add(complexType); } } // extra imports: helperImportList.add("org.jcoderz.commons.InconsistentDatabaseException"); helperImportList.add("org.jcoderz.commons.types.Date"); helperImportList.add("org.jcoderz.commons.types.Period"); helperImportList.add("org.jcoderz.commons.util.Assert"); return helperImportList; } /** {@inheritDoc} */ public String toString () { return "[Phoenix CmpGenerator " + getVersion() + "]"; } public boolean isPrimitiveType (String type) { return TypeMapping.isPrimitiveType(type); } }