1/*
2 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24import java.lang.reflect.Method;
25import java.util.ArrayList;
26
27public class MethodIdentifierParser {
28
29    private String logString;
30    private String className;
31    private String methodName;
32    private String methodDescriptor;
33
34    /**
35     * This is a utility class for parsing the log entries for a method. It supplies
36     * a few select methods for reflecting the class and method from that information.
37     *
38     * Example log entries:
39     * "java.util.TreeMap.successor(Ljava/util/TreeMap$Entry;)Ljava/util/TreeMap$Entry;"
40     */
41
42    public MethodIdentifierParser(String logString) {
43        this.logString = logString;
44
45        int i      = logString.lastIndexOf("."); // find start of method name
46        className  = logString.substring(0, i);  // classname is everything before
47        int i2     = logString.indexOf("(");     // Signature starts with an '('
48        methodName = logString.substring(i+1, i2);
49        methodDescriptor  = logString.substring(i2, logString.length());
50
51        // Add sanity check for extracted fields
52    }
53
54    public Method getMethod() throws NoSuchMethodException, SecurityException, ClassNotFoundException {
55        try {
56            return Class.forName(className).getDeclaredMethod(methodName, getParamenterDescriptorArray());
57        } catch (UnexpectedTokenException e) {
58            throw new RuntimeException("Parse failed");
59        }
60    }
61
62    public Class<?>[] getParamenterDescriptorArray() throws ClassNotFoundException, UnexpectedTokenException {
63        ParameterDecriptorIterator s = new ParameterDecriptorIterator(methodDescriptor);
64        Class<?> paramType;
65        ArrayList<Class<?>> list = new ArrayList<Class<?>>();
66        while ((paramType = s.nextParamType()) != null) {
67            list.add(paramType);
68        }
69        if (list.size() > 0) {
70            return list.toArray(new Class<?>[list.size()]);
71        } else {
72            return null;
73        }
74    }
75
76    class ParameterDecriptorIterator {
77
78        // This class uses charAt() indexing for startMark and i
79        // That is when i points to the last char it can be retrieved with
80        // charAt(i). Including the last char for a subString requires
81        // substring(startMark, i+1);
82
83        private String methodDescriptor;
84        private int startMark;
85
86        public ParameterDecriptorIterator(String signature) {
87            this.methodDescriptor = signature;
88            this.startMark = 0;
89            if (signature.charAt(0) == '(') {
90                this.startMark = 1;
91            }
92        }
93
94        public Class<?> nextParamType() throws UnexpectedTokenException {
95            int i = startMark;
96            while (methodDescriptor.length() > i) {
97                switch (methodDescriptor.charAt(i)) {
98                case 'C':
99                case 'B':
100                case 'I':
101                case 'J':
102                case 'Z':
103                case 'F':
104                case 'D':
105                case 'S':
106                    // Primitive class case, but we may have gotten here with [ as first token
107                    break;
108                case 'L':
109                    // Internal class name suffixed by ';'
110                    while (methodDescriptor.charAt(i) != ';') {
111                        i++;
112                    }
113                    break;
114                case '[':
115                    i++;         // arrays -> do another pass
116                    continue;
117                case ')':
118                    return null; // end found
119                case 'V':
120                case ';':
121                default:
122                    throw new UnexpectedTokenException(methodDescriptor, i);
123                }
124                break;
125            }
126            if (i == startMark) {
127                // Single char -> primitive class case
128                startMark++; // Update for next iteration
129                switch (methodDescriptor.charAt(i)) {
130                case 'C':
131                    return char.class;
132                case 'B':
133                    return byte.class;
134                case 'I':
135                    return int.class;
136                case 'J':
137                    return long.class;
138                case 'F':
139                    return float.class;
140                case 'D':
141                    return double.class;
142                case 'S':
143                    return short.class;
144                case 'Z':
145                    return boolean.class;
146                default:
147                    throw new UnexpectedTokenException(methodDescriptor, i);
148                }
149            } else {
150                // Multi char case
151                String nextParam;
152                if (methodDescriptor.charAt(startMark) == 'L') {
153                    // When reflecting a class the leading 'L' and trailing';' must be removed.
154                    // (When reflecting an array of classes, they must remain...)
155                    nextParam = methodDescriptor.substring(startMark+1, i);
156                } else {
157                    // Any kind of array - simple case, use whole descriptor when reflecting.
158                    nextParam = methodDescriptor.substring(startMark, i+1);
159                }
160                startMark = ++i; // Update for next iteration
161                try {
162                    // The parameter descriptor uses JVM internal class identifier with '/' as
163                    // package separator, but Class.forName expects '.'.
164                    nextParam = nextParam.replace('/', '.');
165                    return Class.forName(nextParam);
166                } catch (ClassNotFoundException e) {
167                    System.out.println("Class not Found: " + nextParam);
168                    return null;
169                }
170            }
171        }
172    }
173
174    class UnexpectedTokenException extends Exception {
175        String descriptor;
176        int i;
177        public UnexpectedTokenException(String descriptor, int i) {
178            this.descriptor = descriptor;
179            this.i = i;
180        }
181
182        @Override
183        public String toString() {
184            return "Unexpected token at: " + i + " in signature: " + descriptor;
185        }
186
187        private static final long serialVersionUID = 1L;
188    }
189
190    public void debugPrint() {
191        System.out.println("mlf in:               " + logString);
192        System.out.println("mlf class:            " + className);
193        System.out.println("mlf method:           " + methodName);
194        System.out.println("mlf methodDescriptor: " + methodDescriptor);
195    }
196}
197