langtools/src/share/classes/com/sun/tools/sjavac/Main.java
changeset 15368 2577ddb7e710
child 16338 7d42d41478e6
equal deleted inserted replaced
15367:31b57f2b8d0b 15368:2577ddb7e710
       
     1 /*
       
     2  * Copyright (c) 2012, 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.sjavac;
       
    27 
       
    28 import java.io.File;
       
    29 import java.util.HashMap;
       
    30 import java.util.HashSet;
       
    31 import java.util.LinkedList;
       
    32 import java.util.List;
       
    33 import java.util.Map;
       
    34 import java.util.Set;
       
    35 import java.util.regex.Matcher;
       
    36 import java.util.regex.Pattern;
       
    37 import com.sun.tools.sjavac.server.JavacServer;
       
    38 import java.io.IOException;
       
    39 import java.io.PrintStream;
       
    40 import java.util.*;
       
    41 
       
    42 /**
       
    43  * The main class of the smart javac wrapper tool.
       
    44  *
       
    45  * <p><b>This is NOT part of any supported API.
       
    46  * If you write code that depends on this, you do so at your own
       
    47  * risk.  This code and its internal interfaces are subject to change
       
    48  * or deletion without notice.</b></p>
       
    49  */
       
    50 public class Main {
       
    51 
       
    52     /*  This is a smart javac wrapper primarily used when building the OpenJDK,
       
    53         though other projects are welcome to use it too. But please be aware
       
    54         that it is not an official api and will change in the future.
       
    55         (We really mean it!)
       
    56 
       
    57         Goals:
       
    58 
       
    59         ** Create a state file, containing information about the build, so
       
    60            that incremental builds only rebuild what is necessary. Also the
       
    61            state file can be used by make/ant to detect when to trigger
       
    62            a call to the smart javac wrapper.
       
    63 
       
    64            This file is called bin/javac_state (assuming that you specified "-d bin")
       
    65            Thus the simplest makefile is:
       
    66 
       
    67            SJAVAC=java -cp .../tools.jar com.sun.tools.sjavac.Main
       
    68            SRCS=$(shell find src -name "*.java")
       
    69            bin/javac_state : $(SRCS)
       
    70                   $(SJAVAC) src -d bin
       
    71 
       
    72            This makefile will run very fast and detect properly when Java code needs to
       
    73            be recompiled. The smart javac wrapper will then use the information in java_state
       
    74            to do an efficient incremental compile.
       
    75 
       
    76            Previously it was near enough impossible to write an efficient makefile for Java
       
    77            with support for incremental builds and dependency tracking.
       
    78 
       
    79         ** Separate java sources to be compiled from java
       
    80            sources used >only< for linking. The options:
       
    81 
       
    82            "dir" points to root dir with sources to be compiled
       
    83            "-sourcepath dir" points to root dir with sources used only for linking
       
    84            "-classpath dir" points to dir with classes used only for linking (as before)
       
    85 
       
    86         ** Use all cores for compilation by default.
       
    87            "-j 4" limit the number of cores to 4.
       
    88            For the moment, the sjavac server additionally limits the number of cores to three.
       
    89            This will improve in the future when more sharing is performed between concurrent JavaCompilers.
       
    90 
       
    91         ** Basic translation support from other sources to java, and then compilation of the generated java.
       
    92            This functionality might be moved into annotation processors instead.
       
    93            Again this is driven by the OpenJDK sources where properties and a few other types of files
       
    94            are converted into Java sources regularily. The javac_state embraces copy and tr, and perform
       
    95            incremental recompiles and copying for these as well. META-INF will be a special copy rule
       
    96            that will copy any files found below any META-INF dir in src to the bin/META-INF dir.
       
    97            "-copy .gif"
       
    98            "-copy META-INF"
       
    99            "-tr .prop=com.sun.tools.javac.smart.CompileProperties
       
   100            "-tr .propp=com.sun.tools.javac.smart.CompileProperties,java.util.ListResourceBundle
       
   101            "-tr .proppp=com.sun.tools.javac.smart.CompileProperties,sun.util.resources.LocaleNamesBundle
       
   102 
       
   103         ** Control which classes in the src,sourcepath and classpath that javac is allowed to see.
       
   104            Again, this is necessary to deal with the source code structure of the OpenJDK which is
       
   105            intricate (read messy).
       
   106 
       
   107            "-i tools.*" to include the tools package and all its subpackages in the build.
       
   108            "-x tools.net.aviancarrier.*" to exclude the aviancarrier package and all its sources and subpackages.
       
   109            "-x tools.net.drums" to exclude the drums package only, keep its subpackages.
       
   110            "-xf tools/net/Bar.java" // Do not compile this file...
       
   111            "-xf *Bor.java" // Do not compile Bor.java wherever it is found, BUT do compile ABor.java!
       
   112            "-if tools/net/Bor.java" // Only compile this file...odd, but sometimes used.
       
   113 
       
   114         ** The smart javac wrapper is driven by the modification time on the source files and compared
       
   115            to the modification times written into the javac_state file.
       
   116 
       
   117            It does not compare the modification time of the source with the modification time of the artifact.
       
   118            However it will detect if the modification time of an artifact has changed compared to the java_state,
       
   119            and this will trigger a delete of the artifact and a subsequent recompile of the source.
       
   120 
       
   121            The smart javac wrapper is not a generic makefile/ant system. Its purpose is to compile java source
       
   122            as the final step before the output dir is finalized and immediately jared, or jmodded. The output
       
   123            dir should be considered opaque. Do not write into the outputdir yourself!
       
   124            Any artifacts found in the outputdir that javac_state does not know of, will be deleted!
       
   125            This can however be prevented, using the switch --permit-unidentified-artifacts
       
   126            This switch is necessary when build the OpenJDK because its makefiles still write directly to
       
   127            the output classes dirs.
       
   128 
       
   129            Any makefile/ant rules that want to put contents into the outputdir should put the content
       
   130            in one of several source roots. Static content that is under version control, can be put in the same source
       
   131            code tree as the Java sources. Dynamic content that is generated by make/ant on the fly, should
       
   132            be put in a separate gensrc_stuff root. The smart javac wrapper call will then take the arguments:
       
   133            "gensrc_stuff src -d bin"
       
   134 
       
   135         The command line:
       
   136         java -cp tools.jar com.sun.tools.sjavac.Main \
       
   137              -i "com.bar.*" -x "com.bar.foo.*" \
       
   138              first_root \
       
   139              -i "com.bar.foo.*" \
       
   140              second_root \
       
   141              -x "org.net.*" \
       
   142              -sourcepath link_root_sources \
       
   143              -classpath link_root_classes \
       
   144              -d bin
       
   145 
       
   146         Will compile all sources for package com.bar and its subpackages, found below first_root,
       
   147         except the package com.bar.foo (and its subpackages), for which the sources are picked
       
   148         from second_root instead. It will link against classes in link_root_classes and against
       
   149         sources in link_root_sources, but will not see (try to link against) sources matching org.net.*
       
   150         but will link against org.net* classes (if they exist) in link_root_classes.
       
   151 
       
   152         (If you want a set of complex filter rules to be applied to several source directories, without
       
   153          having to repeat the the filter rules for each root. You can use the explicit -src option. For example:
       
   154          sjavac -x "com.foo.*" -src root1:root2:root3  )
       
   155 
       
   156         The resulting classes are written into bin.
       
   157     */
       
   158 
       
   159     // This is the final destination for classes and copied files.
       
   160     private File bin_dir;
       
   161     // This is where the annotation process will put generated sources.
       
   162     private File gensrc_dir;
       
   163     // This is where javac -h puts the generated c-header files.
       
   164     private File header_dir;
       
   165 
       
   166     // This file contains the list of sources genereated by the makefile.
       
   167     // We double check that our calculated list of sources matches this list,
       
   168     // if not, then we terminate with an error!
       
   169     private File makefile_source_list;
       
   170     // The challenging task to manage an incremental build is done by javac_state.
       
   171     private JavacState javac_state;
       
   172 
       
   173     // The suffix rules tells you for example, that .java files should be compiled,
       
   174     // and .html files should be copied and .properties files be translated.
       
   175     Map<String,Transformer> suffix_rules;
       
   176 
       
   177     public static void main(String... args)  {
       
   178         if (args.length > 0 && args[0].startsWith("--startserver:")) {
       
   179             if (args.length>1) {
       
   180                 Log.error("When spawning a background server, only a single --startserver argument is allowed.");
       
   181                 return;
       
   182             }
       
   183             // Spawn a background server.
       
   184             int rc = JavacServer.startServer(args[0], System.err);
       
   185             System.exit(rc);
       
   186         }
       
   187         Main main = new Main();
       
   188         int rc = main.go(args, System.out, System.err);
       
   189         // Remove the portfile, but only if this background=false was used.
       
   190         JavacServer.cleanup(args);
       
   191         System.exit(rc);
       
   192     }
       
   193 
       
   194     private void printHelp() {
       
   195         System.out.println("Usage: sjavac <options>\n"+
       
   196                            "where required options are:\n"+
       
   197                            "dir                        Compile all sources in dir recursively\n"+
       
   198                            "-d dir                     Store generated classes here and the javac_state file\n"+
       
   199                            "--server:portfile=/tmp/abc Use a background sjavac server\n\n"+
       
   200                            "All other arguments as javac, except -implicit:none which is forced by default.\n"+
       
   201                            "No java source files can be supplied on the command line, nor can an @file be supplied.\n\n"+
       
   202                            "Warning!\n"+
       
   203                            "This tool might disappear at any time, and its command line options might change at any time!");
       
   204     }
       
   205 
       
   206     public int go(String[] args, PrintStream out, PrintStream err) {
       
   207         try {
       
   208             if (args.length == 0 || findJavaSourceFiles(args) || findAtFile(args) || null==Util.findServerSettings(args)) {
       
   209                 printHelp();
       
   210                 return 0;
       
   211             }
       
   212 
       
   213             Log.setLogLevel(findLogLevel(args), out, err);
       
   214             String server_settings = Util.findServerSettings(args);
       
   215             args = verifyImplicitOption(args);
       
   216             // Find the source root directories, and add the -src option before these, if not there already.
       
   217             args = addSrcBeforeDirectories(args);
       
   218             // Check that there is at least one -src supplied.
       
   219             checkSrcOption(args);
       
   220             // Check that there is one -d supplied.
       
   221             bin_dir = findDirectoryOption(args,"-d","output", true, false, true);
       
   222             gensrc_dir = findDirectoryOption(args,"-s","gensrc", false, false, true);
       
   223             header_dir = findDirectoryOption(args,"-h","headers", false, false, true);
       
   224             makefile_source_list = findFileOption(args,"--compare-found-sources","makefile source list", false);
       
   225 
       
   226             // Load the prev build state database.
       
   227             javac_state = JavacState.load(args, bin_dir, gensrc_dir, header_dir,
       
   228                     findBooleanOption(args, "--permit-unidentified-artifacts"), out, err);
       
   229 
       
   230             // Setup the suffix rules from the command line.
       
   231             suffix_rules = javac_state.getJavaSuffixRule();
       
   232             findTranslateOptions(args, suffix_rules);
       
   233             if (suffix_rules.keySet().size() > 1 && gensrc_dir == null) {
       
   234                 Log.error("You have translators but no gensrc dir (-s) specified!");
       
   235                 return -1;
       
   236             }
       
   237             findCopyOptions(args, suffix_rules);
       
   238 
       
   239             // All found modules are put here.
       
   240             Map<String,Module> modules = new HashMap<String,Module>();
       
   241             // We start out in the legacy empty no-name module.
       
   242             // As soon as we stumble on a module-info.java file we change to that module.
       
   243             Module current_module = new Module("", "");
       
   244             modules.put("", current_module);
       
   245 
       
   246             // Find all sources, use the suffix rules to know which files are sources.
       
   247             Map<String,Source> sources = new HashMap<String,Source>();
       
   248             // Find the files, this will automatically populate the found modules
       
   249             // with found packages where the sources are found!
       
   250             findFiles(args, "-src", suffix_rules.keySet(), sources, modules, current_module, false);
       
   251 
       
   252             if (sources.isEmpty()) {
       
   253                 Log.error("Found nothing to compile!");
       
   254                 return -1;
       
   255             }
       
   256 
       
   257             // Find all source files allowable for linking.
       
   258             // We might find more modules here as well.
       
   259             Map<String,Source> sources_to_link_to = new HashMap<String,Source>();
       
   260             // Always reuse -src for linking as well! This means that we might
       
   261             // get two -sourcepath on the commandline after the rewrite, which is
       
   262             // fine. We can have as many as we like. You need to have separate -src/-sourcepath/-classpath
       
   263             // if you need different filtering rules for different roots. If you have the same filtering
       
   264             // rules for all sourcepath roots, you can concatenate them using :(;) as before.
       
   265               rewriteOptions(args, "-src", "-sourcepath");
       
   266             findFiles(args, "-sourcepath", Util.set(".java"), sources_to_link_to, modules, current_module, true);
       
   267 
       
   268             // Find all class files allowable for linking.
       
   269             // And pickup knowledge of all modules found here.
       
   270             // This cannot currently filter classes inside jar files.
       
   271             Map<String,Source> classes_to_link_to = new HashMap<String,Source>();
       
   272 //          findFiles(args, "-classpath", Util.set(".class"), classes_to_link_to, modules, current_module, true);
       
   273 
       
   274             // Find all module sources allowable for linking.
       
   275             Map<String,Source> modules_to_link_to = new HashMap<String,Source>();
       
   276  //         findFiles(args, "-modulepath", Util.set(".class"), modules_to_link_to, modules, current_module, true);
       
   277 
       
   278             // Add the set of sources to the build database.
       
   279             javac_state.now().collectPackagesSourcesAndArtifacts(modules);
       
   280             javac_state.now().checkInternalState("checking sources", false, sources);
       
   281             javac_state.now().checkInternalState("checking linked sources", true, sources_to_link_to);
       
   282             javac_state.setVisibleSources(sources_to_link_to);
       
   283 
       
   284             // If there is any change in the source files, taint packages
       
   285             // and mark the database in need of saving.
       
   286             javac_state.checkSourceStatus(false);
       
   287 
       
   288             // Find all existing artifacts. Their timestamp will match the last modified timestamps stored
       
   289             // in javac_state, simply because loading of the JavacState will clean out all artifacts
       
   290             // that do not match the javac_state database.
       
   291             javac_state.findAllArtifacts();
       
   292 
       
   293             // Remove unidentified artifacts from the bin, gensrc and header dirs.
       
   294             // (Unless we allow them to be there.)
       
   295             // I.e. artifacts that are not known according to the build database (javac_state).
       
   296             // For examples, files that have been manually copied into these dirs.
       
   297             // Artifacts with bad timestamps (ie the on disk timestamp does not match the timestamp
       
   298             // in javac_state) have already been removed when the javac_state was loaded.
       
   299             if (!findBooleanOption(args, "--permit-unidentified-artifacts")) {
       
   300                 javac_state.removeUnidentifiedArtifacts();
       
   301             }
       
   302             // Go through all sources and taint all packages that miss artifacts.
       
   303             javac_state.taintPackagesThatMissArtifacts();
       
   304 
       
   305             // Now clean out all known artifacts belonging to tainted packages.
       
   306             javac_state.deleteClassArtifactsInTaintedPackages();
       
   307             // Copy files, for example property files, images files, xml files etc etc.
       
   308             javac_state.performCopying(bin_dir, suffix_rules);
       
   309             // Translate files, for example compile properties or compile idls.
       
   310             javac_state.performTranslation(gensrc_dir, suffix_rules);
       
   311             // Add any potentially generated java sources to the tobe compiled list.
       
   312             // (Generated sources must always have a package.)
       
   313             Map<String,Source> generated_sources = new HashMap<String,Source>();
       
   314             Source.scanRoot(gensrc_dir, Util.set(".java"), null, null, null, null,
       
   315                    generated_sources, modules, current_module, false, true, false);
       
   316             javac_state.now().collectPackagesSourcesAndArtifacts(modules);
       
   317             // Recheck the the source files and their timestamps again.
       
   318             javac_state.checkSourceStatus(true);
       
   319 
       
   320             // Now do a safety check that the list of source files is identical
       
   321             // to the list Make believes we are compiling. If we do not get this
       
   322             // right, then incremental builds will fail with subtility.
       
   323             // If any difference is detected, then we will fail hard here.
       
   324             // This is an important safety net.
       
   325             javac_state.compareWithMakefileList(makefile_source_list);
       
   326 
       
   327             // Do the compilations, repeatedly until no tainted packages exist.
       
   328             boolean again;
       
   329             // Collect the name of all compiled packages.
       
   330             Set<String> recently_compiled = new HashSet<String>();
       
   331             boolean[] rc = new boolean[1];
       
   332             do {
       
   333                 // Clean out artifacts in tainted packages.
       
   334                 javac_state.deleteClassArtifactsInTaintedPackages();
       
   335                 again = javac_state.performJavaCompilations(bin_dir, server_settings, args, recently_compiled, rc);
       
   336                 if (!rc[0]) break;
       
   337             } while (again);
       
   338             // Only update the state if the compile went well.
       
   339             if (rc[0]) {
       
   340                 javac_state.save();
       
   341                 // Collect all the artifacts.
       
   342                 javac_state.now().collectArtifacts(modules);
       
   343                 // Remove artifacts that were generated during the last compile, but not this one.
       
   344                 javac_state.removeSuperfluousArtifacts(recently_compiled);
       
   345             }
       
   346             return rc[0] ? 0 : -1;
       
   347         } catch (ProblemException e) {
       
   348             Log.error(e.getMessage());
       
   349             return -1;
       
   350         } catch (Exception e) {
       
   351             e.printStackTrace(err);
       
   352             return -1;
       
   353         }
       
   354     }
       
   355 
       
   356     /**
       
   357      * Are java source files passed on the command line?
       
   358      */
       
   359     private boolean findJavaSourceFiles(String[] args) {
       
   360         String prev = "";
       
   361         for (String s : args) {
       
   362             if (s.endsWith(".java") && !prev.equals("-xf") && !prev.equals("-if")) {
       
   363                 return true;
       
   364             }
       
   365             prev = s;
       
   366         }
       
   367         return false;
       
   368     }
       
   369 
       
   370     /**
       
   371      * Is an at file passed on the command line?
       
   372      */
       
   373     private boolean findAtFile(String[] args) {
       
   374         for (String s : args) {
       
   375             if (s.startsWith("@")) {
       
   376                 return true;
       
   377             }
       
   378         }
       
   379         return false;
       
   380     }
       
   381 
       
   382     /**
       
   383      * Find the log level setting.
       
   384      */
       
   385     private String findLogLevel(String[] args) {
       
   386         for (String s : args) {
       
   387             if (s.startsWith("--log=") && s.length()>6) {
       
   388                 return s.substring(6);
       
   389             }
       
   390             if (s.equals("-verbose")) {
       
   391                 return "info";
       
   392             }
       
   393         }
       
   394         return "info";
       
   395     }
       
   396 
       
   397     /**
       
   398      * Remove smart javac wrapper arguments, before feeding
       
   399      * the args to the plain javac.
       
   400      */
       
   401     static String[] removeWrapperArgs(String[] args) {
       
   402         String[] out = new String[args.length];
       
   403         // The first source path index is remembered
       
   404         // here. So that all following can be concatenated to it.
       
   405         int source_path = -1;
       
   406         // The same for class path.
       
   407         int class_path = -1;
       
   408         // And module path.
       
   409         int module_path = -1;
       
   410         int j = 0;
       
   411         for (int i = 0; i<args.length; ++i) {
       
   412             if (args[i].equals("-src") ||
       
   413                 args[i].equals("-x") ||
       
   414                 args[i].equals("-i") ||
       
   415                 args[i].equals("-xf") ||
       
   416                 args[i].equals("-if") ||
       
   417                 args[i].equals("-copy") ||
       
   418                 args[i].equals("-tr") ||
       
   419                 args[i].equals("-j")) {
       
   420                 // Just skip it and skip following value
       
   421                 i++;
       
   422             } else if (args[i].startsWith("--server:")) {
       
   423                 // Just skip it.
       
   424             } else if (args[i].startsWith("--log=")) {
       
   425                 // Just skip it.
       
   426             } else if (args[i].equals("--permit-unidentified-artifacts")) {
       
   427                 // Just skip it.
       
   428             } else if (args[i].equals("--permit-sources-without-package")) {
       
   429                 // Just skip it.
       
   430             } else if (args[i].equals("--compare-found-sources")) {
       
   431                 // Just skip it and skip verify file name
       
   432                 i++;
       
   433             } else if (args[i].equals("-sourcepath")) {
       
   434                 if (source_path == -1) {
       
   435                     source_path = j;
       
   436                     out[j] = args[i];
       
   437                     out[j+1] = args[i+1];
       
   438                     j+=2;
       
   439                     i++;
       
   440                 } else {
       
   441                     // Skip this and its argument, but
       
   442                     // append argument to found sourcepath.
       
   443                     out[source_path+1] = out[source_path+1]+File.pathSeparatorChar+args[i+1];
       
   444                     i++;
       
   445                 }
       
   446             } else if (args[i].equals("-classpath")) {
       
   447                 if (class_path == -1) {
       
   448                     class_path = j;
       
   449                     out[j] = args[i];
       
   450                     out[j+1] = args[i+1];
       
   451                     j+=2;
       
   452                     i++;
       
   453                 } else {
       
   454                     // Skip this and its argument, but
       
   455                     // append argument to found sourcepath.
       
   456                     out[class_path+1] = out[class_path+1]+File.pathSeparatorChar+args[i+1];
       
   457                     i++;
       
   458                 }
       
   459             } else if (args[i].equals("-modulepath")) {
       
   460                 if (module_path == -1) {
       
   461                     module_path = j;
       
   462                     out[j] = args[i];
       
   463                     out[j+1] = args[i+1];
       
   464                     j+=2;
       
   465                     i++;
       
   466                 } else {
       
   467                     // Skip this and its argument, but
       
   468                     // append argument to found sourcepath.
       
   469                     out[module_path+1] = out[module_path+1]+File.pathSeparatorChar+args[i+1];
       
   470                     i++;
       
   471                 }
       
   472              } else {
       
   473                 // Copy argument.
       
   474                 out[j] = args[i];
       
   475                 j++;
       
   476             }
       
   477         }
       
   478         String[] ret = new String[j];
       
   479         System.arraycopy(out, 0, ret, 0, j);
       
   480         return ret;
       
   481     }
       
   482 
       
   483     /**
       
   484      * Make sure directory exist, create it if not.
       
   485      */
       
   486     private static boolean makeSureExists(File dir) {
       
   487         // Make sure the dest directories exist.
       
   488         if (!dir.exists()) {
       
   489             if (!dir.mkdirs()) {
       
   490                 Log.error("Could not create the directory "+dir.getPath());
       
   491                 return false;
       
   492             }
       
   493         }
       
   494         return true;
       
   495     }
       
   496 
       
   497     /**
       
   498      * Verify that a package pattern is valid.
       
   499      */
       
   500     private static void checkPattern(String s) throws ProblemException {
       
   501         // Package names like foo.bar.gamma are allowed, and
       
   502         // package names suffixed with .* like foo.bar.* are
       
   503         // also allowed.
       
   504         Pattern p = Pattern.compile("[a-zA-Z_]{1}[a-zA-Z0-9_]*(\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*)*(\\.\\*)?+");
       
   505         Matcher m = p.matcher(s);
       
   506         if (!m.matches()) {
       
   507             throw new ProblemException("The string \""+s+"\" is not a proper package name pattern.");
       
   508         }
       
   509     }
       
   510 
       
   511     /**
       
   512      * Verify that a translate pattern is valid.
       
   513      */
       
   514     private static void checkTranslatePattern(String s) throws ProblemException {
       
   515         // .prop=com.sun.tools.javac.smart.CompileProperties
       
   516         // .idl=com.sun.corba.CompileIdl
       
   517         // .g3=antlr.CompileGrammar,debug=true
       
   518         Pattern p = Pattern.compile(
       
   519             "\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*=[a-z_]{1}[a-z0-9_]*(\\.[a-z_]{1}[a-z0-9_]*)*"+
       
   520             "(\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*)(,.*)?");
       
   521         Matcher m = p.matcher(s);
       
   522         if (!m.matches()) {
       
   523             throw new ProblemException("The string \""+s+"\" is not a proper translate pattern.");
       
   524         }
       
   525     }
       
   526 
       
   527     /**
       
   528      * Verify that a copy pattern is valid.
       
   529      */
       
   530     private static void checkCopyPattern(String s) throws ProblemException {
       
   531         // .gif
       
   532         // .html
       
   533         Pattern p = Pattern.compile(
       
   534             "\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*");
       
   535         Matcher m = p.matcher(s);
       
   536         if (!m.matches()) {
       
   537             throw new ProblemException("The string \""+s+"\" is not a proper suffix.");
       
   538         }
       
   539     }
       
   540 
       
   541     /**
       
   542      * Verify that a source file name is valid.
       
   543      */
       
   544     private static void checkFilePattern(String s) throws ProblemException {
       
   545         // File names like foo/bar/gamma/Bar.java are allowed,
       
   546         // as well as /bar/jndi.properties as well as,
       
   547         // */bar/Foo.java
       
   548         Pattern p = null;
       
   549         if (File.separatorChar == '\\') {
       
   550             p = Pattern.compile("\\*?(.+\\\\)*.+");
       
   551         }
       
   552         else if (File.separatorChar == '/') {
       
   553             p = Pattern.compile("\\*?(.+/)*.+");
       
   554         } else {
       
   555             throw new ProblemException("This platform uses the unsupported "+File.separatorChar+
       
   556                                       " as file separator character. Please add support for it!");
       
   557         }
       
   558         Matcher m = p.matcher(s);
       
   559         if (!m.matches()) {
       
   560             throw new ProblemException("The string \""+s+"\" is not a proper file name.");
       
   561         }
       
   562     }
       
   563 
       
   564     /**
       
   565      * Scan the arguments to find an option is used.
       
   566      */
       
   567     private static boolean hasOption(String[] args, String option) {
       
   568         for (String a : args) {
       
   569             if (a.equals(option)) return true;
       
   570         }
       
   571         return false;
       
   572     }
       
   573 
       
   574     /**
       
   575      * Check if -implicit is supplied, if so check that it is none.
       
   576      * If -implicit is not supplied, supply -implicit:none
       
   577      * Only implicit:none is allowed because otherwise the multicore compilations
       
   578      * and dependency tracking will be tangled up.
       
   579      */
       
   580     private static String[] verifyImplicitOption(String[] args)
       
   581         throws ProblemException {
       
   582 
       
   583         boolean foundImplicit = false;
       
   584         for (String a : args) {
       
   585             if (a.startsWith("-implicit:")) {
       
   586                 foundImplicit = true;
       
   587                 if (!a.equals("-implicit:none")) {
       
   588                     throw new ProblemException("The only allowed setting for sjavac is -implicit:none, it is also the default.");
       
   589                 }
       
   590             }
       
   591         }
       
   592         if (foundImplicit) {
       
   593             return args;
       
   594         }
       
   595         // -implicit:none not found lets add it.
       
   596         String[] newargs = new String[args.length+1];
       
   597         System.arraycopy(args,0, newargs, 0, args.length);
       
   598         newargs[args.length] = "-implicit:none";
       
   599         return newargs;
       
   600     }
       
   601 
       
   602     /**
       
   603      * Rewrite a single option into something else.
       
   604      */
       
   605     private static void rewriteOptions(String[] args, String option, String new_option) {
       
   606         for (int i=0; i<args.length; ++i) {
       
   607             if (args[i].equals(option)) {
       
   608                 args[i] = new_option;
       
   609             }
       
   610         }
       
   611     }
       
   612 
       
   613     /**
       
   614      * Scan the arguments to find an option that specifies a directory.
       
   615      * Create the directory if necessary.
       
   616      */
       
   617     private static File findDirectoryOption(String[] args, String option, String name, boolean needed, boolean allow_dups, boolean create)
       
   618         throws ProblemException, ProblemException {
       
   619         File dir = null;
       
   620         for (int i = 0; i<args.length; ++i) {
       
   621             if (args[i].equals(option)) {
       
   622                 if (dir != null) {
       
   623                     throw new ProblemException("You have already specified the "+name+" dir!");
       
   624                 }
       
   625                 if (i+1 >= args.length) {
       
   626                     throw new ProblemException("You have to specify a directory following "+option+".");
       
   627                 }
       
   628                 if (args[i+1].indexOf(File.pathSeparatorChar) != -1) {
       
   629                     throw new ProblemException("You must only specify a single directory for "+option+".");
       
   630                 }
       
   631                 dir = new File(args[i+1]);
       
   632                 if (!dir.exists()) {
       
   633                     if (!create) {
       
   634                          throw new ProblemException("This directory does not exist: "+dir.getPath());
       
   635                     } else
       
   636                     if (!makeSureExists(dir)) {
       
   637                         throw new ProblemException("Cannot create directory "+dir.getPath());
       
   638                     }
       
   639                 }
       
   640                 if (!dir.isDirectory()) {
       
   641                     throw new ProblemException("\""+args[i+1]+"\" is not a directory.");
       
   642                 }
       
   643             }
       
   644         }
       
   645         if (dir == null && needed) {
       
   646             throw new ProblemException("You have to specify "+option);
       
   647         }
       
   648         try {
       
   649             if (dir != null)
       
   650                 return dir.getCanonicalFile();
       
   651         } catch (IOException e) {
       
   652             throw new ProblemException(""+e);
       
   653         }
       
   654         return null;
       
   655     }
       
   656 
       
   657     /**
       
   658      * Option is followed by path.
       
   659      */
       
   660     private static boolean shouldBeFollowedByPath(String o) {
       
   661         return o.equals("-s") ||
       
   662                o.equals("-h") ||
       
   663                o.equals("-d") ||
       
   664                o.equals("-sourcepath") ||
       
   665                o.equals("-classpath") ||
       
   666                o.equals("-bootclasspath") ||
       
   667                o.equals("-src");
       
   668     }
       
   669 
       
   670     /**
       
   671      * Add -src before source root directories if not already there.
       
   672      */
       
   673     private static String[] addSrcBeforeDirectories(String[] args) {
       
   674         List<String> newargs = new ArrayList<String>();
       
   675         for (int i = 0; i<args.length; ++i) {
       
   676             File dir = new File(args[i]);
       
   677             if (dir.exists() && dir.isDirectory()) {
       
   678                 if (i == 0 || !shouldBeFollowedByPath(args[i-1])) {
       
   679                     newargs.add("-src");
       
   680                 }
       
   681             }
       
   682             newargs.add(args[i]);
       
   683         }
       
   684         return newargs.toArray(new String[0]);
       
   685     }
       
   686 
       
   687     /**
       
   688      * Check the -src options.
       
   689      */
       
   690     private static void checkSrcOption(String[] args)
       
   691         throws ProblemException {
       
   692         Set<File> dirs = new HashSet<File>();
       
   693         for (int i = 0; i<args.length; ++i) {
       
   694             if (args[i].equals("-src")) {
       
   695                 if (i+1 >= args.length) {
       
   696                     throw new ProblemException("You have to specify a directory following -src.");
       
   697                 }
       
   698                 StringTokenizer st = new StringTokenizer(args[i+1], File.pathSeparator);
       
   699                 while (st.hasMoreElements()) {
       
   700                     File dir = new File(st.nextToken());
       
   701                     if (!dir.exists()) {
       
   702                         throw new ProblemException("This directory does not exist: "+dir.getPath());
       
   703                     }
       
   704                     if (!dir.isDirectory()) {
       
   705                         throw new ProblemException("\""+dir.getPath()+"\" is not a directory.");
       
   706                     }
       
   707                     if (dirs.contains(dir)) {
       
   708                         throw new ProblemException("The src directory \""+dir.getPath()+"\" is specified more than once!");
       
   709                     }
       
   710                     dirs.add(dir);
       
   711                 }
       
   712             }
       
   713         }
       
   714         if (dirs.isEmpty()) {
       
   715             throw new ProblemException("You have to specify -src.");
       
   716         }
       
   717     }
       
   718 
       
   719     /**
       
   720      * Scan the arguments to find an option that specifies a file.
       
   721      */
       
   722     private static File findFileOption(String[] args, String option, String name, boolean needed)
       
   723         throws ProblemException, ProblemException {
       
   724         File file = null;
       
   725         for (int i = 0; i<args.length; ++i) {
       
   726             if (args[i].equals(option)) {
       
   727                 if (file != null) {
       
   728                     throw new ProblemException("You have already specified the "+name+" file!");
       
   729                 }
       
   730                 if (i+1 >= args.length) {
       
   731                     throw new ProblemException("You have to specify a file following "+option+".");
       
   732                 }
       
   733                 file = new File(args[i+1]);
       
   734                 if (file.isDirectory()) {
       
   735                     throw new ProblemException("\""+args[i+1]+"\" is not a file.");
       
   736                 }
       
   737                 if (!file.exists() && needed) {
       
   738                     throw new ProblemException("The file \""+args[i+1]+"\" does not exist.");
       
   739                 }
       
   740 
       
   741             }
       
   742         }
       
   743         if (file == null && needed) {
       
   744             throw new ProblemException("You have to specify "+option);
       
   745         }
       
   746         return file;
       
   747     }
       
   748 
       
   749     /**
       
   750      * Look for a specific switch, return true if found.
       
   751      */
       
   752     public static boolean findBooleanOption(String[] args, String option) {
       
   753         for (int i = 0; i<args.length; ++i) {
       
   754             if (args[i].equals(option)) return true;
       
   755         }
       
   756         return false;
       
   757     }
       
   758 
       
   759     /**
       
   760      * Scan the arguments to find an option that specifies a number.
       
   761      */
       
   762     public static int findNumberOption(String[] args, String option) {
       
   763         int rc = 0;
       
   764         for (int i = 0; i<args.length; ++i) {
       
   765             if (args[i].equals(option)) {
       
   766                 if (args.length > i+1) {
       
   767                     rc = Integer.parseInt(args[i+1]);
       
   768                 }
       
   769             }
       
   770         }
       
   771         return rc;
       
   772     }
       
   773 
       
   774     /**
       
   775      * Scan the arguments to find the option (-tr) that setup translation rules to java source
       
   776      * from different sources. For example: .properties are translated using CompileProperties
       
   777      * The found translators are stored as suffix rules.
       
   778      */
       
   779     private static void findTranslateOptions(String[] args, Map<String,Transformer> suffix_rules)
       
   780         throws ProblemException, ProblemException {
       
   781 
       
   782         for (int i = 0; i<args.length; ++i) {
       
   783             if (args[i].equals("-tr")) {
       
   784                 if (i+1 >= args.length) {
       
   785                     throw new ProblemException("You have to specify a translate rule following -tr.");
       
   786                 }
       
   787                 String s = args[i+1];
       
   788                 checkTranslatePattern(s);
       
   789                 int ep = s.indexOf("=");
       
   790                 String suffix = s.substring(0,ep);
       
   791                 String classname = s.substring(ep+1);
       
   792                 if (suffix_rules.get(suffix) != null) {
       
   793                     throw new ProblemException("You have already specified a "+
       
   794                                               "rule for the suffix "+suffix);
       
   795                 }
       
   796                 if (s.equals(".class")) {
       
   797                     throw new ProblemException("You cannot have a translator for .class files!");
       
   798                 }
       
   799                 if (s.equals(".java")) {
       
   800                     throw new ProblemException("You cannot have a translator for .java files!");
       
   801                 }
       
   802                 String extra = null;
       
   803                 int exp = classname.indexOf(",");
       
   804                 if (exp != -1) {
       
   805                     extra = classname.substring(exp+1);
       
   806                     classname = classname.substring(0,exp);
       
   807                 }
       
   808                 try {
       
   809                     Class<?> cl = Class.forName(classname);
       
   810                     Transformer t = (Transformer)cl.newInstance();
       
   811                     t.setExtra(extra);
       
   812                     suffix_rules.put(suffix, t);
       
   813                 }
       
   814                 catch (Exception e) {
       
   815                     throw new ProblemException("Cannot use "+classname+" as a translator!");
       
   816                 }
       
   817             }
       
   818         }
       
   819     }
       
   820 
       
   821     /**
       
   822      * Scan the arguments to find the option (-copy) that setup copying rules into the bin dir.
       
   823      * For example: -copy .html
       
   824      * The found copiers are stored as suffix rules as well. No translation is done, just copying.
       
   825      */
       
   826     private void findCopyOptions(String[] args, Map<String,Transformer> suffix_rules)
       
   827         throws ProblemException, ProblemException {
       
   828 
       
   829         for (int i = 0; i<args.length; ++i) {
       
   830             if (args[i].equals("-copy")) {
       
   831                 if (i+1 >= args.length) {
       
   832                     throw new ProblemException("You have to specify a translate rule following -tr.");
       
   833                 }
       
   834                 String s = args[i+1];
       
   835                 checkCopyPattern(s);
       
   836                 if (suffix_rules.get(s) != null) {
       
   837                     throw new ProblemException("You have already specified a "+
       
   838                                               "rule for the suffix "+s);
       
   839                 }
       
   840                 if (s.equals(".class")) {
       
   841                     throw new ProblemException("You cannot have a copy rule for .class files!");
       
   842                 }
       
   843                 if (s.equals(".java")) {
       
   844                     throw new ProblemException("You cannot have a copy rule for .java files!");
       
   845                 }
       
   846                 suffix_rules.put(s, javac_state.getCopier());
       
   847             }
       
   848         }
       
   849     }
       
   850 
       
   851     /**
       
   852      * Rewrite a / separated path into \ separated, but only
       
   853      * if we are running on a platform were File.separatorChar=='\', ie winapi.
       
   854      */
       
   855     private String fixupSeparator(String p) {
       
   856         if (File.separatorChar == '/') return p;
       
   857         return p.replaceAll("/", "\\\\");
       
   858     }
       
   859 
       
   860     /**
       
   861      * Scan the arguments for -i -x -xf -if followed by the option
       
   862      * -src, -sourcepath, -modulepath or -classpath and produce a map of all the
       
   863      * files to referenced for that particular option.
       
   864      *
       
   865      * Store the found sources and the found modules in the supplied maps.
       
   866      */
       
   867     private boolean findFiles(String[] args, String option, Set<String> suffixes,
       
   868                               Map<String,Source> found_files, Map<String, Module> found_modules,
       
   869                               Module current_module, boolean inLinksrc)
       
   870         throws ProblemException, ProblemException
       
   871     {
       
   872         // Track which source roots, source path roots and class path roots have been added.
       
   873         Set<File> roots = new HashSet<File>();
       
   874         // Track the current set of package includes,excludes as well as excluded source files,
       
   875         // to be used in the next -src/-sourcepath/-classpath
       
   876         List<String> includes = new LinkedList<String>();
       
   877         List<String> excludes = new LinkedList<String>();
       
   878         List<String> excludefiles = new LinkedList<String>();
       
   879         List<String> includefiles = new LinkedList<String>();
       
   880         // This include is used to find all modules in the source.
       
   881         List<String> moduleinfo = new LinkedList<String>();
       
   882         moduleinfo.add("module-info.java");
       
   883 
       
   884         for (int i = 0; i<args.length; ++i) {
       
   885             if (args[i].equals("-i")) {
       
   886                 if (i+1 >= args.length) {
       
   887                     throw new ProblemException("You have to specify a package pattern following -i");
       
   888                 }
       
   889                 String incl = args[i+1];
       
   890                 checkPattern(incl);
       
   891                 includes.add(incl);
       
   892             }
       
   893             if (args[i].equals("-x")) {
       
   894                 if (i+1 >= args.length) {
       
   895                     throw new ProblemException("You have to specify a package pattern following -x");
       
   896                 }
       
   897                 String excl = args[i+1];
       
   898                 checkPattern(excl);
       
   899                 excludes.add(excl);
       
   900             }
       
   901             if (args[i].equals("-xf")) {
       
   902                 if (i+1 >= args.length) {
       
   903                     throw new ProblemException("You have to specify a file following -xf");
       
   904                 }
       
   905                 String exclf = args[i+1];
       
   906                 checkFilePattern(exclf);
       
   907                 exclf = Util.normalizeDriveLetter(exclf);
       
   908                 excludefiles.add(fixupSeparator(exclf));
       
   909             }
       
   910             if (args[i].equals("-if")) {
       
   911                 if (i+1 >= args.length) {
       
   912                     throw new ProblemException("You have to specify a file following -xf");
       
   913                 }
       
   914                 String inclf = args[i+1];
       
   915                 checkFilePattern(inclf);
       
   916                 inclf = Util.normalizeDriveLetter(inclf);
       
   917                 includefiles.add(fixupSeparator(inclf));
       
   918             }
       
   919             if (args[i].equals(option)) {
       
   920                 if (i+1 >= args.length) {
       
   921                     throw new ProblemException("You have to specify a directory following "+option);
       
   922                 }
       
   923                 String[] root_dirs = args[i+1].split(File.pathSeparator);
       
   924                 for (String r : root_dirs) {
       
   925                     File root = new File(r);
       
   926                     if (!root.isDirectory()) {
       
   927                         throw new ProblemException("\""+r+"\" is not a directory.");
       
   928                     }
       
   929                     try {
       
   930                         root = root.getCanonicalFile();
       
   931                     } catch (IOException e) {
       
   932                         throw new ProblemException(""+e);
       
   933                     }
       
   934                     if (roots.contains(root)) {
       
   935                         throw new ProblemException("\""+r+"\" has already been used for "+option);
       
   936                     }
       
   937                     if (roots.equals(bin_dir)) {
       
   938                         throw new ProblemException("\""+r+"\" cannot be used both for "+option+" and -d");
       
   939                     }
       
   940                     if (roots.equals(gensrc_dir)) {
       
   941                         throw new ProblemException("\""+r+"\" cannot be used both for "+option+" and -s");
       
   942                     }
       
   943                     if (roots.equals(header_dir)) {
       
   944                         throw new ProblemException("\""+r+"\" cannot be used both for "+option+" and -h");
       
   945                     }
       
   946                     roots.add(root);
       
   947                     Source.scanRoot(root, suffixes, excludes, includes, excludefiles, includefiles,
       
   948                                     found_files, found_modules, current_module,
       
   949                                     findBooleanOption(args, "--permit-sources-without-package"),
       
   950                                     false, inLinksrc);
       
   951                 }
       
   952             }
       
   953             if (args[i].equals("-src") ||
       
   954                 args[i].equals("-sourcepath") ||
       
   955                 args[i].equals("-modulepath") ||
       
   956                 args[i].equals("-classpath"))
       
   957             {
       
   958                 // Reset the includes,excludes and excludefiles after they have been used.
       
   959                 includes = new LinkedList<String>();
       
   960                 excludes = new LinkedList<String>();
       
   961                 excludefiles = new LinkedList<String>();
       
   962                 includefiles = new LinkedList<String>();
       
   963             }
       
   964         }
       
   965         return true;
       
   966     }
       
   967 
       
   968 }
       
   969