com.tangosol.dev.compiler.java.ScriptParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of coherence Show documentation
Show all versions of coherence Show documentation
Oracle Coherence Community Edition
/*
* Copyright (c) 2000, 2020, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
package com.tangosol.dev.compiler.java;
import com.tangosol.dev.compiler.CompilerException;
import com.tangosol.dev.compiler.SyntaxException;
import com.tangosol.util.Base;
import com.tangosol.util.ErrorList;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;
/**
* This class implements the Java script parser. The purpose of this class
* is to extract method "scripts" from a .java file.
*
* @version 1.00, 2001.05.22
* @author Cameron Purdy
*/
public class ScriptParser
extends Base
implements Constants, TokenConstants
{
// ----- construction ---------------------------------------------------
/**
* Construct a Java ScriptParser.
*/
public ScriptParser()
{
}
/**
* Internal initialization.
*/
protected void init()
{
m_toker = null;
m_token = null;
m_asLines = null;
m_map = new HashMap();
m_mapParam = new HashMap();
m_sPackage = null;
m_sImports = null;
m_stackClass = new Stack();
}
// ----- public interface -----------------------------------------------
/**
* Parse the passed script.
*
* @param sScript the script to parse (as a string)
*
* @return a map from method signatures to scripts (scripts may be null)
*/
public synchronized Map parse(String sScript)
{
return parse(sScript, null);
}
/**
* Parse the passed script.
*
* @param sScript the script to parse (as a string)
* @param mapParam the map to store parameter names into
*
* @return a map from method signatures to scripts (scripts may be null)
*/
public synchronized Map parse(String sScript, Map mapParam)
{
init();
// 2002-07-22 cp - use passed map for storing parameters
if (mapParam != null)
{
m_mapParam = mapParam;
}
// script is required
if (sScript == null)
{
throw new IllegalArgumentException(CLASS + ".compile: "
+ "Script required!");
}
// parse script into lines
sScript = replace(sScript, "\r\n", "\n");
sScript = sScript.replace('\r', '\n');
m_asLines = parseDelimitedString(sScript, '\n');
// parse the script
try
{
m_toker = new Tokenizer(sScript, new ErrorList(), OPTIONS);
m_token = next();
parseCompilationUnit();
}
catch (Exception e)
{
if (DEBUG)
{
err("An exception occurred parsing the .java file:");
err(e);
}
}
return m_map;
}
// ----- construction ---------------------------------------------------
/**
* Unit test.
*/
public static void main(String[] asArgs)
{
try
{
String sClass = asArgs[0];
ClassLoader loader = Class.forName(sClass).getClassLoader();
if (loader == null)
{
loader = ClassLoader.getSystemClassLoader();
}
InputStream stream = loader.getResourceAsStream(sClass.replace('.', '/') + ".java");
if (stream == null)
{
out("No source for: " + sClass);
return;
}
byte[] ab = new byte[1000000];
int cb = read(stream, ab);
stream.close();
String sScript = new String(ab, 0, cb);
Map map = new ScriptParser().parse(sScript);
for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); )
{
Map.Entry entry = (Map.Entry) iter.next();
out();
out(entry.getKey());
out(" {");
out(indentString((String) entry.getValue(), " "));
out(" }");
}
}
catch (Exception e)
{
out("Exception occurred in test: " + e);
out(e);
}
}
// ----- script parsing -------------------------------------------------
/**
* Parse the script.
*
* Goal:
* CompilationUnit
* CompilationUnit:
* PackageDeclaration-opt ImportDeclarations-opt TypeDeclarations-opt
*/
protected void parseCompilationUnit()
throws CompilerException
{
String sPackage = parsePackageDeclaration();
m_sPackage = sPackage.length() == 0 ? "" : sPackage + '.';
String sImports = parseImportDeclarations();
if (sImports.length() > 0)
{
m_sImports = sImports;
}
parseTypeDeclarations();
}
/**
* Parse the "package" declaration.
*
* @return the qualified package name (or a zero length string if no package)
*/
protected String parsePackageDeclaration()
throws CompilerException
{
String sPackage = "";
if (peek(KEY_PACKAGE) != null)
{
sPackage = parseQualifiedName();
match(SEP_SEMICOLON);
}
return sPackage;
}
/**
* Parse the "import" declarations.
*
* @return a String containing the import declarations (or a zero length
* string if there are none)
*/
protected String parseImportDeclarations()
throws CompilerException
{
Token tokMark = mark();
while (peek(KEY_IMPORT) != null)
{
parseQualifiedName(true);
match(SEP_SEMICOLON);
}
return snip(tokMark, mark());
}
/**
* Parse "n.n.n".
*/
protected String parseQualifiedName()
throws CompilerException
{
return parseQualifiedName(false);
}
/**
* Parse "n.n.n".
*/
protected String parseQualifiedName(boolean fAllowStar)
throws CompilerException
{
// parse name "n.n.n"
StringBuffer sb = new StringBuffer();
boolean fFirst = true;
do
{
if (fFirst)
{
fFirst = false;
}
else
{
sb.append('.');
}
Token tokName = null;
if (fAllowStar)
{
tokName = peek(OP_MUL);
}
if (tokName == null)
{
tokName = match(IDENT);
}
sb.append(tokName.getText());
// "n.*" -- done after '*'
if (tokName.id == OP_MUL)
{
break;
}
}
while (peek(SEP_DOT) != null);
return sb.toString();
}
/**
* Parse zero or more class/interface declarations.
*
* TypeDeclaration:
* ClassOrInterfaceDeclaration
* ;
*/
protected void parseTypeDeclarations()
throws CompilerException
{
while (hasCurrent())
{
if (peek(SEP_SEMICOLON) != null)
{
continue;
}
parseClassOrInterfaceDeclaration();
}
}
/**
*
* ClassOrInterfaceDeclaration:
* ModifiersOpt (ClassDeclaration | InterfaceDeclaration)
*
* ClassDeclaration:
* class Identifier [extends Type] [implements TypeList] ClassBody
*
* InterfaceDeclaration:
* interface Identifier [extends TypeList] InterfaceBody
*
* TypeList:
* Type { , Type}
*/
protected void parseClassOrInterfaceDeclaration()
throws CompilerException
{
parseModifiers();
switch (m_token.id)
{
case KEY_CLASS:
{
// class Identifier
match(KEY_CLASS);
Token tokName = match(IDENT);
// [extends Type]
if (peek(KEY_EXTENDS) != null)
{
parseQualifiedName();
}
// [implements TypeList]
if (peek(KEY_IMPLEMENTS) != null)
{
do
{
parseQualifiedName();
}
while (peek(SEP_COMMA) != null);
}
// ClassBody
parseClassOrInterfaceBody(tokName);
}
break;
case KEY_INTERFACE:
{
// interface Identifier
match(KEY_INTERFACE);
Token tokName = match(IDENT);
// [extends TypeList]
if (peek(KEY_EXTENDS) != null)
{
do
{
parseQualifiedName();
}
while (peek(SEP_COMMA) != null);
}
// InterfaceBody
parseClassOrInterfaceBody(tokName);
}
break;
default:
throw new SyntaxException();
}
}
/**
* Parse zero or more class/interface declarations.
*
* ModifiersOpt:
* { Modifier }
*
* Modifier:
* public
* protected
* private
* static
* abstract
* final
* native
* synchronized
* transient
* volatile
* strictfp
*/
protected void parseModifiers()
throws CompilerException
{
while (true)
{
switch (m_token.id)
{
case KEY_PUBLIC:
case KEY_PROTECTED:
case KEY_PRIVATE:
case KEY_STATIC:
case KEY_ABSTRACT:
case KEY_FINAL:
case KEY_NATIVE:
case KEY_SYNCHRONIZED:
case KEY_TRANSIENT:
case KEY_VOLATILE:
case KEY_STRICTFP:
next();
break;
default:
return;
}
}
}
/**
* Parse a class or interface body.
*
* ClassBody:
* { {ClassBodyDeclaration} }
*
* InterfaceBody:
* { {InterfaceBodyDeclaration} }
*
* ClassBodyDeclaration:
* ;
* [static] Block
* ModifiersOpt MemberDecl
*
* InterfaceBodyDeclaration:
* ;
* ModifiersOpt InterfaceMemberDecl
*/
protected void parseClassOrInterfaceBody(Token tokName)
throws CompilerException
{
// push the class onto the stack of classes being parsed
Stack stack = m_stackClass;
stack.push(tokName.getText());
try
{
// store the imports as a script
if (m_sImports != null)
{
setScript(IMPORTS, m_sImports);
}
match(SEP_LCURLYBRACE);
nextMember: while (true)
{
switch (m_token.id)
{
case SEP_SEMICOLON:
next();
break;
case SEP_LCURLYBRACE:
parseStatic();
break;
case KEY_STATIC:
if (next().id == SEP_LCURLYBRACE)
{
parseStatic();
}
// fall through
case KEY_PUBLIC:
case KEY_PROTECTED:
case KEY_PRIVATE:
case KEY_ABSTRACT:
case KEY_FINAL:
case KEY_NATIVE:
case KEY_SYNCHRONIZED:
case KEY_TRANSIENT:
case KEY_VOLATILE:
case KEY_STRICTFP:
parseModifiers();
// fall through
case IDENT:
case KEY_VOID:
case KEY_BOOLEAN:
case KEY_BYTE:
case KEY_CHAR:
case KEY_SHORT:
case KEY_INT:
case KEY_LONG:
case KEY_FLOAT:
case KEY_DOUBLE:
case KEY_CLASS:
case KEY_INTERFACE:
parseMember();
break;
default:
break nextMember;
}
}
match(SEP_RCURLYBRACE);
}
finally
{
// pop class off the stack
stack.pop();
}
}
/**
* Parse a static code section.
*/
protected void parseStatic()
throws CompilerException
{
String sScript = parseMethodBody();
addStatic(sScript);
}
/**
* Parse a class member.
*/
protected void parseMember()
throws CompilerException
{
switch (m_token.id)
{
case IDENT:
// could be constructor (name of class)
// could be return type (n or n.n.n)
// could be field declaration (n or n.n.n)
{
Token tokType = current();
// the next token is a left-paren if this is a constructor
if (m_token.id == SEP_LPARENTHESIS)
{
parseMethodDeclaratorRest("");
break;
}
// eat through the rest of the qualified name
// (keep only the short name of the type)
while (peek(SEP_DOT) != null)
{
tokType = match(IDENT);
}
// eat optional brackets
while (peek(SEP_LBRACKET) != null)
{
match(SEP_RBRACKET);
}
// up to the field/method name now
Token tokName = match(IDENT);
if (m_token.id == SEP_LPARENTHESIS)
{
parseMethodDeclaratorRest(tokName.getText());
}
else
{
// it is a field declaration; expurgate
while (current().id != SEP_SEMICOLON)
{
}
}
}
break;
case KEY_BOOLEAN:
case KEY_BYTE:
case KEY_CHAR:
case KEY_SHORT:
case KEY_INT:
case KEY_LONG:
case KEY_FLOAT:
case KEY_DOUBLE:
// could be return type
// could be field declaration
{
Token tokType = current();
// eat optional brackets
while (peek(SEP_LBRACKET) != null)
{
match(SEP_RBRACKET);
}
Token tokName = match(IDENT);
if (m_token.id == SEP_LPARENTHESIS)
{
parseMethodDeclaratorRest(tokName.getText());
}
else
{
// it is a field declaration; expurgate
while (current().id != SEP_SEMICOLON)
{
}
}
}
break;
case KEY_VOID:
// definitely a return type
{
Token tokType = current();
Token tokName = match(IDENT);
parseMethodDeclaratorRest(tokName.getText());
}
break;
case KEY_CLASS:
case KEY_INTERFACE:
parseClassOrInterfaceDeclaration();
break;
}
}
/**
* Parse a method body and return the script.
*/
protected String parseMethodBody()
throws CompilerException
{
match(SEP_LCURLYBRACE);
Token tokStart = mark();
Token tokBeyondEnd;
int cDepth = 1;
ScriptBody: while (true)
{
Token tok = current();
switch (tok.id)
{
case SEP_LCURLYBRACE:
++cDepth;
break;
case SEP_RCURLYBRACE:
if (--cDepth == 0)
{
tokBeyondEnd = tok;
break ScriptBody;
}
break;
}
}
return snip(tokStart, tokBeyondEnd);
}
/**
* Parse the remainder of the method declaration and the method body
* if there is one.
*
* MethodDeclaratorRest:
* FormalParameters BracketsOpt [throws QualifiedIdentifierList]
* -> ( MethodBody | ; )
*/
protected void parseMethodDeclaratorRest(String sMethod)
throws CompilerException
{
// 2002-07-22 cp - collect parameter names
List listParam = new ArrayList();
StringBuffer sb = new StringBuffer(sMethod);
sb.append('(');
// FormalParameters
match(SEP_LPARENTHESIS);
boolean fFirst = true;
NextParam: while (true)
{
// optional "final"
peek(KEY_FINAL);
String sType;
switch (m_token.id)
{
case IDENT:
// n or n.n.n
sType = current().getText();
while (peek(SEP_DOT) != null)
{
sType = current().getText();
}
break;
case KEY_BOOLEAN:
case KEY_BYTE:
case KEY_CHAR:
case KEY_SHORT:
case KEY_INT:
case KEY_LONG:
case KEY_FLOAT:
case KEY_DOUBLE:
sType = current().getText();
break;
default:
break NextParam;
}
// eat optional brackets
while (peek(SEP_LBRACKET) != null)
{
match(SEP_RBRACKET);
sType += "[]";
}
// eat the name
// 2002-07-22 cp - also store the name
listParam.add(match(IDENT).getText());
// eat optional brackets
while (peek(SEP_LBRACKET) != null)
{
match(SEP_RBRACKET);
sType += "[]";
}
// add the short parameter type to the script "key"
int of = sType.lastIndexOf('$');
if (of >= 0)
{
sType = sType.substring(of + 1);
}
if (fFirst)
{
fFirst = false;
}
else
{
sb.append(',');
}
sb.append(sType);
// eat the optional (in the case of a trailing) comma
peek(SEP_COMMA);
}
match(SEP_RPARENTHESIS);
// finish building the script "key"
sb.append(')');
String sSig = sb.toString();
// 2002-07-22 cp - assemble parameter names
String[] asParam = (String[]) listParam.toArray(new String[listParam.size()]);
setParams(sSig, asParam);
// BracketsOpt
while (peek(SEP_LBRACKET) != null)
{
match(SEP_RBRACKET);
}
// [throws QualifiedIdentifierList]
if (peek(KEY_THROWS) != null)
{
while (m_token.id == IDENT)
{
parseQualifiedName();
peek(SEP_COMMA);
}
}
// ( MethodBody | ; )
if (peek(SEP_SEMICOLON) == null)
{
String sScript = parseMethodBody();
setScript(sSig, sScript);
}
}
// ----- script collection helpers --------------------------------------
/**
* Determine the current name.
*/
String getClassName()
{
Stack stack = m_stackClass;
azzert(!stack.isEmpty());
StringBuffer sb = new StringBuffer(m_sPackage);
for (int i = 0, c = stack.size(); i < c; ++i)
{
if (i > 0)
{
sb.append('$');
}
sb.append((String) stack.get(i));
}
return sb.toString();
}
/**
* Accumulate static block code for the class.
*/
void addStatic(String sScript)
{
String sOrig = getScript(STATIC_INIT);
if (sOrig == null)
{
setScript(STATIC_INIT, sScript);
}
else
{
setScript(STATIC_INIT, sOrig + '\n' + sScript);
}
}
/**
* Look up the specified script for the current class and return it.
*
* @return the script or null if there is none
*/
String getScript(String sSig)
{
String sKey = getClassName() + '.' + sSig;
return (String) m_map.get(sKey);
}
/**
* Store the specified script.
*/
void setScript(String sSig, String sScript)
{
String sKey = getClassName() + '.' + sSig;
m_map.put(sKey, sScript);
}
/**
* Look up the specified param names for the specified sig of the current class.
*
* @return the param names or null if there is none
*/
String[] getParams(String sSig)
{
String sKey = getClassName() + '.' + sSig;
return (String[]) m_mapParam.get(sKey);
}
/**
* Store the specified param names.
*/
void setParams(String sSig, String[] asParam)
{
String sKey = getClassName() + '.' + sSig;
m_mapParam.put(sKey, asParam);
}
// ----- parsing helpers ------------------------------------------------
/**
* Determine if there is a current token.
*
* @return true if there is a current token
*/
protected boolean hasCurrent()
{
return m_token != null;
}
/**
* Returns the current token and advances to the next token.
*
* @return the current token
*
* @exception CompilerException potentially thrown by the tokenizer
*/
protected Token current()
throws CompilerException
{
Token current = m_token;
next();
return current;
}
/**
* Determine if there is a next token.
*
* @return true if there is a next token
*/
protected boolean hasNext()
{
return m_toker.hasMoreTokens();
}
/**
* Advances to and returns the next token.
*
* @return the next token
*
* @exception CompilerException potentially thrown by the tokenizer
*/
protected Token next()
throws CompilerException
{
Tokenizer toker = m_toker;
if (toker.hasMoreTokens())
{
return m_token = (Token) toker.nextToken();
}
if (m_token == null)
{
throw new CompilerException();
}
return m_token = null;
}
/**
* Verifies that the current token matches the passed token id and, if so,
* advances to the next token. Otherwise, a syntax exception is thrown.
*
* @param id the token id to match
*
* @return the current token
*
* @exception SyntaxException thrown if the token does not match
* @exception CompilerException potentially thrown by the tokenizer
*/
protected Token match(int id)
throws CompilerException
{
if (m_token.id != id)
{
throw new SyntaxException();
}
return current();
}
/**
* Tests if the current token matches the passed token id and, if so,
* advances to the next token.
*
* @param id the token id to peek for
*
* @return the current token, if matched, or null
*
* @exception CompilerException potentially thrown by the tokenizer
*/
protected Token peek(int id)
throws CompilerException
{
return (m_token.id == id ? current() : null);
}
/**
* Tests if the current token matches the passed token category and
* sub-category. If so, it returns the current token and advances
* to the next token.
*
* @param cat the category to peek for
* @param subcat the sub-category to peek for
*
* @return the current token, if matched, or null
*
* @exception CompilerException potentially thrown by the tokenizer
*/
protected Token peek(int cat, int subcat)
throws CompilerException
{
Token token = m_token;
return (token.cat == cat && token.subcat == subcat ? current() : null);
}
/**
* Marks the current position and returns it as a token.
*
* @return the current token
*/
protected Token mark()
{
return m_token;
}
/**
* Obtains the source code between two marked tokens, incusive of the
* start and exclusive of the end.
*
* @param tokStart the first token of the script to "snip"
* @param tokBeyondEnd the (noninclusive) token that follows the script
* to "snip"
*
* @return the source code starting at tokStart and ending before
* tokBeyondEnd
*/
protected String snip(Token tokStart, Token tokBeyondEnd)
{
if (tokStart == tokBeyondEnd)
{
return "";
}
String[] asLines = m_asLines;
int iStart = tokStart.getLine();
int ofStart = tokStart.getOffset();
int iEnd = tokBeyondEnd.getLine();
int ofEnd = tokBeyondEnd.getOffset(); // non-inclusive
// check if the script is all contained within one line
if (iStart == iEnd)
{
String sLine = asLines[iStart];
String sScript = sLine.substring(ofStart, ofEnd).trim();
return sScript;
}
// collect lines of script
List list = new ArrayList();
if (ofStart > 0)
{
// include leading white space with the first line
String sLine = asLines[iStart];
while (ofStart > 0)
{
char ch = sLine.charAt(ofStart - 1);
if (ch == ' ' || ch == '\t')
{
--ofStart;
}
else
{
break;
}
}
}
list.add(asLines[iStart].substring(ofStart));
for (int i = iStart + 1; i < iEnd; ++i)
{
list.add(asLines[i]);
}
if (ofEnd > 0)
{
list.add(asLines[iEnd].substring(0, ofEnd));
}
// remove leading and trailing whitespace
while (((String) list.get(0)).trim().length() == 0)
{
list.remove(0);
}
if (list.size() == 0)
{
return "";
}
while (((String) list.get(list.size()-1)).trim().length() == 0)
{
list.remove(list.size()-1);
}
// determine the minimum script indent
int ofIndent = -1;
for (int i = 0, c = list.size(); i < c; ++i)
{
String sLine = (String) list.get(i);
if (sLine.trim().length() == 0)
{
list.set(i, "");
}
else
{
// Sun's java code uses tabs for eight spaces
if (sLine.indexOf('\t') != -1)
{
sLine = replace(sLine, "\t", " ");
list.set(i, sLine);
}
// count leading spaces
if (sLine.charAt(0) == ' ')
{
char[] ach = sLine.toCharArray();
int cSpaces = 0;
for (int of = 0, cch = ach.length; of < cch && ach[of] == ' '; ++of)
{
++cSpaces;
}
if (ofIndent == -1 || cSpaces < ofIndent)
{
ofIndent = cSpaces;
}
}
else
{
ofIndent = 0;
}
}
}
// assemble the script
StringBuffer sb = new StringBuffer();
for (int i = 0, c = list.size(); i < c; ++i)
{
String sLine = (String) list.get(i);
if (i > 0)
{
sb.append('\n');
}
if (sLine.length() > 0)
{
if (ofIndent > 0)
{
sLine = sLine.substring(ofIndent);
}
sb.append(sLine);
}
}
return sb.toString();
}
// ----- data members ---------------------------------------------------
/**
* The class name.
*/
private static final String CLASS = "ScriptParser";
/**
* Debug mode.
*/
private static final boolean DEBUG = ("JAVA.PARSER".equalsIgnoreCase(System.getProperty("DEBUG")));
/**
* Static initializer signature.
*/
public static final String STATIC_INIT = "()";
/**
* Imports signature.
*/
public static final String IMPORTS = "_imports()";
/**
* The tokenizer options.
*/
protected static final Properties OPTIONS = new Properties();
static
{
OPTIONS.put(OPT_PARSE_COMMENTS , "true" );
OPTIONS.put(OPT_PARSE_EXTENSIONS, "false");
}
/**
* The lexical tokenizer.
*/
protected Tokenizer m_toker;
/**
* The "current" token being evaluated.
*/
protected Token m_token;
/**
* The script parsed into lines.
*/
protected String[] m_asLines;
/**
* The collected scripts, keyed by signature (string) with values of
* scripts (string).
*/
protected Map m_map;
/**
* The collected param names, keyed by signature (string) with values of
* String[].
*/
protected Map m_mapParam;
/**
* The package name (including a trailing dot).
*/
protected String m_sPackage;
/**
* The imports "script".
*/
protected String m_sImports;
/**
* The stack of class names. The top most element is the class currently
* being parsed.
*/
protected Stack m_stackClass = new Stack();
}