Package org.apache.cocoon.components.flow.javascript.fom

Source Code of org.apache.cocoon.components.flow.javascript.fom.JavaScriptAspectWeaver$InterceptionList

/*
* 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
*      --&gt;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;
    }
}
TOP

Related Classes of org.apache.cocoon.components.flow.javascript.fom.JavaScriptAspectWeaver$InterceptionList

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.