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.Diagnostic;
  34 import javax.tools.DiagnosticCollector;
  35 import javax.tools.JavaCompiler;
  36 import javax.tools.JavaFileObject;
  37 import javax.tools.OptionChecker;
  38 import javax.tools.StandardJavaFileManager;
  39 import javax.tools.ToolProvider;
  40 import javax.xml.bind.JAXBContext;
  41 import java.io.File;
  42 import java.lang.reflect.InvocationTargetException;
  43 import java.lang.reflect.Method;
  44 import java.net.MalformedURLException;
  45 import java.net.URISyntaxException;
  46 import java.net.URL;
  47 import java.net.URLClassLoader;
  48 import java.util.ArrayList;
  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         aptargs.add("--add-modules");
 144         aptargs.add("java.xml.bind");
 145 
 146         if(options.targetDir!=null) {
 147             aptargs.add("-d");
 148             aptargs.add(options.targetDir.getPath());
 149         }
 150 
 151         aptargs.addAll(options.arguments);
 152 
 153         String[] argsarray = aptargs.toArray(new String[aptargs.size()]);
 154         return ((Boolean) compileMethod.invoke(null, argsarray, options.episodeFile)) ? 0 : 1;
 155     }
 156 
 157     private static String setClasspath(String givenClasspath) {
 158         StringBuilder cp = new StringBuilder();
 159         appendPath(cp, givenClasspath);
 160         ClassLoader cl = Thread.currentThread().getContextClassLoader();
 161         while (cl != null) {
 162             if (cl instanceof URLClassLoader) {
 163                 for (URL url : ((URLClassLoader) cl).getURLs()) {
 164                     try {
 165                         appendPath(cp,new File(url.toURI()).getPath());
 166                     } catch(URISyntaxException ex) {
 167                         /*If the URL is not properly formated - skip it*/
 168                         LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
 169                     }
 170                 }
 171             }
 172             cl = cl.getParent();
 173         }
 174 
 175         appendPath(cp, findJaxbApiJar());
 176         return cp.toString();
 177     }
 178 
 179     private static void appendPath(StringBuilder cp, String url) {
 180         if (url == null || url.trim().isEmpty())
 181             return;
 182         if (cp.length() != 0)
 183             cp.append(File.pathSeparatorChar);
 184         cp.append(url);
 185     }
 186 
 187     /**
 188      * Computes the file system path of {@code jaxb-api.jar} so that
 189      * Annotation Processing will see them in the {@code -cp} option.
 190      *
 191      * <p>
 192      * In Java, you can't do this reliably (for that matter there's no guarantee
 193      * that such a jar file exists, such as in Glassfish), so we do the best we can.
 194      *
 195      * @return
 196      *      null if failed to locate it.
 197      */
 198     private static String findJaxbApiJar() {
 199         String url = Which.which(JAXBContext.class);
 200         if(url==null)       return null;    // impossible, but hey, let's be defensive
 201 
 202         if(!url.startsWith("jar:") || url.lastIndexOf('!')==-1)
 203             // no jar file
 204             return null;
 205 
 206         String jarFileUrl = url.substring(4,url.lastIndexOf('!'));
 207         if(!jarFileUrl.startsWith("file:"))
 208             return null;    // not from file system
 209 
 210         try {
 211             File f = new File(new URL(jarFileUrl).toURI());
 212             if (f.exists() && f.getName().endsWith(".jar")) { // see 6510966
 213                 return f.getPath();
 214             }
 215             f = new File(new URL(jarFileUrl).getFile());
 216             if (f.exists() && f.getName().endsWith(".jar")) { // this is here for potential backw. compatibility issues
 217                 return f.getPath();
 218             }
 219         } catch (URISyntaxException ex) {
 220             LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
 221         } catch (MalformedURLException ex) {
 222             LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
 223         }
 224         return null;
 225     }
 226 
 227     private static void usage( ) {
 228         System.out.println(Messages.USAGE.format());
 229     }
 230 
 231     public static final class Runner {
 232         public static boolean compile(String[] args, File episode) throws Exception {
 233 
 234             JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 235             DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
 236             StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
 237             JavacOptions options = JavacOptions.parse(compiler, fileManager, args);
 238             List<String> unrecognizedOptions = options.getUnrecognizedOptions();
 239             if (!unrecognizedOptions.isEmpty()) {
 240                 LOGGER.log(Level.WARNING, "Unrecognized options found: {0}", unrecognizedOptions);
 241             }
 242             Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(options.getFiles());
 243             JavaCompiler.CompilationTask task = compiler.getTask(
 244                     null,
 245                     fileManager,
 246                     diagnostics,
 247                     options.getRecognizedOptions(),
 248                     options.getClassNames(),
 249                     compilationUnits);
 250             com.sun.tools.internal.jxc.ap.SchemaGenerator r = new com.sun.tools.internal.jxc.ap.SchemaGenerator();
 251             if (episode != null)
 252                 r.setEpisodeFile(episode);
 253             task.setProcessors(Collections.singleton(r));
 254             boolean res = task.call();
 255             //Print compiler generated messages
 256             for( Diagnostic<? extends JavaFileObject> d : diagnostics.getDiagnostics() ) {
 257                  System.err.println(d.toString());
 258             }
 259             return res;
 260         }
 261     }
 262 
 263     /**
 264           *  @author Peter von der Ahe
 265           */
 266     private static final class JavacOptions {
 267         private final List<String> recognizedOptions;
 268         private final List<String> classNames;
 269         private final List<File> files;
 270         private final List<String> unrecognizedOptions;
 271 
 272         private JavacOptions(List<String> recognizedOptions, List<String> classNames, List<File> files,
 273                              List<String> unrecognizedOptions) {
 274             this.recognizedOptions = recognizedOptions;
 275             this.classNames = classNames;
 276             this.files = files;
 277             this.unrecognizedOptions = unrecognizedOptions;
 278         }
 279 
 280         public static JavacOptions parse(OptionChecker primary, OptionChecker secondary, String... arguments) {
 281             List<String> recognizedOptions = new ArrayList<String>();
 282             List<String> unrecognizedOptions = new ArrayList<String>();
 283             List<String> classNames = new ArrayList<String>();
 284             List<File> files = new ArrayList<File>();
 285             for (int i = 0; i < arguments.length; i++) {
 286                 String argument = arguments[i];
 287                 int optionCount = primary.isSupportedOption(argument);
 288                 if (optionCount < 0) {
 289                     optionCount = secondary.isSupportedOption(argument);
 290                 }
 291                 if (optionCount < 0) {
 292                     File file = new File(argument);
 293                     if (file.exists())
 294                         files.add(file);
 295                     else if (SourceVersion.isName(argument))
 296                         classNames.add(argument);
 297                     else
 298                         unrecognizedOptions.add(argument);
 299                 } else {
 300                     for (int j = 0; j < optionCount + 1; j++) {
 301                         int index = i + j;
 302                         if (index == arguments.length) throw new IllegalArgumentException(argument);
 303                         recognizedOptions.add(arguments[index]);
 304                     }
 305                     i += optionCount;
 306                 }
 307             }
 308             return new JavacOptions(recognizedOptions, classNames, files, unrecognizedOptions);
 309         }
 310 
 311         /**
 312                      * Returns the list of recognized options and their arguments.
 313                      *
 314                      * @return a list of options
 315                      */
 316         public List<String> getRecognizedOptions() {
 317             return Collections.unmodifiableList(recognizedOptions);
 318         }
 319 
 320         /**
 321                      * Returns the list of file names.
 322                      *
 323                      * @return a list of file names
 324                      */
 325         public List<File> getFiles() {
 326             return Collections.unmodifiableList(files);
 327         }
 328 
 329         /**
 330                      * Returns the list of class names.
 331                      *
 332                      * @return a list of class names
 333                      */
 334         public List<String> getClassNames() {
 335             return Collections.unmodifiableList(classNames);
 336         }
 337 
 338         /**
 339                      * Returns the list of unrecognized options.
 340                      *
 341                      * @return a list of unrecognized options
 342                      */
 343         public List<String> getUnrecognizedOptions() {
 344             return Collections.unmodifiableList(unrecognizedOptions);
 345         }
 346 
 347         @Override
 348         public String toString() {
 349             return String.format("recognizedOptions = %s; classNames = %s; " + "files = %s; unrecognizedOptions = %s", recognizedOptions, classNames, files, unrecognizedOptions);
 350         }
 351     }
 352 }