package org.apache.maven.shared.dependency.analyzer.asm;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import java.util.HashSet;
import java.util.Set;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;

/**
 * Computes the set of classes referenced by visited code.
 * Inspired by <code>org.objectweb.asm.depend.DependencyVisitor</code> in the ASM dependencies example.
 * 
 * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
 * @version $Id: DependencyVisitor.java 1174427 2011-09-22 22:25:17Z hboutemy $
 * @see #getClasses()
 */
public class DependencyVisitor
    implements AnnotationVisitor, SignatureVisitor, ClassVisitor, FieldVisitor, MethodVisitor
{
    // fields -----------------------------------------------------------------

    private final Set<String> classes;

    // constructors -----------------------------------------------------------

    public DependencyVisitor()
    {
        classes = new HashSet<String>();
    }

    // ClassVisitor methods ---------------------------------------------------

    /*
     * @see org.objectweb.asm.ClassVisitor#visit(int, int, java.lang.String, java.lang.String, java.lang.String,
     *      java.lang.String[])
     */
    public void visit( final int version, final int access, final String name, final String signature,
                       final String superName, final String[] interfaces )
    {
        if ( signature == null )
        {
            addName( superName );
            addNames( interfaces );
        }
        else
        {
            addSignature( signature );
        }
    }

    /*
     * @see org.objectweb.asm.ClassVisitor#visitSource(java.lang.String, java.lang.String)
     */
    public void visitSource( final String source, final String debug )
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.ClassVisitor#visitOuterClass(java.lang.String, java.lang.String, java.lang.String)
     */
    public void visitOuterClass( final String owner, final String name, final String desc )
    {
        // addName(owner);
        // addMethodDesc(desc);
    }

    /*
     * @see org.objectweb.asm.ClassVisitor#visitAnnotation(java.lang.String, boolean)
     */
    public AnnotationVisitor visitAnnotation( final String desc, final boolean visible )
    {
        addDesc( desc );
        
        return this;
    }

    /*
     * @see org.objectweb.asm.ClassVisitor#visitAttribute(org.objectweb.asm.Attribute)
     */
    public void visitAttribute( final Attribute attr )
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.ClassVisitor#visitInnerClass(java.lang.String, java.lang.String, java.lang.String, int)
     */
    public void visitInnerClass( final String name, final String outerName, final String innerName, final int access )
    {
        // addName( outerName);
        // addName( innerName);
    }

    /*
     * @see org.objectweb.asm.ClassVisitor#visitField(int, java.lang.String, java.lang.String, java.lang.String,
     *      java.lang.Object)
     */
    public FieldVisitor visitField( final int access, final String name, final String desc, final String signature,
                                    final Object value )
    {
        if ( signature == null )
        {
            addDesc( desc );
        }
        else
        {
            addTypeSignature( signature );
        }

        if ( value instanceof Type )
        {
            addType( (Type) value );
        }

        return this;
    }

    /*
     * @see org.objectweb.asm.ClassVisitor#visitMethod(int, java.lang.String, java.lang.String, java.lang.String,
     *      java.lang.String[])
     */
    public MethodVisitor visitMethod( final int access, final String name, final String desc, final String signature,
                                      final String[] exceptions )
    {
        if ( signature == null )
        {
            addMethodDesc( desc );
        }
        else
        {
            addSignature( signature );
        }

        addNames( exceptions );

        return this;
    }

    // MethodVisitor methods --------------------------------------------------

    /*
     * @see org.objectweb.asm.MethodVisitor#visitAnnotationDefault()
     */
    public AnnotationVisitor visitAnnotationDefault()
    {
        return this;
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitParameterAnnotation(int, java.lang.String, boolean)
     */
    public AnnotationVisitor visitParameterAnnotation( final int parameter, final String desc, final boolean visible )
    {
        addDesc( desc );

        return this;
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitCode()
     */
    public void visitCode()
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitFrame(int, int, java.lang.Object[], int, java.lang.Object[])
     */
    public void visitFrame( final int type, final int nLocal, final Object[] local, final int nStack,
                            final Object[] stack )
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitInsn(int)
     */
    public void visitInsn( final int opcode )
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitIntInsn(int, int)
     */
    public void visitIntInsn( final int opcode, final int operand )
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitVarInsn(int, int)
     */
    public void visitVarInsn( final int opcode, final int var )
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitTypeInsn(int, java.lang.String)
     */
    public void visitTypeInsn( final int opcode, final String desc )
    {
        if ( desc.charAt( 0 ) == '[' )
        {
            addDesc( desc );
        }
        else
        {
            addName( desc );
        }
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitFieldInsn(int, java.lang.String, java.lang.String, java.lang.String)
     */
    public void visitFieldInsn( final int opcode, final String owner, final String name, final String desc )
    {
        addName( owner );
        /*
         * NOTE: Merely accessing a field does not impose a direct dependency on its type. For example, the code line
         * <code>java.lang.Object var = bean.field;</code> does not directly depend on the type of the field. A direct
         * dependency is only introduced when the code explicitly references the field's type by means of a variable
         * declaration or a type check/cast. Those cases are handled by other visitor callbacks.
         */
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitMethodInsn(int, java.lang.String, java.lang.String, java.lang.String)
     */
    public void visitMethodInsn( final int opcode, final String owner, final String name, final String desc )
    {
        addName( owner );
        /*
         * NOTE: Merely invoking a method does not impose a direct dependency on its return type nor its parameter
         * types. For example, the code line <code>bean.method(null);</code> only depends on the owner type of the
         * method. A direct dependency is only introduced when the code explicitly references the method's types by
         * means of a variable declaration or a type check/cast. Those cases are handled by other visitor callbacks.
         */
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitJumpInsn(int, org.objectweb.asm.Label)
     */
    public void visitJumpInsn( final int opcode, final Label label )
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitLabel(org.objectweb.asm.Label)
     */
    public void visitLabel( final Label label )
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitLdcInsn(java.lang.Object)
     */
    public void visitLdcInsn( final Object cst )
    {
        if ( cst instanceof Type )
        {
            addType( (Type) cst );
        }
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitIincInsn(int, int)
     */
    public void visitIincInsn( final int var, final int increment )
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitTableSwitchInsn(int, int, org.objectweb.asm.Label,
     *      org.objectweb.asm.Label[])
     */
    public void visitTableSwitchInsn( final int min, final int max, final Label dflt, final Label[] labels )
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitLookupSwitchInsn(org.objectweb.asm.Label, int[],
     *      org.objectweb.asm.Label[])
     */
    public void visitLookupSwitchInsn( final Label dflt, final int[] keys, final Label[] labels )
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitMultiANewArrayInsn(java.lang.String, int)
     */
    public void visitMultiANewArrayInsn( final String desc, final int dims )
    {
        addDesc( desc );
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitTryCatchBlock(org.objectweb.asm.Label, org.objectweb.asm.Label,
     *      org.objectweb.asm.Label, java.lang.String)
     */
    public void visitTryCatchBlock( final Label start, final Label end, final Label handler, final String type )
    {
        addName( type );
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitLocalVariable(java.lang.String, java.lang.String, java.lang.String,
     *      org.objectweb.asm.Label, org.objectweb.asm.Label, int)
     */
    public void visitLocalVariable( final String name, final String desc, final String signature, final Label start,
                                    final Label end, final int index )
    {
        if ( signature == null )
        {
            addDesc( desc );
        }
        else
        {
            addTypeSignature( signature );
        }
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitLineNumber(int, org.objectweb.asm.Label)
     */
    public void visitLineNumber( final int line, final Label start )
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.MethodVisitor#visitMaxs(int, int)
     */
    public void visitMaxs( final int maxStack, final int maxLocals )
    {
        // no-op
    }

    // AnnotationVisitor methods ----------------------------------------------

    /*
     * @see org.objectweb.asm.AnnotationVisitor#visit(java.lang.String, java.lang.Object)
     */
    public void visit( final String name, final Object value )
    {
        if ( value instanceof Type )
        {
            addType( (Type) value );
        }
    }

    /*
     * @see org.objectweb.asm.AnnotationVisitor#visitEnum(java.lang.String, java.lang.String, java.lang.String)
     */
    public void visitEnum( final String name, final String desc, final String value )
    {
        addDesc( desc );
    }

    /*
     * @see org.objectweb.asm.AnnotationVisitor#visitAnnotation(java.lang.String, java.lang.String)
     */
    public AnnotationVisitor visitAnnotation( final String name, final String desc )
    {
        addDesc( desc );

        return this;
    }

    /*
     * @see org.objectweb.asm.AnnotationVisitor#visitArray(java.lang.String)
     */
    public AnnotationVisitor visitArray( final String name )
    {
        return this;
    }

    /*
     * @see org.objectweb.asm.AnnotationVisitor#visitEnd()
     */
    public void visitEnd()
    {
        // no-op
    }

    // SignatureVisitor methods -----------------------------------------------

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitFormalTypeParameter(java.lang.String)
     */
    public void visitFormalTypeParameter( final String name )
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitClassBound()
     */
    public SignatureVisitor visitClassBound()
    {
        return this;
    }

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitInterfaceBound()
     */
    public SignatureVisitor visitInterfaceBound()
    {
        return this;
    }

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitSuperclass()
     */
    public SignatureVisitor visitSuperclass()
    {
        return this;
    }

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitInterface()
     */
    public SignatureVisitor visitInterface()
    {
        return this;
    }

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitParameterType()
     */
    public SignatureVisitor visitParameterType()
    {
        return this;
    }

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitReturnType()
     */
    public SignatureVisitor visitReturnType()
    {
        return this;
    }

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitExceptionType()
     */
    public SignatureVisitor visitExceptionType()
    {
        return this;
    }

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitBaseType(char)
     */
    public void visitBaseType( final char descriptor )
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitTypeVariable(java.lang.String)
     */
    public void visitTypeVariable( final String name )
    {
        // TODO: verify
    }

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitArrayType()
     */
    public SignatureVisitor visitArrayType()
    {
        return this;
    }

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitClassType(java.lang.String)
     */
    public void visitClassType( final String name )
    {
        addName( name );
    }

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitInnerClassType(java.lang.String)
     */
    public void visitInnerClassType( final String name )
    {
        addName( name );
    }

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitTypeArgument()
     */
    public void visitTypeArgument()
    {
        // no-op
    }

    /*
     * @see org.objectweb.asm.signature.SignatureVisitor#visitTypeArgument(char)
     */
    public SignatureVisitor visitTypeArgument( final char wildcard )
    {
        return this;
    }

    // public methods ---------------------------------------------------------

    public Set<String> getClasses()
    {
        return classes;
    }

    // private methods --------------------------------------------------------

    private void addName( String name )
    {
        if ( name == null )
        {
            return;
        }

        // decode arrays
        if ( name.startsWith( "[L" ) && name.endsWith( ";" ) )
        {
            name = name.substring( 2, name.length() - 1 );
        }

        // decode internal representation
        name = name.replace( '/', '.' );

        classes.add( name );
    }

    private void addNames( final String[] names )
    {
        if ( names == null )
        {
            return;
        }

        for ( String name : names )
        {
            addName( name );
        }
    }

    private void addDesc( final String desc )
    {
        addType( Type.getType( desc ) );
    }

    private void addMethodDesc( final String desc )
    {
        addType( Type.getReturnType( desc ) );

        Type[] types = Type.getArgumentTypes( desc );

        for ( Type type : types )
        {
            addType( type );
        }
    }

    private void addType( final Type t )
    {
        switch ( t.getSort() )
        {
            case Type.ARRAY:
                addType( t.getElementType() );
                break;

            case Type.OBJECT:
                addName( t.getClassName().replace( '.', '/' ) );
                break;
        }
    }

    private void addSignature( final String signature )
    {
        if ( signature != null )
        {
            new SignatureReader( signature ).accept( this );
        }
    }

    private void addTypeSignature( final String signature )
    {
        if ( signature != null )
        {
            new SignatureReader( signature ).acceptType( this );
        }
    }
}
