Skip to main content
summaryrefslogtreecommitdiffstats
blob: 3bceb66e4c9e3c6031e0d83d2386801cac1a4a01 (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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
/*******************************************************************************
 * Copyright (c) 2001, 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jem.internal.proxy.common;
import java.lang.reflect.*;
import java.text.MessageFormat;
import java.util.*;

/**
 * This is a class to do message/constructor work. 
 * Specifically to find the most appropriate method.
 */
public class MethodHelper {
	
	/*
	 * The class that is used to represent Null class type.
	 * 
	 * @since 1.0.0
	 */
	private static class NULL_CLASS {
	}
	
	public static final Class NULL_TYPE = NULL_CLASS.class;
	
	static final ArrayList sPrimitivesOrder;
	static final int sCharPos;
	
	static {
		sPrimitivesOrder = new ArrayList(6);
		sPrimitivesOrder.add(Byte.TYPE);
		sPrimitivesOrder.add(Short.TYPE);
		sPrimitivesOrder.add(Integer.TYPE);
		sPrimitivesOrder.add(Long.TYPE);
		sPrimitivesOrder.add(Float.TYPE);
		sPrimitivesOrder.add(Double.TYPE);

		// char can be treated like a short for purposes of ordering.
		sCharPos = sPrimitivesOrder.indexOf(Short.TYPE);
	}
	
	/**
	 * Return whether the type2 can be assigned to type1 in
	 * method argument conversion.
	 */
	public static boolean isAssignableFrom(Class type1, Class type2) {
		if (type1 == type2)
			return true;	// They're the same, so assignable.
		if (type1.isPrimitive()) {
			if (type2.isPrimitive()) {
				if (type1 == Boolean.TYPE || type2 == Boolean.TYPE)
					return false;	// Since not equal and one is boolean and the other isn't, not assignable
				int type1Pos = (type1 != Character.TYPE) ? sPrimitivesOrder.indexOf(type1) : sCharPos;
				int type2Pos = (type2 != Character.TYPE) ? sPrimitivesOrder.indexOf(type2) : sCharPos;
				return type1Pos > type2Pos;	// It can be widened if type1 is higher in the order
			}
			return false;	// primitive to non-primitive, not assignable.
		} else
			if (type2 == NULL_TYPE)
				return true;	// NULL_TYPE represents null for us, and null can be assigned to any object
			else		
				return type1.isAssignableFrom(type2);	// Can type2 be assigned to type1
	}
	
	
	/**
	 * Every entry in Array2 can be assigned to the corresponding entry in Array1.
	 */
	public static boolean isAssignableFrom(Class[] types1, Class[] types2) {
		if (types1.length != types2.length)
			return false;	// Not the same size, so not compatible.
		for (int i=0; i<types1.length; i++) {
			if (!isAssignableFrom(types1[i], types2[i]))
				return false;
		}
		return true;	// All are assignable
	}
	
	/**
	 * Return the index of the most compatible method/constructor from the lists passed in.
	 * MethodsList: List of methods (if null then this is for constructors)
	 * ParmsList: List of parms for each method (each entry will be Class[]).
	 */
	private static int findMostCompatible(List methods, List parms, String ambiguousName) throws AmbiguousMethodException {
		// The algorithm used is from the Java Language Specification 15.12.2.2
		// Find the maximally specific ones
		// This is defined as the one that is more specific then all of the rest.
		// If there are duplicates parms that are maximally specific, then it doesn't matter which choosen
		// because when invoked the JVM will make sure the right thing is done.
		// 
		Class[][] parmsCopy = (Class[][]) parms.toArray(new Class[parms.size()][]);
		int size = parmsCopy.length;
		// For each entry see if it is maximally specific, i.e. it is more specific then all of the others.
nextMethod:	for (int i=0; i<size; i++) {
			// For ctors we don't need to test the declaring class because it will always be the same class.
			Class dclClassi = methods != null ? ((Method) methods.get(i)).getDeclaringClass() : null;
			Class[] parmsi = parmsCopy[i];
			for (int j=0; j<size; j++) {
				if (i == j)
					continue;
				// Methodi is more specific if
				//   a) Methodi declaring class is assignable to Methodj declaring class
				//   b) Methodi parms are assignable to Methodj parms
				//
				// First see if Methodi is more specific, if it is
				// then throw out Methodj and continue
				// If Methodi is not compatible to Methodj, go to the next method for i. Methodi is not the most specific
				// Something else is either more specific or none are ma
				if (dclClassi != null) {
					// Step a
					if (!isAssignableFrom(((Method) methods.get(j)).getDeclaringClass(), dclClassi))
						continue nextMethod;	// Methodi is not more specific than Methodj, so try next i.
				}
				
				// Step b
				Class[] parmsj = parmsCopy[j];
				if (!isAssignableFrom(parmsj, parmsi)) {
					// Methodi is not more specific than Methodj, so go to next i.
					continue nextMethod;
				}
			}
			return i;	// Methodi is more specific than all of the other ones.
		}

		throw new AmbiguousMethodException(ambiguousName);	// There was not one more specific than all of the others.
	}
	
	/**
	 * Find the most compatible method for the given arguments.
	 */
	public static Method findCompatibleMethod(Class receiver, String methodName, Class[] arguments) throws NoSuchMethodException, AmbiguousMethodException {
		try {
			return receiver.getMethod(methodName, arguments);	// Found exact match in public
		} catch (NoSuchMethodException exc) {
			if (arguments != null) {
				// Need to find most compatible one. We will take protected into consideration (i.e. inheritance).
				ArrayList parmsList = new ArrayList(); // The parm list from each compatible method.
				ArrayList mthdsList = new ArrayList(); // The list of compatible methods, same order as the parms above.
				Class cls = receiver;
				while (cls != null) {
					Method mthds[] = cls.getDeclaredMethods();
					for (int i = 0; i < mthds.length; i++) {
						Method mthd = mthds[i];
						if (!mthd.getName().equals(methodName))
							continue; // Not compatible, not same name
						int modifiers = mthd.getModifiers();
						if (!(Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)))
							continue;	 // Will not call private or package-protected because we don't know the context.
						Class[] parms = mthd.getParameterTypes();
						// If exact match we found a non-public exact match, which is good.
						if (Arrays.equals(arguments, parms))
							return makeMethodAccessable(mthd);
						if (!isAssignableFrom(parms, arguments))
							continue; // Not compatible, parms
						// It is compatible with the requested method - now see if we already have an exact match from a subclass. Don't want to add it twice.
						// We are assuming the actual number of compatible methods is small, so this O(n-squared) search is efficient enough.
						int size = parmsList.size();
						for (int j = 0; j < size; j++) {
							if (Arrays.equals(parms, (Object[]) parmsList.get(j)))
								continue;
						}
						parmsList.add(parms);
						mthdsList.add(mthd);
					}
					cls = cls.getSuperclass();
				}				
				// Now have list of compatible methods.
				if (parmsList.size() == 0)
					throw throwFixedNoSuchMethod(exc, receiver, methodName, arguments); // None found, so rethrow the exception
				if (parmsList.size() == 1)
					return makeMethodAccessable((Method) mthdsList.get(0)); // Only one, so return it

				// Now find the most compatible method
				int mostCompatible = findMostCompatible(mthdsList, parmsList, methodName);
				return makeMethodAccessable((Method) mthdsList.get(mostCompatible));
			} else
				throw throwFixedNoSuchMethod(exc, receiver, methodName, arguments); // None found, so rethrow the exception
		}
	}
	
	private static Method makeMethodAccessable(Method m) {
		m.setAccessible(true);	// We allow all access, let ide and compiler handle security.
		return m;
	}
	
	/*
	 * NoSuchMEthodExeception doesn't include the signature. Since these are dynamic searches, the exception itself is useless without
	 * the signature. So we add it.
	 */
	private static NoSuchMethodException throwFixedNoSuchMethod(NoSuchMethodException e, Class declareClass, String methodName, Class[] argClasses) {

		// The default trace doesn't show what method was being searched for, so recreate with that.
		StringBuffer s = new StringBuffer();
		s.append(declareClass.getName());
		s.append('.');
		s.append(methodName);
		s.append('(');
		if (argClasses != null) {
			for (int i = 0; i < argClasses.length; i++) {
				if (i > 0)
					s.append(',');
				s.append(argClasses[i].getName());
			}
		}
		s.append(')');
		NoSuchMethodException ne = new NoSuchMethodException(s.toString());
		ne.setStackTrace(e.getStackTrace());
		return ne;
	}
	

	/**
	 * Find the most compatible constructor for the class with the given arguments.
	 * @param receiver class to get the constructor for
	 * @param arguments array of argument types
	 * @return the constructor
	 * @throws NoSuchMethodException no compatible constructor can be found
	 * @throws AmbiguousMethodException there is more than one compatible constructor
	 * @throws IllegalAccessException it can't be accessed. Such as it is a non-static inner class.
	 * 
	 * @since 1.2.0
	 */
	public static Constructor findCompatibleConstructor(Class receiver, Class[] arguments) throws NoSuchMethodException, AmbiguousMethodException, IllegalAccessException {
		if (receiver.getDeclaringClass() != null && !Modifier.isStatic(receiver.getModifiers()))
			throw new IllegalAccessException(MessageFormat.format(Messages.getString("MethodHelper.NONSTATICINNERCLASS_WARNING"), new Object[] {receiver.getName()})); //$NON-NLS-1$
		try {
			java.lang.reflect.Constructor ctor = receiver.getDeclaredConstructor(arguments);
			ctor.setAccessible(true);	// We allow all access, let ide and compiler handle security.
			return ctor;	// Found exact match
		} catch (NoSuchMethodException exc) {
			if (arguments != null) {
				// Need to find most compatible one.
				java.lang.reflect.Constructor ctors[] = receiver.getDeclaredConstructors();
				ArrayList parmsList = new ArrayList(ctors.length); // The parm list from each compatible method.
				ArrayList ctorsList = new ArrayList(ctors.length); // The list of compatible methods, same order as the parms above.
				for (int i = 0; i < ctors.length; i++) {
					java.lang.reflect.Constructor ctor = ctors[i];
					Class[] parms = ctor.getParameterTypes();
					if (!isAssignableFrom(parms, arguments))
						continue; // Not compatible, parms
					// It is compatible with the requested method
					parmsList.add(parms);
					ctorsList.add(ctor);
				}

				// Now have list of compatible methods.
				if (parmsList.size() == 0)
					throw exc; // None found, so rethrow the exception
				if (parmsList.size() == 1) {
					java.lang.reflect.Constructor ctor = (java.lang.reflect.Constructor) ctorsList.get(0); // Only one, so return it
					ctor.setAccessible(true); // We allow all access, let ide and compilor handle security.
					return ctor;
				}

				// Now find the most compatible ctor
				int mostCompatible = findMostCompatible(null, parmsList, receiver.getName());
				java.lang.reflect.Constructor ctor = (java.lang.reflect.Constructor) ctorsList.get(mostCompatible);
				ctor.setAccessible(true); // We allow all access, let ide and compilor handle security.
				return ctor;
			} else
				throw exc; // None found, so rethrow the exception
		}
	}	
				
		
}

Back to the top