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.Collection;
  50 import java.util.Collections;
  51 import java.util.List;
  52 import java.util.logging.Level;
  53 import java.util.logging.Logger;
  54 
  55 /**
  56  * CLI entry-point to the schema generator.
  57  *
  58  * @author Bhakti Mehta
  59  */
  60 public class SchemaGenerator {
  61 
  62     private static final Logger LOGGER = Logger.getLogger(SchemaGenerator.class.getName());
  63 
  64     /**
  65      * Runs the schema generator.
  66      */
  67     public static void main(String[] args) throws Exception {
  68         System.exit(run(args));
  69     }
  70 
  71     public static int run(String[] args) throws Exception {
  72         try {
  73             ClassLoader cl = SecureLoader.getClassClassLoader(SchemaGenerator.class);
  74             if (cl==null) {
  75                 cl = SecureLoader.getSystemClassLoader();
  76             }
  77             return run(args, cl);
  78         } catch(Exception e) {
  79             LOGGER.log(Level.SEVERE, e.getMessage(), e);
  80             return -1;
  81         }
  82     }
  83 
  84     /**
  85      * Runs the schema generator.
  86      *
  87      * @param classLoader
  88      *      the schema generator will run in this classLoader.
  89      *      It needs to be able to load annotation processing and JAXB RI classes. Note that
  90      *      JAXB RI classes refer to annotation processing classes. Must not be null.
  91      *
  92      * @return
  93      *      exit code. 0 if success.
  94      *
  95      */
  96     public static int run(String[] args, ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
  97         final Options options = new Options();
  98         if (args.length ==0) {
  99             usage();
 100             return -1;
 101         }
 102         for (String arg : args) {
 103             if (arg.equals("-help")) {
 104                 usage();
 105                 return -1;
 106             }
 107 
 108             if (arg.equals("-version")) {
 109                 System.out.println(Messages.VERSION.format());
 110                 return -1;
 111             }
 112 
 113             if (arg.equals("-fullversion")) {
 114                 System.out.println(Messages.FULLVERSION.format());
 115                 return -1;
 116             }
 117 
 118         }
 119 
 120         try {
 121             options.parseArguments(args);
 122         } catch (BadCommandLineException e) {
 123             // there was an error in the command line.
 124             // print usage and abort.
 125             System.out.println(e.getMessage());
 126             System.out.println();
 127             usage();
 128             return -1;
 129         }
 130 
 131         Class schemagenRunner = classLoader.loadClass(Runner.class.getName());
 132         Method compileMethod = schemagenRunner.getDeclaredMethod("compile",String[].class,File.class);
 133 
 134         List<String> aptargs = new ArrayList<String>();
 135 
 136         if (options.encoding != null) {
 137             aptargs.add("-encoding");
 138             aptargs.add(options.encoding);
 139         }
 140 
 141         aptargs.add("-cp");
 142         aptargs.add(setClasspath(options.classpath)); // set original classpath + jaxb-api to be visible to annotation processor
 143 
 144         if(options.targetDir!=null) {
 145             aptargs.add("-d");
 146             aptargs.add(options.targetDir.getPath());
 147         }
 148 
 149         aptargs.addAll(options.arguments);
 150 
 151         String[] argsarray = aptargs.toArray(new String[aptargs.size()]);
 152         return ((Boolean) compileMethod.invoke(null, argsarray, options.episodeFile)) ? 0 : 1;
 153     }
 154 
 155     private static String setClasspath(String givenClasspath) {
 156         StringBuilder cp = new StringBuilder();
 157         appendPath(cp, givenClasspath);
 158         ClassLoader cl = Thread.currentThread().getContextClassLoader();
 159         while (cl != null) {
 160             if (cl instanceof URLClassLoader) {
 161                 for (URL url : ((URLClassLoader) cl).getURLs()) {
 162                     try {
 163                         appendPath(cp,new File(url.toURI()).getPath());
 164                     } catch(URISyntaxException ex) {
 165                         /*If the URL is not properly formated - skip it*/
 166                         LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
 167                     }
 168                 }
 169             }
 170             cl = cl.getParent();
 171         }
 172 
 173         appendPath(cp, findJaxbApiJar());
 174         return cp.toString();
 175     }
 176 
 177     private static void appendPath(StringBuilder cp, String url) {
 178         if (url == null || url.trim().isEmpty())
 179             return;
 180         if (cp.length() != 0)
 181             cp.append(File.pathSeparatorChar);
 182         cp.append(url);
 183     }
 184 
 185     /**
 186      * Computes the file system path of {@code jaxb-api.jar} so that
 187      * Annotation Processing will see them in the {@code -cp} option.
 188      *
 189      * <p>
 190      * In Java, you can't do this reliably (for that matter there's no guarantee
 191      * that such a jar file exists, such as in Glassfish), so we do the best we can.
 192      *
 193      * @return
 194      *      null if failed to locate it.
 195      */
 196     private static String findJaxbApiJar() {
 197         String url = Which.which(JAXBContext.class);
 198         if(url==null)       return null;    // impossible, but hey, let's be defensive
 199 
 200         if(!url.startsWith("jar:") || url.lastIndexOf('!')==-1)
 201             // no jar file
 202             return null;
 203 
 204         String jarFileUrl = url.substring(4,url.lastIndexOf('!'));
 205         if(!jarFileUrl.startsWith("file:"))
 206             return null;    // not from file system
 207 
 208         try {
 209             File f = new File(new URL(jarFileUrl).toURI());
 210             if (f.exists() && f.getName().endsWith(".jar")) { // see 6510966
 211                 return f.getPath();
 212             }
 213             f = new File(new URL(jarFileUrl).getFile());
 214             if (f.exists() && f.getName().endsWith(".jar")) { // this is here for potential backw. compatibility issues
 215                 return f.getPath();
 216             }
 217         } catch (URISyntaxException ex) {
 218             LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
 219         } catch (MalformedURLException ex) {
 220             LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
 221         }
 222         return null;
 223     }
 224 
 225     private static void usage( ) {
 226         System.out.println(Messages.USAGE.format());
 227     }
 228 
 229     public static final class Runner {
 230         public static boolean compile(String[] args, File episode) throws Exception {
 231 
 232             JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 233             DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
 234             StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
 235             JavacOptions options = JavacOptions.parse(compiler, fileManager, args);
 236             List<String> unrecognizedOptions = options.getUnrecognizedOptions();
 237             if (!unrecognizedOptions.isEmpty()) {
 238                 LOGGER.log(Level.WARNING, "Unrecognized options found: {0}", unrecognizedOptions);
 239             }
 240             Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(options.getFiles());
 241             JavaCompiler.CompilationTask task = compiler.getTask(
 242                     null,
 243                     fileManager,
 244                     diagnostics,
 245                     options.getRecognizedOptions(),
 246                     options.getClassNames(),
 247                     compilationUnits);
 248             com.sun.tools.internal.jxc.ap.SchemaGenerator r = new com.sun.tools.internal.jxc.ap.SchemaGenerator();
 249             if (episode != null)
 250                 r.setEpisodeFile(episode);
 251             task.setProcessors(Collections.singleton(r));
 252             boolean res = task.call();
 253             //Print messages generated by compiler
 254             for (Diagnostic<? extends JavaFileObject> d : diagnostics.getDiagnostics()) {
 255                  System.err.println(d.toString());
 256             }
 257             return res;
 258         }
 259     }
 260 
 261     /**
 262           *  @author Peter von der Ahe
 263           */
 264     private static final class JavacOptions {
 265         private final List<String> recognizedOptions;
 266         private final List<String> classNames;
 267         private final List<File> files;
 268         private final List<String> unrecognizedOptions;
 269 
 270         private JavacOptions(List<String> recognizedOptions, List<String> classNames, List<File> files,
 271                              List<String> unrecognizedOptions) {
 272             this.recognizedOptions = recognizedOptions;
 273             this.classNames = classNames;
 274             this.files = files;
 275             this.unrecognizedOptions = unrecognizedOptions;
 276         }
 277 
 278         public static JavacOptions parse(OptionChecker primary, OptionChecker secondary, String... arguments) {
 279             List<String> recognizedOptions = new ArrayList<String>();
 280             List<String> unrecognizedOptions = new ArrayList<String>();
 281             List<String> classNames = new ArrayList<String>();
 282             List<File> files = new ArrayList<File>();
 283             for (int i = 0; i < arguments.length; i++) {
 284                 String argument = arguments[i];
 285                 int optionCount = primary.isSupportedOption(argument);
 286                 if (optionCount < 0) {
 287                     optionCount = secondary.isSupportedOption(argument);
 288                 }
 289                 if (optionCount < 0) {
 290                     File file = new File(argument);
 291                     if (file.exists())
 292                         files.add(file);
 293                     else if (SourceVersion.isName(argument))
 294                         classNames.add(argument);
 295                     else
 296                         unrecognizedOptions.add(argument);
 297                 } else {
 298                     for (int j = 0; j < optionCount + 1; j++) {
 299                         int index = i + j;
 300                         if (index == arguments.length) throw new IllegalArgumentException(argument);
 301                         recognizedOptions.add(arguments[index]);
 302                     }
 303                     i += optionCount;
 304                 }
 305             }
 306             return new JavacOptions(recognizedOptions, classNames, files, unrecognizedOptions);
 307         }
 308 
 309         /**
 310                      * Returns the list of recognized options and their arguments.
 311                      *
 312                      * @return a list of options
 313                      */
 314         public List<String> getRecognizedOptions() {
 315             return Collections.unmodifiableList(recognizedOptions);
 316         }
 317 
 318         /**
 319                      * Returns the list of file names.
 320                      *
 321                      * @return a list of file names
 322                      */
 323         public List<File> getFiles() {
 324             return Collections.unmodifiableList(files);
 325         }
 326 
 327         /**
 328                      * Returns the list of class names.
 329                      *
 330                      * @return a list of class names
 331                      */
 332         public List<String> getClassNames() {
 333             return Collections.unmodifiableList(classNames);
 334         }
 335 
 336         /**
 337                      * Returns the list of unrecognized options.
 338                      *
 339                      * @return a list of unrecognized options
 340                      */
 341         public List<String> getUnrecognizedOptions() {
 342             return Collections.unmodifiableList(unrecognizedOptions);
 343         }
 344 
 345         @Override
 346         public String toString() {
 347             return String.format("recognizedOptions = %s; classNames = %s; " + "files = %s; unrecognizedOptions = %s", recognizedOptions, classNames, files, unrecognizedOptions);
 348         }
 349     }
 350 }