1 /*
   2  * Copyright (c) 1996, 2007, 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 /*****************************************************************************/
  27 /*                    Copyright (c) IBM Corporation 1998                     */
  28 /*                                                                           */
  29 /* (C) Copyright IBM Corp. 1998                                              */
  30 /*                                                                           */
  31 /*****************************************************************************/
  32 
  33 package sun.rmi.rmic;
  34 
  35 import java.io.File;
  36 import java.io.IOException;
  37 import java.io.OutputStream;
  38 import java.util.Collection;
  39 import java.util.Enumeration;
  40 import java.util.Iterator;
  41 import java.util.LinkedHashSet;
  42 import java.util.StringTokenizer;
  43 import java.util.Vector;
  44 import java.util.jar.JarFile;
  45 import java.util.jar.Manifest;
  46 import java.util.jar.Attributes;
  47 import sun.tools.java.ClassPath;
  48 
  49 /**
  50  * BatchEnvironment for rmic extends javac's version in four ways:
  51  * 1. It overrides errorString() to handle looking for rmic-specific
  52  * error messages in rmic's resource bundle
  53  * 2. It provides a mechanism for recording intermediate generated
  54  * files so that they can be deleted later.
  55  * 3. It holds a reference to the Main instance so that generators
  56  * can refer to it.
  57  * 4. It provides access to the ClassPath passed to the constructor.
  58  *
  59  * WARNING: The contents of this source file are not part of any
  60  * supported API.  Code that depends on them does so at its own risk:
  61  * they are subject to change or removal without notice.
  62  */
  63 
  64 public class BatchEnvironment extends sun.tools.javac.BatchEnvironment {
  65 
  66     /** instance of Main which created this environment */
  67     private Main main;
  68 
  69     /**
  70      * Create a ClassPath object for rmic from a class path string.
  71      */
  72     public static ClassPath createClassPath(String classPathString) {
  73         ClassPath[] paths = classPaths(null, classPathString, null, null);
  74         return paths[1];
  75     }
  76 
  77     /**
  78      * Create a ClassPath object for rmic from the relevant command line
  79      * options for class path, boot class path, and extension directories.
  80      */
  81     public static ClassPath createClassPath(String classPathString,
  82                                             String sysClassPathString,
  83                                             String extDirsString)
  84     {
  85         /**
  86          * Previously, this method delegated to the
  87          * sun.tools.javac.BatchEnvironment.classPaths method in order
  88          * to supply default values for paths not specified on the
  89          * command line, expand extensions directories into specific
  90          * JAR files, and construct the ClassPath object-- but as part
  91          * of the fix for 6473331, which adds support for Class-Path
  92          * manifest entries in JAR files, those steps are now handled
  93          * here directly, with the help of a Path utility class copied
  94          * from the new javac implementation (see below).
  95          */
  96         Path path = new Path();
  97 
  98         if (sysClassPathString == null) {
  99             sysClassPathString = System.getProperty("sun.boot.class.path");
 100         }
 101         if (sysClassPathString != null) {
 102             path.addFiles(sysClassPathString);
 103         }
 104 
 105         /*
 106          * Class-Path manifest entries are supported for JAR files
 107          * everywhere except in the boot class path.
 108          */
 109         path.expandJarClassPaths(true);
 110 
 111         if (extDirsString == null) {
 112             extDirsString = System.getProperty("java.ext.dirs");
 113         }
 114         if (extDirsString != null) {
 115             path.addDirectories(extDirsString);
 116         }
 117 
 118         /*
 119          * In the application class path, an empty element means
 120          * the current working directory.
 121          */
 122         path.emptyPathDefault(".");
 123 
 124         if (classPathString == null) {
 125             // The env.class.path property is the user's CLASSPATH
 126             // environment variable, and it set by the wrapper (ie,
 127             // javac.exe).
 128             classPathString = System.getProperty("env.class.path");
 129             if (classPathString == null) {
 130                 classPathString = ".";
 131             }
 132         }
 133         path.addFiles(classPathString);
 134 
 135         return new ClassPath(path.toArray(new String[path.size()]));
 136     }
 137 
 138     /**
 139      * Create a BatchEnvironment for rmic with the given class path,
 140      * stream for messages and Main.
 141      */
 142     public BatchEnvironment(OutputStream out, ClassPath path, Main main) {
 143         super(out, new ClassPath(""), path);
 144                                 // use empty "sourcePath" (see 4666958)
 145         this.main = main;
 146     }
 147 
 148     /**
 149      * Get the instance of Main which created this environment.
 150      */
 151     public Main getMain() {
 152         return main;
 153     }
 154 
 155     /**
 156      * Get the ClassPath.
 157      */
 158     public ClassPath getClassPath() {
 159         return binaryPath;
 160     }
 161 
 162     /** list of generated source files created in this environment */
 163     private Vector generatedFiles = new Vector();
 164 
 165     /**
 166      * Remember a generated source file generated so that it
 167      * can be removed later, if appropriate.
 168      */
 169     public void addGeneratedFile(File file) {
 170         generatedFiles.addElement(file);
 171     }
 172 
 173     /**
 174      * Delete all the generated source files made during the execution
 175      * of this environment (those that have been registered with the
 176      * "addGeneratedFile" method).
 177      */
 178     public void deleteGeneratedFiles() {
 179         synchronized(generatedFiles) {
 180             Enumeration enumeration = generatedFiles.elements();
 181             while (enumeration.hasMoreElements()) {
 182                 File file = (File) enumeration.nextElement();
 183                 file.delete();
 184             }
 185             generatedFiles.removeAllElements();
 186         }
 187     }
 188 
 189     /**
 190      * Release resources, if any.
 191      */
 192     public void shutdown() {
 193         main = null;
 194         generatedFiles = null;
 195         super.shutdown();
 196     }
 197 
 198     /**
 199      * Return the formatted, localized string for a named error message
 200      * and supplied arguments.  For rmic error messages, with names that
 201      * being with "rmic.", look up the error message in rmic's resource
 202      * bundle; otherwise, defer to java's superclass method.
 203      */
 204     public String errorString(String err,
 205                               Object arg0, Object arg1, Object arg2)
 206     {
 207         if (err.startsWith("rmic.") || err.startsWith("warn.rmic.")) {
 208             String result =  Main.getText(err,
 209                                           (arg0 != null ? arg0.toString() : null),
 210                                           (arg1 != null ? arg1.toString() : null),
 211                                           (arg2 != null ? arg2.toString() : null));
 212 
 213             if (err.startsWith("warn.")) {
 214                 result = "warning: " + result;
 215             }
 216             return result;
 217         } else {
 218             return super.errorString(err, arg0, arg1, arg2);
 219         }
 220     }
 221     public void reset() {
 222     }
 223 
 224     /**
 225      * Utility for building paths of directories and JAR files.  This
 226      * class was copied from com.sun.tools.javac.util.Paths as part of
 227      * the fix for 6473331, which adds support for Class-Path manifest
 228      * entries in JAR files.  Diagnostic code is simply commented out
 229      * because rmic silently ignored these conditions historically.
 230      */
 231     private static class Path extends LinkedHashSet<String> {
 232         private static final long serialVersionUID = 0;
 233         private static final boolean warn = false;
 234 
 235         private static class PathIterator implements Collection<String> {
 236             private int pos = 0;
 237             private final String path;
 238             private final String emptyPathDefault;
 239 
 240             public PathIterator(String path, String emptyPathDefault) {
 241                 this.path = path;
 242                 this.emptyPathDefault = emptyPathDefault;
 243             }
 244             public PathIterator(String path) { this(path, null); }
 245             public Iterator<String> iterator() {
 246                 return new Iterator<String>() {
 247                     public boolean hasNext() {
 248                         return pos <= path.length();
 249                     }
 250                     public String next() {
 251                         int beg = pos;
 252                         int end = path.indexOf(File.pathSeparator, beg);
 253                         if (end == -1)
 254                             end = path.length();
 255                         pos = end + 1;
 256 
 257                         if (beg == end && emptyPathDefault != null)
 258                             return emptyPathDefault;
 259                         else
 260                             return path.substring(beg, end);
 261                     }
 262                     public void remove() {
 263                         throw new UnsupportedOperationException();
 264                     }
 265                 };
 266             }
 267 
 268             // required for Collection.
 269             public int size() {
 270                 throw new UnsupportedOperationException();
 271             }
 272             public boolean isEmpty() {
 273                 throw new UnsupportedOperationException();
 274             }
 275             public boolean contains(Object o) {
 276                 throw new UnsupportedOperationException();
 277             }
 278             public Object[] toArray() {
 279                 throw new UnsupportedOperationException();
 280             }
 281             public <T> T[] toArray(T[] a) {
 282                 throw new UnsupportedOperationException();
 283             }
 284             public boolean add(String o) {
 285                 throw new UnsupportedOperationException();
 286             }
 287             public boolean remove(Object o) {
 288                 throw new UnsupportedOperationException();
 289             }
 290             public boolean containsAll(Collection<?> c) {
 291                 throw new UnsupportedOperationException();
 292             }
 293             public boolean addAll(Collection<? extends String> c) {
 294                 throw new UnsupportedOperationException();
 295             }
 296             public boolean removeAll(Collection<?> c) {
 297                 throw new UnsupportedOperationException();
 298             }
 299             public boolean retainAll(Collection<?> c) {
 300                 throw new UnsupportedOperationException();
 301             }
 302             public void clear() {
 303                 throw new UnsupportedOperationException();
 304             }
 305             public boolean equals(Object o) {
 306                 throw new UnsupportedOperationException();
 307             }
 308             public int hashCode() {
 309                 throw new UnsupportedOperationException();
 310             }
 311         }
 312 
 313         /** Is this the name of a zip file? */
 314         private static boolean isZip(String name) {
 315             return new File(name).isFile();
 316         }
 317 
 318         private boolean expandJarClassPaths = false;
 319 
 320         public Path expandJarClassPaths(boolean x) {
 321             expandJarClassPaths = x;
 322             return this;
 323         }
 324 
 325         /** What to use when path element is the empty string */
 326         private String emptyPathDefault = null;
 327 
 328         public Path emptyPathDefault(String x) {
 329             emptyPathDefault = x;
 330             return this;
 331         }
 332 
 333         public Path() { super(); }
 334 
 335         public Path addDirectories(String dirs, boolean warn) {
 336             if (dirs != null)
 337                 for (String dir : new PathIterator(dirs))
 338                     addDirectory(dir, warn);
 339             return this;
 340         }
 341 
 342         public Path addDirectories(String dirs) {
 343             return addDirectories(dirs, warn);
 344         }
 345 
 346         private void addDirectory(String dir, boolean warn) {
 347             if (! new File(dir).isDirectory()) {
 348 //              if (warn)
 349 //                  log.warning(Position.NOPOS,
 350 //                              "dir.path.element.not.found", dir);
 351                 return;
 352             }
 353 
 354             for (String direntry : new File(dir).list()) {
 355                 String canonicalized = direntry.toLowerCase();
 356                 if (canonicalized.endsWith(".jar") ||
 357                     canonicalized.endsWith(".zip"))
 358                     addFile(dir + File.separator + direntry, warn);
 359             }
 360         }
 361 
 362         public Path addFiles(String files, boolean warn) {
 363             if (files != null)
 364                 for (String file : new PathIterator(files, emptyPathDefault))
 365                     addFile(file, warn);
 366             return this;
 367         }
 368 
 369         public Path addFiles(String files) {
 370             return addFiles(files, warn);
 371         }
 372 
 373         private void addFile(String file, boolean warn) {
 374             if (contains(file)) {
 375                 /* Discard duplicates and avoid infinite recursion */
 376                 return;
 377             }
 378 
 379             File ele = new File(file);
 380             if (! ele.exists()) {
 381                 /* No such file or directory exist */
 382                 if (warn)
 383 //                      log.warning(Position.NOPOS,
 384 //                          "path.element.not.found", file);
 385                     return;
 386             }
 387 
 388             if (ele.isFile()) {
 389                 /* File is an ordinay file  */
 390                 String arcname = file.toLowerCase();
 391                 if (! (arcname.endsWith(".zip") ||
 392                        arcname.endsWith(".jar"))) {
 393                     /* File name don't have right extension */
 394 //                      if (warn)
 395 //                          log.warning(Position.NOPOS,
 396 //                              "invalid.archive.file", file);
 397                     return;
 398                 }
 399             }
 400 
 401             /* Now what we have left is either a directory or a file name
 402                confirming to archive naming convention */
 403 
 404             super.add(file);
 405             if (expandJarClassPaths && isZip(file))
 406                 addJarClassPath(file, warn);
 407         }
 408 
 409         // Adds referenced classpath elements from a jar's Class-Path
 410         // Manifest entry.  In some future release, we may want to
 411         // update this code to recognize URLs rather than simple
 412         // filenames, but if we do, we should redo all path-related code.
 413         private void addJarClassPath(String jarFileName, boolean warn) {
 414             try {
 415                 String jarParent = new File(jarFileName).getParent();
 416                 JarFile jar = new JarFile(jarFileName);
 417 
 418                 try {
 419                     Manifest man = jar.getManifest();
 420                     if (man == null) return;
 421 
 422                     Attributes attr = man.getMainAttributes();
 423                     if (attr == null) return;
 424 
 425                     String path = attr.getValue(Attributes.Name.CLASS_PATH);
 426                     if (path == null) return;
 427 
 428                     for (StringTokenizer st = new StringTokenizer(path);
 429                         st.hasMoreTokens();) {
 430                         String elt = st.nextToken();
 431                         if (jarParent != null)
 432                             elt = new File(jarParent, elt).getCanonicalPath();
 433                         addFile(elt, warn);
 434                     }
 435                 } finally {
 436                     jar.close();
 437                 }
 438             } catch (IOException e) {
 439 //              log.error(Position.NOPOS,
 440 //                        "error.reading.file", jarFileName,
 441 //                        e.getLocalizedMessage());
 442             }
 443         }
 444     }
 445 }