View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package javax.jdo;
18  
19  import java.io.File;
20  import java.net.MalformedURLException;
21  import java.net.URI;
22  import java.net.URL;
23  import java.net.URLClassLoader;
24  
25  import java.util.ArrayList;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Properties;
29  import java.util.Set;
30  import java.util.Map.Entry;
31  
32  import static javax.jdo.Constants.ENHANCER_EXCEPTION;
33  import static javax.jdo.Constants.ENHANCER_NO_JDO_ENHANCER_FOUND;
34  import static javax.jdo.Constants.ENHANCER_USAGE_ERROR;
35  import static javax.jdo.Constants.PROPERTY_ENHANCER_VENDOR_NAME;
36  import static javax.jdo.Constants.PROPERTY_ENHANCER_VERSION_NUMBER;
37  
38  import javax.jdo.spi.I18NHelper;
39  
40  /**
41   * Main class to invoke a JDO Enhancer.
42   * The enhancer is invoked with the following command line:
43   * <xmp>
44   * java -cp <classpath> javax.jdo.Enhancer <options> <directory, file, or resource names>
45   * </xmp>
46   * &lt;classpath&gt; must contain the jdo specification jar, the implementation jar and any 
47   * implementation dependencies, the statically-compiled classes, and the jdo 
48   * metadata files loadable as resources.
49   * 
50   * <p/>&lt;options&gt; include: 
51   * <ul><li>? : print usage to stderr and exit
52   * </li><li>-h : print usage to stderr and exit
53   * </li><li>-help : print usage to stderr and exit
54   * </li><li>-pu &lt;persistence-unit-name&gt; : the name of a persistence unit 
55   * </li><li>-d &lt;target directory&gt; : write the enhanced classes to the specified directory 
56   * </li><li>-checkonly : just check the classes for enhancement status 
57   * </li><li>-v : verbose output
58   * </li><li>-r : recurse through directories to find all classes and metadata files to enhance 
59   * </li><li>-cp &lt;enhancer class loader path&gt; : if not already included in the java class loader, 
60   * this parameter must contain the statically-compiled classes, and the jdo metadata 
61   * files loadable as resources 
62   * </li></ul>&lt;directory, file, or resource names&gt;
63   * <ul><li>Directory names must not end in ".jdo", ".jar", or ".class"
64   * </li><li>Directories will be searched for files with suffixes ".jdo", ".jar", and ".class"
65   * </li><li>Directories will be searched recursively if the -r option is set
66   * </li></ul>
67   * 
68   * @since 3.0
69   */
70  public class Enhancer {
71  
72      /** The Internationalization message helper. */
73      private final static I18NHelper msg = 
74          I18NHelper.getInstance ("javax.jdo.Bundle"); //NOI18N
75  
76      /** New Line */
77      private char NL = '\n'; //NOI18N
78      /** Jar file suffix */
79      private String JAR_FILE_SUFFIX = ".jar"; //NOI18N
80      /** JDO Metadata file suffix */
81      private String JDO_FILE_SUFFIX = ".jdo"; //NOI18N
82      /** Class file suffix */
83      private String CLASS_FILE_SUFFIX = ".class"; //NOI18N
84  
85      /** Error indicator */
86      private boolean error = false;
87      /** If set, process parameters, print usage, and exit. */
88      private boolean printAndExit = false;
89  
90      /** Persistence Units */
91      private List<String> persistenceUnitNames = new ArrayList<String>();
92      /** Target Directory Parameter */
93      private String directoryName = null;
94      /** ClassLoader for JDOEnhancer */
95      private ClassLoader loader = null;
96      /** Classpath (-cp) parameter */
97      private String classPath = null;
98      /** Check Only flag */
99      private boolean checkOnly = false;
100     /** Verbose flag */
101     private boolean verbose = false;
102     /** Recurse flag */
103     private boolean recurse = false;
104     /** Error messages should be empty unless there is an error */
105     private StringBuilder errorBuffer = new StringBuilder();
106     /** Verbose messages are always collected but only output if verbose flag is set */
107     private StringBuilder verboseBuffer = new StringBuilder();
108     /** File Names */
109     private List<String> fileNames = new ArrayList<String>();
110     /** Class File Names */
111     private List<String> classFileNames = new ArrayList<String>();
112     /** JDO File Names */
113     private List<String> jdoFileNames = new ArrayList<String>();
114     /** Jar File Names */
115     private List<String> jarFileNames = new ArrayList<String>();
116     /** The number of classes validated by the JDOEnhancer */
117     private int numberOfValidatedClasses = 0;
118     /** The number of classes enhanced by the JDOEnhancer */
119     private int numberOfEnhancedClasses = 0;
120 
121     /** The properties from the JDOEnhancer */
122     private Properties properties;
123 
124     /** Run the enhancer from the command line.
125      * 
126      * @param args command line arguments
127      */
128     public static void main (String[] args) {
129         Enhancer enhancerMain = new Enhancer();
130         enhancerMain.run(args);
131     }
132 
133     /** Execute the enhancer.
134      * 
135      * @param args the command line arguments
136      */
137     private void run(String[] args) {
138         // processArgs will exit if errors or help
139         processArgs(args);
140         JDOEnhancer enhancer = null;
141         try {
142             enhancer = JDOHelper.getEnhancer();
143         } catch (JDOException jdoex) {
144             jdoex.printStackTrace(); // outputs to stderr
145             exit(ENHANCER_NO_JDO_ENHANCER_FOUND);
146         }
147 
148         try {
149             // provide verbose property settings of the JDOEnhancer we just loaded
150             properties = enhancer.getProperties();
151             addVerboseMessage("MSG_EnhancerClass", enhancer.getClass().getName()); //NOI18N
152             addVerboseMessage("MSG_EnhancerProperty", PROPERTY_ENHANCER_VENDOR_NAME, //NOI18N
153                     properties.getProperty(PROPERTY_ENHANCER_VENDOR_NAME));
154             addVerboseMessage("MSG_EnhancerProperty", PROPERTY_ENHANCER_VERSION_NUMBER, //NOI18N
155                     properties.getProperty(PROPERTY_ENHANCER_VERSION_NUMBER));
156             Set<Entry<Object, Object>> props = properties.entrySet();
157             Iterator<Entry<Object, Object>> entries = props.iterator();
158             while (entries.hasNext()) {
159                 Entry<Object, Object> entry = entries.next();
160                 if (!(PROPERTY_ENHANCER_VENDOR_NAME.equals(entry.getKey()) ||
161                         PROPERTY_ENHANCER_VERSION_NUMBER.equals(entry.getKey()))) {
162                     addVerboseMessage("MSG_EnhancerProperty", (String)entry.getKey(), //NOI18N
163                             (String)entry.getValue());                    
164                 }
165             }
166             enhancer.setVerbose(verbose);
167             if (loader != null) {
168                 enhancer.setClassLoader(loader);
169             }
170 
171             int numberOfClasses = classFileNames.size();
172             if (numberOfClasses != 0) {
173                 enhancer.addClasses(classFileNames.toArray(new String[numberOfClasses]));
174             }
175             int numberOfFiles = jdoFileNames.size();
176             if (numberOfFiles != 0) {
177                 enhancer.addFiles(jdoFileNames.toArray(new String[numberOfFiles]));
178             }
179             if (0 < jarFileNames.size()) {
180                 for (String jarFileName : jarFileNames) {
181                     enhancer.addJar(jarFileName);
182                 }
183             }
184             if (persistenceUnitNames != null) {
185                 for (String persistenceUnitName: persistenceUnitNames) {
186                     enhancer.addPersistenceUnit(persistenceUnitName);
187                 }
188             }
189             if (directoryName != null) {
190                 enhancer.setOutputDirectory(directoryName);
191             }
192             if (checkOnly) {
193                 numberOfValidatedClasses = enhancer.validate();
194                 addVerboseMessage("MSG_EnhancerValidatedClasses", numberOfValidatedClasses); //NOI18N
195             } else {
196                 numberOfEnhancedClasses = enhancer.enhance();
197                 addVerboseMessage("MSG_EnhancerEnhancedClasses", numberOfEnhancedClasses); //NOI18N
198             }
199             exit(0); // good exit
200         } catch (Exception ex) {
201             ex.printStackTrace(); // outputs to stderr
202             exit(ENHANCER_EXCEPTION); // error exit
203         }
204     }
205 
206     /** Process the command line arguments and exit if there is a usage request or an error.
207      * 
208      * @param args the command line arguments
209      */
210     private void processArgs(String[] args) {
211         parseArgs(args);
212         parseFiles(fileNames.toArray(new String[fileNames.size()]), true, recurse);
213         loader = prepareClassLoader(classPath);
214         if (error) {
215             addErrorMessage(msg.msg("MSG_EnhancerUsage")); //NOI18N
216             exit(ENHANCER_USAGE_ERROR); // error exit
217         }
218         if (printAndExit) {
219             addVerboseMessage("MSG_EnhancerUsage"); //NOI18N
220             exit(0); // good exit
221         }
222     }
223 
224     /** Parse the command line arguments. Put the results into fields.
225      * 
226      * @param args the command line arguments
227      */
228     private void parseArgs(String[] args) {
229         boolean doneWithOptions = false;
230         fileNames = new ArrayList<String>();
231         for (int i = 0; i < args.length; ++i) {
232             String arg = args[i];
233             // if first argument is ? then simply print usage and return.
234             if ("?".equals(arg)) {
235                 printAndExit = true;
236                 return;
237             }
238             if (!doneWithOptions) {
239                 if (arg.startsWith("-")) { //NOI18N
240                     String option = arg.substring(1);
241                     if ("help".equals(option)) { //NOI18N
242                         addVerboseMessage("MSG_EnhancerProcessing", "-help"); //NOI18N
243                         setPrintAndExit();
244                     } else if ("h".equals(option)) { //NOI18N
245                         addVerboseMessage("MSG_EnhancerProcessing", "-h"); //NOI18N
246                         setPrintAndExit();
247                     } else if ("v".equals(option)) { //NOI18N
248                         addVerboseMessage("MSG_EnhancerProcessing", "-v"); //NOI18N
249                         verbose = true;
250                     } else if ("verbose".equals(option)) { //NOI18N
251                         addVerboseMessage("MSG_EnhancerProcessing", "-verbose"); //NOI18N
252                         verbose = true;
253                     } else if ("pu".equals(option)) { //NOI18N
254                         if (hasNextArgument("MSG_EnhancerProcessing", "-pu", i, args.length)) { //NOI18N
255                             String puName = args[++i];
256                             addVerboseMessage("MSG_EnhancerPersistenceUnitName", puName); //NOI18N
257                             persistenceUnitNames.add(puName);
258                         } else {
259                             setError();
260                         }
261                     } else if ("cp".equals(option)) { //NOI18N
262                         if (hasNextArgument("MSG_EnhancerProcessing", "-cp", i, args.length)) { //NOI18N
263                             classPath = args[++i];
264                             addVerboseMessage("MSG_EnhancerClassPath", classPath); //NOI18N
265                          } else {
266                              setError();
267                        }
268                     } else if ("d".equals(option)) { //NOI18N
269                         if (hasNextArgument("MSG_EnhancerProcessing", "-d", i, args.length)) { //NOI18N
270                             directoryName = args[++i];
271                             addVerboseMessage("MSG_EnhancerOutputDirectory", directoryName); //NOI18N
272                         } else {
273                             setError();
274                         }
275                     } else if ("checkonly".equals(option)) { //NOI18N
276                         addVerboseMessage("MSG_EnhancerProcessing", "-checkonly"); //NOI18N
277                         checkOnly = true;
278                     } else if ("r".equals(option)) { //NOI18N
279                         addVerboseMessage("MSG_EnhancerProcessing", "-r"); //NOI18N
280                         recurse = true;
281                     } else {
282                         setError();
283                         addErrorMessage(msg.msg("ERR_EnhancerUnrecognizedOption", option)); //NOI18N
284                     }
285                 } else {
286                     doneWithOptions = true;
287                     fileNames.add(arg);
288                 }
289             } else {
290                 fileNames.add(arg);
291             }
292         }
293     }
294 
295     /** Check whether there is another parameter (the argument for an option
296      * that requires an argument).
297      * @param msgId the message id for an error message
298      * @param where the parameter for the message
299      * @param i the index into the parameter array
300      * @param length the length of the parameter array
301      * @return
302      */
303     private boolean hasNextArgument(String msgId, String where, int i, int length) {
304         if (i + 1 >= length) {
305             setError();
306             addErrorMessage(msg.msg(msgId, where));
307             addErrorMessage(msg.msg("ERR_EnhancerRequiredArgumentMissing")); //NOI18N
308             return false;
309         }
310         return true;
311     }
312 
313     /**
314      * Files can be one of four types:
315      * <ol><li>directory: the directory is examined for files of the following types
316      * </li><li>.class: this is a java class file
317      * </li><li>.jdo: this is a jdo metadata file
318      * </li><li>.jar: this is a jar file
319      * </li></ol>
320      * If the recursion flag is set, directories contained in directories are examined,
321      * recursively.
322      */
323     private void parseFiles(String[] fileNames, boolean search, boolean recurse) {
324         for (String fileName: fileNames) {
325             if (fileName.endsWith(JAR_FILE_SUFFIX)) {
326                 // add to jar file names
327                 jarFileNames.add(fileName);
328                 addVerboseMessage("MSG_EnhancerJarFileName", fileName); //NOI18N
329             } else if (fileName.endsWith(JDO_FILE_SUFFIX)) {
330                 // add to jdo file names
331                 jdoFileNames.add(fileName);
332                 addVerboseMessage("MSG_EnhancerJDOFileName", fileName); //NOI18N
333             } else if (fileName.endsWith(CLASS_FILE_SUFFIX)) {
334                 // add to class file names
335                 classFileNames.add(fileName);
336                 addVerboseMessage("MSG_EnhancerClassFileName", fileName); //NOI18N
337             } else {
338                 // assume a directory if no recognized suffix
339                 File directoryFile = new File(fileName);
340                 if (directoryFile.isDirectory() && search) {
341                     String directoryPath = directoryFile.getAbsolutePath();
342                     String[] files = directoryFile.list();
343                     String[] pathName = new String[1];
344                     if (files != null) {
345                         for (String file: files) {
346                             pathName[0] = directoryPath + '/' + file;
347                             parseFiles(pathName, recurse, recurse);
348                         }
349                     }
350                 }
351             }
352         }
353     }
354 
355     /** Prepare the class loader from the classPath specified
356      * 
357      * @param classPath the classPath string from the "-cp classPath" option
358      * @return the class loader
359      */
360     private ClassLoader prepareClassLoader(String classPath) {
361         if (classPath == null)
362             return null;
363         ClassLoader result = null;
364         // separate classPath using system class path separator
365         String separator = System.getProperty("path.separator");
366         String[] paths = classPath.split(separator);
367         List<URL> urls = new ArrayList<URL>();
368         for (String path: paths) {
369             // for each path construct a URL from the File
370             File file = new File(path);
371             URI uri = file.toURI();
372             try {
373                 URL url = uri.toURL();
374                 addVerboseMessage("MSG_EnhancerClassPath", url.toString());
375                 urls.add(url);
376             } catch (MalformedURLException e) {
377                 setError();
378                 addErrorMessage(msg.msg("ERR_EnhancerBadClassPath", file));
379             }
380         }
381         result = new URLClassLoader(urls.toArray(new URL[urls.size()]), null);
382         return result;
383     }
384 
385     /** Add a message to stderr.
386      * 
387      * @param message the internationalized message to add
388      */
389     private void addErrorMessage(String message) {
390         errorBuffer.append(message);
391         errorBuffer.append(NL);
392     }
393 
394     /** Set the error flag.
395      * 
396      */
397     private void setError() {
398         error = true;
399     }
400 
401     /** Set the print-and-exit flag.
402      * 
403      */
404     private void setPrintAndExit() {
405         printAndExit = true;
406     }
407 
408     /** Exit this process.
409      * 
410      * @param exitValue the process exit value
411      */
412     private void exit(int exitValue) {
413         System.out.print(verboseBuffer.toString());
414         System.err.print(errorBuffer.toString());
415         System.exit(exitValue);
416     }
417 
418     /** Add a message to the verbose message buffer.
419      * 
420      * @param msgId the message id
421      * @param where the parameter
422      */
423     private void addVerboseMessage(String msgId, String... where) {
424         verboseBuffer.append(msg.msg(msgId, where));
425         verboseBuffer.append(NL);
426     }
427 
428     /** Add a message to the verbose message buffer.
429      * 
430      * @param msgId the message id
431      * @param where the parameter
432      */
433     private void addVerboseMessage(String msgId, String where) {
434         verboseBuffer.append(msg.msg(msgId, where));
435         verboseBuffer.append(NL);
436     }
437 
438     /** Add a message to the verbose message buffer.
439      * 
440      * @param msgId the message id
441      */
442     private void addVerboseMessage(String msgId) {
443         verboseBuffer.append(msg.msg(msgId));
444         verboseBuffer.append(NL);
445     }
446 
447     /** Add a message to the verbose message buffer.
448      * 
449      * @param msgId the message id
450      * @param where the parameter
451      */
452     private void addVerboseMessage(String msgId, int where) {
453         addVerboseMessage(msgId, String.valueOf(where));
454     }
455 
456 }