com.sun.rt98460.apigen.parser.ApiGen.g Maven / Gradle / Ivy
// Copyright (c) 2004 Sun Microsystems Inc., All Rights Reserved.
/*
* ApiGen.g
*
* SUN PROPRIETARY/CONFIDENTIAL.
* This software is the proprietary information of Sun Microsystems, Inc.
* Use is subject to license terms.
*
*/
header {
// Copyright (c) 2004 Sun Microsystems Inc., All Rights Reserved.
//
package com.sun.rt98460.apigen.parser;
import java.io.File;
import java.io.FileWriter;
import java.util.StringTokenizer;
}
//
// This parser is used to create a customized AST that subsequent phases
// (called tree walkers) will use to generate the described API.
//
class ApiGenParser extends Parser;
options {
buildAST = true; // uses CommonAST by default
k = 1;
}
// This top-level rule matches the whole API document.
doc
: API^ extAttrs (component)*
;
// This rule matches a component declaration.
component
: COMPONENT^
IDENT
(extendsClause)?
(extAttrs)?
LPAREN!
(ACTION)?
(propertyDecl)*
(methodDecl)*
RPAREN!
;
// This rule matches the extends clause of a component declaration.
extendsClause
: EXTENDS^ IDENT
;
// This rule matches extended attributes
extAttrs
: ATTRSROOT^ (IDENT EQUALS! STRING_LITERAL)+ RSQB!
;
// This rule matches a property declaration.
propertyDecl
: PROPERTY^ IDENT IDENT (extAttrs)? (propActions)? SEMI!
;
// This rule matches a method declaration.
methodDecl
: METHOD^ type:IDENT (extAttrs)?
name:IDENT (argList) (extAttrs)? SEMI!
;
// This rule matches a method's argument list.
argList
: LPAREN^ (paramDecl)* RPAREN!
;
// This rule matches a single, attributed parameter declaration.
paramDecl
: type:IDENT name:IDENT (extAttrs)?
;
// This rule matches property actions.
propActions
: (propAction)+
;
// This rule matches various action types.
propAction
: ACTION_GET^ ACTION
| ACTION_GETN^ ACTION
| ACTION_SET^ ACTION
| ACTION_APPEND^ ACTION
| ACTION_REMOVE^ ACTION
;
//
// This lexer is used to scan the source text of an API definition document,
// providing the parser with a token stream composed of the token types
// defined here.
//
class ApiGenLexer extends Lexer;
options {
k = 2;
caseSensitive = false;
caseSensitiveLiterals = false;
charVocabulary = '\3'..'\377';
testLiterals = true;
}
tokens {
API = "api" ;
COMPONENT = "component" ;
PROPERTY = "prop" ;
EXTENDS = "subclasses" ;
METHOD = "method" ;
ACTION_GET = "get" ;
ACTION_GETN = "getN" ;
ACTION_SET = "set" ;
ACTION_REMOVE = "remove" ;
ACTION_APPEND = "append" ;
}
{
/**
* General-purpose utility function for removing characters from the front
* and back of string. This is handy for cleaning up actions.
* @param src The string to process
* @param head The exact string to strip from head
* @param tail The exact string to strip from tail
* @return The stripped string.
*/
public static String stripFrontBack(String src, String head, String tail)
{
int h = src.indexOf(head);
int t = src.lastIndexOf(tail);
if (h == -1 || t == -1)
return src;
StringBuffer sb = new StringBuffer(src.substring(h + 1, t));
// strip trailing blanks
for (int length = sb.length(); length > 0 && sb.charAt(length - 1) == ' '; length--)
sb.deleteCharAt(length - 1);
return sb.toString();
}
/**
* Clean up the given action code string.
* @param src supplied action string to clean up.
* @return cleaned up action code.
*/
public static String cleanCode(String src)
{
String result = null;
int lineEndOfs = -1;
// Remove blank leading lines; these appear due to formatting in the
// API spec.
for (int idx = 0; result == null && idx < src.length(); idx++)
{
switch (src.charAt(idx))
{
case ' ' : case '\t' :
break;
case '\n' : case '\r' :
lineEndOfs = idx;
break;
default :
result = src.substring(lineEndOfs + 1);
break;
}
}
return result != null ? result : src;
}
}
// This rule is used to match whitespace between tokens. Whitespace is ignored.
WS
: ( /* '\r' '\n' can be matched in one alternative or by matching
'\r' in one iteration and '\n' in another. I am trying to
handle any flavor of newline that comes in, but the language
that allows both "\r\n" and "\r" and "\n" to all be valid
newline is ambiguous. Consequently, the resulting grammar
must be ambiguous. I'm shutting this warning off.
*/
options {
generateAmbigWarnings=false;
}
: ' '
| '\t'
| '\r' '\n' { newline(); }
| '\r' { newline(); }
| '\n' { newline(); }
)
{ $setType(Token.SKIP); }
;
// This rule is used to match comments, which are ignored.
COMMENT
:
( SL_COMMENT{$setType(Token.SKIP);} | ML_COMMENT {$setType(Token.SKIP);})
;
// This rule is used to match single like comments.
protected
SL_COMMENT
:
"//"
( ~('\n'|'\r') )*
(
/* '\r' '\n' can be matched in one alternative or by matching
'\r' and then in the next token. The language
that allows both "\r\n" and "\r" and "\n" to all be valid
newline is ambiguous. Consequently, the resulting grammar
must be ambiguous. I'm shutting this warning off.
*/
options {
generateAmbigWarnings=false;
}
: '\r' '\n'
| '\r'
| '\n'
)
{ newline(); }
;
// This rule is used to match multi-line comments of the C style.
protected
ML_COMMENT
:
"/*"
(
/* '\r' '\n' can be matched in one alternative or by matching
'\r' and then in the next token. The language
that allows both "\r\n" and "\r" and "\n" to all be valid
newline is ambiguous. Consequently, the resulting grammar
must be ambiguous. I'm shutting this warning off.
*/
options {
greedy=false; // make it exit upon "*/"
generateAmbigWarnings=false; // shut off newline errors
}
: '\r' '\n' { newline(); }
| '\r' { newline(); }
| '\n' { newline(); }
| ~('\n'|'\r')
)*
"*/"
;
LPAREN: '('
;
RPAREN: ')'
;
// We use the LSQB as the attributes root, so we give it a nice token name to
// reflect this role.
ATTRSROOT: '['
;
RSQB: ']'
;
SEMI: ';'
;
COMMA: ','
;
EQUALS: '='
;
LCURLY: '{'
;
RCURLY: '}'
;
// This rule matches identifiers (such as component and property names)
IDENT
options { testLiterals = false; }
: ('a'..'z') ('a'..'z'|'0'..'9'|'.'|'['|']'|'-')*
;
CHAR_LITERAL
: '\'' (ESC|~'\'') '\''
;
STRING_LITERAL
: '"' (ESC|~'"')* '"'
;
protected
ESC
: '\\'
( 'n'
| 'r'
| 't'
| 'b'
| 'f'
| 'w'
| 'a'
| '"'
| '\''
| '\\'
| ('0'..'3')
(
options {
warnWhenFollowAmbig = false;
}
: ('0'..'9')
(
options {
warnWhenFollowAmbig = false;
}
: '0'..'9'
)?
)?
| ('4'..'7')
(
options {
warnWhenFollowAmbig = false;
}
: ('0'..'9')
)?
| 'u' XDIGIT XDIGIT XDIGIT XDIGIT
)
;
protected
DIGIT
: '0'..'9'
;
protected
XDIGIT
: '0' .. '9'
| 'a' .. 'f'
;
// The rule matches an action -- a bit of Java code embedded in the API
// definition. Rather create a Java parser, we just rely on the Java having
// balanced curly braces, since we use curly braces to contain the action
// itself. Thus we have the notion of "nested" actions.
//
// (This pattern is borrowed from the ANTLR 2.7.2 grammar.)
ACTION
{int actionLine=getLine(); int actionColumn = getColumn(); }
: NESTED_ACTION
{
setText(cleanCode(stripFrontBack(getText(), "{", "}")));
CommonToken t = new CommonToken(_ttype,$getText);
t.setLine(actionLine); // set action line to start
t.setColumn(actionColumn);
$setToken(t);
}
;
protected
NESTED_ACTION
:
LCURLY
(
options {
greedy = false; // exit upon RCURLY
}
:
(
options {
generateAmbigWarnings = false; // shut off newline warning
}
: '\r' '\n' { newline(); }
| '\r' { newline(); }
| '\n' { newline(); }
)
| NESTED_ACTION
| CHAR_LITERAL
| COMMENT
| STRING_LITERAL
| .
)*
RCURLY
;
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
class ApiGenWalker extends TreeParser;
{
// We have three possible targets:
//
// Target isImpl isAbstract
// ------ ------ ----------
// 1. Public API false false
// 2. Abstract implementation false true
// 3. Implementation skeleton true false
//
/** True if the skeleton implementation is being generated. */
private boolean isImpl = false;
/** True if the abstract implementation is being generated. */
private boolean isAbstract = false;
/** Public API package name */
private String packageName = "";
/** Implementation package name (for both abstract and skeleton impl) */
private String implPackageName = "impl";
/** API description */
private String apiDesc = "";
/** Current component being generated */
private String currentComponent = "";
/** File writer for the current component being generated */
private FileWriter currentFW = null;
/** Description of the current component being generated */
private String currentComponentDesc = "";
/** The "extends" value for the current component */
private String currentExtends = "";
/** Description of the current component property being generated */
private String currentPropertyDesc = "";
/**
* The supplied singular name (as opposed to plural) specified for the
* current component property being generated. This is useful for plural
* names that cannot be converted to singular simply by dropping the "s" at
* the end of the plural name. (For example, properties.)
*/
private String currentPropertySingularName = "";
/**
* Strip the quotes surrounding a STRING_LITERAL.
* @param src The STRING_LITERAL to have its quotes stripped.
* @return The src
STRING_LITERAL, bare of its surrounding
* quotation marks.
*/
private static String stripQuotes(String src)
{
StringBuffer sb = new StringBuffer(src);
sb.deleteCharAt(0);
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
/**
* Write the given text to the current generation output file.
* @param str The text to write to the current generation output file.
*/
private void write( String str )
{
try
{
this.currentFW.write( str );
}
catch (java.io.IOException ex)
{
System.err.println( "File write I/O error: " + ex.getMessage() );
}
}
private final String DESC_PREFIX = "// ";
private void writeDesc(String desc, int maxNameLength)
{
// The name is already printed out, and padded to maxNameLength + 1.
final int descLength = desc.length();
final int totalOffset = DESC_PREFIX.length() + 1 + maxNameLength;
if (descLength + totalOffset < MAX_COLUMN)
{
write(desc);
}
else
{
// overflow. Try to break at a space to avoid overflow.
int spaceIdx = desc.lastIndexOf(' ');
if (spaceIdx > 0)
{
while (spaceIdx + totalOffset >= MAX_COLUMN)
{
int newSpaceIdx = desc.lastIndexOf(' ', spaceIdx - 1);
if (newSpaceIdx > 0 )
spaceIdx = newSpaceIdx;
else
break; // Best effort -- will overflow.
}
write(desc.substring(0, spaceIdx) + "\n" );
write(DESC_PREFIX);
for (int idx = 0; idx <= maxNameLength; idx++)
write( " " );
writeDesc(desc.substring(spaceIdx + 1), maxNameLength);
}
else
{
// no place to break the text; just let if overflow.
write(desc);
}
}
}
private int mCurrentJavaDocColumn = 1;
private final int MAX_COLUMN = 80;
private void startMethodJavaDoc()
{
this.mCurrentJavaDocColumn = 1;
}
private void writeMethodJavaDoc(String str)
{
int strLength = str.length();
if (mCurrentJavaDocColumn + strLength < MAX_COLUMN)
{
mCurrentJavaDocColumn += strLength;
write(str);
if (strLength > 0 && str.charAt(strLength - 1) == '\n')
startMethodJavaDoc();
}
else
{
// overflow. Try to break at a space to avoid overflow.
int spaceIdx = str.lastIndexOf(' ');
if (spaceIdx > 0)
{
while ( (mCurrentJavaDocColumn + spaceIdx) >= (MAX_COLUMN - 1))
{
int newSpaceIdx = str.lastIndexOf(' ', spaceIdx - 1);
if (newSpaceIdx > 0 )
spaceIdx = newSpaceIdx;
else
break;
}
writeMethodJavaDoc(str.substring(0, spaceIdx) + "\n" );
writeMethodJavaDoc( " * " );
writeMethodJavaDoc(str.substring(spaceIdx + 1));
}
else // no point to break text at -- overflow!
{
write(str);
mCurrentJavaDocColumn += strLength;
}
}
}
/**
* Close the current component generation file.
*/
private void close()
{
try
{
this.currentFW.flush();
this.currentFW.close();
this.currentFW = null;
}
catch( java.io.IOException ex)
{
System.err.println( "File close I/O error: " + ex.getMessage() );
}
}
/**
* Open a file for generating the current component. It is assumed that
* the current directory is the root of the source tree, and that
* the package name reflects the desired destination directory path relative
* to the source root.
* @return A file for the current component being generated.
*/
private File openFileForCurrentComponent()
{
File result;
StringBuffer pathname = new StringBuffer();
StringTokenizer paths = new StringTokenizer(isImpl || isAbstract ?
implPackageName : packageName,
".");
while (paths.hasMoreTokens())
{
pathname.append(paths.nextToken());
pathname.append(File.separatorChar);
}
if (pathname.length() > 0)
{
new File(pathname.toString()).mkdirs(); // ensure that the path exists.
}
pathname.append(this.currentComponent + (isImpl ? "Impl" : ""));
pathname.append(".java" );
result = new File(pathname.toString());
return result;
}
/**
* Ensure that the first character of the given string is lower case,
* except where it appears that the string starts with an all-caps
* acronym (e.g., DOM).
* @param str String to convert, if necessary.
* @return result of the conversion.
*/
private static String lowerCaseFirstChar(String str)
{
if (str.length() == 0 || Character.isLowerCase(str.charAt(0)) ||
(str.length() >= 2 && Character.isUpperCase(str.charAt(1))))
return str;
StringBuffer result = new StringBuffer(str);
result.setCharAt(0, Character.toLowerCase(result.charAt(0)));
return result.toString();
}
/**
* Ensure that the first character of the given string is upper case.
* @param str String to convert, if necessary.
* @return result of the conversion.
*/
private static String upperCaseFirstChar(String str)
{
if (str.length() == 0 || Character.isUpperCase(str.charAt(0)))
return str;
StringBuffer result = new StringBuffer(str);
result.setCharAt(0, Character.toUpperCase(result.charAt(0)));
return result.toString();
}
/**
* Scan the properties attached to the given component node, to determine
* this maximum size property name in the list.
* @param compNode The component node whose property names are to be scanned.
* @return The length of the largest property name for the compNode
.
*/
private int getMaxNameLength(AST compNode)
{
int result = 0;
for (AST node = compNode.getFirstChild(); node != null; node = node.getNextSibling())
{
if (node.getType() == PROPERTY)
{
AST typeNode = node.getFirstChild();
AST nameNode = typeNode.getNextSibling();
int length = nameNode.getText().length();
if (length > result)
result = length;
}
}
return result;
}
/**
* Generate the import statements for the current component being generated.
* @param compNode AST node for the current component
*/
private void generateImports(AST compNode)
{
boolean importGenerated = false;
for (AST node = compNode.getFirstChild(); node != null; node = node.getNextSibling())
{
if (node.getType() == ATTRSROOT)
{
AST nameNode = node.getFirstChild();
AST valueNode = nameNode != null ? nameNode.getNextSibling() : null;
while (nameNode != null && valueNode != null)
{
if ("import".equals(nameNode.getText()) || ( (isImpl || isAbstract) &&
"impl-import".equals(nameNode.getText())))
{
this.write( "import " );
this.write( stripQuotes( valueNode.getText() ) );
this.write( ";\n" );
importGenerated = true;
}
nameNode = valueNode.getNextSibling();
valueNode = nameNode != null ? nameNode.getNextSibling() : null;
}
}
}
if (importGenerated)
this.write( "\n" );
return;
}
/**
* Generate Java comments with a pretty-printed a list of the properties of
* the current component.
* @param compNode AST node of the current component being generated.
*/
private void listProperties(AST compNode)
{
boolean listStarted = false;
int maxNameLength = getMaxNameLength(compNode);
for (AST node = compNode.getFirstChild(); node != null; node = node.getNextSibling())
{
if (node.getType() == PROPERTY)
{
if (!listStarted)
{
write( "// This component has the following properties:\n" );
listStarted = true;
}
AST typeNode = node.getFirstChild();
AST nameNode = typeNode.getNextSibling();
AST attsNode = nameNode.getNextSibling();
String desc = getAtt("desc", attsNode);
write( "// " );
write( nameNode.getText() );
for (int i = nameNode.getText().length(); i < maxNameLength; i++)
write( " " );
write( " " );
writeDesc( desc, maxNameLength );
write( "\n" );
}
}
if (listStarted)
write( "//\n" );
}
/**
* Get the first instance of the named attribute from the given extended
* attributes list.
* @param name Name of attribute to look for.
* @param attsNode Root node of the attributes list.
*/
private static String getAtt(String name, AST attsNode)
{
String result = null;
if (attsNode != null)
{
AST nameNode = attsNode.getFirstChild();
AST valueNode = nameNode != null ? nameNode.getNextSibling() : null;
while (nameNode != null && valueNode != null && result == null)
{
if (nameNode.getText().equals(name))
result = stripQuotes(valueNode.getText());
nameNode = valueNode.getNextSibling();
valueNode = nameNode != null ? nameNode.getNextSibling() : null;
}
}
return result != null ? result : "";
}
/**
* Get the indexed access attribute of the given attribute list, if any
* is specified. Default is false.
* @param componentProperty Root of current component property to test.
* @return true if "access" is specified as "indexed" in the property's
* attribute list.
*/
private static boolean indexedAccess(AST componentProperty)
{
AST attsNode = componentProperty.getFirstChild().getNextSibling().getNextSibling();
String access = getAtt("access", attsNode);
return "indexed".equals(access);
}
/**
* Check the given component property attributes call have a read-only specifier.
* @param componentProperty The component property node from the AST.
* @return true if read-only access is specified.
*/
private static boolean readOnlyAccess(AST componentProperty)
{
AST attsNode = componentProperty.getFirstChild().getNextSibling().getNextSibling();
String access = getAtt("read-only", attsNode);
return "true".equals(access);
}
/**
* Check the given component property attributes call have a static specifier.
* @param componentProperty The component property node from the AST.
* @return true if read-only access is specified.
*/
private static boolean staticAccess(AST componentProperty)
{
AST attsNode = componentProperty.getFirstChild().getNextSibling().getNextSibling();
String access = getAtt("static", attsNode);
return "true".equals(access);
}
/** All known component names for the current API. Key = name, obj = Component node */
private java.util.HashMap componentNames = new java.util.HashMap();
/**
* Record all component names for the API.
* @param apiNode Root of the API AST.
*/
private void setApiNames(AST apiNode)
{
AST node = apiNode.getFirstChild();
while (node != null)
{
if (node.getType() == COMPONENT)
componentNames.put( node.getFirstChild().getText(), node );
node = node.getNextSibling();
}
}
/**
* Get the qualified name of the given class name, if it is a component of
* this API. This is used to qualify API interface names in the implementation
* code, since the class names collide.
* @param baseName The base name of the type to get a qualified name for.
* @return Qualified name if the base name is a component name, and we
* are generating implementation or abstract base implementation code.
*/
private String getQualifiedName(String baseName)
{
String result;
String plainBaseName = baseName;
while( plainBaseName.endsWith( "[]" ) )
plainBaseName = plainBaseName.substring(0, plainBaseName.length() - 2);
if ((isImpl || isAbstract) && componentNames.get(plainBaseName) != null)
result = packageName + "." + baseName;
else
result = baseName;
return result;
}
//////////////////////////////////////////////////////////////////////////////
/**
* Generate the JavaDoc for a METHOD AST.
* @param methNode Root node of the method's AST.
*/
private void generateMethodJavaDoc(AST methodNode)
{
AST returnType = methodNode.getFirstChild();
AST returnAttrs = returnType.getNextSibling();
AST nameNode;
AST argsNode;
AST methodAttrs;
if (returnAttrs.getType() == ATTRSROOT)
{
nameNode = returnAttrs.getNextSibling();
}
else
{
nameNode = returnAttrs;
returnAttrs = null;
}
argsNode = nameNode.getNextSibling();
if (argsNode != null && argsNode.getType() == LPAREN)
{
methodAttrs = argsNode.getNextSibling();
}
else
{
methodAttrs = argsNode;
argsNode = null;
}
if (returnType.getNextSibling().getType() == ATTRSROOT)
startMethodJavaDoc();
writeMethodJavaDoc( " /**\n" );
String methodDesc = getAtt( "desc", methodAttrs );
if (methodDesc.length() > 0)
{
writeMethodJavaDoc( " * " + methodDesc + "\n" );
writeMethodJavaDoc( " *\n" );
}
generateParamJavaDoc(argsNode);
if (! "void".equals(returnType.getText()))
{
writeMethodJavaDoc( " * @return " );
writeMethodJavaDoc( getAtt( "desc", returnAttrs ));
writeMethodJavaDoc( "\n" );
}
writeMethodJavaDoc( " */\n" );
}
/**
* Generate the JavaDoc for the given argument list AST.
* @param argsNode Root of the argument list, if any.
*/
private void generateParamJavaDoc(AST argsNode)
{
if (argsNode != null && argsNode.getFirstChild() != null)
{
AST typeNode = argsNode.getFirstChild();
AST nameNode = typeNode != null ? typeNode.getNextSibling() : null;
AST attrsNode = nameNode != null ? nameNode.getNextSibling() : null;
if (attrsNode != null && attrsNode.getType() != ATTRSROOT)
attrsNode = null;
while (typeNode != null && nameNode != null)
{
writeMethodJavaDoc( " * @param " );
writeMethodJavaDoc( nameNode.getText() );
writeMethodJavaDoc( " " );
writeMethodJavaDoc( getAtt( "desc", attrsNode));
writeMethodJavaDoc( "\n" );
typeNode = attrsNode != null ? attrsNode.getNextSibling() : nameNode.getNextSibling();
nameNode = typeNode != null ? typeNode.getNextSibling() : null;
attrsNode = nameNode != null ? nameNode.getNextSibling() : null;
if (attrsNode != null && attrsNode.getType() != ATTRSROOT)
attrsNode = null;
}
}
}
/**
* Fetch the named action from the given actions list.
* @param actsNode Start of actions list, if any.
* @return Root of action wanted; null if not found.
*/
protected AST getActionFor(String name, AST actsNode)
{
AST result = null;
while (actsNode != null && result == null)
{
if (name.equals(actsNode.getText()))
result = actsNode.getFirstChild();
actsNode = actsNode.getNextSibling();
}
return result;
}
/**
* Generate a GetN method for an array property.
* @param baseType The base type of the current property.
* @param pluralName The plural name for the current property.
* @param acts The actions for the current property.
* @param attrs The extended attributes for the current property.
*/
private void generateGetNMethod(String baseType, String pluralName, AST acts)
{
AST act = getActionFor( "getN", acts);
boolean emitGetNCode = isImpl && act == null || // generate skeleton
isAbstract && act != null || // generate body in abstract base
!isImpl && !isAbstract; // generate API
if (emitGetNCode)
{
this.write(" /**\n" );
this.write(" * Get the number of " + baseType + " items in " + pluralName + ".\n" );
this.write(" *\n" );
this.write(" * @return The number of " + baseType + " items in " + pluralName + "\n");
this.write(" */\n" );
this.write(" public " + (this.isAbstract && act == null ? "abstract " : "") + "int get" + upperCaseFirstChar(pluralName) + "Length()");
if (isAbstract)
{
this.write( "\n {\n" );
if (act != null)
{
this.write(act.getText());
}
else
{
this.write( " return 0; // $$TODO\n" );
}
this.write( " }\n" );
}
else
{
this.write( ";\n" );
}
this.write( "\n" );
}
}
/**
* Generate the default return statement for a skeleton implemention.
* @param baseType The base type of the method's return value.
*/
private void generateSkeletonReturn(String baseType)
{
this.write( " return" );
if ("void".equals(baseType))
this.write( "" );
else if ("int".equals(baseType))
this.write( " 0" );
else if ("float".equals(baseType))
this.write( " 0.0" );
else if ("double".equals(baseType))
this.write( " 0" );
else if ("char".equals(baseType))
this.write( " '\\0'" );
else if ("byte".equals(baseType))
this.write( " 0" );
else if ("boolean".equals(baseType))
this.write( " false" );
else
this.write( " null" );
this.write( "; // $$TODO\n" );
}
/**
* Generate the accessor method for the current property.
* @param isIndexed Access is by index number.
* @param baseType The base type of the current property.
* @param isStatic The accessor method is to be class-static.
* @param pluralName The plural name for the current property.
* @param singularName The singular name for the current property.
* @param acts The actions for the current property.
*/
private void generateAccessorMethod(boolean isIndexed, String baseType,
boolean isStatic, String pluralName, String singularName, AST acts)
{
AST act = this.getActionFor("get", acts);
boolean emit = isImpl && act == null || // generate skeleton
isAbstract && act != null || // generate body in abstract base
!isImpl && !isAbstract; // generate API
if (emit)
{
startMethodJavaDoc();
writeMethodJavaDoc(" /**\n" );
writeMethodJavaDoc(" * Get " );
if (this.currentPropertyDesc.length() > 0)
writeMethodJavaDoc(lowerCaseFirstChar(currentPropertyDesc));
else
writeMethodJavaDoc(pluralName);
if (isIndexed)
{
writeMethodJavaDoc( " by indexed position.\n" );
writeMethodJavaDoc( " *\n" );
writeMethodJavaDoc( " * @param index Indexed position value 0..length-1\n" );
writeMethodJavaDoc( " * @return " );
if (this.currentPropertyDesc.length() > 0)
writeMethodJavaDoc(currentPropertyDesc);
else
writeMethodJavaDoc(pluralName);
writeMethodJavaDoc( " at given index
position.\n" );
writeMethodJavaDoc( " */\n");
this.write(" public " + (this.isAbstract && act == null ? "abstract " : "") );
if (isStatic)
this.write( "static " );
this.write( getQualifiedName(baseType) );
this.write( " get" );
this.write( upperCaseFirstChar(singularName) );
this.write( "(int index)" );
if (this.isImpl || this.isAbstract && act != null)
{
this.write( "\n {\n" );
if (act != null)
this.write( act.getText() );
else
this.write( " return null; // $$TODO\n" );
this.write( " }\n" );
}
else
{
this.write( ";\n" );
}
}
else // not indexed
{
writeMethodJavaDoc( ".\n" );
writeMethodJavaDoc( " *\n" );
writeMethodJavaDoc( " * @return " );
if (this.currentPropertyDesc.length() > 0)
writeMethodJavaDoc(currentPropertyDesc);
else
writeMethodJavaDoc(pluralName);
writeMethodJavaDoc( "\n" );
writeMethodJavaDoc( " */\n" );
this.write(" public "+ (this.isAbstract && act == null ? "abstract " : "") );
if (isStatic)
this.write( "static " );
this.write( getQualifiedName(baseType) );
this.write( " get" );
this.write( upperCaseFirstChar(pluralName) );
this.write( "()" );
if (this.isImpl || this.isAbstract && act != null)
{
this.write( "\n {\n" );
if (act != null)
this.write( act.getText() );
else
generateSkeletonReturn(baseType);
this.write( " }\n" );
}
else
{
this.write( ";\n" );
}
}
this.write( "\n" );
} // if emit
}
/**
* Generate the mutator methods for the current property.
* @param isIndexed Access is by index number.
* @param baseType The base type of the current property.
* @param pluralName The plural name for the current property.
* @param singularName The singular name for the current property.
* @param acts The actions for the current property.
*/
private void generateMutatorMethods(boolean isIndexed,
String baseType, String pluralName, String singularName, AST acts)
{
AST act = this.getActionFor("set", acts);
boolean emit = isImpl && act == null || // generate skeleton
isAbstract && act != null || // generate body in abstract base
!isImpl && !isAbstract; // generate API
String paramPrefix = "boolean".equals(baseType) ? "is" : "the";
if (emit)
{
startMethodJavaDoc();
this.writeMethodJavaDoc(" /**\n" );
this.writeMethodJavaDoc(" * Set " );
if (this.currentPropertyDesc.length() > 0)
this.writeMethodJavaDoc(lowerCaseFirstChar(currentPropertyDesc));
else
this.writeMethodJavaDoc(pluralName);
if (isIndexed)
{
writeMethodJavaDoc( " by indexed position.\n" );
writeMethodJavaDoc( " *\n" );
writeMethodJavaDoc( " * @param index Indexed position value (0..length-1) of the item to set\n" );
writeMethodJavaDoc( " * @param " );
writeMethodJavaDoc( paramPrefix );
writeMethodJavaDoc( upperCaseFirstChar(singularName) );
writeMethodJavaDoc( " Item to add at position index
.\n" );
writeMethodJavaDoc( " */\n" );
this.write( " public " + (this.isAbstract && act == null ? "abstract " : "") + "void set" );
this.write( upperCaseFirstChar(singularName) );
this.write( "(int index, " );
this.write( getQualifiedName(baseType) );
this.write( " " + paramPrefix );
this.write( upperCaseFirstChar(singularName) );
this.write( ")" );
if (this.isImpl || this.isAbstract && act != null)
{
this.write( "\n {\n" );
if (act != null)
this.write( act.getText() );
else
this.write( " return; // $$TODO\n" );
this.write( " }\n" );
}
else
{
this.write( ";\n" );
}
this.write( "\n" );
}
else
{
writeMethodJavaDoc( ".\n" );
writeMethodJavaDoc( " *\n" );
writeMethodJavaDoc( " * @param " + paramPrefix );
writeMethodJavaDoc( upperCaseFirstChar(pluralName) );
writeMethodJavaDoc( " " );
if (this.currentPropertyDesc.length() > 0)
writeMethodJavaDoc(currentPropertyDesc);
else
writeMethodJavaDoc(pluralName);
writeMethodJavaDoc( "\n");
writeMethodJavaDoc(" */\n");
this.write(" public " + (this.isAbstract && act == null? "abstract " : "") + "void set");
this.write( upperCaseFirstChar(pluralName) );
this.write( "(" );
this.write( getQualifiedName(baseType) );
this.write( " " + paramPrefix );
this.write( upperCaseFirstChar(pluralName) );
this.write( ")" );
if (this.isImpl || this.isAbstract && act != null)
{
this.write( "\n {\n" );
if (act != null)
this.write( act.getText() );
else
generateSkeletonReturn( "void" );
this.write( " }\n" );
}
else
{
this.write( ";\n" );
}
this.write( "\n" );
}
} // if emit
////////////////////////////////////////////////////////////////////////////
// Generate append mutator (indexed access only).
////////////////////////////////////////////////////////////////////////////
act = getActionFor("append", acts);
emit = isImpl && act == null || // generate skeleton
isAbstract && act != null || // generate body in abstract base
!isImpl && !isAbstract; // generate API
if (emit)
{
if (isIndexed)
{
startMethodJavaDoc();
writeMethodJavaDoc(" /**\n" );
writeMethodJavaDoc(" * Append an item to " );
if (this.currentPropertyDesc.length() > 0)
writeMethodJavaDoc(lowerCaseFirstChar(currentPropertyDesc));
else
writeMethodJavaDoc(pluralName);
writeMethodJavaDoc(".\n");
writeMethodJavaDoc(" *\n" );
writeMethodJavaDoc(" * @param " + paramPrefix );
writeMethodJavaDoc( upperCaseFirstChar(singularName) );
writeMethodJavaDoc( " Item to append to " );
writeMethodJavaDoc( pluralName );
writeMethodJavaDoc( "\n");
writeMethodJavaDoc(" */\n" );
this.write( " public " + (this.isAbstract && act == null ? "abstract " : "") );
this.write( "void append" );
this.write( upperCaseFirstChar(singularName) );
this.write( "(" );
this.write( getQualifiedName(baseType) );
this.write( " " + paramPrefix );
this.write( upperCaseFirstChar(singularName) );
this.write( ")" );
if (this.isImpl || this.isAbstract && act != null)
{
this.write( "\n {\n" );
if (act != null)
this.write( act.getText() );
else
generateSkeletonReturn( "void" );
this.write( " }\n" );
}
else
{
this.write( ";\n" );
}
this.write( "\n" );
}
}
////////////////////////////////////////////////////////////////////////////
// Generate remove mutator (indexed access only).
////////////////////////////////////////////////////////////////////////////
act = getActionFor("remove", acts);
emit = isImpl && act == null || // generate skeleton
isAbstract && act != null || // generate body in abstract base
!isImpl && !isAbstract; // generate API
if (emit)
{
if (isIndexed)
{
startMethodJavaDoc();
writeMethodJavaDoc(" /**\n" );
writeMethodJavaDoc(" * Remove " );
if (this.currentPropertyDesc.length() > 0)
writeMethodJavaDoc(lowerCaseFirstChar(currentPropertyDesc));
else
writeMethodJavaDoc(pluralName);
writeMethodJavaDoc( " by index position.\n" );
writeMethodJavaDoc( " *\n" );
writeMethodJavaDoc( " * @param index The index position of the " );
writeMethodJavaDoc( singularName );
writeMethodJavaDoc( " to remove\n" );
writeMethodJavaDoc( " * @return The " + baseType + " removed, if any.\n" );
writeMethodJavaDoc( " */\n" );
this.write( " public " + (this.isAbstract && act == null ? "abstract " : "") );
this.write( getQualifiedName(baseType) );
this.write( " remove" );
this.write( upperCaseFirstChar(singularName) );
this.write( "(int index)" );
if (this.isImpl || this.isAbstract && act != null)
{
this.write( "\n {\n" );
if (act != null)
this.write(act.getText());
else
generateSkeletonReturn( baseType );
this.write( " }\n" );
}
else
{
this.write( ";\n" );
}
this.write( "\n" );
}
}
return;
}
}
// This starting rule is used to generate the public API code.
// It matches the top-level API production.
genPublic
: #(a:API {setApiNames(a); isAbstract = false; isImpl = false;} apiattrs (component)*)
;
// This starting rule is used to generate the skeleton implementation code.
// It matches the top-level API production.
genImpl
: #(a:API {setApiNames(a); isAbstract = false; isImpl = true;} apiattrs (component)*)
;
// This starting rule is used to generate the abstract base implementation code.
// It matches the top-level API production.
genAbstract
: #(a:API {setApiNames(a); isAbstract = true; isImpl = false;} apiattrs (component)*)
;
// This rule matches the attributes list for the API.
apiattrs: #(ATTRSROOT (apiprop)*)
;
// This rule matches an individual API attributes.
// We take the opportunity to sniff out some attributes of interest to the
// code generator.
apiprop: i:IDENT s:STRING_LITERAL
{
if ("desc".equals(i.getText()))
this.apiDesc = stripQuotes(s.getText());
else if ("package".equals(i.getText()))
this.packageName = stripQuotes(s.getText());
else if ("impl-package".equals(i.getText()))
this.implPackageName = stripQuotes(s.getText());
}
;
// This rule matches a single component declaration.
// This is used to drive the generation of a single component in the API.
component: #(
c:COMPONENT n:IDENT
{
this.currentComponent = n.getText();
try
{
this.currentFW = new FileWriter(this.openFileForCurrentComponent());
}
catch(java.io.IOException ex)
{
System.err.println( "File I/O error opening file: " + ex.getMessage());
this.currentFW = null;
}
this.write( "// Copyright (c) 2004 Sun Microsystems Inc., All Rights Reserved.\n" );
this.write( "/*\n" );
this.write( " * " + this.currentComponent + (isImpl ? "Impl" : "") );
this.write( ".java\n" );
this.write( " *\n" );
this.write( " * SUN PROPRIETARY/CONFIDENTIAL.\n" );
this.write( " * This software is the proprietary information of Sun Microsystems, Inc.\n" );
this.write( " * Use is subject to license terms.\n" );
this.write( " *\n" );
this.write( " */\n\n" );
if (this.isImpl)
this.write( "// Implementation / customisation of generated API code.\n" );
else
this.write( "// This file is generated; DO NOT EDIT.\n" );
this.write( "//\n// API Description: " );
this.write( this.apiDesc);
this.write( "\n// Component Name: " );
this.write( this.currentComponent );
this.write( "\n//\n" );
this.listProperties(c);
if (this.packageName != null && this.packageName.length() > 0)
{
this.write( "package " );
this.write( !isImpl && !isAbstract ? this.packageName : this.implPackageName);
this.write( ";\n\n" );
}
generateImports(c);
this.currentExtends = "";
}
(extendsClause)?
{
this.currentComponentDesc = "";
}
(compAttrs)?
{
if (this.currentComponentDesc.length() > 0)
{
this.write( "/**\n" );
this.write( " * " );
if (this.isImpl)
write( "Implementation of " );
else if (this.isAbstract)
write( "Abstract implementation of" );
else
write( "API for " );
if (this.isAbstract || this.isImpl)
write ("\n * ");
else
write(" ");
this.write( this.currentComponentDesc );
this.write( "\n *\n * @author ApiGen AX.00\n */\n" );
}
if (this.isImpl)
this.write( "class " );
else if (this.isAbstract)
this.write( "abstract class " );
else
this.write( "public interface " );
this.write( this.currentComponent + (this.isImpl ? "Impl" : "") );
if (this.isImpl)
{
this.write( " extends " );
this.write( this.currentComponent );
}
else if (this.currentExtends.length() > 0)
{
this.write( " extends " );
this.write( this.currentExtends );
}
this.write( "\n" );
if (this.isAbstract)
{
this.write( " implements " ); // TAG-IMPLEMENTS
this.write( this.packageName );
this.write( "." );
this.write( currentComponent );
this.write( "\n" );
}
this.write( "{\n" );
}
(firstAction)?
(property)*
(method)*
)
{
this.write( "}\n" );
this.write( "\n// End-of-file: " );
this.write( this.currentComponent );
if (this.isImpl)
this.write( "Impl" );
this.write( ".java\n" );
this.close();
}
;
// This rule matches the so-called first action of a component. This preceeds
// the properties and methods, and thus is considered "global" to the abstract
// base implementation.
firstAction
: act:ACTION
{
if (this.isAbstract)
{
this.write( act.getText() );
this.write( "\n" );
}
}
;
// This rule matches a component method declaration.
method
: #(m:METHOD type:IDENT
{
generateMethodJavaDoc(m);
write( " public " + (this.isAbstract ? "abstract " : ""));
write( getQualifiedName( type.getText() ) );
write( " " );
}
(p:methodTypeAttrs)? name:IDENT
{
write( name.getText() );
}
(args)? (methodAttrs)?)
{
if (this.isImpl)
{
write( "\n {\n" );
generateSkeletonReturn( type.getText() );
write( " }\n" );
}
else
{
write( ";\n" );
}
write( "\n" );
}
;
// This rule matches the return type properties list for a method declaration.
methodTypeAttrs
: #(ATTRSROOT name:IDENT value:STRING_LITERAL)
;
// This rule matches the properties list of a method declaration.
methodAttrs
: #(ATTRSROOT name:IDENT value:STRING_LITERAL)
;
// This rule matches the argument list for a method declaration.
args
: #(LPAREN {write("(");} (arg)* {write(")");} )
;
// This rule matches an individual argument in an argument list for a method
// declaration.
arg
: type:IDENT name:IDENT (p:argAttrs)?
{
this.write("\n ");
this.write(getQualifiedName(type.getText()));
this.write(" " );
this.write(name.getText());
if (p != null && p.getNextSibling() != null ||
p == null && name.getNextSibling() != null)
this.write( ",");
}
;
// This rule matches the attributes list for an individual argument in a method
// declaration.
argAttrs
: #(ATTRSROOT name:IDENT value:STRING_LITERAL)
;
// This rule matches an extends clause for a component declaration.
extendsClause: #(EXTENDS i:IDENT)
{
this.currentExtends = i.getText();
}
;
// The rule matches the attributes list for a component declaration.
compAttrs: #(ATTRSROOT (compAttr)*)
;
// The rule matches a single attribute in the attribute list for a component
// declaration. We take this as an opportunity to sniff out attributes of
// interest to the code generator.
compAttr: i:IDENT s:STRING_LITERAL
{
if ("desc".equals(i.getText()))
this.currentComponentDesc = stripQuotes(s.getText());
}
;
// This rule matches a component PROPERTY declaration.
// We use this opportunity to generate property accessors and mutators. This
// is fairly complex, as we generate three different files, with two different
// property classes: singular item, and array.
property
{
this.currentPropertySingularName = "";
this.currentPropertyDesc = "";
}
: #(p:PROPERTY type:IDENT name:IDENT (attrs:propAttrs)* (acts:propActions)? )
{
boolean isIndexed = indexedAccess(p);
boolean isReadOnly = readOnlyAccess(p);
boolean isStatic = staticAccess(p);
String typeName = type.getText();
String baseType = isIndexed ? typeName.substring(0, typeName.length() - 2) : typeName;
String pluralName = name.getText();
String singularName = isIndexed ? pluralName.substring(0, pluralName.length() - 1) : pluralName;
if (this.currentPropertySingularName.length() > 0)
singularName = this.currentPropertySingularName;
if (isIndexed)
generateGetNMethod(baseType, pluralName, acts);
generateAccessorMethod(isIndexed, baseType, isStatic, pluralName, singularName, acts);
if (!isReadOnly)
generateMutatorMethods(isIndexed, baseType, pluralName, singularName, acts);
}
;
// This rule matches a PROPERTY attributes list.
propAttrs: #(ATTRSROOT (propAttr)*)
;
// This rule matches a single PROPERTY attribute.
// We use this opportunity to sniff out some attributes of interest to the
// code generator.
propAttr: i:IDENT s:STRING_LITERAL
{
if ("desc".equals(i.getText()))
this.currentPropertyDesc = stripQuotes(s.getText());
else if ("singularName".equals(i.getText()))
this.currentPropertySingularName = stripQuotes(s.getText());
}
;
// This rule matches a sequence of actions.
propActions
:
(propAction)+
;
// This rule matches a particular action.
propAction
: ACTION_GET
| ACTION_GETN
| ACTION_SET
| ACTION_REMOVE
| ACTION_APPEND
;
© 2015 - 2025 Weber Informatics LLC | Privacy Policy