001: /*
002: * Copyright 2005 Brian S O'Neill
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: * http://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:
017: package org.cojen.classfile;
018:
019: import java.util.ArrayList;
020:
021: /**
022: * Utility class that supports parsing of Java method declarations. For
023: * example, <code>public static int myMethod(java.lang.String, int value,
024: * java.lang.String... extra)</code>.
025: *
026: * <p>The parser is fairly lenient. It doesn't care if the set of modifiers is
027: * illegal, and it doesn't require that arguments have variable names assigned.
028: * A semi-colon may appear at the end of the signature.
029: *
030: * <p>At most one variable argument is supported (at the end), and all class
031: * names must be fully qualified.
032: *
033: * @author Brian S O'Neill
034: */
035: public class MethodDeclarationParser {
036: /**
037: * @param src descriptor source string
038: * @param pos position in source, updated at exit
039: */
040: private static void skipWhitespace(String src, int[] pos) {
041: int length = src.length();
042: int i = pos[0];
043: while (i < length) {
044: char c = src.charAt(i);
045: if (Character.isWhitespace(c)) {
046: i++;
047: } else {
048: break;
049: }
050: }
051: pos[0] = i;
052: }
053:
054: /**
055: * @param src descriptor source string
056: * @param pos pos[0] is position in source, updated at exit
057: * @return null if nothing left
058: */
059: private static String parseIdentifier(String src, int[] pos) {
060: // Skip leading whitespace.
061: skipWhitespace(src, pos);
062: int i = pos[0];
063: int length = src.length();
064: if (i >= length) {
065: return null;
066: }
067:
068: int startPos = i;
069: char c = src.charAt(i);
070: if (!Character.isJavaIdentifierStart(c)) {
071: return null;
072: }
073: i++;
074:
075: while (i < length) {
076: c = src.charAt(i);
077: if (Character.isJavaIdentifierPart(c)) {
078: i++;
079: } else {
080: break;
081: }
082: }
083:
084: pos[0] = i;
085:
086: // Skip trailing whitespace too.
087: skipWhitespace(src, pos);
088:
089: return src.substring(startPos, i);
090: }
091:
092: private static TypeDesc parseTypeDesc(String src, int[] pos) {
093: // Skip leading whitespace.
094: skipWhitespace(src, pos);
095: int i = pos[0];
096: int length = src.length();
097: if (i >= length) {
098: return null;
099: }
100:
101: int startPos = i;
102: char c = src.charAt(i);
103: if (!Character.isJavaIdentifierStart(c)) {
104: return null;
105: }
106: i++;
107:
108: while (i < length) {
109: c = src.charAt(i);
110: if (c == '.') {
111: // Don't allow double dots.
112: if (i + 1 < length && src.charAt(i + 1) == '.') {
113: break;
114: } else {
115: i++;
116: }
117: } else if (Character.isJavaIdentifierPart(c) || c == '['
118: || c == ']') {
119: i++;
120: } else {
121: break;
122: }
123: }
124:
125: pos[0] = i;
126:
127: // Skip trailing whitespace too.
128: skipWhitespace(src, pos);
129:
130: return TypeDesc.forClass(src.substring(startPos, i));
131: }
132:
133: /**
134: * @param src descriptor source string
135: * @param pos pos[0] is position in source, updated at exit
136: */
137: private static Modifiers parseModifiers(String src, int[] pos) {
138: Modifiers modifiers = Modifiers.NONE;
139:
140: int length = src.length();
141: loop: while (pos[0] < length) {
142: int savedPos = pos[0];
143: String ident = parseIdentifier(src, pos);
144: int newPos = pos[0];
145: pos[0] = savedPos;
146:
147: if (ident == null) {
148: break;
149: }
150:
151: switch (ident.charAt(0)) {
152: case 'a':
153: if ("abstract".equals(ident)) {
154: modifiers = modifiers.toAbstract(true);
155: } else {
156: break loop;
157: }
158: break;
159:
160: case 'f':
161: if ("final".equals(ident)) {
162: modifiers = modifiers.toFinal(true);
163: } else {
164: break loop;
165: }
166: break;
167:
168: case 'n':
169: if ("native".equals(ident)) {
170: modifiers = modifiers.toNative(true);
171: } else {
172: break loop;
173: }
174: break;
175:
176: case 'p':
177: if ("public".equals(ident)) {
178: modifiers = modifiers.toPublic(true);
179: } else if ("private".equals(ident)) {
180: modifiers = modifiers.toPrivate(true);
181: } else if ("protected".equals(ident)) {
182: modifiers = modifiers.toProtected(true);
183: } else {
184: break loop;
185: }
186: break;
187:
188: case 's':
189: if ("static".equals(ident)) {
190: modifiers = modifiers.toStatic(true);
191: } else if ("synchronized".equals(ident)) {
192: modifiers = modifiers.toSynchronized(true);
193: } else if ("strict".equals(ident)) {
194: modifiers = modifiers.toStrict(true);
195: } else {
196: break loop;
197: }
198: break;
199:
200: case 't':
201: if ("transient".equals(ident)) {
202: modifiers = modifiers.toTransient(true);
203: } else {
204: break loop;
205: }
206: break;
207:
208: case 'v':
209: if ("volatile".equals(ident)) {
210: modifiers = modifiers.toVolatile(true);
211: } else {
212: break loop;
213: }
214: break;
215:
216: default:
217: break loop;
218: } // end switch
219:
220: // If this point is reached, valid modifier was parsed. Advance position.
221: pos[0] = newPos;
222: }
223:
224: return modifiers;
225: }
226:
227: private static TypeDesc[] parseParameters(String src, int[] pos,
228: boolean[] isVarArgs) {
229: // Skip trailing whitespace too.
230: skipWhitespace(src, pos);
231: int length = src.length();
232: if (pos[0] < length && src.charAt(pos[0]) != '(') {
233: throw new IllegalArgumentException("Left paren expected");
234: }
235: pos[0]++;
236:
237: ArrayList list = new ArrayList();
238:
239: boolean expectParam = false;
240: while (pos[0] < length) {
241: TypeDesc type = parseTypeDesc(src, pos);
242: if (type == null) {
243: if (expectParam) {
244: throw new IllegalArgumentException(
245: "Parameter type expected");
246: }
247: break;
248: }
249: list.add(type);
250:
251: // Parse optional parameter name
252: parseIdentifier(src, pos);
253:
254: if (pos[0] < length) {
255: char c = src.charAt(pos[0]);
256: if (c == ',') {
257: // More params to follow...
258: pos[0]++;
259: expectParam = true;
260: continue;
261: } else if (c == ')') {
262: pos[0]++;
263: break;
264: }
265:
266: // Handle varargs.
267: if (c == '.' && pos[0] + 2 < length) {
268: if (src.charAt(pos[0] + 1) == '.'
269: && src.charAt(pos[0] + 2) == '.') {
270: type = type.toArrayType();
271: isVarArgs[0] = true;
272: list.set(list.size() - 1, type);
273: pos[0] += 3;
274: expectParam = false;
275: continue;
276: }
277: }
278:
279: throw new IllegalArgumentException(
280: "Expected comma or right paren");
281: }
282: }
283:
284: return (TypeDesc[]) list.toArray(new TypeDesc[list.size()]);
285: }
286:
287: private final Modifiers mModifiers;
288: private final TypeDesc mReturnType;
289: private final String mMethodName;
290: private final TypeDesc[] mParameters;
291:
292: /**
293: * Parse the given method declaration, throwing a exception if the syntax
294: * is wrong.
295: *
296: * @param declaration declaration to parse, which matches Java syntax
297: * @throws IllegalArgumentException if declaration syntax is wrong
298: */
299: public MethodDeclarationParser(String declaration)
300: throws IllegalArgumentException {
301: int[] pos = new int[1];
302: Modifiers modifiers = parseModifiers(declaration, pos);
303: mReturnType = parseTypeDesc(declaration, pos);
304: if (mReturnType == null) {
305: throw new IllegalArgumentException("No return type");
306: }
307: mMethodName = parseIdentifier(declaration, pos);
308: if (mMethodName == null) {
309: throw new IllegalArgumentException("No method name");
310: }
311: boolean[] isVarArgs = new boolean[1];
312: mParameters = parseParameters(declaration, pos, isVarArgs);
313: if (isVarArgs[0]) {
314: modifiers = modifiers.toVarArgs(true);
315: }
316: mModifiers = modifiers;
317: }
318:
319: public Modifiers getModifiers() {
320: return mModifiers;
321: }
322:
323: public TypeDesc getReturnType() {
324: return mReturnType;
325: }
326:
327: public String getMethodName() {
328: return mMethodName;
329: }
330:
331: public TypeDesc[] getParameters() {
332: return (TypeDesc[]) mParameters.clone();
333: }
334: }
|