org.eclipse.jdt.internal.compiler.batch.ModuleFinder Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2016, 2020 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.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.env.IModule;
import org.eclipse.jdt.internal.compiler.env.PackageExportImpl;
import org.eclipse.jdt.internal.compiler.env.IModule.IPackageExport;
import org.eclipse.jdt.internal.compiler.parser.Parser;
import org.eclipse.jdt.internal.compiler.util.JRTUtil;
import org.eclipse.jdt.internal.compiler.util.Util;
public class ModuleFinder {
public static List findModules(File f, String destinationPath, Parser parser, Map options, boolean isModulepath, String release) {
List collector = new ArrayList<>();
scanForModules(destinationPath, parser, options, isModulepath, false, collector, f, release);
return collector;
}
protected static FileSystem.Classpath findModule(final File file, String destinationPath, Parser parser,
Map options, boolean isModulepath, String release) {
FileSystem.Classpath modulePath = FileSystem.getClasspath(file.getAbsolutePath(), null, !isModulepath, null,
destinationPath == null ? null : (destinationPath + File.separator + file.getName()), options, release);
if (modulePath != null) {
scanForModule(modulePath, file, parser, isModulepath, release);
}
return modulePath;
}
protected static void scanForModules(String destinationPath, Parser parser, Map options, boolean isModulepath,
boolean thisAnAutomodule, List collector, final File file, String release) {
FileSystem.Classpath entry = FileSystem.getClasspath(
file.getAbsolutePath(),
null,
!isModulepath,
null,
destinationPath == null ? null : (destinationPath + File.separator + file.getName()),
options,
release);
if (entry != null) {
IModule module = scanForModule(entry, file, parser, thisAnAutomodule, release);
if (module != null) {
collector.add(entry);
} else {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
scanForModules(destinationPath, parser, options, isModulepath, isModulepath, collector, f, release);
}
}
}
}
}
protected static IModule scanForModule(FileSystem.Classpath modulePath, final File file, Parser parser, boolean considerAutoModules, String release) {
IModule module = null;
if (file.isDirectory()) {
String[] list = file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
if (dir == file && (name.equalsIgnoreCase(IModule.MODULE_INFO_CLASS)
|| name.equalsIgnoreCase(IModule.MODULE_INFO_JAVA))) {
return true;
}
return false;
}
});
if (list.length > 0) {
String fileName = list[0];
switch (fileName) {
case IModule.MODULE_INFO_CLASS:
module = ModuleFinder.extractModuleFromClass(new File(file, fileName), modulePath);
break;
case IModule.MODULE_INFO_JAVA:
module = ModuleFinder.extractModuleFromSource(new File(file, fileName), parser, modulePath);
if (module == null)
return null;
String modName = new String(module.name());
if (!modName.equals(file.getName())) {
throw new IllegalArgumentException("module name " + modName + " does not match expected name " + file.getName()); //$NON-NLS-1$ //$NON-NLS-2$
}
break;
}
}
} else {
String moduleDescPath = getModulePathForArchive(file);
if (moduleDescPath != null) {
module = extractModuleFromArchive(file, modulePath, moduleDescPath, release);
}
}
if (considerAutoModules && module == null && !(modulePath instanceof ClasspathJrt)) {
if (!file.isDirectory()) {
String fileName = getFileName(file);
if (!fileName.isEmpty())
module = IModule.createAutomatic(fileName, file.isFile(), getManifest(file));
}
}
if (module != null)
modulePath.acceptModule(module);
return module;
}
private static Manifest getManifest(File file) {
if (getModulePathForArchive(file) == null)
return null;
try (JarFile jar = new JarFile(file)) {
return jar.getManifest();
} catch (IOException e) {
String error = "Failed to read manifest from " + file; //$NON-NLS-1$
if (JRTUtil.PROPAGATE_IO_ERRORS) {
throw new IllegalStateException(error, e);
} else {
System.err.println(error);
e.printStackTrace();
}
return null;
}
}
private static String getFileName(File file) {
String name = file.getName();
int index = name.lastIndexOf('.');
if (index == -1)
return name;
return name.substring(0, index);
}
/**
* Extracts the single reads clause from the given
* command line option (--add-reads). The result is a String[] with two
* element, first being the source module and second being the target module.
* The expected format is:
* {@code --add-reads =}
* @return a String[] with source and target module of the "reads" clause.
*/
protected static String[] extractAddonRead(String option) {
StringTokenizer tokenizer = new StringTokenizer(option, "="); //$NON-NLS-1$
String source = null;
String target = null;
if (tokenizer.hasMoreTokens()) {
source = tokenizer.nextToken();
} else {
// Handle error
return null;
}
if (tokenizer.hasMoreTokens()) {
target = tokenizer.nextToken();
} else {
// Handle error
return null;
}
return new String[]{source, target};
}
/**
* Simple structure representing one --add-exports
value.
*/
static class AddExport {
/** the name of the exporting module. */
public final String sourceModuleName;
/** the export structure */
public final IModule.IPackageExport export;
public AddExport(String moduleName, IPackageExport export) {
this.sourceModuleName = moduleName;
this.export = export;
}
}
/**
* Parses the --add-exports command line option and returns the package export definitions.
*
*
* The expected format is:
*
*
* {@code
* --add-exports /=(,)*
* }
*
* @param option the option to parse
* @return an {@link AddExport} structure.
*/
protected static AddExport extractAddonExport(String option) {
StringTokenizer tokenizer = new StringTokenizer(option, "/"); //$NON-NLS-1$
String source = null;
String pack = null;
List targets = new ArrayList<>();
if (tokenizer.hasMoreTokens()) {
source = tokenizer.nextToken("/"); //$NON-NLS-1$
} else {
// Handle error
return null;
}
if (tokenizer.hasMoreTokens()) {
pack = tokenizer.nextToken("/="); //$NON-NLS-1$
} else {
// Handle error
return null;
}
while (tokenizer.hasMoreTokens()) {
targets.add(tokenizer.nextToken("=,")); //$NON-NLS-1$
}
PackageExportImpl export = new PackageExportImpl();
export.pack = pack.toCharArray();
export.exportedTo = new char[targets.size()][];
for(int i = 0; i < export.exportedTo.length; i++) {
export.exportedTo[i] = targets.get(i).toCharArray();
}
return new AddExport(source, export);
}
private static String getModulePathForArchive(File file) {
int format = Util.archiveFormat(file.getAbsolutePath());
if (format == Util.ZIP_FILE) {
return IModule.MODULE_INFO_CLASS;
} else if(format == Util.JMOD_FILE) {
return "classes/" + IModule.MODULE_INFO_CLASS; //$NON-NLS-1$
}
return null;
}
private static IModule extractModuleFromArchive(File file, Classpath pathEntry, String path, String release) {
try (ZipFile zipFile = new ZipFile(file)) {
if (release != null) {
String releasePath = "META-INF/versions/" + release + "/" + path; //$NON-NLS-1$ //$NON-NLS-2$
ZipEntry entry = zipFile.getEntry(releasePath);
if (entry != null) {
path = releasePath;
}
}
ClassFileReader reader = ClassFileReader.read(zipFile, path);
IModule module = getModule(reader);
if (module != null) {
return reader.getModuleDeclaration();
}
return null;
} catch (ClassFormatException e) {
// Nothing to be done here
} catch (IOException e) {
String error = "Failed to read module for path " + path + " and release " + release + " from " + file; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (JRTUtil.PROPAGATE_IO_ERRORS) {
throw new IllegalStateException(error, e);
} else {
System.err.println(error);
e.printStackTrace();
}
}
return null;
}
private static IModule extractModuleFromClass(File classfilePath, Classpath pathEntry) {
ClassFileReader reader;
try {
reader = ClassFileReader.read(classfilePath);
IModule module = getModule(reader);
if (module != null) {
return reader.getModuleDeclaration();
}
return null;
} catch (ClassFormatException e) {
e.printStackTrace();
} catch (IOException e) {
String error = "Failed to read module from " + classfilePath; //$NON-NLS-1$
if (JRTUtil.PROPAGATE_IO_ERRORS) {
throw new IllegalStateException(error, e);
} else {
System.err.println(error);
e.printStackTrace();
}
}
return null;
}
private static IModule getModule(ClassFileReader classfile) {
if (classfile != null) {
return classfile.getModuleDeclaration();
}
return null;
}
private static IModule extractModuleFromSource(File file, Parser parser, Classpath pathEntry) {
CompilationUnit cu = new CompilationUnit(null, file.getAbsolutePath(), null, pathEntry.getDestinationPath());
CompilationResult compilationResult = new CompilationResult(cu, 0, 1, 10);
CompilationUnitDeclaration unit = parser.parse(cu, compilationResult);
if (unit.isModuleInfo() && unit.moduleDeclaration != null) {
cu.module = unit.moduleDeclaration.moduleName;
return new BasicModule(unit.moduleDeclaration, pathEntry);
}
return null;
}
}