001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.util;
016    
017    import com.liferay.portal.kernel.io.Deserializer;
018    import com.liferay.portal.kernel.io.Serializer;
019    
020    import java.io.Externalizable;
021    import java.io.IOException;
022    import java.io.ObjectInput;
023    import java.io.ObjectOutput;
024    
025    import java.lang.reflect.Method;
026    
027    import java.nio.ByteBuffer;
028    
029    import java.util.Arrays;
030    
031    /**
032     * Provides a serializable loose representation for {@link Method}, considering
033     * the declaring class, name, and parameter types of the {@link Method}, while
034     * ignoring its return type and exceptions. This means the compiler generated
035     * bridging method is considered logically the same as it source counterpart. On
036     * deserialization for a generic {@link Method}, the {@link Method} that is
037     * resolved (bridge method or source method) is runtime environment dependent.
038     * Whether it is resolved to a bridge method or source method is of no
039     * consequence, as a force cast is performed on the method's return value,
040     * assuring the same result.
041     *
042     * @author Brian Wing Shun Chan
043     * @author Shuyang Zhou
044     */
045    public class MethodKey implements Externalizable {
046    
047            /**
048             * The empty constructor is required by {@link Externalizable}. Do not use
049             * this for any other purpose.
050             */
051            public MethodKey() {
052            }
053    
054            public MethodKey(
055                    Class<?> declaringClass, String methodName,
056                    Class<?>... parameterTypes) {
057    
058                    _declaringClass = declaringClass;
059                    _methodName = methodName;
060                    _parameterTypes = parameterTypes;
061            }
062    
063            public MethodKey(Method method) {
064                    this(
065                            method.getDeclaringClass(), method.getName(),
066                            method.getParameterTypes());
067            }
068    
069            /**
070             * @deprecated As of 6.2.0, replaced by {@link #MethodKey(Class, String,
071             *             Class...)}
072             */
073            @Deprecated
074            public MethodKey(
075                    String declaringClassName, String methodName,
076                    Class<?>... parameterTypes) {
077    
078                    Thread currentThread = Thread.currentThread();
079    
080                    ClassLoader classLoader = currentThread.getContextClassLoader();
081    
082                    try {
083                            _declaringClass = classLoader.loadClass(declaringClassName);
084                    }
085                    catch (ClassNotFoundException cnfe) {
086                            throw new RuntimeException(cnfe);
087                    }
088    
089                    _methodName = methodName;
090                    _parameterTypes = parameterTypes;
091            }
092    
093            @Override
094            public boolean equals(Object obj) {
095                    if (this == obj) {
096                            return true;
097                    }
098    
099                    if (!(obj instanceof MethodKey)) {
100                            return false;
101                    }
102    
103                    MethodKey methodKey = (MethodKey)obj;
104    
105                    if ((_declaringClass == methodKey._declaringClass) &&
106                            Validator.equals(_methodName, methodKey._methodName) &&
107                            Arrays.equals(_parameterTypes, methodKey._parameterTypes)) {
108    
109                            return true;
110                    }
111    
112                    return false;
113            }
114    
115            public Class<?> getDeclaringClass() {
116                    return _declaringClass;
117            }
118    
119            public Method getMethod() throws NoSuchMethodException {
120                    return MethodCache.get(this);
121            }
122    
123            public String getMethodName() {
124                    return _methodName;
125            }
126    
127            public Class<?>[] getParameterTypes() {
128                    return _parameterTypes;
129            }
130    
131            @Override
132            public int hashCode() {
133    
134                    // Using the same hash algorithm as java.lang.reflect.Method
135    
136                    return _declaringClass.getName().hashCode() ^ _methodName.hashCode();
137            }
138    
139            @Override
140            public void readExternal(ObjectInput objectInput)
141                    throws ClassNotFoundException, IOException {
142    
143                    int size = objectInput.readInt();
144    
145                    byte[] data = new byte[size];
146    
147                    objectInput.readFully(data);
148    
149                    Deserializer deserializer = new Deserializer(ByteBuffer.wrap(data));
150    
151                    _declaringClass = deserializer.readObject();
152                    _methodName = deserializer.readString();
153    
154                    int parameterTypesLength = deserializer.readInt();
155    
156                    _parameterTypes = new Class<?>[parameterTypesLength];
157    
158                    for (int i = 0; i < parameterTypesLength; i++) {
159                            _parameterTypes[i] = deserializer.readObject();
160                    }
161            }
162    
163            @Override
164            public String toString() {
165                    if (_toString != null) {
166                            return _toString;
167                    }
168    
169                    StringBundler sb = new StringBundler(4 + _parameterTypes.length * 2);
170    
171                    sb.append(_declaringClass.getName());
172                    sb.append(StringPool.PERIOD);
173                    sb.append(_methodName);
174                    sb.append(StringPool.OPEN_PARENTHESIS);
175    
176                    for (Class<?> parameterType : _parameterTypes) {
177                            sb.append(parameterType.getName());
178                            sb.append(StringPool.COMMA);
179                    }
180    
181                    sb.setIndex(sb.index() - 1);
182    
183                    sb.append(StringPool.CLOSE_PARENTHESIS);
184    
185                    _toString = sb.toString();
186    
187                    return _toString;
188            }
189    
190            public MethodKey transform(ClassLoader classLoader)
191                    throws ClassNotFoundException {
192    
193                    Class<?> declaringClass = classLoader.loadClass(
194                            _declaringClass.getName());
195    
196                    Class<?>[] parameterTypes = new Class<?>[_parameterTypes.length];
197    
198                    for (int i = 0; i < _parameterTypes.length; i++) {
199                            parameterTypes[i] = classLoader.loadClass(
200                                    _parameterTypes[i].getName());
201                    }
202    
203                    return new MethodKey(declaringClass, _methodName, parameterTypes);
204            }
205    
206            @Override
207            public void writeExternal(ObjectOutput objectOutput) throws IOException {
208                    Serializer serializer = new Serializer();
209    
210                    serializer.writeObject(_declaringClass);
211                    serializer.writeString(_methodName);
212                    serializer.writeInt(_parameterTypes.length);
213    
214                    for (Class<?> parameterType : _parameterTypes) {
215                            serializer.writeObject(parameterType);
216                    }
217    
218                    ByteBuffer byteBuffer = serializer.toByteBuffer();
219    
220                    objectOutput.writeInt(byteBuffer.remaining());
221                    objectOutput.write(
222                            byteBuffer.array(), byteBuffer.position(), byteBuffer.remaining());
223            }
224    
225            private static final long serialVersionUID = 1L;
226    
227            private Class<?> _declaringClass;
228            private String _methodName;
229            private Class<?>[] _parameterTypes;
230    
231            // Transient cache
232    
233            private String _toString;
234    
235    }