1 /*
   2  * Copyright (c) 2014, 2018, 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.javac.platform;
  27 
  28 import java.io.IOException;
  29 import java.io.PrintWriter;
  30 import java.net.URI;
  31 import java.nio.charset.Charset;
  32 import java.nio.file.DirectoryStream;
  33 import java.nio.file.FileSystem;
  34 import java.nio.file.FileSystems;
  35 import java.nio.file.Files;
  36 import java.nio.file.Path;
  37 import java.nio.file.Paths;
  38 import java.nio.file.ProviderNotFoundException;
  39 import java.util.ArrayList;
  40 import java.util.Arrays;
  41 import java.util.Collections;
  42 import java.util.Comparator;
  43 import java.util.EnumSet;
  44 import java.util.HashMap;
  45 import java.util.Iterator;
  46 import java.util.List;
  47 import java.util.Map;
  48 import java.util.Map.Entry;
  49 import java.util.NoSuchElementException;
  50 import java.util.Set;
  51 import java.util.TreeSet;
  52 import java.util.stream.Collectors;
  53 import java.util.stream.Stream;
  54 
  55 import javax.annotation.processing.Processor;
  56 import javax.tools.ForwardingJavaFileObject;
  57 import javax.tools.JavaFileManager;
  58 import javax.tools.JavaFileManager.Location;
  59 import javax.tools.JavaFileObject;
  60 import javax.tools.JavaFileObject.Kind;
  61 import javax.tools.StandardJavaFileManager;
  62 import javax.tools.StandardLocation;
  63 
  64 import com.sun.source.util.Plugin;
  65 import com.sun.tools.javac.code.Source;
  66 import com.sun.tools.javac.code.Source.Feature;
  67 import com.sun.tools.javac.file.CacheFSInfo;
  68 import com.sun.tools.javac.file.JavacFileManager;
  69 import com.sun.tools.javac.jvm.Target;
  70 import com.sun.tools.javac.util.Context;
  71 import com.sun.tools.javac.util.Log;
  72 import com.sun.tools.javac.util.StringUtils;
  73 
  74 /** PlatformProvider for JDK N.
  75  *
  76  *  <p><b>This is NOT part of any supported API.
  77  *  If you write code that depends on this, you do so at your own risk.
  78  *  This code and its internal interfaces are subject to change or
  79  *  deletion without notice.</b>
  80  */
  81 public class JDKPlatformProvider implements PlatformProvider {
  82 
  83     @Override
  84     public Iterable<String> getSupportedPlatformNames() {
  85         return SUPPORTED_JAVA_PLATFORM_VERSIONS;
  86     }
  87 
  88     @Override
  89     public PlatformDescription getPlatform(String platformName, String options) {
  90         return new PlatformDescriptionImpl(platformName);
  91     }
  92 
  93     private static final String[] symbolFileLocation = { "lib", "ct.sym" };
  94 
  95     private static final Set<String> SUPPORTED_JAVA_PLATFORM_VERSIONS;
  96     public static final Comparator<String> NUMERICAL_COMPARATOR = (s1, s2) -> {
  97         int i1;
  98         try {
  99             i1 = Integer.parseInt(s1);
 100         } catch (NumberFormatException ex) {
 101             i1 = Integer.MAX_VALUE;
 102         }
 103         int i2;
 104         try {
 105             i2 = Integer.parseInt(s2);
 106         } catch (NumberFormatException ex) {
 107             i2 = Integer.MAX_VALUE;
 108         }
 109         return i1 != i2 ? i1 - i2 : s1.compareTo(s2);
 110     };
 111 
 112     static {
 113         SUPPORTED_JAVA_PLATFORM_VERSIONS = new TreeSet<>(NUMERICAL_COMPARATOR);
 114         Path ctSymFile = findCtSym();
 115         if (Files.exists(ctSymFile)) {
 116             try (FileSystem fs = FileSystems.newFileSystem(ctSymFile, null);
 117                  DirectoryStream<Path> dir =
 118                          Files.newDirectoryStream(fs.getRootDirectories().iterator().next())) {
 119                 for (Path section : dir) {
 120                     if (section.getFileName().toString().contains("-"))
 121                         continue;
 122                     for (char ver : section.getFileName().toString().toCharArray()) {
 123                         String verString = Character.toString(ver);
 124                         Target t = Target.lookup("" + Integer.parseInt(verString, 16));
 125 
 126                         if (t != null) {
 127                             SUPPORTED_JAVA_PLATFORM_VERSIONS.add(targetNumericVersion(t));
 128                         }
 129                     }
 130                 }
 131             } catch (IOException | ProviderNotFoundException ex) {
 132             }
 133         }
 134     }
 135 
 136     private static String targetNumericVersion(Target target) {
 137         return Integer.toString(target.ordinal() - Target.JDK1_1.ordinal() + 1);
 138     }
 139 
 140     static class PlatformDescriptionImpl implements PlatformDescription {
 141 
 142         private final Map<Path, FileSystem> ctSym2FileSystem = new HashMap<>();
 143         private final String sourceVersion;
 144         private final String ctSymVersion;
 145 
 146         PlatformDescriptionImpl(String sourceVersion) {
 147             this.sourceVersion = sourceVersion;
 148             this.ctSymVersion =
 149                     StringUtils.toUpperCase(Integer.toHexString(Integer.parseInt(sourceVersion)));
 150         }
 151 
 152         @Override
 153         public JavaFileManager getFileManager() {
 154             Context context = new Context();
 155             PrintWriter pw = new PrintWriter(System.err, true);
 156             context.put(Log.errKey, pw);
 157             CacheFSInfo.preRegister(context);
 158             JavacFileManager fm = new JavacFileManager(context, true, null) {
 159                 @Override
 160                 public boolean hasLocation(Location location) {
 161                     return super.hasExplicitLocation(location);
 162                 }
 163 
 164                 @Override
 165                 public JavaFileObject getJavaFileForInput(Location location, String className,
 166                                                           Kind kind) throws IOException {
 167                     if (kind == Kind.CLASS) {
 168                         String fileName = className.replace('.', '/');
 169                         JavaFileObject result =
 170                                 (JavaFileObject) getFileForInput(location,
 171                                                                  "",
 172                                                                  fileName + ".sig");
 173 
 174                         if (result == null) {
 175                             //in jrt://, the classfile may have the .class extension:
 176                             result = (JavaFileObject) getFileForInput(location,
 177                                                                       "",
 178                                                                       fileName + ".class");
 179                         }
 180 
 181                         if (result != null) {
 182                             return new SigJavaFileObject(result);
 183                         } else {
 184                             return null;
 185                         }
 186                     }
 187 
 188                     return super.getJavaFileForInput(location, className, kind);
 189                 }
 190 
 191                 @Override
 192                 public Iterable<JavaFileObject> list(Location location,
 193                                                      String packageName,
 194                                                      Set<Kind> kinds,
 195                                                      boolean recurse) throws IOException {
 196                     Set<Kind> enhancedKinds = EnumSet.copyOf(kinds);
 197 
 198                     enhancedKinds.add(Kind.OTHER);
 199 
 200                     Iterable<JavaFileObject> listed = super.list(location, packageName,
 201                                                                  enhancedKinds, recurse);
 202 
 203                     return () -> new Iterator<JavaFileObject>() {
 204                         private final Iterator<JavaFileObject> original = listed.iterator();
 205                         private JavaFileObject next;
 206                         @Override
 207                         public boolean hasNext() {
 208                             if (next == null) {
 209                                 while (original.hasNext()) {
 210                                     JavaFileObject fo = original.next();
 211 
 212                                     if (fo.getKind() == Kind.OTHER &&
 213                                         fo.getName().endsWith(".sig")) {
 214                                         next = new SigJavaFileObject(fo);
 215                                         break;
 216                                     }
 217 
 218                                     if (kinds.contains(fo.getKind())) {
 219                                         next = fo;
 220                                         break;
 221                                     }
 222                                 }
 223                             }
 224                             return next != null;
 225                         }
 226 
 227                         @Override
 228                         public JavaFileObject next() {
 229                             if (!hasNext())
 230                                 throw new NoSuchElementException();
 231                             JavaFileObject result = next;
 232                             next = null;
 233                             return result;
 234                         }
 235 
 236                     };
 237                 }
 238 
 239                 @Override
 240                 public String inferBinaryName(Location location, JavaFileObject file) {
 241                     if (file instanceof SigJavaFileObject) {
 242                         file = ((SigJavaFileObject) file).getDelegate();
 243                     }
 244                     return super.inferBinaryName(location, file);
 245                 }
 246 
 247             };
 248 
 249             Path file = findCtSym();
 250             // file == ${jdk.home}/lib/ct.sym
 251             if (Files.exists(file)) {
 252                 try {
 253                     FileSystem fs = ctSym2FileSystem.get(file);
 254                     if (fs == null) {
 255                         ctSym2FileSystem.put(file, fs = FileSystems.newFileSystem(file, null));
 256                     }
 257 
 258                     Path root = fs.getRootDirectories().iterator().next();
 259                     boolean hasModules =
 260                             Feature.MODULES.allowedInSource(Source.lookup(sourceVersion));
 261                     Path systemModules = root.resolve(ctSymVersion).resolve("system-modules");
 262                     Charset utf8 = Charset.forName("UTF-8");
 263 
 264                     if (!hasModules) {
 265                         List<Path> paths = new ArrayList<>();
 266 
 267                         try (DirectoryStream<Path> dir = Files.newDirectoryStream(root)) {
 268                             for (Path section : dir) {
 269                                 if (section.getFileName().toString().contains(ctSymVersion) &&
 270                                     !section.getFileName().toString().contains("-")) {
 271                                     try (DirectoryStream<Path> modules = Files.newDirectoryStream(section)) {
 272                                         for (Path module : modules) {
 273                                             paths.add(module);
 274                                         }
 275                                     }
 276                                 }
 277                             }
 278                         }
 279 
 280                         fm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, paths);
 281                     } else if (Files.isRegularFile(systemModules)) {
 282                         fm.handleOption("--system", Arrays.asList("none").iterator());
 283 
 284                         Path jrtModules =
 285                                 FileSystems.getFileSystem(URI.create("jrt:/"))
 286                                            .getPath("modules");
 287                         try (Stream<String> lines =
 288                                 Files.lines(systemModules, utf8)) {
 289                             lines.map(line -> jrtModules.resolve(line))
 290                                  .filter(mod -> Files.exists(mod))
 291                                  .forEach(mod -> setModule(fm, mod));
 292                         }
 293                     } else {
 294                         Map<String, List<Path>> module2Paths = new HashMap<>();
 295 
 296                         try (DirectoryStream<Path> dir = Files.newDirectoryStream(root)) {
 297                             for (Path section : dir) {
 298                                 if (section.getFileName().toString().contains(ctSymVersion) &&
 299                                     !section.getFileName().toString().contains("-")) {
 300                                     try (DirectoryStream<Path> modules = Files.newDirectoryStream(section)) {
 301                                         for (Path module : modules) {
 302                                             module2Paths.computeIfAbsent(module.getFileName().toString(), dummy -> new ArrayList<>()).add(module);
 303                                         }
 304                                     }
 305                                 }
 306                             }
 307                         }
 308 
 309                         fm.handleOption("--system", Arrays.asList("none").iterator());
 310 
 311                         for (Entry<String, List<Path>> e : module2Paths.entrySet()) {
 312                             fm.setLocationForModule(StandardLocation.SYSTEM_MODULES,
 313                                                     e.getKey(),
 314                                                     e.getValue());
 315                         }
 316                     }
 317 
 318                     return fm;
 319                 } catch (IOException ex) {
 320                     throw new IllegalStateException(ex);
 321                 }
 322             } else {
 323                 throw new IllegalStateException("Cannot find ct.sym!");
 324             }
 325         }
 326 
 327         private static void setModule(StandardJavaFileManager fm, Path mod) {
 328             try {
 329                 fm.setLocationForModule(StandardLocation.SYSTEM_MODULES,
 330                                         mod.getFileName().toString(),
 331                                         Collections.singleton(mod));
 332             } catch (IOException ex) {
 333                 throw new IllegalStateException(ex);
 334             }
 335         }
 336 
 337         private static class SigJavaFileObject extends ForwardingJavaFileObject<JavaFileObject> {
 338 
 339             public SigJavaFileObject(JavaFileObject fileObject) {
 340                 super(fileObject);
 341             }
 342 
 343             @Override
 344             public Kind getKind() {
 345                 return Kind.CLASS;
 346             }
 347 
 348             @Override
 349             public boolean isNameCompatible(String simpleName, Kind kind) {
 350                 return super.isNameCompatible(simpleName + ".sig", Kind.OTHER);
 351             }
 352 
 353             public JavaFileObject getDelegate() {
 354                 return fileObject;
 355             }
 356         }
 357 
 358         @Override
 359         public String getSourceVersion() {
 360             return sourceVersion;
 361         }
 362 
 363         @Override
 364         public String getTargetVersion() {
 365             return sourceVersion;
 366         }
 367 
 368         @Override
 369         public List<PluginInfo<Processor>> getAnnotationProcessors() {
 370             return Collections.emptyList();
 371         }
 372 
 373         @Override
 374         public List<PluginInfo<Plugin>> getPlugins() {
 375             return Collections.emptyList();
 376         }
 377 
 378         @Override
 379         public List<String> getAdditionalOptions() {
 380             return Collections.emptyList();
 381         }
 382 
 383         @Override
 384         public void close() throws IOException {
 385             for (FileSystem fs : ctSym2FileSystem.values()) {
 386                 fs.close();
 387             }
 388             ctSym2FileSystem.clear();
 389         }
 390 
 391     }
 392 
 393     static Path findCtSym() {
 394         String javaHome = System.getProperty("java.home");
 395         Path file = Paths.get(javaHome);
 396         // file == ${jdk.home}
 397         for (String name : symbolFileLocation)
 398             file = file.resolve(name);
 399         return file;
 400     }
 401 
 402 }