| /******************************************************************************* |
| * Copyright (c) 2000, 2018 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Stephan Herrmann - Contribution for |
| * Bug 440687 - [compiler][batch][null] improve command line option for external annotations |
| * Lars Vogel <Lars.Vogel@vogella.com> - Contributions for |
| * Bug 473178 |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.batch; |
| |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.compiler.CompilationResult; |
| import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; |
| import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; |
| import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationProvider; |
| import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; |
| import org.eclipse.jdt.internal.compiler.env.IModule; |
| import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; |
| import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; |
| import org.eclipse.jdt.internal.compiler.parser.Parser; |
| import org.eclipse.jdt.internal.compiler.parser.ScannerHelper; |
| import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; |
| import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; |
| import org.eclipse.jdt.internal.compiler.util.Util; |
| |
| import java.io.File; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Function; |
| import java.util.stream.Stream; |
| |
| @SuppressWarnings({"rawtypes", "unchecked"}) |
| public class ClasspathDirectory extends ClasspathLocation { |
| |
| private Hashtable directoryCache; |
| private String[] missingPackageHolder = new String[1]; |
| private int mode; // ability to only consider one kind of files (source vs. binaries), by default use both |
| private String encoding; // only useful if referenced in the source path |
| private Hashtable<String, Hashtable<String, String>> packageSecondaryTypes = null; |
| Map options; |
| |
| ClasspathDirectory(File directory, String encoding, int mode, |
| AccessRuleSet accessRuleSet, String destinationPath, Map options) { |
| super(accessRuleSet, destinationPath); |
| this.mode = mode; |
| this.options = options; |
| try { |
| this.path = directory.getCanonicalPath(); |
| } catch (IOException e) { |
| // should not happen as we know that the file exists |
| this.path = directory.getAbsolutePath(); |
| } |
| if (!this.path.endsWith(File.separator)) |
| this.path += File.separator; |
| this.directoryCache = new Hashtable(11); |
| this.encoding = encoding; |
| } |
| String[] directoryList(String qualifiedPackageName) { |
| String[] dirList = (String[]) this.directoryCache.get(qualifiedPackageName); |
| if (dirList == this.missingPackageHolder) return null; // package exists in another classpath directory or jar |
| if (dirList != null) return dirList; |
| |
| File dir = new File(this.path + qualifiedPackageName); |
| notFound : if (dir.isDirectory()) { |
| // must protect against a case insensitive File call |
| // walk the qualifiedPackageName backwards looking for an uppercase character before the '/' |
| int index = qualifiedPackageName.length(); |
| int last = qualifiedPackageName.lastIndexOf(File.separatorChar); |
| while (--index > last && !ScannerHelper.isUpperCase(qualifiedPackageName.charAt(index))){/*empty*/} |
| if (index > last) { |
| if (last == -1) { |
| if (!doesFileExist(qualifiedPackageName, Util.EMPTY_STRING)) |
| break notFound; |
| } else { |
| String packageName = qualifiedPackageName.substring(last + 1); |
| String parentPackage = qualifiedPackageName.substring(0, last); |
| if (!doesFileExist(packageName, parentPackage)) |
| break notFound; |
| } |
| } |
| if ((dirList = dir.list()) == null) |
| dirList = CharOperation.NO_STRINGS; |
| this.directoryCache.put(qualifiedPackageName, dirList); |
| return dirList; |
| } |
| this.directoryCache.put(qualifiedPackageName, this.missingPackageHolder); |
| return null; |
| } |
| boolean doesFileExist(String fileName, String qualifiedPackageName) { |
| String[] dirList = directoryList(qualifiedPackageName); |
| if (dirList == null) return false; // most common case |
| |
| for (int i = dirList.length; --i >= 0;) |
| if (fileName.equals(dirList[i])) |
| return true; |
| return false; |
| } |
| @Override |
| public List fetchLinkedJars(FileSystem.ClasspathSectionProblemReporter problemReporter) { |
| return null; |
| } |
| private NameEnvironmentAnswer findClassInternal(char[] typeName, String qualifiedPackageName, String qualifiedBinaryFileName, boolean asBinaryOnly) { |
| if (!isPackage(qualifiedPackageName, null)) return null; // most common case TODO(SHMOD): use module name from this.module? |
| String fileName = new String(typeName); |
| boolean binaryExists = ((this.mode & BINARY) != 0) && doesFileExist(fileName + SUFFIX_STRING_class, qualifiedPackageName); |
| boolean sourceExists = ((this.mode & SOURCE) != 0) && doesFileExist(fileName + SUFFIX_STRING_java, qualifiedPackageName); |
| if (sourceExists && !asBinaryOnly) { |
| String fullSourcePath = this.path + qualifiedBinaryFileName.substring(0, qualifiedBinaryFileName.length() - 6) + SUFFIX_STRING_java; |
| CompilationUnit unit = new CompilationUnit(null, fullSourcePath, this.encoding, this.destinationPath); |
| unit.module = this.module == null ? null : this.module.name(); |
| if (!binaryExists) |
| return new NameEnvironmentAnswer(unit, |
| fetchAccessRestriction(qualifiedBinaryFileName)); |
| String fullBinaryPath = this.path + qualifiedBinaryFileName; |
| long binaryModified = new File(fullBinaryPath).lastModified(); |
| long sourceModified = new File(fullSourcePath).lastModified(); |
| if (sourceModified > binaryModified) |
| return new NameEnvironmentAnswer(unit, |
| fetchAccessRestriction(qualifiedBinaryFileName)); |
| } |
| if (binaryExists) { |
| try { |
| ClassFileReader reader = ClassFileReader.read(this.path + qualifiedBinaryFileName); |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=321115, package names are to be treated case sensitive. |
| String typeSearched = qualifiedPackageName.length() > 0 ? |
| qualifiedPackageName.replace(File.separatorChar, '/') + "/" + fileName //$NON-NLS-1$ |
| : fileName; |
| if (!CharOperation.equals(reader.getName(), typeSearched.toCharArray())) { |
| reader = null; |
| } |
| if (reader != null) { |
| char[] modName = reader.moduleName != null ? reader.moduleName : this.module != null ? this.module.name() : null; |
| return new NameEnvironmentAnswer( |
| reader, |
| fetchAccessRestriction(qualifiedBinaryFileName), |
| modName); |
| } |
| } catch (IOException e) { |
| // treat as if file is missing |
| } catch (ClassFormatException e) { |
| // treat as if file is missing |
| } |
| } |
| return null; |
| } |
| public NameEnvironmentAnswer findSecondaryInClass(char[] typeName, String qualifiedPackageName, String qualifiedBinaryFileName) { |
| //"package-info" is a reserved class name and can never be a secondary type (it is much faster to stop the search here). |
| if(CharOperation.equals(TypeConstants.PACKAGE_INFO_NAME, typeName)) { |
| return null; |
| } |
| |
| String typeNameString = new String(typeName); |
| String moduleName = this.module != null ? String.valueOf(this.module.name()) : null; // TODO(SHMOD): test for ModuleBinding.ANY & UNNAMED |
| boolean prereqs = this.options != null && isPackage(qualifiedPackageName, moduleName) && ((this.mode & SOURCE) != 0) && doesFileExist(typeNameString + SUFFIX_STRING_java, qualifiedPackageName); |
| return prereqs ? null : findSourceSecondaryType(typeNameString, qualifiedPackageName, qualifiedBinaryFileName); /* only secondary types */ |
| } |
| |
| @Override |
| public boolean hasAnnotationFileFor(String qualifiedTypeName) { |
| int pos = qualifiedTypeName.lastIndexOf('/'); |
| if (pos != -1 && (pos + 1 < qualifiedTypeName.length())) { |
| String fileName = qualifiedTypeName.substring(pos + 1) + ExternalAnnotationProvider.ANNOTATION_FILE_SUFFIX; |
| return doesFileExist(fileName, qualifiedTypeName.substring(0, pos)); |
| } |
| return false; |
| } |
| @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 (File.separatorChar == '/') |
| return findClassInternal(typeName, qualifiedPackageName, qualifiedBinaryFileName, asBinaryOnly); |
| |
| return findClassInternal(typeName, qualifiedPackageName.replace('/', File.separatorChar), |
| qualifiedBinaryFileName.replace('/', File.separatorChar), asBinaryOnly); |
| } |
| /** |
| * Add all the secondary types in the package |
| */ |
| private Hashtable<String, String> getSecondaryTypes(String qualifiedPackageName) { |
| Hashtable<String, String> packageEntry = new Hashtable<>(); |
| |
| String[] dirList = (String[]) this.directoryCache.get(qualifiedPackageName); |
| if (dirList == this.missingPackageHolder // package exists in another classpath directory or jar |
| || dirList == null) |
| return packageEntry; |
| |
| File dir = new File(this.path + qualifiedPackageName); |
| File[] listFiles = dir.isDirectory() ? dir.listFiles() : null; |
| if (listFiles == null) return packageEntry; |
| |
| for (int i = 0, l = listFiles.length; i < l; ++i) { |
| File f = listFiles[i]; |
| if (f.isDirectory()) continue; |
| String s = f.getAbsolutePath(); |
| if (s == null) continue; |
| if (!(s.endsWith(SUFFIX_STRING_java) || s.endsWith(SUFFIX_STRING_JAVA))) continue; |
| CompilationUnit cu = new CompilationUnit(null, s, this.encoding, this.destinationPath); |
| CompilationResult compilationResult = new CompilationResult(s.toCharArray(), 1, 1, 10); |
| ProblemReporter problemReporter = |
| new ProblemReporter( |
| DefaultErrorHandlingPolicies.proceedWithAllProblems(), |
| new CompilerOptions(this.options), |
| new DefaultProblemFactory()); |
| Parser parser = new Parser(problemReporter, false); |
| parser.reportSyntaxErrorIsRequired = false; |
| |
| CompilationUnitDeclaration unit = parser.parse(cu, compilationResult); |
| org.eclipse.jdt.internal.compiler.ast.TypeDeclaration[] types = unit != null ? unit.types : null; |
| if (types == null) continue; |
| for (int j = 0, k = types.length; j < k; j++) { |
| TypeDeclaration type = types[j]; |
| char[] name = type.isSecondary() ? type.name : null; // add only secondary types |
| if (name != null) |
| packageEntry.put(new String(name), s); |
| } |
| } |
| return packageEntry; |
| } |
| private NameEnvironmentAnswer findSourceSecondaryType(String typeName, String qualifiedPackageName, String qualifiedBinaryFileName) { |
| |
| if (this.packageSecondaryTypes == null) this.packageSecondaryTypes = new Hashtable<>(); |
| Hashtable<String, String> packageEntry = this.packageSecondaryTypes.get(qualifiedPackageName); |
| if (packageEntry == null) { |
| packageEntry = getSecondaryTypes(qualifiedPackageName); |
| this.packageSecondaryTypes.put(qualifiedPackageName, packageEntry); |
| } |
| String fileName = packageEntry.get(typeName); |
| return fileName != null ? new NameEnvironmentAnswer(new CompilationUnit(null, |
| fileName, this.encoding, this.destinationPath), |
| fetchAccessRestriction(qualifiedBinaryFileName)) : null; |
| } |
| |
| |
| @Override |
| public char[][][] findTypeNames(String qualifiedPackageName, String moduleName) { |
| if (!isPackage(qualifiedPackageName, moduleName)) { |
| return null; // most common case |
| } |
| File dir = new File(this.path + qualifiedPackageName); |
| if (!dir.exists() || !dir.isDirectory()) { |
| return null; |
| } |
| String[] listFiles = dir.list(new FilenameFilter() { |
| @Override |
| public boolean accept(File directory1, String name) { |
| String fileName = name.toLowerCase(); |
| return fileName.endsWith(".class") || fileName.endsWith(".java"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| }); |
| int length; |
| if (listFiles == null || (length = listFiles.length) == 0) { |
| return null; |
| } |
| Set<String> secondary = getSecondaryTypes(qualifiedPackageName).keySet(); |
| char[][][] result = new char[length + secondary.size()][][]; |
| char[][] packageName = CharOperation.splitOn(File.separatorChar, qualifiedPackageName.toCharArray()); |
| for (int i = 0; i < length; i++) { |
| String fileName = listFiles[i]; |
| int indexOfLastDot = fileName.indexOf('.'); |
| String typeName = indexOfLastDot > 0 ? fileName.substring(0, indexOfLastDot) : fileName; |
| result[i] = CharOperation.arrayConcat(packageName, typeName.toCharArray()); |
| } |
| if (secondary.size() > 0) { |
| int idx = length; |
| for (String type : secondary) { |
| result[idx++] = CharOperation.arrayConcat(packageName, type.toCharArray()); |
| } |
| } |
| return result; |
| } |
| @Override |
| public void initialize() throws IOException { |
| // nothing to do |
| } |
| @Override |
| public char[][] getModulesDeclaringPackage(String qualifiedPackageName, /*@Nullable*/String moduleName) { |
| String qp2 = File.separatorChar == '/' ? qualifiedPackageName : qualifiedPackageName.replace('/', File.separatorChar); |
| return singletonModuleNameIf(directoryList(qp2) != null); |
| } |
| @Override |
| public boolean hasCompilationUnit(String qualifiedPackageName, String moduleName) { |
| String qp2 = File.separatorChar == '/' ? qualifiedPackageName : qualifiedPackageName.replace('/', File.separatorChar); |
| String[] dirList = directoryList(qp2); |
| if (dirList != null) { |
| for (String entry : dirList) { |
| String entryLC = entry.toLowerCase(); |
| if (entryLC.endsWith(SUFFIX_STRING_java) || entryLC.endsWith(SUFFIX_STRING_class)) |
| return true; |
| } |
| } |
| return false; |
| } |
| @Override |
| public boolean hasCUDeclaringPackage(String qualifiedPackageName, Function<CompilationUnit, String> pkgNameExtractor) { |
| String qp2 = File.separatorChar == '/' ? qualifiedPackageName : qualifiedPackageName.replace('/', File.separatorChar); |
| return Stream.of(directoryList(qp2)).anyMatch(entry -> { |
| String entryLC = entry.toLowerCase(); |
| boolean hasDeclaration = false; |
| String fullPath = this.path + qp2 + "/" + entry; //$NON-NLS-1$ |
| String pkgName = null; |
| if (entryLC.endsWith(SUFFIX_STRING_class)) { |
| return true; |
| } else if (entryLC.endsWith(SUFFIX_STRING_java)) { |
| CompilationUnit cu = new CompilationUnit(null, fullPath, this.encoding); |
| pkgName = pkgNameExtractor.apply(cu); |
| } |
| if (pkgName != null && pkgName.equals(qp2.replace(File.separatorChar, '.'))) |
| hasDeclaration = true; |
| return hasDeclaration; |
| }); |
| } |
| @Override |
| public void reset() { |
| super.reset(); |
| this.directoryCache = new Hashtable(11); |
| } |
| @Override |
| public String toString() { |
| return "ClasspathDirectory " + this.path; //$NON-NLS-1$ |
| } |
| @Override |
| public char[] normalizedPath() { |
| if (this.normalizedPath == null) { |
| this.normalizedPath = this.path.toCharArray(); |
| if (File.separatorChar == '\\') { |
| CharOperation.replace(this.normalizedPath, '\\', '/'); |
| } |
| } |
| return this.normalizedPath; |
| } |
| @Override |
| public String getPath() { |
| return this.path; |
| } |
| @Override |
| public int getMode() { |
| return this.mode; |
| } |
| @Override |
| public IModule getModule() { |
| if (this.isAutoModule && this.module == null) { |
| return this.module = IModule.createAutomatic(this.path, false, null/*no manifest*/); |
| } |
| return this.module; |
| } |
| } |