/* * Copyright 2005-2010 Brian S O'Neill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.cojen.classfile; import java.util.ArrayList; /** * Utility class that supports parsing of Java method declarations. For * example, <code>public static int myMethod(java.lang.String, int value, * java.lang.String... extra)</code>. * * <p>The parser is fairly lenient. It doesn't care if the set of modifiers is * illegal, and it doesn't require that arguments have variable names assigned. * A semi-colon may appear at the end of the signature. * * <p>At most one variable argument is supported (at the end), and all class * names must be fully qualified. * * @author Brian S O'Neill */ public class MethodDeclarationParser { /** * @param src descriptor source string * @param pos position in source, updated at exit */ private static void skipWhitespace(String src, int[] pos) { int length = src.length(); int i = pos[0]; while (i < length) { char c = src.charAt(i); if (Character.isWhitespace(c)) { i++; } else { break; } } pos[0] = i; } /** * @param src descriptor source string * @param pos pos[0] is position in source, updated at exit * @return null if nothing left */ private static String parseIdentifier(String src, int[] pos) { // Skip leading whitespace. skipWhitespace(src, pos); int i = pos[0]; int length = src.length(); if (i >= length) { return null; } int startPos = i; char c = src.charAt(i); if (!Character.isJavaIdentifierStart(c)) { return null; } i++; while (i < length) { c = src.charAt(i); if (Character.isJavaIdentifierPart(c)) { i++; } else { break; } } pos[0] = i; // Skip trailing whitespace too. skipWhitespace(src, pos); return src.substring(startPos, i); } private static TypeDesc parseTypeDesc(String src, int[] pos) { // Skip leading whitespace. skipWhitespace(src, pos); int i = pos[0]; int length = src.length(); if (i >= length) { return null; } int startPos = i; char c = src.charAt(i); if (!Character.isJavaIdentifierStart(c)) { return null; } i++; while (i < length) { c = src.charAt(i); if (c == '.') { // Don't allow double dots. if (i + 1 < length && src.charAt(i + 1) == '.') { break; } else { i++; } } else if (Character.isJavaIdentifierPart(c) || c == '[' || c == ']') { i++; } else { break; } } pos[0] = i; // Skip trailing whitespace too. skipWhitespace(src, pos); return TypeDesc.forClass(src.substring(startPos, i)); } /** * @param src descriptor source string * @param pos pos[0] is position in source, updated at exit */ private static Modifiers parseModifiers(String src, int[] pos) { Modifiers modifiers = Modifiers.NONE; int length = src.length(); loop: while (pos[0] < length) { int savedPos = pos[0]; String ident = parseIdentifier(src, pos); int newPos = pos[0]; pos[0] = savedPos; if (ident == null) { break; } switch (ident.charAt(0)) { case 'a': if ("abstract".equals(ident)) { modifiers = modifiers.toAbstract(true); } else { break loop; } break; case 'f': if ("final".equals(ident)) { modifiers = modifiers.toFinal(true); } else { break loop; } break; case 'n': if ("native".equals(ident)) { modifiers = modifiers.toNative(true); } else { break loop; } break; case 'p': if ("public".equals(ident)) { modifiers = modifiers.toPublic(true); } else if ("private".equals(ident)) { modifiers = modifiers.toPrivate(true); } else if ("protected".equals(ident)) { modifiers = modifiers.toProtected(true); } else { break loop; } break; case 's': if ("static".equals(ident)) { modifiers = modifiers.toStatic(true); } else if ("synchronized".equals(ident)) { modifiers = modifiers.toSynchronized(true); } else if ("strict".equals(ident)) { modifiers = modifiers.toStrict(true); } else { break loop; } break; case 't': if ("transient".equals(ident)) { modifiers = modifiers.toTransient(true); } else { break loop; } break; case 'v': if ("volatile".equals(ident)) { modifiers = modifiers.toVolatile(true); } else { break loop; } break; default: break loop; } // end switch // If this point is reached, valid modifier was parsed. Advance position. pos[0] = newPos; } return modifiers; } private static TypeDesc[] parseParameters(String src, int[] pos, boolean[] isVarArgs) { // Skip trailing whitespace too. skipWhitespace(src, pos); int length = src.length(); if (pos[0] < length && src.charAt(pos[0]) != '(') { throw new IllegalArgumentException("Left paren expected"); } pos[0]++; ArrayList<TypeDesc> list = new ArrayList<TypeDesc>(); boolean expectParam = false; while (pos[0] < length) { TypeDesc type = parseTypeDesc(src, pos); if (type == null) { if (expectParam) { throw new IllegalArgumentException("Parameter type expected"); } break; } list.add(type); // Parse optional parameter name parseIdentifier(src, pos); if (pos[0] < length) { char c = src.charAt(pos[0]); if (c == ',') { // More params to follow... pos[0]++; expectParam = true; continue; } else if (c == ')') { pos[0]++; break; } // Handle varargs. if (c == '.' && pos[0] + 2 < length) { if (src.charAt(pos[0] + 1) == '.' && src.charAt(pos[0] + 2) == '.') { type = type.toArrayType(); isVarArgs[0] = true; list.set(list.size() - 1, type); pos[0] += 3; expectParam = false; continue; } } throw new IllegalArgumentException("Expected comma or right paren"); } } return list.toArray(new TypeDesc[list.size()]); } private final Modifiers mModifiers; private final TypeDesc mReturnType; private final String mMethodName; private final TypeDesc[] mParameters; /** * Parse the given method declaration, throwing a exception if the syntax * is wrong. * * @param declaration declaration to parse, which matches Java syntax * @throws IllegalArgumentException if declaration syntax is wrong */ public MethodDeclarationParser(String declaration) throws IllegalArgumentException { int[] pos = new int[1]; Modifiers modifiers = parseModifiers(declaration, pos); mReturnType = parseTypeDesc(declaration, pos); if (mReturnType == null) { throw new IllegalArgumentException("No return type"); } mMethodName = parseIdentifier(declaration, pos); if (mMethodName == null) { throw new IllegalArgumentException("No method name"); } boolean[] isVarArgs = new boolean[1]; mParameters = parseParameters(declaration, pos, isVarArgs); if (isVarArgs[0]) { modifiers = modifiers.toVarArgs(true); } mModifiers = modifiers; } public Modifiers getModifiers() { return mModifiers; } public TypeDesc getReturnType() { return mReturnType; } public String getMethodName() { return mMethodName; } public TypeDesc[] getParameters() { return (TypeDesc[])mParameters.clone(); } }