1 /*
   2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.internal.jxc;
  27 
  28 import com.sun.tools.internal.jxc.ap.Options;
  29 import com.sun.tools.internal.xjc.BadCommandLineException;
  30 import com.sun.xml.internal.bind.util.Which;
  31 
  32 import javax.lang.model.SourceVersion;
  33 import javax.tools.DiagnosticCollector;
  34 import javax.tools.JavaCompiler;
  35 import javax.tools.JavaFileObject;
  36 import javax.tools.OptionChecker;
  37 import javax.tools.StandardJavaFileManager;
  38 import javax.tools.ToolProvider;
  39 import javax.xml.bind.JAXBContext;
  40 import java.io.File;
  41 import java.lang.reflect.InvocationTargetException;
  42 import java.lang.reflect.Method;
  43 import java.net.MalformedURLException;
  44 import java.net.URISyntaxException;
  45 import java.net.URL;
  46 import java.net.URLClassLoader;
  47 import java.util.ArrayList;
  48 import java.util.Collection;
  49 import java.util.Collections;
  50 import java.util.List;
  51 import java.util.logging.Level;
  52 import java.util.logging.Logger;
  53 
  54 /**
  55  * CLI entry-point to the schema generator.
  56  *
  57  * @author Bhakti Mehta
  58  */
  59 public class SchemaGenerator {
  60 
  61     private static final Logger LOGGER = Logger.getLogger(SchemaGenerator.class.getName());
  62 
  63     /**
  64      * Runs the schema generator.
  65      */
  66     public static void main(String[] args) throws Exception {
  67         System.exit(run(args));
  68     }
  69 
  70     public static int run(String[] args) throws Exception {
  71         try {
  72             ClassLoader cl = SecureLoader.getClassClassLoader(SchemaGenerator.class);
  73             if (cl==null) {
  74                 cl = SecureLoader.getSystemClassLoader();
  75             }
  76             return run(args, cl);
  77         } catch(Exception e) {
  78             LOGGER.log(Level.SEVERE, e.getMessage(), e);
  79             return -1;
  80         }
  81     }
  82 
  83     /**
  84      * Runs the schema generator.
  85      *
  86      * @param classLoader
  87      *      the schema generator will run in this classLoader.
  88      *      It needs to be able to load annotation processing and JAXB RI classes. Note that
  89      *      JAXB RI classes refer to annotation processing classes. Must not be null.
  90      *
  91      * @return
  92      *      exit code. 0 if success.
  93      *
  94      */
  95     public static int run(String[] args, ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
  96         final Options options = new Options();
  97         if (args.length ==0) {
  98             usage();
  99             return -1;
 100         }
 101         for (String arg : args) {
 102             if (arg.equals("-help")) {
 103                 usage();
 104                 return -1;
 105             }
 106 
 107             if (arg.equals("-version")) {
 108                 System.out.println(Messages.VERSION.format());
 109                 return -1;
 110             }
 111 
 112             if (arg.equals("-fullversion")) {
 113                 System.out.println(Messages.FULLVERSION.format());
 114                 return -1;
 115             }
 116 
 117         }
 118 
 119         try {
 120             options.parseArguments(args);
 121         } catch (BadCommandLineException e) {
 122             // there was an error in the command line.
 123             // print usage and abort.
 124             System.out.println(e.getMessage());
 125             System.out.println();
 126             usage();
 127             return -1;
 128         }
 129 
 130         Class schemagenRunner = classLoader.loadClass(Runner.class.getName());
 131         Method compileMethod = schemagenRunner.getDeclaredMethod("compile",String[].class,File.class);
 132 
 133         List<String> aptargs = new ArrayList<String>();
 134 
 135         if (options.encoding != null) {
 136             aptargs.add("-encoding");
 137             aptargs.add(options.encoding);
 138         }
 139 
 140         aptargs.add("-cp");
 141         aptargs.add(setClasspath(options.classpath)); // set original classpath + jaxb-api to be visible to annotation processor
 142 
 143         if(options.targetDir!=null) {
 144             aptargs.add("-d");
 145             aptargs.add(options.targetDir.getPath());
 146         }
 147 
 148         aptargs.addAll(options.arguments);
 149 
 150         String[] argsarray = aptargs.toArray(new String[aptargs.size()]);
 151         return ((Boolean) compileMethod.invoke(null, argsarray, options.episodeFile)) ? 0 : 1;
 152     }
 153 
 154     private static String setClasspath(String givenClasspath) {
 155         StringBuilder cp = new StringBuilder();
 156         appendPath(cp, givenClasspath);
 157         ClassLoader cl = Thread.currentThread().getContextClassLoader();
 158         while (cl != null) {
 159             if (cl instanceof URLClassLoader) {
 160                 for (URL url : ((URLClassLoader) cl).getURLs()) {
 161                     try {
 162                         appendPath(cp,new File(url.toURI()).getPath());
 163                     } catch(URISyntaxException ex) {
 164                         /*If the URL is not properly formated - skip it*/
 165                         LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
 166                     }
 167                 }
 168             }
 169             cl = cl.getParent();
 170         }
 171 
 172         appendPath(cp, findJaxbApiJar());
 173         return cp.toString();
 174     }
 175 
 176     private static void appendPath(StringBuilder cp, String url) {
 177         if (url == null || url.trim().isEmpty())
 178             return;
 179         if (cp.length() != 0)
 180             cp.append(File.pathSeparatorChar);
 181         cp.append(url);
 182     }
 183 
 184     /**
 185      * Computes the file system path of {@code jaxb-api.jar} so that
 186      * Annotation Processing will see them in the {@code -cp} option.
 187      *
 188      * <p>
 189      * In Java, you can't do this reliably (for that matter there's no guarantee
 190      * that such a jar file exists, such as in Glassfish), so we do the best we can.
 191      *
 192      * @return
 193      *      null if failed to locate it.
 194      */
 195     private static String findJaxbApiJar() {
 196         String url = Which.which(JAXBContext.class);
 197         if(url==null)       return null;    // impossible, but hey, let's be defensive
 198 
 199         if(!url.startsWith("jar:") || url.lastIndexOf('!')==-1)
 200             // no jar file
 201             return null;
 202 
 203         String jarFileUrl = url.substring(4,url.lastIndexOf('!'));
 204         if(!jarFileUrl.startsWith("file:"))
 205             return null;    // not from file system
 206 
 207         try {
 208             File f = new File(new URL(jarFileUrl).toURI());
 209             if (f.exists() && f.getName().endsWith(".jar")) { // see 6510966
 210                 return f.getPath();
 211             }
 212             f = new File(new URL(jarFileUrl).getFile());
 213             if (f.exists() && f.getName().endsWith(".jar")) { // this is here for potential backw. compatibility issues
 214                 return f.getPath();
 215             }
 216         } catch (URISyntaxException ex) {
 217             LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
 218         } catch (MalformedURLException ex) {
 219             LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
 220         }
 221         return null;
 222     }
 223 
 224     private static void usage( ) {
 225         System.out.println(Messages.USAGE.format());
 226     }
 227 
 228     public static final class Runner {
 229         public static boolean compile(String[] args, File episode) throws Exception {
 230 
 231             JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 232             DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
 233             StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
 234             JavacOptions options = JavacOptions.parse(compiler, fileManager, args);
 235             List<String> unrecognizedOptions = options.getUnrecognizedOptions();
 236             if (!unrecognizedOptions.isEmpty()) {
 237                 LOGGER.log(Level.WARNING, "Unrecognized options found: {0}", unrecognizedOptions);
 238             }
 239             Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(options.getFiles());
 240             JavaCompiler.CompilationTask task = compiler.getTask(
 241                     null,
 242                     fileManager,
 243                     diagnostics,
 244                     options.getRecognizedOptions(),
 245                     options.getClassNames(),
 246                     compilationUnits);
 247             com.sun.tools.internal.jxc.ap.SchemaGenerator r = new com.sun.tools.internal.jxc.ap.SchemaGenerator();
 248             if (episode != null)
 249                 r.setEpisodeFile(episode);
 250             task.setProcessors(Collections.singleton(r));
 251             return task.call();
 252         }
 253     }
 254 
 255     /**
 256           *  @author Peter von der Ahe
 257           */
 258     private static final class JavacOptions {
 259         private final List<String> recognizedOptions;
 260         private final List<String> classNames;
 261         private final List<File> files;
 262         private final List<String> unrecognizedOptions;
 263 
 264         private JavacOptions(List<String> recognizedOptions, List<String> classNames, List<File> files,
 265                              List<String> unrecognizedOptions) {
 266             this.recognizedOptions = recognizedOptions;
 267             this.classNames = classNames;
 268             this.files = files;
 269             this.unrecognizedOptions = unrecognizedOptions;
 270         }
 271 
 272         public static JavacOptions parse(OptionChecker primary, OptionChecker secondary, String... arguments) {
 273             List<String> recognizedOptions = new ArrayList<String>();
 274             List<String> unrecognizedOptions = new ArrayList<String>();
 275             List<String> classNames = new ArrayList<String>();
 276             List<File> files = new ArrayList<File>();
 277             for (int i = 0; i < arguments.length; i++) {
 278                 String argument = arguments[i];
 279                 int optionCount = primary.isSupportedOption(argument);
 280                 if (optionCount < 0) {
 281                     optionCount = secondary.isSupportedOption(argument);
 282                 }
 283                 if (optionCount < 0) {
 284                     File file = new File(argument);
 285                     if (file.exists())
 286                         files.add(file);
 287                     else if (SourceVersion.isName(argument))
 288                         classNames.add(argument);
 289                     else
 290                         unrecognizedOptions.add(argument);
 291                 } else {
 292                     for (int j = 0; j < optionCount + 1; j++) {
 293                         int index = i + j;
 294                         if (index == arguments.length) throw new IllegalArgumentException(argument);
 295                         recognizedOptions.add(arguments[index]);
 296                     }
 297                     i += optionCount;
 298                 }
 299             }
 300             return new JavacOptions(recognizedOptions, classNames, files, unrecognizedOptions);
 301         }
 302 
 303         /**
 304                      * Returns the list of recognized options and their arguments.
 305                      *
 306                      * @return a list of options
 307                      */
 308         public List<String> getRecognizedOptions() {
 309             return Collections.unmodifiableList(recognizedOptions);
 310         }
 311 
 312         /**
 313                      * Returns the list of file names.
 314                      *
 315                      * @return a list of file names
 316                      */
 317         public List<File> getFiles() {
 318             return Collections.unmodifiableList(files);
 319         }
 320 
 321         /**
 322                      * Returns the list of class names.
 323                      *
 324                      * @return a list of class names
 325                      */
 326         public List<String> getClassNames() {
 327             return Collections.unmodifiableList(classNames);
 328         }
 329 
 330         /**
 331                      * Returns the list of unrecognized options.
 332                      *
 333                      * @return a list of unrecognized options
 334                      */
 335         public List<String> getUnrecognizedOptions() {
 336             return Collections.unmodifiableList(unrecognizedOptions);
 337         }
 338 
 339         @Override
 340         public String toString() {
 341             return String.format("recognizedOptions = %s; classNames = %s; " + "files = %s; unrecognizedOptions = %s", recognizedOptions, classNames, files, unrecognizedOptions);
 342         }
 343     }
 344 }