001/*
002 * Copyright 2003,2004 The Apache Software Foundation
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.cglib.core;
018
019import java.beans.BeanInfo;
020import java.beans.IntrospectionException;
021import java.beans.Introspector;
022import java.beans.PropertyDescriptor;
023import java.lang.invoke.MethodHandles;
024import java.lang.reflect.Constructor;
025import java.lang.reflect.InvocationTargetException;
026import java.lang.reflect.Member;
027import java.lang.reflect.Method;
028import java.lang.reflect.Modifier;
029import java.security.AccessController;
030import java.security.PrivilegedAction;
031import java.security.PrivilegedExceptionAction;
032import java.security.ProtectionDomain;
033import java.util.ArrayList;
034import java.util.Arrays;
035import java.util.HashMap;
036import java.util.HashSet;
037import java.util.List;
038import java.util.Map;
039import java.util.Set;
040
041import org.springframework.asm.Attribute;
042import org.springframework.asm.Type;
043
044/**
045 * @version $Id: ReflectUtils.java,v 1.30 2009/01/11 19:47:49 herbyderby Exp $
046 */
047@SuppressWarnings({"rawtypes", "unchecked"})
048public class ReflectUtils {
049
050        private ReflectUtils() {
051        }
052
053        private static final Map primitives = new HashMap(8);
054
055        private static final Map transforms = new HashMap(8);
056
057        private static final ClassLoader defaultLoader = ReflectUtils.class.getClassLoader();
058
059        // SPRING PATCH BEGIN
060        private static final Method privateLookupInMethod;
061
062        private static final Method lookupDefineClassMethod;
063
064        private static final Method classLoaderDefineClassMethod;
065
066        private static final ProtectionDomain PROTECTION_DOMAIN;
067
068        private static final Throwable THROWABLE;
069
070        private static final List<Method> OBJECT_METHODS = new ArrayList<Method>();
071
072        static {
073                Method privateLookupIn;
074                Method lookupDefineClass;
075                Method classLoaderDefineClass;
076                ProtectionDomain protectionDomain;
077                Throwable throwable = null;
078                try {
079                        privateLookupIn = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
080                                public Object run() throws Exception {
081                                        try {
082                                                return MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
083                                        }
084                                        catch (NoSuchMethodException ex) {
085                                                return null;
086                                        }
087                                }
088                        });
089                        lookupDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
090                                public Object run() throws Exception {
091                                        try {
092                                                return MethodHandles.Lookup.class.getMethod("defineClass", byte[].class);
093                                        }
094                                        catch (NoSuchMethodException ex) {
095                                                return null;
096                                        }
097                                }
098                        });
099                        classLoaderDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
100                                public Object run() throws Exception {
101                                        return ClassLoader.class.getDeclaredMethod("defineClass",
102                                                        String.class, byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class);
103                                }
104                        });
105                        protectionDomain = getProtectionDomain(ReflectUtils.class);
106                        AccessController.doPrivileged(new PrivilegedExceptionAction() {
107                                public Object run() throws Exception {
108                                        Method[] methods = Object.class.getDeclaredMethods();
109                                        for (Method method : methods) {
110                                                if ("finalize".equals(method.getName())
111                                                                || (method.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) > 0) {
112                                                        continue;
113                                                }
114                                                OBJECT_METHODS.add(method);
115                                        }
116                                        return null;
117                                }
118                        });
119                }
120                catch (Throwable t) {
121                        privateLookupIn = null;
122                        lookupDefineClass = null;
123                        classLoaderDefineClass = null;
124                        protectionDomain = null;
125                        throwable = t;
126                }
127                privateLookupInMethod = privateLookupIn;
128                lookupDefineClassMethod = lookupDefineClass;
129                classLoaderDefineClassMethod = classLoaderDefineClass;
130                PROTECTION_DOMAIN = protectionDomain;
131                THROWABLE = throwable;
132        }
133        // SPRING PATCH END
134
135        private static final String[] CGLIB_PACKAGES = {
136                        "java.lang",
137        };
138
139        static {
140                primitives.put("byte", Byte.TYPE);
141                primitives.put("char", Character.TYPE);
142                primitives.put("double", Double.TYPE);
143                primitives.put("float", Float.TYPE);
144                primitives.put("int", Integer.TYPE);
145                primitives.put("long", Long.TYPE);
146                primitives.put("short", Short.TYPE);
147                primitives.put("boolean", Boolean.TYPE);
148
149                transforms.put("byte", "B");
150                transforms.put("char", "C");
151                transforms.put("double", "D");
152                transforms.put("float", "F");
153                transforms.put("int", "I");
154                transforms.put("long", "J");
155                transforms.put("short", "S");
156                transforms.put("boolean", "Z");
157        }
158
159        public static ProtectionDomain getProtectionDomain(final Class source) {
160                if (source == null) {
161                        return null;
162                }
163                return (ProtectionDomain) AccessController.doPrivileged(new PrivilegedAction() {
164                        public Object run() {
165                                return source.getProtectionDomain();
166                        }
167                });
168        }
169
170        public static Type[] getExceptionTypes(Member member) {
171                if (member instanceof Method) {
172                        return TypeUtils.getTypes(((Method) member).getExceptionTypes());
173                }
174                else if (member instanceof Constructor) {
175                        return TypeUtils.getTypes(((Constructor) member).getExceptionTypes());
176                }
177                else {
178                        throw new IllegalArgumentException("Cannot get exception types of a field");
179                }
180        }
181
182        public static Signature getSignature(Member member) {
183                if (member instanceof Method) {
184                        return new Signature(member.getName(), Type.getMethodDescriptor((Method) member));
185                }
186                else if (member instanceof Constructor) {
187                        Type[] types = TypeUtils.getTypes(((Constructor) member).getParameterTypes());
188                        return new Signature(Constants.CONSTRUCTOR_NAME,
189                                        Type.getMethodDescriptor(Type.VOID_TYPE, types));
190
191                }
192                else {
193                        throw new IllegalArgumentException("Cannot get signature of a field");
194                }
195        }
196
197        public static Constructor findConstructor(String desc) {
198                return findConstructor(desc, defaultLoader);
199        }
200
201        public static Constructor findConstructor(String desc, ClassLoader loader) {
202                try {
203                        int lparen = desc.indexOf('(');
204                        String className = desc.substring(0, lparen).trim();
205                        return getClass(className, loader).getConstructor(parseTypes(desc, loader));
206                }
207                catch (ClassNotFoundException | NoSuchMethodException ex) {
208                        throw new CodeGenerationException(ex);
209                }
210        }
211
212        public static Method findMethod(String desc) {
213                return findMethod(desc, defaultLoader);
214        }
215
216        public static Method findMethod(String desc, ClassLoader loader) {
217                try {
218                        int lparen = desc.indexOf('(');
219                        int dot = desc.lastIndexOf('.', lparen);
220                        String className = desc.substring(0, dot).trim();
221                        String methodName = desc.substring(dot + 1, lparen).trim();
222                        return getClass(className, loader).getDeclaredMethod(methodName, parseTypes(desc, loader));
223                }
224                catch (ClassNotFoundException | NoSuchMethodException ex) {
225                        throw new CodeGenerationException(ex);
226                }
227        }
228
229        private static Class[] parseTypes(String desc, ClassLoader loader) throws ClassNotFoundException {
230                int lparen = desc.indexOf('(');
231                int rparen = desc.indexOf(')', lparen);
232                List params = new ArrayList();
233                int start = lparen + 1;
234                for (; ; ) {
235                        int comma = desc.indexOf(',', start);
236                        if (comma < 0) {
237                                break;
238                        }
239                        params.add(desc.substring(start, comma).trim());
240                        start = comma + 1;
241                }
242                if (start < rparen) {
243                        params.add(desc.substring(start, rparen).trim());
244                }
245                Class[] types = new Class[params.size()];
246                for (int i = 0; i < types.length; i++) {
247                        types[i] = getClass((String) params.get(i), loader);
248                }
249                return types;
250        }
251
252        private static Class getClass(String className, ClassLoader loader) throws ClassNotFoundException {
253                return getClass(className, loader, CGLIB_PACKAGES);
254        }
255
256        private static Class getClass(String className, ClassLoader loader, String[] packages) throws ClassNotFoundException {
257                String save = className;
258                int dimensions = 0;
259                int index = 0;
260                while ((index = className.indexOf("[]", index) + 1) > 0) {
261                        dimensions++;
262                }
263                StringBuffer brackets = new StringBuffer(className.length() - dimensions);
264                for (int i = 0; i < dimensions; i++) {
265                        brackets.append('[');
266                }
267                className = className.substring(0, className.length() - 2 * dimensions);
268
269                String prefix = (dimensions > 0) ? brackets + "L" : "";
270                String suffix = (dimensions > 0) ? ";" : "";
271                try {
272                        return Class.forName(prefix + className + suffix, false, loader);
273                }
274                catch (ClassNotFoundException ignore) {
275                }
276                for (int i = 0; i < packages.length; i++) {
277                        try {
278                                return Class.forName(prefix + packages[i] + '.' + className + suffix, false, loader);
279                        }
280                        catch (ClassNotFoundException ignore) {
281                        }
282                }
283                if (dimensions == 0) {
284                        Class c = (Class) primitives.get(className);
285                        if (c != null) {
286                                return c;
287                        }
288                }
289                else {
290                        String transform = (String) transforms.get(className);
291                        if (transform != null) {
292                                try {
293                                        return Class.forName(brackets + transform, false, loader);
294                                }
295                                catch (ClassNotFoundException ignore) {
296                                }
297                        }
298                }
299                throw new ClassNotFoundException(save);
300        }
301
302        public static Object newInstance(Class type) {
303                return newInstance(type, Constants.EMPTY_CLASS_ARRAY, null);
304        }
305
306        public static Object newInstance(Class type, Class[] parameterTypes, Object[] args) {
307                return newInstance(getConstructor(type, parameterTypes), args);
308        }
309
310        @SuppressWarnings("deprecation")  // on JDK 9
311        public static Object newInstance(final Constructor cstruct, final Object[] args) {
312                boolean flag = cstruct.isAccessible();
313                try {
314                        if (!flag) {
315                                cstruct.setAccessible(true);
316                        }
317                        Object result = cstruct.newInstance(args);
318                        return result;
319                }
320                catch (InstantiationException e) {
321                        throw new CodeGenerationException(e);
322                }
323                catch (IllegalAccessException e) {
324                        throw new CodeGenerationException(e);
325                }
326                catch (InvocationTargetException e) {
327                        throw new CodeGenerationException(e.getTargetException());
328                }
329                finally {
330                        if (!flag) {
331                                cstruct.setAccessible(flag);
332                        }
333                }
334        }
335
336        public static Constructor getConstructor(Class type, Class[] parameterTypes) {
337                try {
338                        Constructor constructor = type.getDeclaredConstructor(parameterTypes);
339                        if (System.getSecurityManager() != null) {
340                                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
341                                        constructor.setAccessible(true);
342                                        return null;
343                                });
344                        }
345                        else {
346                                constructor.setAccessible(true);
347                        }
348                        return constructor;
349                }
350                catch (NoSuchMethodException e) {
351                        throw new CodeGenerationException(e);
352                }
353        }
354
355        public static String[] getNames(Class[] classes) {
356                if (classes == null)
357                        return null;
358                String[] names = new String[classes.length];
359                for (int i = 0; i < names.length; i++) {
360                        names[i] = classes[i].getName();
361                }
362                return names;
363        }
364
365        public static Class[] getClasses(Object[] objects) {
366                Class[] classes = new Class[objects.length];
367                for (int i = 0; i < objects.length; i++) {
368                        classes[i] = objects[i].getClass();
369                }
370                return classes;
371        }
372
373        public static Method findNewInstance(Class iface) {
374                Method m = findInterfaceMethod(iface);
375                if (!m.getName().equals("newInstance")) {
376                        throw new IllegalArgumentException(iface + " missing newInstance method");
377                }
378                return m;
379        }
380
381        public static Method[] getPropertyMethods(PropertyDescriptor[] properties, boolean read, boolean write) {
382                Set methods = new HashSet();
383                for (int i = 0; i < properties.length; i++) {
384                        PropertyDescriptor pd = properties[i];
385                        if (read) {
386                                methods.add(pd.getReadMethod());
387                        }
388                        if (write) {
389                                methods.add(pd.getWriteMethod());
390                        }
391                }
392                methods.remove(null);
393                return (Method[]) methods.toArray(new Method[methods.size()]);
394        }
395
396        public static PropertyDescriptor[] getBeanProperties(Class type) {
397                return getPropertiesHelper(type, true, true);
398        }
399
400        public static PropertyDescriptor[] getBeanGetters(Class type) {
401                return getPropertiesHelper(type, true, false);
402        }
403
404        public static PropertyDescriptor[] getBeanSetters(Class type) {
405                return getPropertiesHelper(type, false, true);
406        }
407
408        private static PropertyDescriptor[] getPropertiesHelper(Class type, boolean read, boolean write) {
409                try {
410                        BeanInfo info = Introspector.getBeanInfo(type, Object.class);
411                        PropertyDescriptor[] all = info.getPropertyDescriptors();
412                        if (read && write) {
413                                return all;
414                        }
415                        List properties = new ArrayList(all.length);
416                        for (int i = 0; i < all.length; i++) {
417                                PropertyDescriptor pd = all[i];
418                                if ((read && pd.getReadMethod() != null) ||
419                                                (write && pd.getWriteMethod() != null)) {
420                                        properties.add(pd);
421                                }
422                        }
423                        return (PropertyDescriptor[]) properties.toArray(new PropertyDescriptor[properties.size()]);
424                }
425                catch (IntrospectionException e) {
426                        throw new CodeGenerationException(e);
427                }
428        }
429
430        public static Method findDeclaredMethod(final Class type,
431                        final String methodName, final Class[] parameterTypes)
432                        throws NoSuchMethodException {
433
434                Class cl = type;
435                while (cl != null) {
436                        try {
437                                return cl.getDeclaredMethod(methodName, parameterTypes);
438                        }
439                        catch (NoSuchMethodException e) {
440                                cl = cl.getSuperclass();
441                        }
442                }
443                throw new NoSuchMethodException(methodName);
444        }
445
446        public static List addAllMethods(final Class type, final List list) {
447                if (type == Object.class) {
448                        list.addAll(OBJECT_METHODS);
449                }
450                else
451                        list.addAll(java.util.Arrays.asList(type.getDeclaredMethods()));
452
453                Class superclass = type.getSuperclass();
454                if (superclass != null) {
455                        addAllMethods(superclass, list);
456                }
457                Class[] interfaces = type.getInterfaces();
458                for (int i = 0; i < interfaces.length; i++) {
459                        addAllMethods(interfaces[i], list);
460                }
461
462                return list;
463        }
464
465        public static List addAllInterfaces(Class type, List list) {
466                Class superclass = type.getSuperclass();
467                if (superclass != null) {
468                        list.addAll(Arrays.asList(type.getInterfaces()));
469                        addAllInterfaces(superclass, list);
470                }
471                return list;
472        }
473
474
475        public static Method findInterfaceMethod(Class iface) {
476                if (!iface.isInterface()) {
477                        throw new IllegalArgumentException(iface + " is not an interface");
478                }
479                Method[] methods = iface.getDeclaredMethods();
480                if (methods.length != 1) {
481                        throw new IllegalArgumentException("expecting exactly 1 method in " + iface);
482                }
483                return methods[0];
484        }
485
486        // SPRING PATCH BEGIN
487        public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception {
488                return defineClass(className, b, loader, null, null);
489        }
490
491        public static Class defineClass(String className, byte[] b, ClassLoader loader,
492                        ProtectionDomain protectionDomain) throws Exception {
493
494                return defineClass(className, b, loader, protectionDomain, null);
495        }
496
497        @SuppressWarnings("deprecation")  // on JDK 9
498        public static Class defineClass(String className, byte[] b, ClassLoader loader,
499                        ProtectionDomain protectionDomain, Class<?> contextClass) throws Exception {
500
501                Class c = null;
502
503                // Preferred option: JDK 9+ Lookup.defineClass API if ClassLoader matches
504                if (contextClass != null && contextClass.getClassLoader() == loader &&
505                                privateLookupInMethod != null && lookupDefineClassMethod != null) {
506                        try {
507                                MethodHandles.Lookup lookup = (MethodHandles.Lookup)
508                                                privateLookupInMethod.invoke(null, contextClass, MethodHandles.lookup());
509                                c = (Class) lookupDefineClassMethod.invoke(lookup, b);
510                        }
511                        catch (InvocationTargetException ex) {
512                                Throwable target = ex.getTargetException();
513                                if (target.getClass() != LinkageError.class && target.getClass() != IllegalArgumentException.class) {
514                                        throw new CodeGenerationException(target);
515                                }
516                                // in case of plain LinkageError (class already defined)
517                                // or IllegalArgumentException (class in different package):
518                                // fall through to traditional ClassLoader.defineClass below
519                        }
520                        catch (Throwable ex) {
521                                throw new CodeGenerationException(ex);
522                        }
523                }
524
525                // Classic option: protected ClassLoader.defineClass method
526                if (c == null && classLoaderDefineClassMethod != null) {
527                        if (protectionDomain == null) {
528                                protectionDomain = PROTECTION_DOMAIN;
529                        }
530                        Object[] args = new Object[]{className, b, 0, b.length, protectionDomain};
531                        try {
532                                if (!classLoaderDefineClassMethod.isAccessible()) {
533                                        classLoaderDefineClassMethod.setAccessible(true);
534                                }
535                                c = (Class) classLoaderDefineClassMethod.invoke(loader, args);
536                        }
537                        catch (InvocationTargetException ex) {
538                                throw new CodeGenerationException(ex.getTargetException());
539                        }
540                        catch (Throwable ex) {
541                                // Fall through if setAccessible fails with InaccessibleObjectException on JDK 9+
542                                // (on the module path and/or with a JVM bootstrapped with --illegal-access=deny)
543                                if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
544                                        throw new CodeGenerationException(ex);
545                                }
546                        }
547                }
548
549                // Fallback option: JDK 9+ Lookup.defineClass API even if ClassLoader does not match
550                if (c == null && contextClass != null && contextClass.getClassLoader() != loader &&
551                                privateLookupInMethod != null && lookupDefineClassMethod != null) {
552                        try {
553                                MethodHandles.Lookup lookup = (MethodHandles.Lookup)
554                                                privateLookupInMethod.invoke(null, contextClass, MethodHandles.lookup());
555                                c = (Class) lookupDefineClassMethod.invoke(lookup, b);
556                        }
557                        catch (InvocationTargetException ex) {
558                                throw new CodeGenerationException(ex.getTargetException());
559                        }
560                        catch (Throwable ex) {
561                                throw new CodeGenerationException(ex);
562                        }
563                }
564
565                // No defineClass variant available at all?
566                if (c == null) {
567                        throw new CodeGenerationException(THROWABLE);
568                }
569
570                // Force static initializers to run.
571                Class.forName(className, true, loader);
572                return c;
573        }
574        // SPRING PATCH END
575
576        public static int findPackageProtected(Class[] classes) {
577                for (int i = 0; i < classes.length; i++) {
578                        if (!Modifier.isPublic(classes[i].getModifiers())) {
579                                return i;
580                        }
581                }
582                return 0;
583        }
584
585        public static MethodInfo getMethodInfo(final Member member, final int modifiers) {
586                final Signature sig = getSignature(member);
587                return new MethodInfo() {
588                        private ClassInfo ci;
589
590                        public ClassInfo getClassInfo() {
591                                if (ci == null)
592                                        ci = ReflectUtils.getClassInfo(member.getDeclaringClass());
593                                return ci;
594                        }
595
596                        public int getModifiers() {
597                                return modifiers;
598                        }
599
600                        public Signature getSignature() {
601                                return sig;
602                        }
603
604                        public Type[] getExceptionTypes() {
605                                return ReflectUtils.getExceptionTypes(member);
606                        }
607
608                        public Attribute getAttribute() {
609                                return null;
610                        }
611                };
612        }
613
614        public static MethodInfo getMethodInfo(Member member) {
615                return getMethodInfo(member, member.getModifiers());
616        }
617
618        public static ClassInfo getClassInfo(final Class clazz) {
619                final Type type = Type.getType(clazz);
620                final Type sc = (clazz.getSuperclass() == null) ? null : Type.getType(clazz.getSuperclass());
621                return new ClassInfo() {
622                        public Type getType() {
623                                return type;
624                        }
625                        public Type getSuperType() {
626                                return sc;
627                        }
628                        public Type[] getInterfaces() {
629                                return TypeUtils.getTypes(clazz.getInterfaces());
630                        }
631                        public int getModifiers() {
632                                return clazz.getModifiers();
633                        }
634                };
635        }
636
637        // used by MethodInterceptorGenerated generated code
638        public static Method[] findMethods(String[] namesAndDescriptors, Method[] methods) {
639                Map map = new HashMap();
640                for (int i = 0; i < methods.length; i++) {
641                        Method method = methods[i];
642                        map.put(method.getName() + Type.getMethodDescriptor(method), method);
643                }
644                Method[] result = new Method[namesAndDescriptors.length / 2];
645                for (int i = 0; i < result.length; i++) {
646                        result[i] = (Method) map.get(namesAndDescriptors[i * 2] + namesAndDescriptors[i * 2 + 1]);
647                        if (result[i] == null) {
648                                // TODO: error?
649                        }
650                }
651                return result;
652        }
653
654}