Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 882a62cda2204a3d75d9b11229621f953f43132f (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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/*****************************************************************************
 * Copyright (c) 2021 Christian W. Damus, CEA LIST, and others.
 *
 * All rights reserved. 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
 * http://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Christian W. Damus - Initial API and implementation
 *
 *****************************************************************************/

package org.eclipse.papyrus.infra.tools.util;

import java.util.stream.Stream;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.emf.common.util.URI;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.papyrus.infra.tools.Activator;

/**
 * Optional support for a JDT workspace, to find development-time classes such as may be referenced for commands
 * in <em>Architecture Models</em>.
 *
 * @since 4.1
 */
public class ClasspathHelper {

	public static final ClasspathHelper INSTANCE;

	static {
		ClasspathHelper instance;

		try {
			instance = new JDTHelper();
		} catch (Exception e) {
			// JDT is not available
			instance = new ClasspathHelper();
		}

		INSTANCE = instance;
	}

	/**
	 * Find a class of the given {@code name} that is referenced by a model in the given {@code context} resource,
	 * with an optional {@code constraint}.
	 *
	 * @param name
	 *            the class name to find (using the canonical <tt>$</tt> separator for nested types
	 * @param context
	 *            the contextual model resource URI
	 * @param constraint
	 *            a class to which the result must conform (as a supertype), or {@code null} if no constraint is needed
	 * @return the class, or {@code null} if the class doesn't exist or is invalid (such as not conforming to the {@code constraint}).
	 *         The result may be a Java {@link Class} or a JDT {@link IType}, depending whether JDT is available
	 */
	public Object findClass(String name, URI context, Class<?> constraint) {
		return constraint == null
				? ClassLoaderHelper.loadClass(name, context)
				: ClassLoaderHelper.loadClass(name, constraint, context);
	}

	//
	// Nested types
	//

	/**
	 * The JDT helper instance looks for classes in the Java workspace (and typically in the current PDE target platform).
	 */
	private static final class JDTHelper extends ClasspathHelper {

		@Override
		public Object findClass(String name, URI context, Class<?> constraint) {
			Object result = null;

			IJavaProject project = getJavaProject(context);
			if (project == null) {
				// This search is not in a Java project context. So, it's a run-time environment
				// and we need to search the classpath.
				result = super.findClass(name, context, constraint);
			} else {
				result = findType(project, name, constraint);

				if (result == null) {
					// Search the workspace index
					result = searchType(name, constraint);
				}
			}

			return result;
		}

		private IJavaProject getJavaProject(URI context) {
			IJavaProject result = null;

			if (context != null && (context.isPlatformResource() || context.isPlatformPlugin())) {
				String projectName = context.segment(1);
				IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
				if (project.isAccessible()) {
					result = JavaCore.create(project);
				}
			}

			return result;
		}

		IType findType(IJavaProject project, String name, Class<?> constraint) {
			IType result;

			try {
				result = project.findType(jdtQualifiedName(name));

				if (result != null && constraint != null && !conformsTo(result, constraint)) {
					result = null;
				}
			} catch (JavaModelException e) {
				// Maybe we already found a result? If not, assume no result exists
				Activator.log.error("Failed to find Java type on the project classpath.", e); //$NON-NLS-1$
				result = null;
			}

			return result;
		}

		private static String jdtQualifiedName(String qualifiedName) {
			// JDT doesn't use the JVM syntax for nested classes but the Java source syntax
			return qualifiedName.replace('$', '.');
		}

		private IType searchType(String name, Class<?> constraint) {
			SearchEngine search = new SearchEngine();
			SearchPattern pattern = SearchPattern.createPattern(jdtQualifiedName(name),
					IJavaSearchConstants.TYPE, IJavaSearchConstants.DECLARATIONS,
					SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE);

			IType[] result = { null };
			IProgressMonitor monitor = new NullProgressMonitor();

			SearchRequestor requestor = new SearchRequestor() {

				@Override
				public void acceptSearchMatch(SearchMatch match) throws CoreException {
					if (match.getElement() instanceof IType) {
						IType foundType = (IType) match.getElement();

						if (constraint == null || conformsTo(foundType, constraint)) {
							result[0] = foundType;
							monitor.setCanceled(true);
						}
					}
				}
			};

			try {
				search.search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() },
						SearchEngine.createWorkspaceScope(), requestor, monitor);
			} catch (CoreException e) {
				// Maybe we already found a result? If not, assume no result exists
				Activator.log.error("Failed to find Java type on the workspace classpath.", e); //$NON-NLS-1$
			} catch (OperationCanceledException e) {
				// It's okay: I cancelled this, myself
			}

			return result[0];
		}

		private boolean conformsTo(IType type, Class<?> supertype) {
			boolean result = false;

			try {
				ITypeHierarchy hierarchy = type.newSupertypeHierarchy(new NullProgressMonitor());
				result = Stream.of(hierarchy.getAllSupertypes(type)).map(t -> t.getFullyQualifiedName('$'))
						.anyMatch(supertype.getCanonicalName()::equals);
			} catch (JavaModelException e) {
				Activator.log.error("Failed to find Java type on the workspace classpath.", e); //$NON-NLS-1$
			}

			return result;
		}

	}

}

Back to the top