Copyright (c) 2000, 2018 IBM Corporation and others. 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
/******************************************************************************* * Copyright (c) 2000, 2018 IBM Corporation and others. * * 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.core.builder; import java.io.IOException; import java.util.HashSet; import java.util.Set; import java.util.function.Predicate; import java.util.zip.ZipFile; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; 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.NameEnvironmentAnswer; import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable; import org.eclipse.jdt.internal.compiler.util.SuffixConstants; import org.eclipse.jdt.internal.core.util.Util; public class ClasspathDirectory extends ClasspathLocation { final boolean isOnModulePath; IContainer binaryFolder; // includes .class files for a single directory boolean isOutputFolder; SimpleLookupTable directoryCache; String[] missingPackageHolder = new String[1]; AccessRuleSet accessRuleSet; ZipFile annotationZipFile; String externalAnnotationPath; ClasspathDirectory(IContainer binaryFolder, boolean isOutputFolder, AccessRuleSet accessRuleSet, IPath externalAnnotationPath, boolean isOnModulePath) { this.binaryFolder = binaryFolder; this.isOutputFolder = isOutputFolder || binaryFolder.getProjectRelativePath().isEmpty(); // if binaryFolder == project, then treat it as an outputFolder this.directoryCache = new SimpleLookupTable(5); this.accessRuleSet = accessRuleSet; if (externalAnnotationPath != null) this.externalAnnotationPath = externalAnnotationPath.toOSString(); this.isOnModulePath = isOnModulePath; } @Override public void cleanup() { if (this.annotationZipFile != null) { try { this.annotationZipFile.close(); } catch(IOException e) { // ignore it } this.annotationZipFile = null; } this.directoryCache = null; } IModule initializeModule() { IResource[] members = null; try { members = this.binaryFolder.members(); if (members != null) { for (int i = 0, l = members.length; i < l; i++) { IResource m = members[i]; String name = m.getName(); // Note: Look only inside the default package. if (m.getType() == IResource.FILE && org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(name)) { if (name.equalsIgnoreCase(IModule.MODULE_INFO_CLASS)) { try { ClassFileReader cfr = Util.newClassFileReader(m); return cfr.getModuleDeclaration(); } catch (ClassFormatException | IOException e) { // TODO Java 9 Auto-generated catch block e.printStackTrace(); } } } } } } catch (CoreException e1) { e1.printStackTrace(); } return null; } 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; try { IResource container = this.binaryFolder.findMember(qualifiedPackageName); // this is a case-sensitive check if (container instanceof IContainer) { IResource[] members = ((IContainer) container).members(); dirList = new String[members.length]; int index = 0; for (int i = 0, l = members.length; i < l; i++) { IResource m = members[i]; String name = m.getName(); if (m.getType() == IResource.FILE && org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(name)) { // add exclusion pattern check here if we want to hide .class files dirList[index++] = name; } } if (index < dirList.length) System.arraycopy(dirList, 0, dirList = new String[index], 0, index); this.directoryCache.put(qualifiedPackageName, dirList); return dirList; } } catch(CoreException ignored) { // ignore } this.directoryCache.put(qualifiedPackageName, this.missingPackageHolder); return null; } boolean doesFileExist(String fileName, String qualifiedPackageName, String qualifiedFullName) { 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 boolean equals(Object o) { if (this == o) return true; if (!(o instanceof ClasspathDirectory)) return false; ClasspathDirectory dir = (ClasspathDirectory) o; if (this.accessRuleSet != dir.accessRuleSet) if (this.accessRuleSet == null || !this.accessRuleSet.equals(dir.accessRuleSet)) return false; if (this.isOnModulePath != dir.isOnModulePath) return false; return this.binaryFolder.equals(dir.binaryFolder) && areAllModuleOptionsEqual(dir); } @Override public NameEnvironmentAnswer findClass(String binaryFileName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly, Predicate<String> moduleNameFilter) { if (!doesFileExist(binaryFileName, qualifiedPackageName, qualifiedBinaryFileName)) return null; // most common case IBinaryType reader = null; try { reader = Util.newClassFileReader(this.binaryFolder.getFile(new Path(qualifiedBinaryFileName))); } catch (CoreException | ClassFormatException | IOException e) { return null; } if (reader != null) { char[] modName = this.module == null ? null : this.module.name(); if (reader instanceof ClassFileReader) { ClassFileReader cfReader = (ClassFileReader) reader; if (cfReader.moduleName == null) cfReader.moduleName = modName; else modName = cfReader.moduleName; } String fileNameWithoutExtension = qualifiedBinaryFileName.substring(0, qualifiedBinaryFileName.length() - SuffixConstants.SUFFIX_CLASS.length); if (this.externalAnnotationPath != null) { try { if (this.annotationZipFile == null) { this.annotationZipFile = ExternalAnnotationDecorator .getAnnotationZipFile(this.externalAnnotationPath, null); } reader = ExternalAnnotationDecorator.create(reader, this.externalAnnotationPath, fileNameWithoutExtension, this.annotationZipFile); } catch (IOException e) { // don't let error on annotations fail class reading } } if (this.accessRuleSet == null) return this.module == null ? new NameEnvironmentAnswer(reader, null) : new NameEnvironmentAnswer(reader, null, modName); return new NameEnvironmentAnswer(reader, this.accessRuleSet.getViolatedRestriction(fileNameWithoutExtension.toCharArray()), modName); } return null; } @Override public IPath getProjectRelativePath() { return this.binaryFolder.getProjectRelativePath(); } @Override public int hashCode() { return this.binaryFolder == null ? super.hashCode() : this.binaryFolder.hashCode(); } protected boolean isExcluded(IResource resource) { return false; } @Override public boolean isOutputFolder() { return this.isOutputFolder; } @Override public boolean isPackage(String qualifiedPackageName, String moduleName) { if (moduleName != null) { if (this.module == null || !moduleName.equals(String.valueOf(this.module.name()))) return false; } return directoryList(qualifiedPackageName) != null; } @Override public boolean hasCompilationUnit(String qualifiedPackageName, String moduleName) { String[] dirList = directoryList(qualifiedPackageName); if (dirList != null) { for (String entry : dirList) { String entryLC = entry.toLowerCase(); if (entryLC.endsWith(SuffixConstants.SUFFIX_STRING_class) || entryLC.endsWith(SuffixConstants.SUFFIX_STRING_java)) return true; } } return false; } @Override public void reset() { this.directoryCache = new SimpleLookupTable(5); } @Override public String toString() { String start = "Binary classpath directory " + this.binaryFolder.getFullPath().toString(); //$NON-NLS-1$ if (this.accessRuleSet == null) return start; return start + " with " + this.accessRuleSet; //$NON-NLS-1$ } @Override public String debugPathString() { return this.binaryFolder.getFullPath().toString(); } @Override public NameEnvironmentAnswer findClass(String typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) { // return findClass(typeName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, false, null); } @Override public char[][] listPackages() { Set<String> packageNames = new HashSet<>(); IPath basePath = this.binaryFolder.getFullPath(); try { this.binaryFolder.accept(r -> { String extension = r.getFileExtension(); if (r instanceof IFile && extension != null && SuffixConstants.EXTENSION_class.equals(extension.toLowerCase())) { packageNames.add(r.getParent().getFullPath().makeRelativeTo(basePath).toString().replace('/', '.')); } return true; }); } catch (CoreException e) { Util.log(e, "Failed to scan packages of "+this.binaryFolder); //$NON-NLS-1$ } return packageNames.stream().map(String::toCharArray).toArray(char[][]::new); } }