blob: 836c64b2a36cffd1e992b465baeaf97e3623bb9a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017, 2018 GK Software AG, 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:
* Stephan Herrmann - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.core;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaModelStatusConstants;
import org.eclipse.jdt.core.IModuleDescription;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.env.IModuleAwareNameEnvironment;
import org.eclipse.jdt.internal.compiler.env.IUpdatableModule;
import org.eclipse.jdt.internal.compiler.env.IUpdatableModule.UpdateKind;
import org.eclipse.jdt.internal.compiler.env.IUpdatableModule.UpdatesByKind;
import org.eclipse.jdt.internal.compiler.lookup.ModuleBinding;
import org.eclipse.jdt.internal.core.util.Util;
/**
* An instance of this class collects <code>add-exports</code> and <code>add-reads</code> options from
* a project's class path entries, and performs the corresponding updates when requested by the compiler.
* <p>For <code>patch-module</code> and <code>limit-modules</code> see
* org.eclipse.jdt.internal.core.builder.ModuleEntryProcessor.</p>
*/
public class ModuleUpdater {
private JavaProject javaProoject;
private Map<String,UpdatesByKind> moduleUpdates = new HashMap<>();
public ModuleUpdater(JavaProject javaProject) {
this.javaProoject = javaProject;
}
/**
* Detects any ADD_EXPORTS or ADD_READS classpath attributes, parses the value,
* and collects the resulting module updates.
* @param entry a classpath entry of the current project.
* @throws JavaModelException
*/
public void computeModuleUpdates(IClasspathEntry entry) throws JavaModelException {
for (IClasspathAttribute attribute : entry.getExtraAttributes()) {
String attributeName = attribute.getName();
String values = attribute.getValue(); // the attributes considered here may have multiple values separated by ':'
if (attributeName.equals(IClasspathAttribute.ADD_EXPORTS)) {
for (String value : values.split(":")) { // format: <source-module>/<package>=<target-module>(,<target-module>)* //$NON-NLS-1$
int slash = value.indexOf('/');
int equals = value.indexOf('=');
if (slash != -1 && equals != -1) {
String modName = value.substring(0, slash);
char[] packName = value.substring(slash+1, equals).toCharArray();
char[][] targets = CharOperation.splitOn(',', value.substring(equals+1).toCharArray());
addModuleUpdate(modName, new IUpdatableModule.AddExports(packName, targets), UpdateKind.PACKAGE);
} else {
Util.log(IStatus.WARNING, "Invalid argument to add-exports: "+value); //$NON-NLS-1$
}
}
} else if (attributeName.equals(IClasspathAttribute.ADD_READS)) {
for (String value : values.split(":")) { // format: <source-module>=<target-module> //$NON-NLS-1$
int equals = value.indexOf('=');
if (equals != -1) {
String srcMod = value.substring(0, equals);
char[] targetMod = value.substring(equals+1).toCharArray();
addModuleUpdate(srcMod, new IUpdatableModule.AddReads(targetMod), UpdateKind.MODULE);
} else {
Util.log(IStatus.WARNING, "Invalid argument to add-reads: "+value); //$NON-NLS-1$
}
}
} else if (attributeName.equals(IClasspathAttribute.MODULE_MAIN_CLASS)) {
IModuleDescription module = this.javaProoject.getModuleDescription();
if (module == null)
throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.ELEMENT_DOES_NOT_EXIST));
addModuleUpdate(module.getElementName(), m -> m.setMainClassName(values.toCharArray()), UpdateKind.MODULE);
}
}
}
public void addModuleUpdate(String moduleName, Consumer<IUpdatableModule> update, UpdateKind kind) {
UpdatesByKind updates = this.moduleUpdates.get(moduleName);
if (updates == null) {
this.moduleUpdates.put(moduleName, updates = new UpdatesByKind());
}
updates.getList(kind, true).add(update);
}
/**
* @see IModuleAwareNameEnvironment#applyModuleUpdates(IUpdatableModule, UpdateKind)
*/
public void applyModuleUpdates(IUpdatableModule compilerModule, UpdateKind kind) {
char[] name = compilerModule.name();
if (name != ModuleBinding.UNNAMED) { // can't update the unnamed module
UpdatesByKind updates = this.moduleUpdates.get(String.valueOf(name));
if (updates != null) {
for (Consumer<IUpdatableModule> update : updates.getList(kind, false))
update.accept(compilerModule);
}
}
}
private static boolean containsNonModularDependency(IClasspathEntry[] entries) {
for (IClasspathEntry e : entries) {
if (e.getEntryKind() != IClasspathEntry.CPE_SOURCE && !((ClasspathEntry) e).isModular())
return true;
}
return false;
}
// Bug 520713: allow test code to access code on the classpath
public void addReadUnnamedForNonEmptyClasspath(JavaProject project, IClasspathEntry[] expandedClasspath)
throws JavaModelException {
for (String moduleName : determineModulesOfProjectsWithNonEmptyClasspath(project, expandedClasspath)) {
addModuleUpdate(moduleName, new IUpdatableModule.AddReads(ModuleBinding.ALL_UNNAMED), UpdateKind.MODULE);
}
}
public static Set<String> determineModulesOfProjectsWithNonEmptyClasspath(JavaProject project,
IClasspathEntry[] expandedClasspath) throws JavaModelException {
LinkedHashSet<String> list = new LinkedHashSet<>();
if (containsNonModularDependency(expandedClasspath)) {
IModuleDescription moduleDescription = project.getModuleDescription();
if (moduleDescription != null) {
list.add(moduleDescription.getElementName());
}
}
for (IClasspathEntry e1 : expandedClasspath) {
if (e1.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
Object target = JavaModel.getTarget(e1.getPath(), true);
if (target instanceof IProject) {
IProject prereqProject = (IProject) target;
if (JavaProject.hasJavaNature(prereqProject)) {
JavaProject prereqJavaProject = (JavaProject) JavaCore.create(prereqProject);
if (containsNonModularDependency(prereqJavaProject.getResolvedClasspath())) {
IModuleDescription prereqModuleDescription = prereqJavaProject.getModuleDescription();
if (prereqModuleDescription != null) {
list.add(prereqModuleDescription.getElementName());
}
}
}
}
}
}
return list;
}
public UpdatesByKind getUpdates(String moduleName) {
return this.moduleUpdates.get(moduleName);
}
}