org.directwebremoting.impl.SignatureParser Maven / Gradle / Ivy
package org.directwebremoting.impl;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.directwebremoting.extend.ConverterManager;
import org.directwebremoting.extend.Creator;
import org.directwebremoting.extend.CreatorManager;
import org.directwebremoting.extend.MethodDeclaration;
import org.directwebremoting.extend.OverrideProperty;
import org.directwebremoting.extend.ParameterProperty;
import org.directwebremoting.extend.Property;
import org.directwebremoting.util.LocalUtil;
import org.directwebremoting.util.Loggers;
/**
* A parser for type info in a dwr.xml signature.
* @author Joe Walker [joe at getahead dot ltd dot uk]
*/
public class SignatureParser
{
/**
* Simple ctor
* @param converterManager Having understood the extra type info we add it in here.
* @param creatorManager If we can't find a class by Java name we can lookup by Javascript name
*/
public SignatureParser(ConverterManager converterManager, CreatorManager creatorManager)
{
this.converterManager = converterManager;
this.creatorManager = creatorManager;
packageImports.add("java.lang");
}
/**
* Parse some text and add it into the converter manager.
* @param sigtext The text to parse
*/
public void parse(String sigtext)
{
try
{
Loggers.STARTUP.debug("Parsing extra type info: ");
String reply = LegacyCompressor.stripMultiLineComments(sigtext);
reply = LegacyCompressor.stripSingleLineComments(reply);
String process = reply;
process = process.replace('\n', ' ');
process = process.replace('\r', ' ');
process = process.replace('\t', ' ');
StringTokenizer st = new StringTokenizer(process, ";");
while (st.hasMoreTokens())
{
String line = st.nextToken();
line = line.trim();
if (line.length() == 0)
{
continue;
}
if (line.startsWith("import "))
{
parseImportLine(line);
}
else
{
parseDeclarationLine(line);
}
}
}
catch (Exception ex)
{
Loggers.STARTUP.error("Unexpected Error", ex);
}
}
/**
* Parse a single import line
* @param line The import statement
*/
private void parseImportLine(String line)
{
String shortcut = line.substring(7, line.length());
shortcut = shortcut.trim();
if (line.endsWith(".*"))
{
shortcut = shortcut.substring(0, shortcut.length() - 2);
packageImports.add(shortcut);
}
else
{
int lastDot = line.lastIndexOf('.');
if (lastDot == -1)
{
Loggers.STARTUP.error("Missing . from import statement: " + line);
return;
}
String leaf = line.substring(lastDot + 1);
classImports.put(leaf, shortcut);
}
}
/**
* Parse a single declaration line.
* Where line is defined as being everything in between 2 ; chars.
* @param line The line to parse
*/
private void parseDeclarationLine(String line)
{
int openBrace = line.indexOf('(');
int closeBrace = line.indexOf(')');
if (openBrace == -1)
{
Loggers.STARTUP.error("Missing ( in declaration: " + line);
return;
}
if (closeBrace == -1)
{
Loggers.STARTUP.error("Missing ) in declaration: " + line);
return;
}
if (openBrace > closeBrace)
{
Loggers.STARTUP.error("( Must come before ) in declaration: " + line);
return;
}
// Class name and method name come before the opening (
String classMethod = line.substring(0, openBrace).trim();
Method method = findMethod(classMethod);
if (method == null)
{
// Debug is done by findMethod()
return;
}
// Now we need to get a list of all the parameters
String paramDecl = line.substring(openBrace + 1, closeBrace);
String[] paramNames = split(paramDecl);
// Check that we have the right number
if (method.getParameterTypes().length != paramNames.length)
{
Loggers.STARTUP.error("Parameter mismatch parsing signatures section in dwr.xml on line: " + line);
Loggers.STARTUP.info("- Reflected method had: " + method.getParameterTypes().length + " parameters: " + method.toString());
Loggers.STARTUP.info("- Signatures section had: " + paramNames.length + " parameters");
Loggers.STARTUP.info("- This can be caused by method overloading which is not supported by Javascript or DWR");
return;
}
for (int i = 0; i < paramNames.length; i++)
{
String[] genericList = getGenericParameterTypeList(paramNames[i]);
for (int j = 0; j < genericList.length; j++)
{
String type = genericList[j].trim();
Class> clazz = findClass(type);
if (clazz != null)
{
Property parent = new ParameterProperty(new MethodDeclaration(method), i);
Property child = parent.createChild(j);
child = converterManager.checkOverride(child);
Property replacement = new OverrideProperty(clazz);
converterManager.setOverrideProperty(child, replacement);
if (Loggers.STARTUP.isDebugEnabled())
{
Loggers.STARTUP.debug("- " + child + " = " + clazz.getName());
}
}
else
{
Loggers.STARTUP.warn("Missing class (" + type + ") while parsing signature section on line: " + line);
}
}
}
}
/**
* Lookup a class according to the import rules
* @param type The name of the class to find
* @return The found class, or null if it does not exist
*/
private Class> findClass(String type)
{
String itype = type;
Class> clazz = null;
// If type is an Array work it.
try
{
if (type.contains("[]"))
{
itype = type.substring(0, type.indexOf('['));
}
} catch (Exception ex) {
log.debug("SignatureParser can't find class: " + ex.getMessage());
}
// Handle inner classes
if (itype.indexOf('.') != -1)
{
Loggers.STARTUP.debug("Inner class detected: " + itype);
itype = itype.replace('.', '$');
}
clazz = retrieveClassFromClassImports(itype);
if (null == clazz)
{
clazz = retrieveClassFromPackageImports(itype);
}
if (null == clazz)
{
// So we've failed to find a Java class name. We can also lookup by
// Javascript name to help the situation where there is a dynamic proxy
// in the way.
Creator creator = creatorManager.getCreator(type, false);
if (creator != null)
{
clazz = creator.getType();
}
}
if (null != clazz)
{
if (type.contains("[]"))
{
return Array.newInstance(clazz, 0).getClass();
}
return clazz;
}
Loggers.STARTUP.error("Failed to find class: '" + itype + "' from block.");
Loggers.STARTUP.info("- Looked in the following class imports:");
for (Entry entry : classImports.entrySet())
{
Loggers.STARTUP.info(" - " + entry.getKey() + " -> " + entry.getValue());
}
Loggers.STARTUP.info("- Looked in the following package imports:");
for (String pkg : packageImports)
{
Loggers.STARTUP.info(" - " + pkg);
}
return null;
}
/**
* Attempts to retrieve a Class from the signatures class imports. Returns null
* if no class can be found.
*
* @param itype
* @return Class>
*/
private Class> retrieveClassFromClassImports(String itype)
{
try
{
String full = classImports.get(itype);
if (full == null)
{
full = itype;
}
return LocalUtil.classForName(full);
}
catch (Exception ex)
{
log.debug("SignatureParser - Can't find class in signature class imports, will attempt package imports.");
}
return null;
}
/**
* Attempts to retrieve a Class from the package imports. Returns null
* if no class can be found.
*
* @param itype
* @return Class>
*/
private Class> retrieveClassFromPackageImports(String itype)
{
for (String pkg : packageImports)
{
String lookup = pkg + '.' + itype;
try
{
return LocalUtil.classForName(lookup);
}
catch (Exception ex)
{
log.debug("SignatureParser - Can't find class in package imports: " + lookup);
}
}
return null;
}
/**
* Convert a parameter like "Map<Integer, URL>" into an array,
* something like [Integer, URL].
* @param paramName The parameter declaration string
* @return The array of generic types as strings
*/
private static String[] getGenericParameterTypeList(String paramName)
{
int openGeneric = paramName.indexOf('<');
if (openGeneric == -1)
{
Loggers.STARTUP.debug("No < in paramter declaration: " + paramName);
return new String[0];
}
int closeGeneric = paramName.lastIndexOf('>');
if (closeGeneric == -1)
{
Loggers.STARTUP.error("Missing > in generic declaration: " + paramName);
return new String[0];
}
String generics = paramName.substring(openGeneric + 1, closeGeneric);
StringTokenizer st = new StringTokenizer(generics, ",");
String[] types = new String[st.countTokens()];
int i = 0;
while (st.hasMoreTokens())
{
types[i] = st.nextToken();
i++;
}
return types;
}
/**
* Find a method from the declaration string
* @param classMethod The declaration that comes before the (
* @return The found method, or null if one was not found.
*/
private Method findMethod(String classMethod)
{
String classMethodChop = classMethod;
// If there is a return type then it must be before the last space.
int lastSpace = classMethodChop.lastIndexOf(' ');
if (lastSpace >= 0)
{
classMethodChop = classMethodChop.substring(lastSpace);
}
// The method name comes after the last .
int lastDot = classMethodChop.lastIndexOf('.');
if (lastDot == -1)
{
Loggers.STARTUP.error("Missing . to separate class name and method: " + classMethodChop);
return null;
}
String className = classMethodChop.substring(0, lastDot).trim();
String methodName = classMethodChop.substring(lastDot + 1).trim();
Class> clazz = findClass(className);
if (clazz == null)
{
// Debug is done by findClass()
return null;
}
Method method = null;
Method[] methods = clazz.getMethods();
for (Method test : methods)
{
if (test.getName().equals(methodName))
{
if (method == null)
{
method = test;
}
else
{
Loggers.STARTUP.warn("Setting extra type info to overloaded methods may fail with ");
}
}
}
if (method == null)
{
Loggers.STARTUP.error("Unable to find method called: " + methodName + " on type: " + clazz.getName());
}
return method;
}
/**
* Chop a parameter declaration string into separate parameters
* @param paramDecl The full set of parameter declarations
* @return An array of found parameters
*/
private static String[] split(String paramDecl)
{
List params = new ArrayList();
boolean inGeneric = false;
int start = 0;
for (int i = 0; i < paramDecl.length(); i++)
{
char c = paramDecl.charAt(i);
if (c == '<')
{
if (inGeneric)
{
Loggers.STARTUP.error("Found < while parsing generic section: " + paramDecl);
break;
}
inGeneric = true;
}
if (c == '>')
{
if (!inGeneric)
{
Loggers.STARTUP.error("Found > while not parsing generic section: " + paramDecl);
break;
}
inGeneric = false;
}
if (!inGeneric && c == ',')
{
// This is the start of a new parameter
String param = paramDecl.substring(start, i);
params.add(param);
start = i + 1;
}
}
// Add in the bit at the end:
String param = paramDecl.substring(start, paramDecl.length());
params.add(param);
return params.toArray(new String[params.size()]);
}
/**
* The map of specific class imports that we have parsed.
*/
private final Map classImports = new HashMap();
/**
* The map of package imports that we have parsed.
*/
private final List packageImports = new ArrayList();
/**
* Having understood the extra type info we add it in here.
*/
private final ConverterManager converterManager;
/**
* If we can't find a class by Java name we can lookup by Javascript name
*/
private final CreatorManager creatorManager;
/**
* The log stream
*/
private static final Log log = LogFactory.getLog(SignatureParser.class);
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy