1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 * <classpath> 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/><options> 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 <persistence-unit-name> : the name of a persistence unit
55 * </li><li>-d <target directory> : 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 <enhancer class loader path> : 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><directory, file, or resource names>
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");
75
76 /** New Line */
77 private char NL = '\n';
78 /** Jar file suffix */
79 private String JAR_FILE_SUFFIX = ".jar";
80 /** JDO Metadata file suffix */
81 private String JDO_FILE_SUFFIX = ".jdo";
82 /** Class file suffix */
83 private String CLASS_FILE_SUFFIX = ".class";
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
139 processArgs(args);
140 JDOEnhancer enhancer = null;
141 try {
142 enhancer = JDOHelper.getEnhancer();
143 } catch (JDOException jdoex) {
144 jdoex.printStackTrace();
145 exit(ENHANCER_NO_JDO_ENHANCER_FOUND);
146 }
147
148 try {
149
150 properties = enhancer.getProperties();
151 addVerboseMessage("MSG_EnhancerClass", enhancer.getClass().getName());
152 addVerboseMessage("MSG_EnhancerProperty", PROPERTY_ENHANCER_VENDOR_NAME,
153 properties.getProperty(PROPERTY_ENHANCER_VENDOR_NAME));
154 addVerboseMessage("MSG_EnhancerProperty", PROPERTY_ENHANCER_VERSION_NUMBER,
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(),
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);
195 } else {
196 numberOfEnhancedClasses = enhancer.enhance();
197 addVerboseMessage("MSG_EnhancerEnhancedClasses", numberOfEnhancedClasses);
198 }
199 exit(0);
200 } catch (Exception ex) {
201 ex.printStackTrace();
202 exit(ENHANCER_EXCEPTION);
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"));
216 exit(ENHANCER_USAGE_ERROR);
217 }
218 if (printAndExit) {
219 addVerboseMessage("MSG_EnhancerUsage");
220 exit(0);
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
234 if ("?".equals(arg)) {
235 printAndExit = true;
236 return;
237 }
238 if (!doneWithOptions) {
239 if (arg.startsWith("-")) {
240 String option = arg.substring(1);
241 if ("help".equals(option)) {
242 addVerboseMessage("MSG_EnhancerProcessing", "-help");
243 setPrintAndExit();
244 } else if ("h".equals(option)) {
245 addVerboseMessage("MSG_EnhancerProcessing", "-h");
246 setPrintAndExit();
247 } else if ("v".equals(option)) {
248 addVerboseMessage("MSG_EnhancerProcessing", "-v");
249 verbose = true;
250 } else if ("verbose".equals(option)) {
251 addVerboseMessage("MSG_EnhancerProcessing", "-verbose");
252 verbose = true;
253 } else if ("pu".equals(option)) {
254 if (hasNextArgument("MSG_EnhancerProcessing", "-pu", i, args.length)) {
255 String puName = args[++i];
256 addVerboseMessage("MSG_EnhancerPersistenceUnitName", puName);
257 persistenceUnitNames.add(puName);
258 } else {
259 setError();
260 }
261 } else if ("cp".equals(option)) {
262 if (hasNextArgument("MSG_EnhancerProcessing", "-cp", i, args.length)) {
263 classPath = args[++i];
264 addVerboseMessage("MSG_EnhancerClassPath", classPath);
265 } else {
266 setError();
267 }
268 } else if ("d".equals(option)) {
269 if (hasNextArgument("MSG_EnhancerProcessing", "-d", i, args.length)) {
270 directoryName = args[++i];
271 addVerboseMessage("MSG_EnhancerOutputDirectory", directoryName);
272 } else {
273 setError();
274 }
275 } else if ("checkonly".equals(option)) {
276 addVerboseMessage("MSG_EnhancerProcessing", "-checkonly");
277 checkOnly = true;
278 } else if ("r".equals(option)) {
279 addVerboseMessage("MSG_EnhancerProcessing", "-r");
280 recurse = true;
281 } else {
282 setError();
283 addErrorMessage(msg.msg("ERR_EnhancerUnrecognizedOption", option));
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"));
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
327 jarFileNames.add(fileName);
328 addVerboseMessage("MSG_EnhancerJarFileName", fileName);
329 } else if (fileName.endsWith(JDO_FILE_SUFFIX)) {
330
331 jdoFileNames.add(fileName);
332 addVerboseMessage("MSG_EnhancerJDOFileName", fileName);
333 } else if (fileName.endsWith(CLASS_FILE_SUFFIX)) {
334
335 classFileNames.add(fileName);
336 addVerboseMessage("MSG_EnhancerClassFileName", fileName);
337 } else {
338
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
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
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 }