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