1 /*******************************************************************************
2  * Copyright (c) 2000, 2011 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.jdt.internal.corext.refactoring.structure;
15 
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.Collections;
19 import java.util.HashSet;
20 import java.util.List;
21 import java.util.Set;
22 
23 import org.eclipse.core.runtime.Assert;
24 
25 import org.eclipse.jdt.core.IField;
26 import org.eclipse.jdt.core.IMember;
27 import org.eclipse.jdt.core.IMethod;
28 import org.eclipse.jdt.core.IType;
29 import org.eclipse.jdt.core.ITypeParameter;
30 import org.eclipse.jdt.core.JavaModelException;
31 import org.eclipse.jdt.core.Signature;
32 
33 import org.eclipse.jdt.internal.ui.JavaPlugin;
34 
35 /**
36  * Utilities to create mappings between type variables of different types in a type hierarchy.
37  */
38 public final class TypeVariableUtil {
39 
40 	/**
41 	 * Returns the composition of two type variable mappings. The type variables signatures can have an arbitrary format.
42 	 *
43 	 * @param first
44 	 *        the first type variable mapping
45 	 * @param second
46 	 *        the second type variable mapping
47 	 * @return the possibly empty composed type variable mapping
48 	 */
composeMappings(final TypeVariableMaplet[] first, final TypeVariableMaplet[] second)49 	public static TypeVariableMaplet[] composeMappings(final TypeVariableMaplet[] first, final TypeVariableMaplet[] second) {
50 		Assert.isNotNull(first);
51 		Assert.isNotNull(second);
52 
53 		if (first.length == 0)
54 			return first;
55 		else if (second.length == 0)
56 			return second;
57 		else {
58 			TypeVariableMaplet source= null;
59 			TypeVariableMaplet target= null;
60 			final Set<TypeVariableMaplet> set= new HashSet<>(first.length * second.length);
61 			for (int index= 0; index < first.length; index++) {
62 				for (int offset= 0; offset < second.length; offset++) {
63 					source= first[index];
64 					target= second[offset];
65 					if (source.getTargetIndex() == target.getSourceIndex() && source.getTargetName().equals(target.getSourceName()))
66 						set.add(new TypeVariableMaplet(source.getSourceName(), index, target.getTargetName(), offset));
67 				}
68 			}
69 			final TypeVariableMaplet[] mapping= new TypeVariableMaplet[set.size()];
70 			set.toArray(mapping);
71 			return mapping;
72 		}
73 	}
74 
75 	/**
76 	 * Extracts the type variables from a signature
77 	 *
78 	 * @param signature
79 	 *        the signature to extract the type variables from
80 	 * @param variables
81 	 *        the set of variables to fill in
82 	 */
extractTypeVariables(final String signature, final Set<String> variables)83 	private static void extractTypeVariables(final String signature, final Set<String> variables) {
84 		Assert.isNotNull(signature);
85 		Assert.isNotNull(variables);
86 
87 		final String[] arguments= Signature.getTypeArguments(signature);
88 		if (arguments.length == 0) {
89 			variables.add(Signature.toString(signature));
90 		} else {
91 			for (String argument : arguments) {
92 				variables.add(Signature.toString(argument));
93 			}
94 		}
95 	}
96 
97 	/**
98 	 * Returns the type variables referenced in the signature of the specified member.
99 	 *
100 	 * @param declaring
101 	 *        The declaring type of the specified member
102 	 * @param member
103 	 *        the member to get its type variables. Can be a type, field or a method.
104 	 * @return a possibly empty array of type variable candidates
105 	 * @throws JavaModelException
106 	 *         if the signature of the specified member could not be resolved
107 	 */
getReferencedVariables(final IType declaring, final IMember member)108 	private static String[] getReferencedVariables(final IType declaring, final IMember member) throws JavaModelException {
109 
110 		Assert.isNotNull(declaring);
111 		Assert.isNotNull(member);
112 
113 		final String[] variables= parametersToVariables(declaring.getTypeParameters());
114 		String[] result= new String[0];
115 		if (member instanceof IField) {
116 			final String signature= ((IField) member).getTypeSignature();
117 			final String[] signatures= getVariableSignatures(signature);
118 			if (signatures.length == 0) {
119 				final String variable= Signature.toString(signature);
120 				for (String v : variables) {
121 					if (variable.equals(v)) {
122 						result= new String[] { variable};
123 						break;
124 					}
125 				}
126 			} else {
127 				result= new String[signatures.length];
128 				for (int index= 0; index < result.length; index++)
129 					result[index]= Signature.toString(signatures[index]);
130 			}
131 		} else if (member instanceof IMethod) {
132 			final IMethod method= (IMethod) member;
133 			final HashSet<String> set= new HashSet<>();
134 			for (String type : method.getParameterTypes()) {
135 				extractTypeVariables(type, set);
136 			}
137 			extractTypeVariables(method.getReturnType(), set);
138 			final String[] arguments= parametersToVariables(((IMethod) member).getTypeParameters());
139 			Collections.addAll(set, arguments);
140 			result= new String[set.size()];
141 			set.toArray(result);
142 		} else if (member instanceof IType)
143 			result= parametersToVariables(((IType) member).getTypeParameters());
144 		else {
145 			JavaPlugin.logErrorMessage("Unexpected sub-type of IMember: " + member.getClass().getName()); //$NON-NLS-1$
146 			Assert.isTrue(false);
147 		}
148 
149 		final List<String> list= new ArrayList<>(variables.length);
150 		for (String variable : variables) {
151 			for (String r : result) {
152 				if (variable.equals(r)) {
153 					list.add(r);
154 				}
155 			}
156 		}
157 		result= new String[list.size()];
158 		list.toArray(result);
159 		return result;
160 	}
161 
162 	/**
163 	 * Returns all type variable names of the indicated member not mapped by the specified type variable mapping.
164 	 *
165 	 * @param mapping
166 	 *        the type variable mapping. The entries of this mapping must be simple type variable names.
167 	 * @param declaring
168 	 *        the declaring type of the indicated member
169 	 * @param member
170 	 *        the member to determine its unmapped type variable names
171 	 * @return a possibly empty array of unmapped type variable names
172 	 * @throws JavaModelException
173 	 *         if the type parameters of the member could not be determined
174 	 */
getUnmappedVariables(final TypeVariableMaplet[] mapping, final IType declaring, final IMember member)175 	public static String[] getUnmappedVariables(final TypeVariableMaplet[] mapping, final IType declaring, final IMember member) throws JavaModelException {
176 
177 		Assert.isNotNull(mapping);
178 		Assert.isNotNull(declaring);
179 		Assert.isNotNull(member);
180 
181 		List<String> list= null;
182 		final String[] types= getReferencedVariables(declaring, member);
183 		if (mapping.length == 0) {
184 			list= new ArrayList<>(types.length);
185 			list.addAll(Arrays.asList(types));
186 		} else {
187 			final Set<String> mapped= new HashSet<>(types.length);
188 			for (String type : types) {
189 				for (TypeVariableMaplet m : mapping) {
190 					if (m.getSourceName().equals(type)) {
191 						mapped.add(type);
192 					}
193 				}
194 			}
195 			list= new ArrayList<>(types.length - mapped.size());
196 			for (String type : types) {
197 				if (!mapped.contains(type))
198 					list.add(type);
199 			}
200 		}
201 		final String[] result= new String[list.size()];
202 		list.toArray(result);
203 		return result;
204 	}
205 
206 	/**
207 	 * Returns the type variable signatures of the specified parameterized type signature, or an empty array if none.
208 	 *
209 	 * @param signature
210 	 *        the signature to get its type variable signatures from. The signature must be a parameterized type signature.
211 	 * @return a possibly empty array of type variable signatures
212 	 * @see Signature#getTypeArguments(String)
213 	 */
getVariableSignatures(final String signature)214 	private static String[] getVariableSignatures(final String signature) {
215 		Assert.isNotNull(signature);
216 
217 		String[] result= null;
218 		try {
219 			result= Signature.getTypeArguments(signature);
220 		} catch (IllegalArgumentException exception) {
221 			result= new String[0];
222 		}
223 		return result;
224 	}
225 
226 	/**
227 	 * Returns the reversed type variable mapping of the specified mapping.
228 	 *
229 	 * @param mapping
230 	 *        the mapping to inverse
231 	 * @return the possibly empty inverse mapping
232 	 */
inverseMapping(final TypeVariableMaplet[] mapping)233 	public static TypeVariableMaplet[] inverseMapping(final TypeVariableMaplet[] mapping) {
234 		Assert.isNotNull(mapping);
235 
236 		final TypeVariableMaplet[] result= new TypeVariableMaplet[mapping.length];
237 		TypeVariableMaplet maplet= null;
238 		for (int index= 0; index < mapping.length; index++) {
239 			maplet= mapping[index];
240 			result[index]= new TypeVariableMaplet(maplet.getTargetName(), maplet.getTargetIndex(), maplet.getSourceName(), maplet.getSourceIndex());
241 		}
242 		return result;
243 	}
244 
245 	/**
246 	 * Creates a type variable mapping from a domain to a range.
247 	 *
248 	 * @param domain
249 	 *        the domain of the mapping
250 	 * @param range
251 	 *        the range of the mapping
252 	 * @param indexes
253 	 *        <code>true</code> if the indexes should be compared, <code>false</code> if the names should be compared
254 	 * @return a possibly empty type variable mapping
255 	 */
parametersToSignatures(final ITypeParameter[] domain, final String[] range, final boolean indexes)256 	private static TypeVariableMaplet[] parametersToSignatures(final ITypeParameter[] domain, final String[] range, final boolean indexes) {
257 		Assert.isNotNull(domain);
258 		Assert.isNotNull(range);
259 
260 		final Set<TypeVariableMaplet> set= new HashSet<>();
261 		ITypeParameter source= null;
262 		String target= null;
263 		String element= null;
264 		String signature= null;
265 		for (int index= 0; index < domain.length; index++) {
266 			source= domain[index];
267 			for (int offset= 0; offset < range.length; offset++) {
268 				target= range[offset];
269 				element= source.getElementName();
270 				signature= Signature.toString(target);
271 				if (indexes) {
272 					if (offset == index)
273 						set.add(new TypeVariableMaplet(element, index, signature, offset));
274 				} else {
275 					if (element.equals(signature))
276 						set.add(new TypeVariableMaplet(element, index, signature, offset));
277 				}
278 			}
279 		}
280 		final TypeVariableMaplet[] result= new TypeVariableMaplet[set.size()];
281 		set.toArray(result);
282 		return result;
283 	}
284 
285 	/**
286 	 * Converts the specified type parameters to type variable names.
287 	 *
288 	 * @param parameters
289 	 *        the type parameters to convert.
290 	 * @return the converted type variable names
291 	 * @see ITypeParameter#getElementName()
292 	 */
parametersToVariables(final ITypeParameter[] parameters)293 	private static String[] parametersToVariables(final ITypeParameter[] parameters) {
294 		Assert.isNotNull(parameters);
295 
296 		String[] result= new String[parameters.length];
297 		for (int index= 0; index < parameters.length; index++)
298 			result[index]= parameters[index].getElementName();
299 
300 		return result;
301 	}
302 
303 	/**
304 	 * Creates a type variable mapping from a domain to a range.
305 	 *
306 	 * @param domain
307 	 *        the domain of the mapping
308 	 * @param range
309 	 *        the range of the mapping
310 	 * @return a possibly empty type variable mapping
311 	 */
signaturesToParameters(final String[] domain, final ITypeParameter[] range)312 	private static TypeVariableMaplet[] signaturesToParameters(final String[] domain, final ITypeParameter[] range) {
313 		Assert.isNotNull(domain);
314 		Assert.isNotNull(range);
315 		Assert.isTrue(domain.length == 0 || domain.length == range.length);
316 
317 		final List<TypeVariableMaplet> list= new ArrayList<>();
318 		String source= null;
319 		String target= null;
320 		for (int index= 0; index < domain.length; index++) {
321 			source= Signature.toString(domain[index]);
322 			target= range[index].getElementName();
323 			list.add(new TypeVariableMaplet(source, index, target, index));
324 		}
325 		final TypeVariableMaplet[] result= new TypeVariableMaplet[list.size()];
326 		list.toArray(result);
327 		return result;
328 	}
329 
330 	/**
331 	 * Returns a type variable mapping from a subclass to a superclass.
332 	 *
333 	 * @param type
334 	 *        the type representing the subclass class
335 	 * @return a type variable mapping. The mapping entries consist of simple type variable names.
336 	 * @throws JavaModelException
337 	 *         if the signature of one of the types involved could not be retrieved
338 	 */
subTypeToInheritedType(final IType type)339 	public static TypeVariableMaplet[] subTypeToInheritedType(final IType type) throws JavaModelException {
340 		Assert.isNotNull(type);
341 
342 		final ITypeParameter[] domain= type.getTypeParameters();
343 		if (domain.length > 0) {
344 			final String signature= type.getSuperclassTypeSignature();
345 			if (signature != null) {
346 				final String[] range= getVariableSignatures(signature);
347 				if (range.length > 0)
348 					return parametersToSignatures(domain, range, false);
349 			}
350 		}
351 		return new TypeVariableMaplet[0];
352 	}
353 
354 	/**
355 	 * Returns a type variable mapping from a subclass to a superclass.
356 	 *
357 	 * @param subtype
358 	 *        the type representing the subclass
359 	 * @param supertype
360 	 *        the type representing the superclass
361 	 * @return a type variable mapping. The mapping entries consist of simple type variable names.
362 	 * @throws JavaModelException
363 	 *         if the signature of one of the types involved could not be retrieved
364 	 */
subTypeToSuperType(final IType subtype, final IType supertype)365 	public static TypeVariableMaplet[] subTypeToSuperType(final IType subtype, final IType supertype) throws JavaModelException {
366 		Assert.isNotNull(subtype);
367 		Assert.isNotNull(supertype);
368 
369 		final TypeVariableMaplet[] mapping= subTypeToInheritedType(subtype);
370 		if (mapping.length > 0) {
371 			final ITypeParameter[] range= supertype.getTypeParameters();
372 			if (range.length > 0) {
373 				final String signature= subtype.getSuperclassTypeSignature();
374 				if (signature != null) {
375 					final String[] domain= getVariableSignatures(signature);
376 					if (domain.length > 0)
377 						return composeMappings(mapping, signaturesToParameters(domain, range));
378 				}
379 			}
380 		}
381 		return mapping;
382 	}
383 
384 	/**
385 	 * Returns a type variable mapping from a superclass to a subclass.
386 	 *
387 	 * @param supertype
388 	 *        the type representing the superclass
389 	 * @param subtype
390 	 *        the type representing the subclass
391 	 * @return a type variable mapping. The mapping entries consist of simple type variable names.
392 	 * @throws JavaModelException
393 	 *         if the signature of one of the types involved could not be retrieved
394 	 */
superTypeToInheritedType(final IType supertype, final IType subtype)395 	public static TypeVariableMaplet[] superTypeToInheritedType(final IType supertype, final IType subtype) throws JavaModelException {
396 		Assert.isNotNull(subtype);
397 		Assert.isNotNull(supertype);
398 
399 		final ITypeParameter[] domain= supertype.getTypeParameters();
400 		if (domain.length > 0) {
401 			final String signature= subtype.getSuperclassTypeSignature();
402 			if (signature != null) {
403 				final String[] range= getVariableSignatures(signature);
404 				if (range.length > 0)
405 					return parametersToSignatures(domain, range, true);
406 			}
407 		}
408 		return new TypeVariableMaplet[0];
409 	}
410 
411 	/**
412 	 * Returns a type variable mapping from a superclass to a subclass.
413 	 *
414 	 * @param supertype
415 	 *        the type representing the superclass
416 	 * @param subtype
417 	 *        the type representing the subclass
418 	 * @return a type variable mapping. The mapping entries consist of simple type variable names.
419 	 * @throws JavaModelException
420 	 *         if the signature of one of the types involved could not be retrieved
421 	 */
superTypeToSubType(final IType supertype, final IType subtype)422 	public static TypeVariableMaplet[] superTypeToSubType(final IType supertype, final IType subtype) throws JavaModelException {
423 		Assert.isNotNull(supertype);
424 		Assert.isNotNull(subtype);
425 
426 		return inverseMapping(subTypeToSuperType(subtype, supertype));
427 	}
428 
TypeVariableUtil()429 	private TypeVariableUtil() {
430 		// Not to be instantiated
431 	}
432 }
433