Skip to main content
summaryrefslogtreecommitdiffstats
blob: 8984f57d81ce89de69afc91e653dc2e51f22dd67 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/*******************************************************************************
 * 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, m -> m.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);
	}
}

Back to the top