| /******************************************************************************* |
| * Copyright (c) 2016, 2018 IBM Corporation. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.batch; |
| |
| import java.io.File; |
| import java.io.IOException; |
| |
| import java.nio.file.FileVisitResult; |
| import java.nio.file.Path; |
| import java.nio.file.attribute.BasicFileAttributes; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Function; |
| import java.util.zip.ZipFile; |
| |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; |
| import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator; |
| import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; |
| import org.eclipse.jdt.internal.compiler.env.IBinaryType; |
| import org.eclipse.jdt.internal.compiler.env.IModule; |
| import org.eclipse.jdt.internal.compiler.env.IMultiModuleEntry; |
| import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; |
| import org.eclipse.jdt.internal.compiler.env.IModule.IPackageExport; |
| import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.ExternalAnnotationStatus; |
| import org.eclipse.jdt.internal.compiler.util.JRTUtil; |
| import org.eclipse.jdt.internal.compiler.util.SuffixConstants; |
| |
| @SuppressWarnings({"rawtypes", "unchecked"}) |
| public class ClasspathJrt extends ClasspathLocation implements IMultiModuleEntry { |
| public File file; |
| protected ZipFile annotationZipFile; |
| protected boolean closeZipFileAtEnd; |
| protected static HashMap<String, Map<String,IModule>> ModulesCache = new HashMap<>(); |
| public final Set<String> moduleNamesCache; |
| //private Set<String> packageCache; |
| protected List<String> annotationPaths; |
| |
| public ClasspathJrt(File file, boolean closeZipFileAtEnd, |
| AccessRuleSet accessRuleSet, String destinationPath) { |
| super(accessRuleSet, destinationPath); |
| this.file = file; |
| this.closeZipFileAtEnd = closeZipFileAtEnd; |
| this.moduleNamesCache = new HashSet<>(); |
| } |
| |
| @Override |
| public List fetchLinkedJars(FileSystem.ClasspathSectionProblemReporter problemReporter) { |
| return null; |
| } |
| @Override |
| public char[][] getModulesDeclaringPackage(String qualifiedPackageName, String moduleName) { |
| List<String> modules = JRTUtil.getModulesDeclaringPackage(this.file, qualifiedPackageName, moduleName); |
| return CharOperation.toCharArrays(modules); |
| } |
| @Override |
| public boolean hasCompilationUnit(String qualifiedPackageName, String moduleName) { |
| return JRTUtil.hasCompilationUnit(this.file, qualifiedPackageName, moduleName); |
| } |
| @Override |
| public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) { |
| return findClass(typeName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, false); |
| } |
| @Override |
| public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { |
| if (!isPackage(qualifiedPackageName, moduleName)) |
| return null; // most common case |
| |
| try { |
| IBinaryType reader = ClassFileReader.readFromModule(this.file, moduleName, qualifiedBinaryFileName, this.moduleNamesCache::contains); |
| |
| if (reader != null) { |
| searchPaths: |
| if (this.annotationPaths != null) { |
| String qualifiedClassName = qualifiedBinaryFileName.substring(0, qualifiedBinaryFileName.length()-SuffixConstants.EXTENSION_CLASS.length()-1); |
| for (String annotationPath : this.annotationPaths) { |
| try { |
| if (this.annotationZipFile == null) { |
| this.annotationZipFile = ExternalAnnotationDecorator.getAnnotationZipFile(annotationPath, null); |
| } |
| reader = ExternalAnnotationDecorator.create(reader, annotationPath, qualifiedClassName, this.annotationZipFile); |
| |
| if (reader.getExternalAnnotationStatus() == ExternalAnnotationStatus.TYPE_IS_ANNOTATED) { |
| break searchPaths; |
| } |
| } catch (IOException e) { |
| // don't let error on annotations fail class reading |
| } |
| } |
| // location is configured for external annotations, but no .eea found, decorate in order to answer NO_EEA_FILE: |
| reader = new ExternalAnnotationDecorator(reader, null); |
| } |
| char[] answerModuleName = reader.getModule(); |
| if (answerModuleName == null && moduleName != null) |
| answerModuleName = moduleName.toCharArray(); |
| return new NameEnvironmentAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName), answerModuleName); |
| } |
| } catch (ClassFormatException | IOException e) { |
| // treat as if class file is missing |
| } |
| return null; |
| } |
| @Override |
| public boolean hasAnnotationFileFor(String qualifiedTypeName) { |
| return false; // TODO(SHMOD): implement |
| } |
| @Override |
| public char[][][] findTypeNames(final String qualifiedPackageName, final String moduleName) { |
| if (!isPackage(qualifiedPackageName, moduleName)) |
| return null; // most common case |
| final char[] packageArray = qualifiedPackageName.toCharArray(); |
| final ArrayList answers = new ArrayList(); |
| |
| try { |
| JRTUtil.walkModuleImage(this.file, new JRTUtil.JrtFileVisitor<java.nio.file.Path>() { |
| |
| @Override |
| public FileVisitResult visitPackage(java.nio.file.Path dir, java.nio.file.Path modPath, BasicFileAttributes attrs) throws IOException { |
| if (qualifiedPackageName.startsWith(dir.toString())) { |
| return FileVisitResult.CONTINUE; |
| } |
| return FileVisitResult.SKIP_SUBTREE; |
| } |
| |
| @Override |
| public FileVisitResult visitFile(java.nio.file.Path dir, java.nio.file.Path modPath, BasicFileAttributes attrs) throws IOException { |
| Path parent = dir.getParent(); |
| if (parent == null) |
| return FileVisitResult.CONTINUE; |
| if (!parent.toString().equals(qualifiedPackageName)) { |
| return FileVisitResult.CONTINUE; |
| } |
| String fileName = dir.getName(dir.getNameCount() - 1).toString(); |
| // The path already excludes the folders and all the '/', hence the -1 for last index of '/' |
| addTypeName(answers, fileName, -1, packageArray); |
| return FileVisitResult.CONTINUE; |
| } |
| |
| @Override |
| public FileVisitResult visitModule(Path p, String name) throws IOException { |
| if (moduleName == null) |
| return FileVisitResult.CONTINUE; |
| if (!moduleName.equals(name)) { |
| return FileVisitResult.SKIP_SUBTREE; |
| } |
| return FileVisitResult.CONTINUE; |
| } |
| |
| }, JRTUtil.NOTIFY_ALL); |
| } catch (IOException e) { |
| // Ignore and move on |
| } |
| |
| int size = answers.size(); |
| if (size != 0) { |
| char[][][] result = new char[size][][]; |
| answers.toArray(result); |
| return result; |
| } |
| return null; |
| } |
| |
| protected void addTypeName(final ArrayList answers, String fileName, int last, char[] packageName) { |
| int indexOfDot = fileName.lastIndexOf('.'); |
| if (indexOfDot != -1) { |
| String typeName = fileName.substring(last + 1, indexOfDot); |
| answers.add( |
| CharOperation.arrayConcat( |
| CharOperation.splitOn('/', packageName), |
| typeName.toCharArray())); |
| } |
| } |
| @Override |
| public void initialize() throws IOException { |
| loadModules(); |
| } |
| // public void acceptModule(IModuleDeclaration mod) { |
| // if (this.isJrt) |
| // return; |
| // this.module = mod; |
| // } |
| public void loadModules() { |
| Map<String,IModule> cache = ModulesCache.get(this.file.getPath()); |
| |
| if (cache == null) { |
| try { |
| HashMap<String,IModule> newCache = new HashMap<>(); |
| org.eclipse.jdt.internal.compiler.util.JRTUtil.walkModuleImage(this.file, |
| new org.eclipse.jdt.internal.compiler.util.JRTUtil.JrtFileVisitor<Path>() { |
| |
| @Override |
| public FileVisitResult visitPackage(Path dir, Path mod, BasicFileAttributes attrs) |
| throws IOException { |
| return FileVisitResult.CONTINUE; |
| } |
| |
| @Override |
| public FileVisitResult visitFile(Path f, Path mod, BasicFileAttributes attrs) |
| throws IOException { |
| return FileVisitResult.CONTINUE; |
| } |
| |
| @Override |
| public FileVisitResult visitModule(Path p, String name) throws IOException { |
| try { |
| ClasspathJrt.this.acceptModule(JRTUtil.getClassfileContent(ClasspathJrt.this.file, IModule.MODULE_INFO_CLASS, name), newCache); |
| ClasspathJrt.this.moduleNamesCache.add(name); |
| } catch (ClassFormatException e) { |
| e.printStackTrace(); |
| } |
| return FileVisitResult.SKIP_SUBTREE; |
| } |
| }, JRTUtil.NOTIFY_MODULES); |
| |
| synchronized(ModulesCache) { |
| if (ModulesCache.get(this.file.getPath()) == null) { |
| ModulesCache.put(this.file.getPath(), Collections.unmodifiableMap(newCache)); |
| } |
| } |
| } catch (IOException e) { |
| // TODO: Java 9 Should report better |
| } |
| } else { |
| this.moduleNamesCache.addAll(cache.keySet()); |
| } |
| } |
| void acceptModule(ClassFileReader reader, Map<String, IModule> cache) { |
| if (reader != null) { |
| IModule moduleDecl = reader.getModuleDeclaration(); |
| if (moduleDecl != null) { |
| cache.put(String.valueOf(moduleDecl.name()), moduleDecl); |
| } |
| } |
| } |
| |
| void acceptModule(byte[] content, Map<String, IModule> cache) { |
| if (content == null) |
| return; |
| ClassFileReader reader = null; |
| try { |
| reader = new ClassFileReader(content, IModule.MODULE_INFO_CLASS.toCharArray()); |
| } catch (ClassFormatException e) { |
| e.printStackTrace(); |
| } |
| if (reader != null) { |
| acceptModule(reader, cache); |
| } |
| } |
| |
| @Override |
| public Collection<String> getModuleNames(Collection<String> limitModule, Function<String, IModule> getModule) { |
| Map<String, IModule> cache = ModulesCache.get(this.file.getPath()); |
| return selectModules(cache.keySet(), limitModule, getModule); |
| } |
| @Override |
| protected <T> List<String> allModules(Iterable<T> allSystemModules, Function<T,String> getModuleName, Function<T,IModule> getModule) { |
| List<String> result = new ArrayList<>(); |
| boolean hasJavaDotSE = false; |
| for (T mod : allSystemModules) { |
| String moduleName = getModuleName.apply(mod); |
| if ("java.se".equals(moduleName)) { //$NON-NLS-1$ |
| result.add(moduleName); |
| hasJavaDotSE = true; |
| break; |
| } |
| } |
| for (T mod : allSystemModules) { |
| String moduleName = getModuleName.apply(mod); |
| boolean isJavaDotStart = moduleName.startsWith("java."); //$NON-NLS-1$ |
| boolean isPotentialRoot = !isJavaDotStart; // always include non-java.* |
| if (!hasJavaDotSE) |
| isPotentialRoot |= isJavaDotStart; // no java.se => add all java.* |
| |
| if (isPotentialRoot) { |
| IModule m = getModule.apply(mod); |
| if (m != null) { |
| for (IPackageExport packageExport : m.exports()) { |
| if (!packageExport.isQualified()) { |
| result.add(moduleName); |
| break; |
| } |
| } |
| } |
| } |
| } |
| return result; |
| } |
| // protected void addToPackageCache(String fileName, boolean endsWithSep) { |
| // int last = endsWithSep ? fileName.length() : fileName.lastIndexOf('/'); |
| // while (last > 0) { |
| // // extract the package name |
| // String packageName = fileName.substring(0, last); |
| // if (this.packageCache.contains(packageName)) |
| // return; |
| // this.packageCache.add(packageName); |
| // last = packageName.lastIndexOf('/'); |
| // } |
| // } |
| // public synchronized boolean isPackage(String qualifiedPackageName) { |
| // if (this.packageCache != null) |
| // return this.packageCache.contains(qualifiedPackageName); |
| // |
| // this.packageCache = new HashSet<>(41); |
| // this.packageCache.add(Util.EMPTY_STRING); |
| // |
| // try { |
| // JRTUtil.walkModuleImage(this.file, new JRTUtil.JrtFileVisitor<java.nio.file.Path>() { |
| // |
| // @Override |
| // public FileVisitResult visitPackage(java.nio.file.Path dir, java.nio.file.Path mod, BasicFileAttributes attrs) throws IOException { |
| // addToPackageCache(dir.toString(), true); |
| // return FileVisitResult.CONTINUE; |
| // } |
| // |
| // @Override |
| // public FileVisitResult visitFile(java.nio.file.Path dir, java.nio.file.Path mod, BasicFileAttributes attrs) throws IOException { |
| // return FileVisitResult.CONTINUE; |
| // } |
| // |
| // @Override |
| // public FileVisitResult visitModule(java.nio.file.Path mod) throws IOException { |
| // return FileVisitResult.CONTINUE; |
| // } |
| // |
| // }, JRTUtil.NOTIFY_PACKAGES); |
| // } catch (IOException e) { |
| // // Ignore and move on |
| // } |
| // return this.packageCache.contains(qualifiedPackageName); |
| // } |
| @Override |
| public void reset() { |
| if (this.closeZipFileAtEnd) { |
| if (this.annotationZipFile != null) { |
| try { |
| this.annotationZipFile.close(); |
| } catch(IOException e) { |
| // ignore |
| } |
| this.annotationZipFile = null; |
| } |
| } |
| if (this.annotationPaths != null) { |
| //this.packageCache = null; |
| this.annotationPaths = null; |
| } |
| } |
| @Override |
| public String toString() { |
| return "Classpath for JRT System " + this.file.getPath(); //$NON-NLS-1$ |
| } |
| @Override |
| public char[] normalizedPath() { |
| if (this.normalizedPath == null) { |
| String path2 = this.getPath(); |
| char[] rawName = path2.toCharArray(); |
| if (File.separatorChar == '\\') { |
| CharOperation.replace(rawName, '\\', '/'); |
| } |
| this.normalizedPath = CharOperation.subarray(rawName, 0, CharOperation.lastIndexOf('.', rawName)); |
| } |
| return this.normalizedPath; |
| } |
| @Override |
| public String getPath() { |
| if (this.path == null) { |
| try { |
| this.path = this.file.getCanonicalPath(); |
| } catch (IOException e) { |
| // in case of error, simply return the absolute path |
| this.path = this.file.getAbsolutePath(); |
| } |
| } |
| return this.path; |
| } |
| @Override |
| public int getMode() { |
| return BINARY; |
| } |
| @Override |
| public boolean hasModule() { |
| return true; |
| } |
| @Override |
| public IModule getModule(char[] moduleName) { |
| Map<String, IModule> modules = ModulesCache.get(this.file.getPath()); |
| if (modules != null) { |
| return modules.get(String.valueOf(moduleName)); |
| } |
| return null; |
| } |
| @Override |
| public boolean servesModule(char[] moduleName) { |
| return getModule(moduleName) != null; |
| } |
| } |