FragmentInvocationHandler.java

/*
 * Copyright 2007 Rickard Öberg
 * 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.qi4j.runtime.composite;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * JAVADOC
 */
abstract class FragmentInvocationHandler
    implements InvocationHandler
{
    private static final String COMPACT_TRACE = "qi4j.compacttrace";

    private static final CompactLevel compactLevel;

    static
    {
        compactLevel = CompactLevel.valueOf( System.getProperty( COMPACT_TRACE, "proxy" ) );
    }

    protected Object fragment;
    protected Method method;

    void setFragment( Object fragment )
    {
        this.fragment = fragment;
    }

    public void setMethod( Method method )
    {
        this.method = method;
    }

    protected Throwable cleanStackTrace( Throwable throwable, Object proxy, Method method )
    {
        if( compactLevel == CompactLevel.off )
        {
            return throwable;
        }

        StackTraceElement[] trace = throwable.getStackTrace();

        // Check if exception originated within Zest or JDK - if so then skip compaction
        if( trace.length == 0 || !isApplicationClass( trace[ 0 ].getClassName() ) )
        {
            return throwable;
        }

        int count = 0;
        for( int i = 0; i < trace.length; i++ )
        {
            StackTraceElement stackTraceElement = trace[ i ];
            if( !isApplicationClass( stackTraceElement.getClassName() ) )
            {
                // TODO: Should find stack entry outside Runtime, and compact beyond that
                trace[ i ] = null;
                count++;
            }
            else
            {
                boolean classOrigin = stackTraceElement.getClassName().equals( proxy.getClass().getSimpleName() );
                boolean methodOrigin = stackTraceElement.getMethodName().equals( method.getName() );
                if( classOrigin && methodOrigin && compactLevel == CompactLevel.proxy )
                {
                    // Stop removing if the originating method call has been located in the stack.
                    // For 'semi' and 'extensive' compaction, we don't and do the entire stack instead.
                    trace[ i ] = new StackTraceElement( proxy.getClass()
                                                            .getInterfaces()[ 0 ].getName(), method.getName(), null, -1 );
                    break; // Stop compacting this trace
                }
            }
        }

        // Create new trace array
        int idx = 0;
        StackTraceElement[] newTrace = new StackTraceElement[ trace.length - count ];
        for( StackTraceElement stackTraceElement : trace )
        {
            if( stackTraceElement != null )
            {
                newTrace[ idx++ ] = stackTraceElement;
            }
        }
        throwable.setStackTrace( newTrace );

        Throwable nested = throwable.getCause();
        if( nested != null )
        {
            //noinspection ThrowableResultOfMethodCallIgnored
            cleanStackTrace( nested, proxy, method );
        }
        return throwable;
    }

    private boolean isApplicationClass( String className )
    {
        if( compactLevel == CompactLevel.semi )
        {
            return !isJdkInternals( className );
        }
        return !( className.endsWith( FragmentClassLoader.GENERATED_POSTFIX ) ||
                  className.startsWith( "org.qi4j.runtime" ) ||
                  isJdkInternals( className ) );
    }

    private boolean isJdkInternals( String className )
    {
        return className.startsWith( "java.lang.reflect" )
               || className.startsWith( "com.sun.proxy" )
               || className.startsWith( "sun.reflect" );
    }
}