View Javadoc

1   //$Id: CodeWriter.java,v 1.19 2007/10/19 10:43:36 kameleono Exp $
2   
3   package net.sourceforge.sql2java;
4   
5   //TODO: Add exception handling in this class for the VelocityEngine.
6   
7   import java.io.File;
8   import java.io.FileFilter;
9   import java.io.FileOutputStream;
10  import java.io.OutputStreamWriter;
11  import java.io.PrintWriter;
12  import java.io.StringWriter;
13  import java.util.ArrayList;
14  import java.util.Date;
15  import java.util.Hashtable;
16  import java.util.Iterator;
17  import java.util.List;
18  import java.util.Properties;
19  import java.util.StringTokenizer;
20  import java.util.Vector;
21  
22  import org.apache.velocity.VelocityContext;
23  import org.apache.velocity.app.FieldMethodizer;
24  import org.apache.velocity.app.Velocity;
25  import org.apache.velocity.exception.ParseErrorException;
26  import org.apache.velocity.exception.ResourceNotFoundException;
27  
28  // this class is a mess, would need some brushing, however the generated code is clean and
29  // that's what really matters
30  public class CodeWriter
31  {
32      static protected Properties props;
33  
34      public static String MGR_CLASS="Manager";
35  
36      protected static String dateClassName;
37      protected static String timeClassName;
38      protected static String timestampClassName;
39  
40      protected static Database db;
41      protected static Hashtable includeHash, excludeHash;
42  
43      protected static String basePackage;
44      protected static String destDir;
45      protected static String optimisticLockType;
46      protected static String optimisticLockColumn;
47      public static String classPrefix;
48  
49      protected VelocityContext vc;
50  
51      public Table table;
52      protected VelocityContext current_vc;
53  
54      ///////////////////////////////////////////////////////
55      // CODE WRITER INIT
56      ///////////////////////////////////////////////////////
57  
58      /** The Default Constructor
59       * @author Kelvin Nishikawa
60       * @param props Properties for configuring this instance
61       */
62      public CodeWriter (Database db, Properties props) {
63      	try {
64              CodeWriter.db  = db;
65      		CodeWriter.props = props;
66  
67              dateClassName = props.getProperty("jdbc2java.date", "java.sql.Date");
68              timeClassName = props.getProperty("jdbc2java.time", "java.sql.Time");
69              timestampClassName = props.getProperty("jdbc2java.timestamp", "java.sql.Timestamp");
70  
71              // Set properties
72              basePackage = props.getProperty("codewriter.package");
73              if(basePackage == null)
74              {
75                  throw new Exception("Missing property: codewriter.package");
76              }
77  
78              classPrefix = props.getProperty("codewriter.classprefix");
79              setDestinationFolder(props.getProperty("codewriter.destdir"));
80  
81              excludeHash = setHash(props.getProperty("tables.exclude"));
82              if (excludeHash.size() != 0)
83                  System.out.println("Excluding the following tables: " +  props.getProperty("tables.exclude"));
84              includeHash = setHash(props.getProperty("tables.include"));
85              if (includeHash.size() != 0)
86                  System.out.println("Including only the following tables: " +  props.getProperty("tables.include"));
87  
88              optimisticLockType = props.getProperty("optimisticlock.type", "none");
89              optimisticLockColumn = props.getProperty("optimisticlock.column");
90  
91      	} catch (Exception e) {
92      		//knishikawa - maybe this needs better exception handling for the Velocity inits
93      		System.err.println("Threw an exception in the CodeWriter constructor:" + e.getMessage());
94      		e.printStackTrace();
95      	}
96      }
97  
98  
99      public void setDestinationFolder(String destDir) throws Exception
100     {
101         CodeWriter.destDir = destDir;
102         if(destDir == null)
103         {
104             throw new Exception("Missing property: codewriter.destdir");
105         }
106 
107         File dir = new File(destDir);
108         try {
109             dir.mkdirs();
110         } catch (Exception e) {
111             // ignore
112         }
113 
114         if(!dir.isDirectory() || !dir.canWrite())
115         {
116             throw new Exception("Cannot write to: " + destDir);
117         }
118     }
119 
120 
121     private Hashtable setHash(String str)
122     {
123         if(str == null || str.trim().equals(""))
124             return new Hashtable();
125         else
126         {
127             Hashtable hash = new Hashtable();
128             StringTokenizer st = new StringTokenizer(str);
129             while(st.hasMoreTokens())
130             {
131                 String val = st.nextToken().toLowerCase();
132                 hash.put(val, val);
133             }
134 
135             return hash;
136         }
137     }
138 
139     public boolean checkTable(Table newTable) throws Exception
140     {
141         System.out.println("    checking table " + newTable.getName() + " ...");
142         boolean error = false;
143         Column[] primaryKeys = newTable.getPrimaryKeys();
144         if (newTable.getColumns().length == 0)
145         {
146             System.err.println("        WARN : no column found !");
147             error = false;
148         }
149         if (primaryKeys.length == 0)
150         {
151             System.err.println("        WARN : No primary key is defined on table " + newTable.getName());
152             System.err.println("            Tables without primary key are not fully supported");
153             error = false;
154         }
155         else
156         {
157             if (primaryKeys.length > 1)
158             {
159                 System.err.print("        WARN : Composite primary key ");
160                 for (int ii = 0; ii < primaryKeys.length; ii++)
161                     System.err.print(primaryKeys[ii].getFullName() + ", ");
162                 System.err.println();
163                 System.err.println("            Tables with composite primary key are not fully supported");
164             }
165             else
166             {
167                 Column pk = (Column) primaryKeys[0];
168                 String pkName = pk.getName();
169                 String normalKey = newTable.getName() + "_id";
170                 if (pkName.equalsIgnoreCase(normalKey) == false)
171                 {
172                     System.err.println("          WARN : primary key should be of form <TABLE>_ID");
173                     System.err.println("              found " + pkName + " expected " + normalKey);
174                 }
175                 if (pk.isColumnNumeric() == false)
176                 {
177                     System.err.println("          WARN : primary key should be a number ");
178                     System.err.println("              found " + pk.getJavaType());
179                 }
180             }
181         }
182         return error;
183     }
184 
185     public void checkDatabase() throws Exception
186     {
187         System.out.println("Checking database tables");
188         boolean error = false;
189         Table tables[] = db.getTables();
190         for (int i = 0; i < tables.length; i++)
191         {
192             if (authorizeProcess(tables[i].getName(), "tables.include", "tables.exclude"))
193             {
194                 boolean b = checkTable(tables[i]);
195                 if (b == true)
196                     error = true;
197             }
198         }
199         if (error == true)
200         {
201             System.err.println("    Failed : at least one of the mandatory rule for sql2java is followed by your schema.");
202             System.err.println("    Please check the documentation for more information");
203             System.exit(-1);
204         }
205         System.out.println("    Passed.");
206     }
207 
208     ///////////////////////////////////////////////////////
209     // CODE WRITER CORE
210     ///////////////////////////////////////////////////////
211 
212     /** The entry point for generating code. */
213     public synchronized void process() throws Exception
214     {
215         if ("true".equalsIgnoreCase(Main.getProperty("check.database")))
216             checkDatabase();
217 
218         if ("true".equalsIgnoreCase(Main.getProperty("check.only.database")))
219             return;
220 
221         // Init Velocity
222         Properties vprops = new Properties();
223         vprops.put("file.resource.loader.path", getLoadingPath());
224         vprops.put("velocimacro.library", "macros.include.vm");
225 
226         Velocity.init(vprops);
227         vc = new VelocityContext();
228         vc.put("CodeWriter", new FieldMethodizer( this ));
229         vc.put("codewriter", this );
230         vc.put("pkg", basePackage);
231         vc.put("pkgPath", basePackage.replace('.', '/'));
232         vc.put("strUtil", StringUtilities.getInstance());
233         vc.put("fecha", new Date());
234         current_vc = new VelocityContext(vc);
235 
236         //System.out.println("Generation in folder " + destDir + " ...");
237         String[] schema_templates = getSchemaTemplates("velocity.templates");
238         for(int i=0; i<schema_templates.length; i++) {
239             writeComponent(schema_templates[i]);
240         }
241         if ("true".equalsIgnoreCase(Main.getProperty("write.only.per.schema.templates")))
242             return;
243 
244         // Generate core and manager classes for all tables
245         Table tables[] = db.getTables();
246         for(int i = 0; i < tables.length; i++) {
247             if (authorizeProcess(tables[i].getName(), "tables.include", "tables.exclude"))
248                 writeTable(tables[i]);
249         }
250     }
251 
252     private void writeTable(Table currentTable) throws Exception
253     {
254         if (currentTable.getColumns().length == 0) {
255             return;
256         }
257         current_vc = new VelocityContext(vc);
258         this.table = currentTable;
259         current_vc.put("table", currentTable);
260 
261         String[] table_templates = getTableTemplates("velocity.templates");
262         for(int i=0; i<table_templates.length; i++) {
263             writeComponent(table_templates[i]);
264         }
265     }
266 
267     /** This method creates a file and generates the class; it is based on the original SQL2Java methods
268      * The filename for the class is based on the value of the Velocity variable passed in.
269      * @author Kelvin Nishikawa
270      * @param templateName The template to parse and generate from
271      * @param variableFileName A velocity variable on which to base the filename
272      * @throws Exception (IOExceptions?)
273      */
274     public void writeComponent(String templateName) throws Exception {
275 
276     	//check the integrity of our velocity template
277     	try
278         {
279             System.out.println("Generating template " + templateName);
280     		Velocity.getTemplate(templateName);
281     	}
282         catch (ResourceNotFoundException rnfe) {
283     		System.err.println( "Aborted writing component:" + templateName
284     				          + (table!=null?(" for table:" + table.getName()):"")
285 							  + " because Velocity could not find the resource." );
286     		return;
287     	}
288         catch (ParseErrorException pee) {
289     		System.err.println( "Aborted writing component:" + templateName
290 			          + (table!=null?(" for table:" + table.getName()):"")
291 					  + " because there was a parse error in the resource.\n" + pee.getLocalizedMessage() );
292     		return;
293     	}
294         catch (Exception e) {
295     		System.err.println( "Aborted writing component:" + templateName
296 			          + (table!=null?(" for table:" + table.getName()):"")
297 					  + " there was an error initializing the template.\n" + e.getLocalizedMessage() );
298     		return;
299     	}
300 
301 
302         // dummy process the template
303         StringWriter sw = new StringWriter();
304     	Velocity.mergeTemplate(templateName ,Velocity.ENCODING_DEFAULT, current_vc ,sw);
305 
306         System.out.println(" .... writing to " + current_fullfilename);
307 
308         //this logging should be moved somewhere else?
309         File file = new File(current_fullfilename);
310         (new File(file.getParent())).mkdirs();
311         PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(current_fullfilename)));
312         writer.write(sw.toString());
313         writer.flush();
314         writer.close();
315         System.out.println("    " + current_filename + " done.");
316     }
317 
318     ////////////////////////////////////////////////////////////////
319     // METHOD CALLED FROM TEMPLATES
320     ////////////////////////////////////////////////////////////////
321 
322     String current_fullfilename = "";
323     String current_filename = "";
324 
325     public void setCurrentFilename(String relpath_or_package, String fn) throws Exception {
326         current_filename = relpath_or_package.replace('.', File.separatorChar) + File.separatorChar + fn;
327         current_fullfilename = destDir + File.separatorChar +
328         relpath_or_package.replace('.', File.separatorChar) + File.separatorChar + fn;
329         UserCodeParser uc = new UserCodeParser(current_fullfilename);
330         current_vc.put("userCode", uc);
331     }
332 
333     public void setCurrentJavaFilename(String relpath_or_package, String fn) throws Exception {
334             setCurrentFilename("java" + File.separatorChar + relpath_or_package, fn);
335     }
336 
337 
338     /** System.out.println() wrapper */
339     public void log(String logStr ) {
340     	System.out.println("        " + logStr );
341     }
342 
343     public String getClassPrefix() {
344         return classPrefix;
345     }
346 
347     /** Public accessor for db */
348     public Database getDb() {
349     	return db;
350     }
351 
352     /** Public db.getTables() */
353     public List getTables() {
354     	Table[] tabs = db.getTables();
355     	List tables = new ArrayList(tabs.length);
356     	for ( int i = 0; i < tabs.length; i++ ) tables.add(tabs[i]);
357     	return tables;
358     }
359 
360     /** Public db.getTable() */
361     public Table getTable(String tableName) {
362         return db.getTable(tableName);
363     }
364 
365     /** Returns from db.getTables() those having isRelationTable() evaluating to true. */
366     public List getRelationTables() {
367     	Table[] tabs = db.getTables();
368     	List tables = new ArrayList(tabs.length);
369     	for ( int i = 0; i < tabs.length; i++ ) {
370     		if (tabs[i].isRelationTable()) {
371     			tables.add(tabs[i]);
372     		}
373     	}
374     	return tables;
375     }
376 
377     /** Return table.getName() */
378     public String tableName() {
379     	if (table == null) return "";
380     	return table.getName();
381     }
382 
383     /** public accessor for table */
384     public Table getTable() {
385     	return table;
386     }
387 
388     /** Check if a list contains an item that is equal() to a string */
389     public boolean listContainsString( List list, String string ) {
390     	Object obj = null;
391     	for ( Iterator iter = list.iterator();
392     		  iter.hasNext();
393     		  obj = iter.next() ) {
394     		if ( string.equals(obj) ) return true;
395     	}
396     	return false;
397     }
398 
399 
400     //////////////////////////////////////////////////////
401     // PROPERTY UTILS
402     //////////////////////////////////////////////////////
403     /** Convenience property chop method
404      * @author Kelvin Nishikawa
405      * @param key the property to get from this.props
406      * @return the associated value
407      */
408     static public String getProperty(String key)
409     {
410         String s = props.getProperty(key);
411         return s!=null?s.trim():s;
412     }
413 
414     /** Convenience property chop method
415      * @author Kelvin Nishikawa
416      * @param key the property to get from this.props
417      * @param default_val the default value to return in case not found
418      * @return the associated value
419      */
420     static public String getProperty(String key, String default_val) {
421     	String s = props.getProperty(key,default_val);
422         return s!=null?s.trim():s;
423     }
424 
425 
426     /**
427      * Return as a String array the key's value.
428      */
429     static public String[] getPropertyExploded(String key)
430     {
431         return getPropertyExploded(key, "");
432     }
433 
434     static public String[] getPropertyExploded(String mkey, String defaultValue)
435     {
436         String v = getProperty(mkey);
437         if (v==null) {
438             v = defaultValue;
439         }
440         return getExplodedString(v);
441     }
442 
443     static public String[] getExplodedString(String value)
444     {
445         ArrayList al = new ArrayList();
446         if (value == null)
447             return new String[0];
448 
449         StringTokenizer st = new StringTokenizer(value, " ,;\t");
450         while (st.hasMoreTokens()) {
451             al.add(st.nextToken().trim());
452         }
453 
454         return (String[])al.toArray(new String[al.size()]);
455     }
456 
457     /**
458      * get loading path
459      */
460     public String getLoadingPath()
461     {
462         String ret = "";
463         String[] paths = getPropertyExploded("velocity.templates.loadingpath", ".");
464         for(int i=0; i<paths.length; i++) {
465             ret = ret + paths[i] + ",";
466         }
467         System.out.println("getLoadingPath = " + ret);
468         return ret;
469     }
470 
471     public String[] getSchemaTemplates(String property)
472     {
473         return getTemplates(property, true);
474     }
475 
476     public String[] getTableTemplates(String property)
477     {
478         return getTemplates(property, false);
479     }
480 
481     public String[] getTemplates(String property, boolean perShema)
482     {
483         Vector files = new Vector();
484         recurseTemplate(files, Main.getProperty(property), perShema);
485         return (String[])files.toArray(new String[files.size()]);
486     }
487 
488     /**
489      * recurse in the template folders
490      */
491     public Vector recurseTemplate(Vector files, String folder, boolean perSchema)
492     {
493         String schemaOrTable = "perschema";
494         if (perSchema == false)
495             schemaOrTable = "pertable";
496         FileFilter filter = new FileFilter()
497         {
498             public boolean accept(File filename)
499             {
500                 if (filename.isDirectory())
501                     return true;
502                 if (filename.getName().endsWith(".vm") == false)
503                     return false;
504                 return authorizeProcess(filename.getName(), "template.file.include", "template.file.exclude");
505             }
506         };
507 
508         File[] dirEntries = (new File(folder)).listFiles(filter);
509         if (dirEntries == null)
510             return files;
511         for (int i=0; i<dirEntries.length; i++)
512         {
513             // is file
514             if (dirEntries[i].isFile())
515             {
516                 // and is in a folder hierarchy containing the endsWith (pertable/perschema)
517                 if (authorizeFile(folder, schemaOrTable))
518                 {
519                     files.add(folder + "/" + dirEntries[i].getName());
520                 }
521             }
522             else if(dirEntries[i].isDirectory())
523                 recurseTemplate(files, folder + "/" + dirEntries[i].getName(), perSchema);
524         }
525         return files;
526     }
527 
528     /**
529      * check the value for include & exclude lists defined in the config file
530      */
531     public static boolean authorizeProcess(String autorizePattern, String includeProperty, String excludeProperty)
532     {
533     	boolean accept = true;
534         String include[] = getPropertyExploded(includeProperty);
535         String exclude[] = getPropertyExploded(excludeProperty);
536         if (include.length != 0)
537         {
538             if (Main.isInArray(include, autorizePattern))
539             {
540                 System.out.println("Processing " + autorizePattern + " (specified in " + includeProperty + ")");
541                 return true;
542             }
543             accept = false;
544         }
545         if (exclude.length != 0)
546         {
547             if (Main.isInArray(exclude, autorizePattern))
548             {
549                 System.out.println("Skipping " + autorizePattern + " (specified in " + excludeProperty + ")");
550                 return false;
551             }
552         }
553         return accept;
554     }
555 
556     public static boolean folderContainsPattern(String folder, String[] patterns)
557     {
558         if (patterns == null || folder == null)
559             return false;
560         for (int i = 0; i < patterns.length; i ++)
561         {
562             String pattern = "/" + patterns[i].toLowerCase() + "/";
563             if (folder.toLowerCase().indexOf(pattern) != -1)
564             {
565                 return true;
566             }
567         }
568         return false;
569     }
570 
571     /**
572      * is this file to be processed or not ?
573      */
574     public static boolean authorizeFile(String folder, String schemaOrTable)
575     {
576         if (folder.toLowerCase().indexOf(schemaOrTable.toLowerCase()) == -1)
577             return false;
578         String include[] = getPropertyExploded("template.folder.include");
579         String exclude[] = getPropertyExploded("template.folder.exclude");
580         if (include.length != 0)
581         {
582             if (folderContainsPattern(folder, include) == true)
583                 return true;
584             return false;
585         }
586         if (exclude.length != 0)
587         {
588             if (folderContainsPattern(folder, exclude) == true)
589                 return false;
590             else
591                 return true;
592         }
593         return true;
594     }
595 }