001: /*
002: * Copyright 2005 Joe Walker
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: package org.directwebremoting.impl;
017:
018: import java.lang.reflect.Method;
019: import java.util.ArrayList;
020: import java.util.HashMap;
021: import java.util.List;
022: import java.util.Map;
023: import java.util.StringTokenizer;
024: import java.util.Map.Entry;
025:
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.directwebremoting.extend.ConverterManager;
029: import org.directwebremoting.extend.Creator;
030: import org.directwebremoting.extend.CreatorManager;
031: import org.directwebremoting.extend.TypeHintContext;
032: import org.directwebremoting.util.LocalUtil;
033:
034: /**
035: * A parser for type info in a dwr.xml signature.
036: * @author Joe Walker [joe at getahead dot ltd dot uk]
037: */
038: public class SignatureParser {
039: /**
040: * Simple ctor
041: * @param converterManager Having understood the extra type info we add it in here.
042: * @param creatorManager If we can't find a class by Java name we can lookup by Javascript name
043: */
044: public SignatureParser(ConverterManager converterManager,
045: CreatorManager creatorManager) {
046: this .converterManager = converterManager;
047: this .creatorManager = creatorManager;
048:
049: packageImports.add("java.lang");
050: }
051:
052: /**
053: * Parse some text and add it into the converter manager.
054: * @param sigtext The text to parse
055: */
056: public void parse(String sigtext) {
057: try {
058: log.debug("Parsing extra type info: ");
059:
060: String reply = LegacyCompressor
061: .stripMultiLineComments(sigtext);
062: reply = LegacyCompressor.stripSingleLineComments(reply);
063: String process = reply;
064:
065: process = process.replace('\n', ' ');
066: process = process.replace('\r', ' ');
067: process = process.replace('\t', ' ');
068:
069: StringTokenizer st = new StringTokenizer(process, ";");
070: while (st.hasMoreTokens()) {
071: String line = st.nextToken();
072: line = line.trim();
073: if (line.length() == 0) {
074: continue;
075: }
076:
077: if (line.startsWith("import ")) {
078: parseImportLine(line);
079: } else {
080: parseDeclarationLine(line);
081: }
082: }
083: } catch (Exception ex) {
084: log.error("Unexpected Error", ex);
085: }
086: }
087:
088: /**
089: * Parse a single import line
090: * @param line The import statement
091: */
092: private void parseImportLine(String line) {
093: String shortcut = line.substring(7, line.length());
094: shortcut = shortcut.trim();
095:
096: if (line.endsWith(".*")) {
097: shortcut = shortcut.substring(0, shortcut.length() - 2);
098: packageImports.add(shortcut);
099: } else {
100: int lastDot = line.lastIndexOf('.');
101: if (lastDot == -1) {
102: log.error("Missing . from import statement: " + line);
103: return;
104: }
105:
106: String leaf = line.substring(lastDot + 1);
107: classImports.put(leaf, shortcut);
108: }
109: }
110:
111: /**
112: * Parse a single declaration line.
113: * Where line is defined as being everything in between 2 ; chars.
114: * @param line The line to parse
115: */
116: private void parseDeclarationLine(String line) {
117: int openBrace = line.indexOf('(');
118: int closeBrace = line.indexOf(')');
119:
120: if (openBrace == -1) {
121: log.error("Missing ( in declaration: " + line);
122: return;
123: }
124:
125: if (closeBrace == -1) {
126: log.error("Missing ) in declaration: " + line);
127: return;
128: }
129:
130: if (openBrace > closeBrace) {
131: log.error("( Must come before ) in declaration: " + line);
132: return;
133: }
134:
135: // Class name and method name come before the opening (
136: String classMethod = line.substring(0, openBrace).trim();
137:
138: Method method = findMethod(classMethod);
139: if (method == null) {
140: // Debug is done by findMethod()
141: return;
142: }
143:
144: // Now we need to get a list of all the parameters
145: String paramDecl = line.substring(openBrace + 1, closeBrace);
146: String[] paramNames = split(paramDecl);
147:
148: // Check that we have the right number
149: if (method.getParameterTypes().length != paramNames.length) {
150: log
151: .error("Parameter mismatch parsing signatures section in dwr.xml on line: "
152: + line);
153: log.info("- Reflected method had: "
154: + method.getParameterTypes().length
155: + " parameters: " + method.toString());
156: log.info("- Signatures section had: " + paramNames.length
157: + " parameters");
158: log
159: .info("- This can be caused by method overloading which is not supported by Javascript or DWR");
160: return;
161: }
162:
163: for (int i = 0; i < paramNames.length; i++) {
164: String[] genericList = getGenericParameterTypeList(paramNames[i]);
165: for (int j = 0; j < genericList.length; j++) {
166: String type = genericList[j].trim();
167: Class<?> clazz = findClass(type);
168:
169: if (clazz != null) {
170: TypeHintContext thc = new TypeHintContext(
171: converterManager, method, i)
172: .createChildContext(j);
173: converterManager.setExtraTypeInfo(thc, clazz);
174:
175: if (log.isDebugEnabled()) {
176: log.debug("- " + thc + " = " + clazz.getName());
177: }
178: } else {
179: log
180: .warn("Missing class ("
181: + type
182: + ") while parsing signature section on line: "
183: + line);
184: }
185: }
186: }
187: }
188:
189: /**
190: * Lookup a class acording to the import rules
191: * @param type The name of the class to find
192: * @return The found class, or null if it does not exist
193: */
194: private Class<?> findClass(String type) {
195: String itype = type;
196:
197: // Handle inner classes
198: if (itype.indexOf('.') != -1) {
199: log.debug("Inner class detected: " + itype);
200: itype = itype.replace('.', '$');
201: }
202:
203: try {
204: String full = classImports.get(itype);
205: if (full == null) {
206: full = itype;
207: }
208:
209: return LocalUtil.classForName(full);
210: } catch (Exception ex) {
211: // log.debug("Trying to find class in package imports");
212: }
213:
214: for (String pkg : packageImports) {
215: String lookup = pkg + '.' + itype;
216:
217: try {
218: return LocalUtil.classForName(lookup);
219: } catch (Exception ex) {
220: // log.debug("Not found: " + lookup);
221: }
222: }
223:
224: // So we've failed to find a Java class name. We can also lookup by
225: // Javascript name to help the situation where there is a dynamic proxy
226: // in the way.
227: Creator creator = creatorManager.getCreator(type);
228: if (creator != null) {
229: return creator.getType();
230: }
231:
232: log.error("Failed to find class: '" + itype
233: + "' from <signature> block.");
234: log.info("- Looked in the following class imports:");
235: for (Entry<String, String> entry : classImports.entrySet()) {
236: log.info(" - " + entry.getKey() + " -> "
237: + entry.getValue());
238: }
239: log.info("- Looked in the following package imports:");
240: for (String pkg : packageImports) {
241: log.info(" - " + pkg);
242: }
243:
244: return null;
245: }
246:
247: /**
248: * Convert a parameter like "Map<Integer, URL>" into an array,
249: * something like [Integer, URL].
250: * @param paramName The parameter declaration string
251: * @return The array of generic types as strings
252: */
253: private static String[] getGenericParameterTypeList(String paramName) {
254: int openGeneric = paramName.indexOf('<');
255: if (openGeneric == -1) {
256: log.debug("No < in paramter declaration: " + paramName);
257: return new String[0];
258: }
259:
260: int closeGeneric = paramName.lastIndexOf('>');
261: if (closeGeneric == -1) {
262: log.error("Missing > in generic declaration: " + paramName);
263: return new String[0];
264: }
265:
266: String generics = paramName.substring(openGeneric + 1,
267: closeGeneric);
268: StringTokenizer st = new StringTokenizer(generics, ",");
269: String[] types = new String[st.countTokens()];
270: int i = 0;
271: while (st.hasMoreTokens()) {
272: types[i] = st.nextToken();
273: i++;
274: }
275:
276: return types;
277: }
278:
279: /**
280: * Find a method from the declaration string
281: * @param classMethod The declaration that comes before the (
282: * @return The found method, or null if one was not found.
283: */
284: private Method findMethod(String classMethod) {
285: String classMethodChop = classMethod;
286:
287: // If there is a return type then it must be before the last space.
288: int lastSpace = classMethodChop.lastIndexOf(' ');
289: if (lastSpace >= 0) {
290: classMethodChop = classMethodChop.substring(lastSpace);
291: }
292:
293: // The method name comes after the last .
294: int lastDot = classMethodChop.lastIndexOf('.');
295: if (lastDot == -1) {
296: log.error("Missing . to separate class name and method: "
297: + classMethodChop);
298: return null;
299: }
300:
301: String className = classMethodChop.substring(0, lastDot).trim();
302: String methodName = classMethodChop.substring(lastDot + 1)
303: .trim();
304:
305: Class<?> clazz = findClass(className);
306: if (clazz == null) {
307: // Debug is done by findClass()
308: return null;
309: }
310:
311: Method method = null;
312: Method[] methods = clazz.getMethods();
313: for (Method test : methods) {
314: if (test.getName().equals(methodName)) {
315: if (method == null) {
316: method = test;
317: } else {
318: log
319: .warn("Setting extra type info to overloaded methods may fail with <parameter .../>");
320: }
321: }
322: }
323:
324: if (method == null) {
325: log.error("Unable to find method called: " + methodName
326: + " on type: " + clazz.getName());
327: }
328:
329: return method;
330: }
331:
332: /**
333: * Chop a parameter declaration string into separate parameters
334: * @param paramDecl The full set of parameter declarations
335: * @return An array of found parameters
336: */
337: private static String[] split(String paramDecl) {
338: List<String> params = new ArrayList<String>();
339:
340: boolean inGeneric = false;
341: int start = 0;
342: for (int i = 0; i < paramDecl.length(); i++) {
343: char c = paramDecl.charAt(i);
344: if (c == '<') {
345: if (inGeneric) {
346: log.error("Found < while parsing generic section: "
347: + paramDecl);
348: break;
349: }
350:
351: inGeneric = true;
352: }
353:
354: if (c == '>') {
355: if (!inGeneric) {
356: log
357: .error("Found > while not parsing generic section: "
358: + paramDecl);
359: break;
360: }
361:
362: inGeneric = false;
363: }
364:
365: if (!inGeneric && c == ',') {
366: // This is the start of a new parameter
367: String param = paramDecl.substring(start, i);
368: params.add(param);
369: start = i + 1;
370: }
371: }
372:
373: // Add in the bit at the end:
374: String param = paramDecl.substring(start, paramDecl.length());
375: params.add(param);
376:
377: return params.toArray(new String[params.size()]);
378: }
379:
380: /**
381: * The map of specific class imports that we have parsed.
382: */
383: private Map<String, String> classImports = new HashMap<String, String>();
384:
385: /**
386: * The map of package imports that we have parsed.
387: */
388: private List<String> packageImports = new ArrayList<String>();
389:
390: /**
391: * Having understood the extra type info we add it in here.
392: */
393: private ConverterManager converterManager;
394:
395: /**
396: * If we can't find a class by Java name we can lookup by Javascript name
397: */
398: private CreatorManager creatorManager;
399:
400: /**
401: * The log stream
402: */
403: public static final Log log = LogFactory
404: .getLog(SignatureParser.class);
405: }
|