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