| /* |
| * Copyright 1999-2004 The Apache Software Foundation. |
| * |
| * 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.apache.cocoon.components.flow.javascript.fom; |
| |
| import java.io.BufferedReader; |
| import java.io.ByteArrayOutputStream; |
| import java.io.CharArrayReader; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.StringTokenizer; |
| |
| import org.apache.avalon.framework.configuration.Configuration; |
| import org.apache.avalon.framework.configuration.ConfigurationException; |
| import org.apache.avalon.framework.logger.AbstractLogEnabled; |
| import org.apache.avalon.framework.service.ServiceException; |
| import org.apache.avalon.framework.service.ServiceManager; |
| import org.apache.avalon.framework.service.Serviceable; |
| import org.apache.excalibur.source.Source; |
| import org.apache.excalibur.source.SourceResolver; |
| import org.apache.cocoon.matching.helpers.WildcardHelper; |
| |
| |
| /** |
| * <p> |
| * The <code>JavaScriptAspectWeaver</code> provides functionality to intercept |
| * JavaScript functions |
| * </p> |
| * <p> |
| * Known restrictions, to be implemented, open questions:<br/> |
| * <ul> |
| * <li>Add interception support for scripts loaded by |
| * <code>"cocoon.load( uri, aspectFiles[] )"</code> |
| * <br/> |
| * So we have to find the best solution to provide access to a configured |
| * <code>JavaScriptAspectWeaver</code> within the cocoon.load |
| * method. Possible solutions: |
| * <ul> |
| * <li>Convert the <code>JavaScriptAspectWeaver</code> to a |
| * regular Avalon component which is configureable itself.</li> |
| * <li>Add the <code>JavaScriptAspectWeaver</code> to the |
| * <code>setup()</code> method of <code>AO_FOM_Cocoon</code></li> |
| * </ul> |
| * (RP) I prefer the second possibility to avoid another component |
| * </li> |
| * <li>the result script should be pretty printed |
| * and put into the directory of the base script |
| * if file protocol is used |
| * -->enables easier debugging</li> |
| * <li>after() interceptions have to be added in reverse order, haven't they?</li> |
| * <li>add support for object property functions</li> |
| * <li>how to deal with more than one around interception?</li> |
| * <li>if applied scripts change they are not reloaded (a change in |
| the base script is necessary)</li> |
| * <li>add syntax check for base script</li> |
| * <li>add syntax check for result scripts</li> |
| * <li>add syntax check for interception definitions</li> |
| * <li>pass the calling function name to the interception scripts</li> |
| * <li>review the naming of all classes and methods</li> |
| * <li>What's the purpose of the arguments in continueExecution(arguments)? |
| * See Stefano's proposal?</li> |
| * </ul> |
| * |
| * @author <a href="mailto:reinhard@apache.org">Reinhard Pötz</a> |
| * @since Sept, 2003 |
| * @version CVS $Id: JavaScriptAspectWeaver.java,v 1.11 2004/03/05 10:07:25 bdelacretaz Exp $ |
| */ |
| public class JavaScriptAspectWeaver extends AbstractLogEnabled implements Serviceable { |
| |
| /** All Interceptors in the right order */ |
| ArrayList interceptorGroups = new ArrayList(); |
| |
| /** The service manager */ |
| ServiceManager manager = null; |
| |
| /** If debugging is true, the intercepted script is writen to filesystem */ |
| boolean serializeResultScript = false; |
| |
| /** Base script <code>org.apache.excalibur.source.Source</code>*/ |
| Source source = null; |
| |
| /** The javascript repsented in <code>JSToken</code>*/ |
| JSTokenList baseScriptTokenList = null; |
| |
| /** Does this base script contain applied scripts? */ |
| boolean areScriptsApplied = false; |
| |
| /** Provided configuration (part of the Interpreter configuration) **/ |
| ArrayList stopExecutionFunctions = null; |
| |
| /** |
| * Set the base script (the script which is scanned for applied |
| * intercepting scripts) and if scripts are applied those are |
| * scanned the code is added to <code>interceptorGroups</code> |
| */ |
| public void setBaseScript( Source source ) throws Exception { |
| |
| this.source = source; |
| |
| // create tokens for javascript code |
| this.baseScriptTokenList = JSParser.parse( |
| readSourceIntoCharArray( source.getInputStream() ) ); |
| |
| // read out all interceptor sources |
| List scriptsApplied = baseScriptTokenList.getScriptsApplied(); |
| |
| // set all interceptors |
| for( int i = 0; i < scriptsApplied.size(); i++ ) { |
| this.addInterceptorGroup( (String) scriptsApplied.get(i) ); |
| areScriptsApplied = true; |
| } |
| } |
| |
| /** |
| * Get the intercepted base script (all interceptions found in |
| * cocoon.apply(..) are added to the script. |
| */ |
| public Reader getInterceptedScriptAsReader() throws Exception { |
| |
| // comment out all cocoon.apply(..) parts |
| this.baseScriptTokenList.commentScriptsApplied(); |
| |
| // add interception events to make parsing easier |
| this.baseScriptTokenList.addInterceptionEvents( this.stopExecutionFunctions ); |
| |
| // replace return statements with variable defintions and put |
| // it to the end of the function |
| this.baseScriptTokenList.replaceReturnStatements(); |
| |
| // add the interceptions |
| this.baseScriptTokenList.addInterceptions( this.interceptorGroups ); |
| |
| // pretty print script |
| // TODO tbd |
| |
| // logging |
| this.getLogger().info( "\n\n" + this.baseScriptTokenList.toString() + "\n" ); |
| |
| // create a file to make debugging easier |
| if( serializeResultScript ) { |
| this.baseScriptTokenList.writeToFile( this.source ); |
| } |
| |
| // return the intercepted script |
| return this.baseScriptTokenList.getScriptAsReader(); |
| } |
| |
| /** |
| * Add a group of interceptor tokens to the AspectWeaver. Note that the |
| * order is important if more interceptions match. (The first applied |
| * script is added first, ...) |
| */ |
| protected void addInterceptorGroup( String source ) |
| throws Exception { |
| |
| this.getLogger().info( "applied script: " + source ); |
| SourceResolver resolver = (SourceResolver)this.manager.lookup(SourceResolver.ROLE); |
| Source src = resolver.resolveURI(source); |
| JSTokenList interceptorsTokensList; |
| try { |
| interceptorsTokensList = JSParser.parse( |
| readSourceIntoCharArray( src.getInputStream() ) ); |
| } finally { |
| resolver.release(src); |
| this.manager.release(resolver); |
| } |
| |
| InterceptionList interceptors = interceptorsTokensList.readInterceptionTokens(); |
| interceptors.setSourceScript( source ); |
| for( int i = 0; i < interceptors.size(); i++ ) { |
| Interceptor interceptor = (Interceptor) interceptors.get( i ); |
| // TODO logging here |
| this.getLogger().info( "added Interceptor name[" + interceptor.getName() + |
| "], type[" + interceptor.getType() + "] value[" + new String(interceptor.stream()) + "]" ); |
| } |
| interceptorGroups.add( interceptors ); |
| } |
| |
| /** |
| * Should the JavaScriptAspectWeaver write the result script |
| * into separate file in the same directory as the basescript? |
| */ |
| public void setSerializeResultScriptParam( boolean serialize ) { |
| this.serializeResultScript = serialize; |
| } |
| |
| /** |
| * Provide configuration (part of the Interpreter configuration) |
| */ |
| public void setStopExecutionFunctionsConf( Configuration conf ) throws ConfigurationException { |
| this.stopExecutionFunctions = new ArrayList(); |
| Configuration stopExecutionFunctionsConf[] = conf.getChildren(); |
| for( int i = 0; i < stopExecutionFunctionsConf.length; i++ ) { |
| stopExecutionFunctions.add( stopExecutionFunctionsConf[i].getValue() ); |
| } |
| } |
| |
| /** |
| * Reset the variable containing all interceptions |
| * |
| */ |
| protected void clearInterceptorGroups() { |
| interceptorGroups.clear(); |
| } |
| |
| /** |
| * Convert an input stream into an array of <code>char</code> |
| */ |
| public static char[] readSourceIntoCharArray( InputStream is ) |
| throws IOException { |
| |
| BufferedReader br = new BufferedReader( new InputStreamReader(is) ); |
| char[] b = new char[1024]; |
| int n; |
| char charComplete[] = new char[0]; |
| |
| while( (n = br.read(b)) > 0) { |
| char copy[] = new char[charComplete.length + n]; |
| System.arraycopy( charComplete, 0, copy, 0, charComplete.length); |
| System.arraycopy( b, 0, copy, charComplete.length, n ); |
| charComplete = copy; |
| } |
| return charComplete; |
| } |
| |
| public boolean areScriptsApplied() { |
| return this.areScriptsApplied; |
| } |
| |
| /** |
| * parse JavaScript and get a <code>JSTokenList</code> |
| */ |
| static class JSParser { |
| |
| public static int SPACES_FOR_TAB = 4; |
| |
| public static int AT_START = 0; |
| public static int IN_COMMENT = 1; |
| public static int IN_LINECOMMENT = 2; |
| public static int IN_LF = 3; |
| public static int IN_WHITESPACE = 4; |
| public static int IN_CODE = 5; |
| public static int IN_CODE_LIT = 6; |
| public static int IN_DOUBLE_QUOTES = 7; |
| public static int IN_SINGLE_QUOTES = 8; |
| public static int IN_REGEXP = 9; |
| |
| /** |
| * Parse an array of <code>char</code> and get a list |
| * of <code>JSToken</code> objects within a <code>JSTokensList</code> |
| */ |
| public static JSTokenList parse( char[] c ) { |
| int parsingState = AT_START; |
| JSTokenList tokenList = new JSTokenList(); |
| JSToken curToken = null; |
| |
| // add start token |
| // tokenList.add( new JSToken( JSToken.START ) ); |
| |
| // parse char array and create a list with all tokens |
| for( int i = 0; i < c.length; i++ ) { |
| char thisChar = c[i]; |
| char nextChar = 0; |
| if( i < c.length - 1 ) nextChar = c[i+1]; |
| |
| if( parsingState == IN_COMMENT ) { |
| if( thisChar == '*' && nextChar == '/' ) { |
| curToken.append('*').append('/'); |
| tokenList.add( curToken.getClone() ); |
| parsingState = AT_START; |
| i++; |
| continue; |
| } |
| else { |
| curToken.append( thisChar ); |
| continue; |
| } |
| } |
| else if( parsingState == IN_LINECOMMENT ) { |
| if( thisChar == '\n' ) { |
| curToken.append( thisChar ); |
| if( nextChar == '\r' ) { |
| // don't append win LF characters |
| i++; |
| } |
| tokenList.add( curToken.getClone() ); |
| parsingState = AT_START; |
| } |
| else { |
| curToken.append( thisChar ); |
| } |
| } |
| else if( parsingState == IN_CODE ) { |
| if(! isJSIdentifierPartCharacter( thisChar ) ) { |
| tokenList.add( curToken.getClone() ); |
| parsingState = AT_START; |
| } |
| else { |
| curToken.append( thisChar ); |
| } |
| } |
| |
| else if( parsingState == IN_WHITESPACE ) { |
| if( thisChar != ' ' || thisChar != '\t' ) { |
| tokenList.add( curToken.getClone() ); |
| parsingState = AT_START; |
| } |
| else if( thisChar == ' ' ) { |
| curToken.append( thisChar ); |
| } |
| else if( thisChar == '\t' ) { |
| for( int count = 1; count <= SPACES_FOR_TAB; count++ ) { |
| curToken.append( ' ' ); |
| } |
| } |
| } |
| else if( parsingState == IN_SINGLE_QUOTES ) { |
| curToken.append( thisChar ); |
| if( thisChar == '\'' ) { |
| tokenList.add( curToken.getClone() ); |
| parsingState = AT_START; |
| continue; |
| } |
| } |
| else if( parsingState == IN_DOUBLE_QUOTES ) { |
| curToken.append( thisChar ); |
| if( thisChar == '\"' ) { |
| tokenList.add( curToken.getClone() ); |
| parsingState = AT_START; |
| continue; |
| } |
| } |
| else if( parsingState == IN_REGEXP ) { |
| curToken.append( thisChar ); |
| if( thisChar == '/' ) { |
| tokenList.add( curToken.getClone() ); |
| parsingState = AT_START; |
| continue; |
| } |
| } |
| |
| // at start or token has been finished |
| if( parsingState == AT_START ) { |
| // check for comments |
| if( thisChar == '/' && nextChar == '*' ) { |
| JSToken t = new JSToken( JSToken.COMMENT ); |
| curToken = t.append('/').append('*'); |
| parsingState = IN_COMMENT; |
| i++; |
| continue; |
| } |
| else if( thisChar == '/' && nextChar == '/' ) { |
| JSToken t = new JSToken( JSToken.LINE_COMMENT ); |
| curToken = t.append( thisChar ).append( nextChar ); |
| parsingState = IN_LINECOMMENT; |
| i++; |
| continue; |
| } |
| else if( isJSIdentifierStartCharacter( thisChar ) ) { |
| JSToken t = new JSToken( JSToken.CODE ); |
| curToken = t.append( thisChar ); |
| parsingState = IN_CODE; |
| continue; |
| } |
| else if( thisChar == ' ' ) { |
| JSToken t = new JSToken( JSToken.WHITESPACE ); |
| curToken = t.append( thisChar ); |
| parsingState = IN_WHITESPACE; |
| continue; |
| } |
| else if( thisChar == '\t' ) { |
| JSToken t = new JSToken( JSToken.WHITESPACE ); |
| curToken = t; |
| for( int count = 1; count <= SPACES_FOR_TAB; count++ ) { |
| curToken.append( ' ' ); |
| } |
| parsingState = IN_WHITESPACE; |
| continue; |
| } |
| else if( thisChar == '\"' ) { |
| JSToken t = new JSToken( JSToken.CODE_LITERAL ); |
| curToken = t.append( thisChar ); |
| parsingState = IN_DOUBLE_QUOTES; |
| continue; |
| } |
| else if( thisChar == '\'' ) { |
| JSToken t = new JSToken( JSToken.CODE_LITERAL ); |
| curToken = t.append( thisChar ); |
| parsingState = IN_SINGLE_QUOTES; |
| continue; |
| } |
| else if( thisChar == '\r' ) { |
| // jump over win LFs |
| parsingState = AT_START; |
| continue; |
| } |
| else if( thisChar == '\n' ) { |
| JSToken t = new JSToken( JSToken.LF ); |
| curToken = t.append( thisChar ); |
| if( nextChar == '\r' ) { |
| // t.append( nextChar ); |
| i++; |
| } |
| tokenList.add( t.getClone() ); |
| parsingState = AT_START; |
| continue; |
| } |
| else if( thisChar == '/' ) { |
| JSToken t = new JSToken( JSToken.REGEXP ); |
| curToken = t.append( thisChar ); |
| parsingState = IN_REGEXP; |
| continue; |
| } |
| |
| // single character strings |
| else if( thisChar == '(' ) { |
| JSToken t = new JSToken( JSToken.BRACKET_LEFT ); |
| tokenList.add( t.append( thisChar ) ); |
| parsingState = AT_START; |
| continue; |
| } |
| else if( thisChar == ')' ) { |
| JSToken t = new JSToken( JSToken.BRACKET_RIGHT ); |
| tokenList.add( t.append( thisChar ) ); |
| parsingState = AT_START; |
| continue; |
| } |
| else if( thisChar == '[' ) { |
| JSToken t = new JSToken( JSToken.BRACKET1_LEFT ); |
| tokenList.add( t.append( thisChar ) ); |
| parsingState = AT_START; |
| continue; |
| } |
| else if( thisChar == ']' ) { |
| JSToken t = new JSToken( JSToken.BRACKET1_RIGHT ); |
| curToken = t.append( thisChar ); |
| parsingState = AT_START; |
| continue; |
| } |
| else if( thisChar == '{' ) { |
| JSToken t = new JSToken( JSToken.BRACKET2_LEFT ); |
| tokenList.add( t.append( thisChar ) ); |
| parsingState = AT_START; |
| continue; |
| } |
| else if( thisChar == '}' ) { |
| JSToken t = new JSToken( JSToken.BRACKET2_RIGHT ); |
| tokenList.add( t.append( thisChar ) ); |
| parsingState = AT_START; |
| continue; |
| } |
| else if( thisChar == '.' ) { |
| JSToken t = new JSToken( JSToken.POINT ); |
| tokenList.add( t.append( thisChar ) ); |
| parsingState = AT_START; |
| continue; |
| } |
| else if( thisChar == ';' ) { |
| JSToken t = new JSToken( JSToken.SEMICOLON ); |
| tokenList.add( t.append( thisChar ) ); |
| parsingState = AT_START; |
| continue; |
| } |
| else if( thisChar == '=' ) { |
| JSToken t = new JSToken( JSToken.EQUAL_SIGN ); |
| tokenList.add( t.append( thisChar ) ); |
| parsingState = AT_START; |
| continue; |
| } |
| else if( thisChar == ',' ) { |
| JSToken t = new JSToken( JSToken.COMMA ); |
| tokenList.add( t.append( thisChar ) ); |
| parsingState = AT_START; |
| continue; |
| } |
| else if( thisChar == ':' ) { |
| JSToken t = new JSToken( JSToken.COLON ); |
| tokenList.add( t.append( thisChar ) ); |
| parsingState = AT_START; |
| continue; |
| } |
| else if( thisChar == '*' ) { |
| JSToken t = new JSToken( JSToken.ASTERISK ); |
| tokenList.add( t.append( thisChar ) ); |
| parsingState = AT_START; |
| continue; |
| } |
| else { |
| JSToken t = new JSToken( JSToken.UNKNOWN ); |
| tokenList.add( t.append( thisChar ) ); |
| parsingState = AT_START; |
| continue; |
| } |
| |
| } |
| } |
| return tokenList; |
| } |
| |
| /** |
| * Check if a character is a valid JavaScript identifier |
| * character. |
| * |
| * TODO implement it according the ECMA spec |
| */ |
| private static boolean isJSIdentifierPartCharacter( char c ) { |
| if( Character.isJavaIdentifierPart( c ) && |
| c != '(' && |
| c != ')' && |
| c != '[' && |
| c != ']' && |
| c != '{' && |
| c != '}') { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Check if a character is a valid *starting* JavaScript identifier |
| * character. |
| * |
| * TODO implement it according the ECMA spec |
| */ |
| private static boolean isJSIdentifierStartCharacter( char c ) { |
| if( Character.isJavaIdentifierStart( c ) ) return true; |
| return false; |
| } |
| |
| } |
| |
| /** |
| * A JavaScript file represented as linked list of tokens. It |
| * has serveral methods implemented to get information (e.g. names |
| * of scripts applied) or to change the content (e.g. comment out all |
| * occurencies of <code>cocoon.apply( .. )</code>) |
| */ |
| static class JSTokenList extends LinkedList { |
| |
| public static String RETURN_VARIABLE = "____interceptionReturn____"; |
| |
| /** |
| * Token ids of all cocoon object occurencies followed by '.apply' |
| * ( cocoon.apply( "bla" ); ) |
| */ |
| ArrayList cocoonPositions = new ArrayList(); |
| |
| /** |
| * List of all functions that lead to new continuations |
| */ |
| List stopExecutionFunctions = null; |
| |
| |
| /** |
| * Add the code fragement of the passed interceptions at the right places |
| */ |
| protected void addInterceptions( ArrayList interceptionsList ) { |
| |
| ListIterator li = this.listIterator(); |
| boolean inAround = false; |
| while( li.hasNext() ) { |
| Object o = li.next(); |
| // remove all tokens if there is an "AROUND" interceptors |
| if( inAround ) { |
| li.remove(); |
| } |
| if( o instanceof InterceptorEvent ) { |
| InterceptorEvent ie = (InterceptorEvent) o; |
| int type = ie.getType(); |
| if( type == InterceptorEvent.FNC_START ) { |
| // add all before interceptors |
| addInterceptionTokens( interceptionsList, |
| ie.getName(), |
| Interceptor.STR_BEFORE, |
| li ); |
| // add all around interceptors |
| inAround = addInterceptionTokens( interceptionsList, |
| ie.getName(), |
| Interceptor.STR_AROUND, |
| li ); |
| } |
| else if( type == InterceptorEvent.FNC_END ) { |
| // add all after interceptors |
| addInterceptionTokens( interceptionsList, |
| ie.getName(), |
| Interceptor.STR_AFTER, |
| li ); |
| inAround = false; |
| } |
| else if( type == InterceptorEvent.CONT_EXEC ) { |
| addInterceptionTokens( interceptionsList, |
| ie.getName(), |
| Interceptor.STR_CONT_EXEC, |
| li ); |
| } |
| else if( type == InterceptorEvent.STOP_EXEC ) { |
| addInterceptionTokens( interceptionsList, |
| ie.getName(), |
| Interceptor.STR_STOP_EXEC, |
| li ); |
| } |
| } |
| } |
| } |
| |
| public void writeToFile( Source source ) throws Exception { |
| if( source.getScheme().equals( "file" ) ) { |
| String filename = source.getURI().substring("file:/".length() ) + |
| AO_FOM_JavaScriptInterpreter.INTERCEPTION_POSTFIX; |
| FileOutputStream fos = new FileOutputStream( filename ); |
| ByteArrayOutputStream bs = new ByteArrayOutputStream(); |
| char completeChar[] = this.getScriptAsCharArray(); |
| for( int i = 0; i < completeChar.length; i++ ) { |
| bs.write( completeChar[i] ); |
| } |
| fos.write( bs.toByteArray() ); |
| fos.close(); |
| } |
| } |
| |
| /** |
| * Add all tokens in the correct order |
| * |
| * @param interceptionGroupList - sorted list of all available interceptions |
| * @param functionName - name of the intercepted function |
| * @param eventType - event type |
| * @param tokensListIt - ListIterator where the new tokens found |
| * in the interceptionsList are added |
| * @return true if the those elements replace other JSTokens |
| */ |
| private boolean addInterceptionTokens( ArrayList interceptionGroupList, |
| String functionName, |
| String eventType, |
| ListIterator tokensListIt |
| ) { |
| |
| JSToken t = new JSToken( JSToken.COMMENT ); |
| ArrayList matchingInterceptors = new ArrayList(); |
| |
| // read out all matching interceptions from interceptionsList |
| for( int i = 0; i < interceptionGroupList.size(); i++ ) { |
| InterceptionList interceptionList = (InterceptionList) interceptionGroupList.get(i); |
| ListIterator li = interceptionList.listIterator(); |
| while( li.hasNext() ) { |
| Interceptor interceptor = (Interceptor) li.next(); |
| interceptor.setBaseScript( interceptionList.getSourceScript() ); |
| boolean success = WildcardHelper.match( |
| new HashMap(), functionName + Interceptor.DELIMITER + eventType, |
| WildcardHelper.compilePattern( interceptor.getName() )); |
| if( success ) { |
| matchingInterceptors.add( interceptor ); |
| } |
| } |
| } |
| if( matchingInterceptors.size() > 0 ) { |
| // add a comment showing the interception type |
| t.append( "\n\n/* " + eventType + "():" + " */\n" ); |
| tokensListIt.add( t); |
| ListIterator ili = matchingInterceptors.listIterator(); |
| while( ili.hasNext() ) { |
| Interceptor interceptor = (Interceptor) ili.next(); |
| // add a comment where the interception is defined |
| t = new JSToken( JSToken.COMMENT ); |
| t.append( "\n// interception from: " + interceptor.getName() + " [" + interceptor.getBaseScript() + "]\n" ); |
| tokensListIt.add( t.getClone() ); |
| // add all tokens that the interception definition contains |
| ListIterator interceptorTokensIt = interceptor.getTokens().listIterator(); |
| while( interceptorTokensIt.hasNext() ) { |
| tokensListIt.add( interceptorTokensIt.next() ); |
| } |
| } |
| // end comment |
| t = new JSToken( JSToken.COMMENT ); |
| t.append( "\n/* end " + eventType + "():" + " */\n\n" ); |
| tokensListIt.add( t.getClone() ); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| |
| /** |
| * help method to get a function name |
| * |
| * TODO add support for property functions |
| */ |
| private String getFunctionName( int functionPosition ) { |
| String functionName = ""; |
| boolean foundName = false; |
| ListIterator li = this.listIterator( functionPosition ); |
| |
| // search for 'function myFunc(..) {' |
| while( li.hasNext() && ! foundName ) { |
| JSToken t = (JSToken) li.next(); |
| if( t.getType() == JSToken.CODE ) { |
| functionName = t.toString(); |
| foundName = true; |
| } |
| else if( t.getType() == JSToken.BRACKET_LEFT ) { |
| break; |
| } |
| } |
| |
| // search for 'x.prototype.myFunc = function(args) {' |
| /*if(! foundName ) { |
| not implemented yet --> do we need it? |
| }*/ |
| return functionName; |
| } |
| |
| /** |
| * Get a list of all occurencies of "function" |
| */ |
| private List getFunctionPositions() { |
| ArrayList functionPosition = new ArrayList(); |
| ListIterator li = this.listIterator(); |
| while( li.hasNext() ) { |
| JSToken t = (JSToken) li.next(); |
| if( t.getType() == JSToken.CODE && t.equals( "function" ) ) { |
| functionPosition.add( new Integer(li.nextIndex() - 1 ) ); |
| } |
| } |
| return functionPosition; |
| } |
| |
| /** |
| * Check a token if it is function call that leads to a stop of |
| * execution (continuation is created) |
| */ |
| private String isStopFunction( JSToken curToken, ListIterator liTokens ) { |
| JSToken n1token = (JSToken) liTokens.next(); |
| JSToken n2token = (JSToken) liTokens.next(); |
| liTokens.previous(); |
| liTokens.previous(); |
| |
| ListIterator li = this.stopExecutionFunctions.listIterator(); |
| while( li.hasNext() ) { |
| String object = ""; |
| String function = ""; |
| StringTokenizer st = new StringTokenizer( (String) li.next(), "."); |
| int countTokens = st.countTokens(); |
| |
| if( countTokens == 1 ) { |
| function = st.nextToken(); |
| function = function.substring( 0, function.indexOf("(") ); |
| if( function.equals( curToken.toString() ) ) return function; |
| } |
| else if( countTokens == 2 ) { |
| object = st.nextToken(); |
| function = st.nextToken(); |
| function = function.substring( 0, function.indexOf("(") ); |
| if( object.equals( curToken.toString() ) && |
| n1token.getType() == JSToken.POINT && |
| function.equals( n2token.toString() ) ) { |
| return object + "." + function; |
| } |
| } |
| else { |
| // not allowed! |
| } |
| |
| } |
| return null; |
| } |
| |
| |
| /** |
| * to make intercepting easier events are added (start function, |
| * stop function, stop execution, continue execution) |
| */ |
| protected void addInterceptionEvents( List stopExecutionFunctions ) { |
| |
| this.stopExecutionFunctions = stopExecutionFunctions; |
| |
| List functionPositions = this.getFunctionPositions(); |
| // count all added interceptor events to jump into the right |
| // position of the tokens list |
| |
| int diff = 1; |
| // loop over all functions |
| for( int i = 0; i < functionPositions.size(); i++ ) { |
| int pos = ((Integer) functionPositions.get( i )).intValue(); |
| String functionName = getFunctionName( pos + diff ); |
| ListIterator li = this.listIterator( pos + diff ); |
| boolean functionStartSet = false; |
| boolean isExecStopFunctionSet = false; |
| int countOpenBrackets = 0; |
| int countCurToken = pos - 1; // get the current index |
| |
| // continue in the token list to find opening and closing brackets |
| while( li.hasNext() ) { |
| countCurToken++; |
| JSToken t = (JSToken) li.next(); |
| int type = t.getType(); |
| // function start is found |
| if( type == JSToken.BRACKET2_LEFT && ! functionStartSet ) { |
| li.add( new InterceptorEvent( InterceptorEvent.FNC_START, |
| functionName ) ); |
| diff++; |
| functionStartSet = true; |
| continue; |
| } |
| // within a function |
| if( functionStartSet ) { |
| // end of a function is reached |
| if( type == JSToken.BRACKET2_RIGHT && countOpenBrackets == 0 ) { |
| li.previous(); |
| li.add( new InterceptorEvent( InterceptorEvent.FNC_END, |
| functionName ) ); |
| diff++; |
| break; |
| } |
| // count brackets |
| if( type == JSToken.BRACKET2_LEFT ) { |
| countOpenBrackets++; |
| } |
| else if ( type == JSToken.BRACKET2_RIGHT ) { |
| countOpenBrackets--; |
| } |
| // end of a continuation creating function reached |
| else if( isExecStopFunctionSet ) { |
| if( type == JSToken.SEMICOLON ) { |
| li.add( new InterceptorEvent( |
| InterceptorEvent.CONT_EXEC, |
| functionName )); |
| diff++; |
| isExecStopFunctionSet = false; |
| } |
| continue; |
| } |
| // check if a function is reached that creates continuations |
| else if( type == JSToken.CODE ) { |
| String fnc = isStopFunction( t, li ); |
| if( null != fnc ) { |
| li.previous(); |
| li.add( new InterceptorEvent( InterceptorEvent.STOP_EXEC, |
| functionName )); |
| diff++; |
| isExecStopFunctionSet = true; |
| continue; |
| } |
| } |
| } |
| } // end while |
| } // end for |
| } // end method |
| |
| /** |
| * Get a list of sources as string (the src attribute of the |
| * cocoon.apply( src) function) of all scripts to apply |
| */ |
| protected List getScriptsApplied() { |
| ArrayList scriptsApplied = new ArrayList(); |
| boolean foundCocoon = false; |
| boolean foundApply = false; |
| |
| int curCocoonPosition = 0; |
| |
| ListIterator li = this.listIterator(); |
| while( li.hasNext() ) { |
| JSToken t = (JSToken) li.next(); |
| if( t.getType() == JSToken.CODE ) { |
| if( t.equals( "cocoon") ) { |
| foundCocoon = true; |
| curCocoonPosition = li.previousIndex() + 1; |
| } |
| else if( foundCocoon && t.equals( "apply") ) { |
| foundApply = true; |
| } |
| else { |
| foundCocoon = false; |
| foundApply = false; |
| curCocoonPosition = 0; |
| } |
| } |
| else if( foundApply && t.getType() == JSToken.CODE_LITERAL ) { |
| String script = t.toString(); |
| script = script.substring( 1, script.length() - 1 ); |
| scriptsApplied.add( script ); |
| cocoonPositions.add( new Integer(curCocoonPosition) ); |
| foundCocoon = false; |
| foundApply = false; |
| } |
| } |
| |
| return scriptsApplied; |
| } |
| |
| /** |
| * comment out all occurencies of cocoon.apply( "..." ); |
| */ |
| protected void commentScriptsApplied() { |
| int diff = -1; |
| for( int i = 0; i < this.cocoonPositions.size(); i++ ) { |
| int pos = ((Integer) this.cocoonPositions.get(i)).intValue(); |
| ListIterator li = this.listIterator(pos + diff); |
| JSToken t = new JSToken( JSToken.COMMENT ); |
| t.append("/* -> not needed in result script: "); |
| li.add( t ); |
| diff++; |
| while( li.hasNext() ) { |
| JSToken tt = (JSToken) li.next(); |
| t.append(tt.stream()); |
| li.remove(); |
| diff--; |
| if( tt.getType() == JSToken.SEMICOLON ) { |
| break; |
| } |
| } |
| t.append('*').append('/'); |
| } |
| } |
| |
| /** |
| * Replace all occurencies of return (except those which |
| * are in separte code blocks) |
| */ |
| protected void replaceReturnStatements() { |
| ListIterator li = this.listIterator(); |
| int countOpenBrackets = 0; |
| boolean inFunction = false; |
| boolean foundReturnStatement = false; |
| while( li.hasNext() ) { |
| JSToken t = (JSToken) li.next(); |
| int type = t.getType(); |
| if( t instanceof InterceptorEvent ) { |
| InterceptorEvent ie =(InterceptorEvent) t; |
| int ieType = ie.getType(); |
| if( ieType == InterceptorEvent.FNC_START ) { |
| inFunction = true; |
| foundReturnStatement = false; |
| } |
| else if( ieType == InterceptorEvent.FNC_END ) { |
| if( foundReturnStatement ) { |
| li.add( new JSToken( JSToken.COMMENT, "/* moved return statement */" )); |
| li.add( new JSToken( JSToken.LF, "\n" )); |
| li.add( new JSToken( JSToken.CODE, "return" )); |
| li.add( new JSToken( JSToken.WHITESPACE, " " )); |
| li.add( new JSToken( JSToken.CODE, RETURN_VARIABLE )); |
| li.add( new JSToken( JSToken.SEMICOLON, ";" )); |
| li.add( new JSToken( JSToken.LF, "\n" )); |
| } |
| inFunction = false; |
| } |
| } |
| else if( inFunction ) { |
| if( type == JSToken.BRACKET2_LEFT ) { |
| countOpenBrackets++; |
| } |
| else if( type == JSToken.BRACKET2_RIGHT ) { |
| countOpenBrackets--; |
| } |
| else if( countOpenBrackets == 0 && |
| type == JSToken.CODE && |
| t.equals( "return") ) { |
| |
| li.remove(); |
| li.add( new JSToken( JSToken.CODE, "var" )); |
| li.add( new JSToken( JSToken.WHITESPACE, " " )); |
| li.add( new JSToken( JSToken.CODE, RETURN_VARIABLE )); |
| li.add( new JSToken( JSToken.WHITESPACE, " " )); |
| li.add( new JSToken( JSToken.EQUAL_SIGN, "=" )); |
| foundReturnStatement = true; |
| } |
| } |
| } |
| } |
| |
| // ------------ TokenList of appield files --------------------------- |
| |
| /** |
| * help method to read an intercepting function name - the difference |
| * to getFunctionName( pos ) is the wildcards "*" |
| */ |
| private String getInterceptorFunctionName( int functionPosition ) { |
| StringBuffer functionName = new StringBuffer(); |
| boolean partOfFunctionFound = false; |
| ListIterator li = this.listIterator( functionPosition + 1 ); |
| while( li.hasNext() ) { |
| JSToken t = (JSToken) li.next(); |
| int type = t.getType(); |
| // search for any combination of code and asterisks |
| if( type == JSToken.CODE || type == JSToken.ASTERISK ) { |
| functionName.append( t.stream() ); |
| partOfFunctionFound = true; |
| } |
| else if( partOfFunctionFound ) { |
| break; |
| } |
| } |
| return functionName.toString(); |
| } |
| |
| /** |
| * Get a list of all defined interceptors. Search for: |
| * <ul> |
| * <li>before(): { ... }</li> |
| * <li>after(): { ... }</li> |
| * <li>around(): { ... }</li> |
| * </ul> |
| */ |
| protected InterceptionList readInterceptionTokens() { |
| InterceptionList interceptors = new InterceptionList(); |
| List functionPositions = this.getFunctionPositions(); |
| // loop over all intercepting functions |
| for( int i = 0; i < functionPositions.size(); i++ ) { |
| int pos = ((Integer) functionPositions.get( i )).intValue(); |
| String interceptingFunctionName = getInterceptorFunctionName( pos ); |
| |
| boolean inFunction = false; |
| boolean inInterceptor = false; |
| Interceptor curInterceptor = null; |
| String interceptorType = ""; |
| int countOpenBrackets = 0; |
| ListIterator li = this.listIterator( pos + 2 ); |
| while( li.hasNext() ) { |
| JSToken t = (JSToken) li.next(); |
| int type = t.getType(); |
| // find the start of the function |
| if( type == JSToken.BRACKET2_LEFT && ! inFunction ) { |
| inFunction = true; |
| } |
| // within an intercepting function |
| else if( inFunction ) { |
| // find an interception definition |
| if( !inInterceptor && type == JSToken.CODE ) { |
| |
| if( t.equals( Interceptor.STR_BEFORE ) ) { |
| interceptorType = Interceptor.STR_BEFORE; |
| } |
| else if( t.equals( Interceptor.STR_AFTER ) ) { |
| interceptorType = Interceptor.STR_AFTER; |
| } |
| else if( t.equals( Interceptor.STR_AROUND ) ) { |
| interceptorType = Interceptor.STR_AROUND; |
| } |
| else if( t.equals( Interceptor.STR_STOP_EXEC )) { |
| interceptorType = Interceptor.STR_STOP_EXEC; |
| } |
| else if( t.equals( Interceptor.STR_CONT_EXEC )) { |
| interceptorType = Interceptor.STR_CONT_EXEC; |
| } |
| } |
| // after interception defintion is found |
| else if( !inInterceptor && type == JSToken.BRACKET2_LEFT ) { |
| |
| inInterceptor = true; |
| curInterceptor = new Interceptor( interceptingFunctionName, |
| interceptorType ); |
| countOpenBrackets++; |
| } |
| // and add it to the list |
| else if( inInterceptor && type == JSToken.BRACKET2_RIGHT && |
| countOpenBrackets == 0 ) { |
| |
| } |
| // within intercepting code add all tokens |
| else if( inInterceptor ) { |
| // count brackets |
| if( type == JSToken.BRACKET2_LEFT) { |
| countOpenBrackets++; |
| } |
| else if( type == JSToken.BRACKET2_RIGHT ) { |
| countOpenBrackets--; |
| } |
| // end of intercepting code is reached - clone interception token |
| if( type == JSToken.BRACKET2_RIGHT && countOpenBrackets == 0) { |
| interceptors.add( curInterceptor.getClone() ); |
| inInterceptor = false; |
| interceptorType = ""; |
| curInterceptor = null; |
| } |
| // within intercepting code |
| else { |
| curInterceptor.addToken( t ); |
| } |
| } |
| } |
| } |
| } |
| return interceptors; |
| } |
| |
| // ------------ common methods --------------------------- |
| |
| /** |
| * return the tokens as Reader |
| */ |
| public Reader getScriptAsReader() { |
| return new CharArrayReader( getScriptAsCharArray() ); |
| } |
| |
| public char[] getScriptAsCharArray() { |
| ListIterator li = this.listIterator(); |
| char charComplete[] = new char[0]; |
| while( li.hasNext() ) { |
| JSToken t = (JSToken) li.next(); |
| char theChar[] = t.stream(); |
| char copy[] = new char[charComplete.length + theChar.length]; |
| System.arraycopy( charComplete, 0, copy, 0, charComplete.length); |
| System.arraycopy( theChar, 0, copy, charComplete.length, theChar.length ); |
| charComplete = copy; |
| } |
| return charComplete; |
| } |
| |
| public String toString() { |
| StringBuffer sb = new StringBuffer(); |
| ListIterator li = this.listIterator(); |
| while( li.hasNext() ) { |
| JSToken t = (JSToken) li.next(); |
| sb.append( t.stream() ); |
| } |
| return sb.toString(); |
| } |
| |
| public String debugToString() { |
| StringBuffer sb = new StringBuffer(); |
| ListIterator li = this.listIterator(); |
| while( li.hasNext() ) { |
| JSToken t = (JSToken) li.next(); |
| sb.append("\n------------------------------------\n"); |
| sb.append("type: ").append(t.getType()).append("\n"); |
| sb.append( t.stream() ); |
| } |
| return sb.toString(); |
| } |
| |
| } |
| |
| /** |
| * A collection of interceptors with the possibility to |
| * set the source of the script where they are read from |
| */ |
| static class InterceptionList extends ArrayList { |
| |
| String sourceScript; |
| |
| public void setSourceScript( String source ) { |
| this.sourceScript = source; |
| } |
| |
| public String getSourceScript() { |
| return this.sourceScript; |
| } |
| |
| } |
| |
| /** |
| * A special JSToken - the Interceptor |
| */ |
| static class Interceptor implements Cloneable { |
| |
| public static String STR_BEFORE = "before"; |
| public static String STR_AFTER = "after"; |
| public static String STR_AROUND = "around"; |
| public static String STR_STOP_EXEC = "stopExecution"; |
| public static String STR_CONT_EXEC = "continueExecution"; |
| |
| public static String DELIMITER = ":"; |
| |
| String name; |
| String type; |
| JSTokenList tokens = new JSTokenList(); |
| String baseScript; |
| |
| public Interceptor( String functionName, String type ) { |
| this.name = functionName + DELIMITER + type; |
| this.type = type; |
| } |
| |
| public void setBaseScript(String src) { |
| this.baseScript = src; |
| } |
| |
| public String getBaseScript() { |
| return this.baseScript; |
| } |
| |
| public void addToken( JSToken token ) { |
| tokens.add( token ); |
| } |
| |
| public JSTokenList getTokens() { |
| return this.tokens; |
| } |
| |
| public String getName() { |
| return this.name; |
| } |
| |
| public String getType() { |
| return this.type; |
| } |
| |
| public char[] stream() { |
| char charComplete[] = new char[0]; |
| ListIterator li = this.tokens.listIterator(); |
| while( li.hasNext() ) { |
| JSToken t = (JSToken) li.next(); |
| char theChar[] = t.stream(); |
| char copy[] = new char[charComplete.length + theChar.length]; |
| System.arraycopy( charComplete, 0, copy, 0, charComplete.length); |
| System.arraycopy( theChar, 0, copy, charComplete.length, theChar.length ); |
| charComplete = copy; |
| } |
| return charComplete; |
| } |
| |
| |
| // cloning |
| public Object clone() throws CloneNotSupportedException { |
| return super.clone(); |
| } |
| |
| public Interceptor getClone() { |
| Interceptor interceptor = null; |
| try { |
| interceptor = (Interceptor) this.clone(); |
| } catch (CloneNotSupportedException e) {} |
| return interceptor; |
| } |
| |
| } |
| |
| /** |
| * A JavaScript token like whitespace, code, brackets, ... |
| */ |
| static class JSToken implements Cloneable { |
| |
| public static int START = 0; |
| public static int COMMENT = 1; |
| public static int LINE_COMMENT = 2; |
| public static int LF = 3; |
| public static int WHITESPACE = 4; |
| public static int BRACKET_LEFT = 5; |
| public static int BRACKET_RIGHT = 6; |
| public static int BRACKET1_LEFT = 7; |
| public static int BRACKET1_RIGHT = 8; |
| public static int BRACKET2_LEFT = 9; |
| public static int BRACKET2_RIGHT = 10; |
| public static int CODE = 11; |
| public static int CODE_LITERAL = 12; |
| public static int SEMICOLON = 13; |
| public static int POINT = 14; |
| public static int EQUAL_SIGN = 15; |
| public static int COMMA = 16; |
| public static int COLON = 17; |
| public static int REGEXP = 18; |
| public static int UNKNOWN = 19; |
| public static int ASTERISK = 20; |
| |
| char token[]; |
| int type; |
| |
| public JSToken(int type) { |
| this.type = type; |
| token = new char[0]; |
| } |
| |
| public JSToken( int type, String tokenValue ) { |
| this.type = type; |
| this.token = tokenValue.toCharArray(); |
| } |
| |
| public void setType(int type) { |
| this.type = type; |
| } |
| |
| public int getType() { |
| return this.type; |
| } |
| |
| public boolean equals(String comp) { |
| if ((new String(token)).equals(comp)) { |
| return true; |
| } |
| return false; |
| } |
| |
| public char[] stream() { |
| return token; |
| } |
| |
| public int size() { |
| return token.length; |
| } |
| |
| public JSToken append(char c) { |
| char copy[] = new char[token.length + 1]; |
| System.arraycopy(token, 0, copy, 0, token.length); |
| token = copy; |
| token[token.length - 1] = c; |
| return this; |
| } |
| |
| public JSToken append(char c[]) { |
| char copy[] = new char[token.length + c.length]; |
| System.arraycopy( token, 0, copy, 0, token.length ); |
| System.arraycopy( c, 0, copy, token.length, c.length ); |
| token = copy; |
| return this; |
| } |
| |
| public JSToken append( String str ) { |
| char c[] = str.toCharArray(); |
| this.append(c); |
| return this; |
| } |
| |
| public String toString() { |
| StringBuffer sb = new StringBuffer(); |
| for( int i = 0; i < token.length; i++ ) { |
| sb.append( token[i] ); |
| } |
| return sb.toString(); |
| } |
| |
| // cloning |
| public Object clone() throws CloneNotSupportedException { |
| return super.clone(); |
| } |
| |
| public JSToken getClone() { |
| JSToken t = null; |
| try { |
| t = (JSToken) this.clone(); |
| } catch (CloneNotSupportedException e) {} |
| return t; |
| } |
| |
| } |
| |
| /** |
| * A token representing the start or the end of a function. This |
| * is needed to make adding of the interception code snippets easier. |
| * |
| * It extends JSToken by the function name and two new status. |
| */ |
| static class InterceptorEvent extends JSToken { |
| |
| public static int FNC_START = 51; |
| public static int FNC_END = 52; |
| public static int STOP_EXEC = 53; |
| public static int CONT_EXEC = 54; |
| |
| String interceptorName; |
| int type; |
| |
| public InterceptorEvent( int type ) { |
| super( type ); |
| } |
| |
| public InterceptorEvent( int type, String name ) { |
| super( type ); |
| this.interceptorName = name; |
| } |
| |
| public String getName() { |
| return this.interceptorName; |
| } |
| |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager) |
| */ |
| public void service(ServiceManager manager) throws ServiceException |
| { |
| this.manager = manager; |
| } |
| } |