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 }