/* MethodAnalyzer Copyright (C) 1998-2002 Jochen Hoenicke.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; see the file COPYING.LESSER.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: MethodAnalyzer.java.in,v 4.8.2.4 2002/05/28 17:34:03 hoenicke Exp $
 */

package jode.decompiler;
import jode.AssertError;
import jode.GlobalOptions;
import jode.bytecode.BytecodeInfo;
import jode.bytecode.ClassInfo;
import jode.bytecode.MethodInfo;
import jode.bytecode.Handler;
import jode.bytecode.Instruction;
import jode.bytecode.LocalVariableInfo;
import jode.jvm.SyntheticAnalyzer;
import jode.decompiler.Options;
import jode.type.*;
import jode.expr.Expression;
import jode.expr.ConstOperator;
import jode.expr.CheckNullOperator;
import jode.expr.ThisOperator;
import jode.expr.LocalLoadOperator;
import jode.expr.OuterLocalOperator;
import jode.expr.InvokeOperator;
import jode.flow.StructuredBlock;
import jode.flow.FlowBlock;
import jode.flow.TransformExceptionHandlers;
import jode.flow.Jump;
import jode.jvm.CodeVerifier;
import jode.jvm.VerifyException;
import jode.util.SimpleMap;

import java.lang.reflect.Modifier;
import java.util.BitSet;
import java.util.Stack;
import java.util.Vector;
import java.util.Enumeration;
import java.io.DataInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;

import @COLLECTIONS@.Map;
import @COLLECTIONS@.Collection;
import @COLLECTIONS@.ArrayList;
import @COLLECTIONS@.Iterator;
import @COLLECTIONS@.Set;

/**
 * A method analyzer is the main class for analyzation of methods.
 * There is exactly one MethodAnalyzer object for each method (even
 * for abstract methods), that should be decompiled.
 *
 * Method analyzation is done in three passes:
 * <dl>
 * <dt><code>analyze()</code></dt>
 * <dd>the main analyzation, decompiles the code of the method</dd>
 * <dt><code>analyzeInners()</code></dt>
 * <dd>This will analyze method scopes classes by calling their
 * <code>analyze()</code> and <code>analyzeInners()</code>
 * methods.</dd>
 * <dt><code>makeDeclaration()</code></dt>
 * <dd>This will determine when to declare variables. For constructors
 * it will do special transformations like field initialization.</dd>
 */
public class MethodAnalyzer implements Scope, ClassDeclarer {
    /**
     * The minimal visible complexity.
     */
    private static double STEP_COMPLEXITY = 0.01;
    /**
     * The value of the strictfp modifier.
     * JDK1.1 doesn't define it.
     */
    private static int STRICTFP = 0x800;
    /**
     * The import handler where we should register our types.
     */
    ImportHandler imports;
    /**
     * The class analyzer of the class that contains this method.
     */
    ClassAnalyzer classAnalyzer;
    /**
     * The method info structure for this method.
     */
    MethodInfo minfo;
    /**
     * This is the bytecode info structure, or null if this method has 
     * no code (abstract or native).
     */
    BytecodeInfo code;

    /**
     * The method name.
     */
    String methodName;
    /**
     * The type of this method (parameter types + return type).
     */
    MethodType methodType;
    /**
     * True, iff this method is a constructor, i.e. methodName == <(cl)?init>
     */
    boolean isConstructor;

    /**
     * The exceptions this method may throw.
     */
    Type[] exceptions;

    /**
     * If the method is synthetic (access$, class$, etc.), this is the
     * synthetic analyzer describing the function of this method, otherwise
     * this is null.
     */
    SyntheticAnalyzer synth;

    /**
     * This is the first flow block of the method code.  If this
     * method has no code, this is null.  This is initialized at the
     * end of the <code>analyze()</code> phase.  
     */
    FlowBlock methodHeader;
    /**
     * A list of all locals contained in this method.
     */
    Vector allLocals = new Vector();

    /**
     * This array contains the locals in the parameter list, including
     * the implicit <i>this</i> parameter for nonstatic methods.  
     */
    LocalInfo[] param;
    /**
     * The local variable table containing info about names and types of
     * locals.
     */
    LocalVariableTable lvt;

    /**
     * If this method is the special constructor, that is generated
     * by jikes (constructor$xx), this points to the real constructor.
     * If this is the real constructor and calls a constructor$xx, it
     * points to this. Otherwise this is null.
     */
    MethodAnalyzer jikesConstructor;
    /**
     * True, iff this method is the special constructor, and its first
     * parameter is a reference to the outer class.
     */
    boolean hasJikesOuterValue;
    /**
     * True, iff this method is an anonymous constructor, that is
     * omitted even if it has parameters.
     */
    boolean isAnonymousConstructor;
    /**
     * True, if this method is the special block$ method generated by jikes
     * to initialize field members.
     */
    boolean isJikesBlockInitializer;

    /**
     * This list contains the InvokeOperator objects in the code of
     * this method, that create method scoped classes.  */
    Vector anonConstructors = new Vector();

    /**
     * This list contains the class analyzers of all method scoped
     * classes that should be declared in this method or in a class
     * that is declared in this method.
     */
    Vector innerAnalyzers;
    /**
     * This list contains the class analyzers of all method scoped
     * classes that are used in this method.
     */
    Collection usedAnalyzers;

    /**
     * This is the default constructor.
     * @param cla the ClassAnalyzer of the class that contains this method.
     * @param minfo the method info structure for this method.
     * @param imports the import handler that should be informed about types.
     */
    public MethodAnalyzer(ClassAnalyzer cla, MethodInfo minfo,
                          ImportHandler imports) {
        this.classAnalyzer = cla;
        this.imports = imports;
	this.minfo = minfo;
        this.methodName = minfo.getName();
        this.methodType = Type.tMethod(minfo.getType());
        this.isConstructor = 
            methodName.equals("<init>") || methodName.equals("<clinit>");
        
	if (minfo.getBytecode() != null) {
	    code = minfo.getBytecode();
	}
        String[] excattr = minfo.getExceptions();
        if (excattr == null) {
            exceptions = new Type[0];
        } else {
	    int excCount = excattr.length;
	    this.exceptions = new Type[excCount];
	    for (int i=0; i< excCount; i++)
		exceptions[i] = Type.tClass(excattr[i]);
        }
	if (minfo.isSynthetic() || methodName.indexOf('$') != -1)
	    synth = new SyntheticAnalyzer(minfo, true);
    }

    /**
     * Returns the name of this method.
     */
    public String getName() {
	return methodName;
    }

    /**
     * Returns the type of this method.
     * @return the type of this method.
     */
    public MethodType getType() {
	return methodType;
    }

    /**
     * Returns the first flow block of the code.
     * @return the first flow block of the code.
     */
    public FlowBlock getMethodHeader() {
        return methodHeader;
    }

    /**
     * Returns the bytecode info for this method.
     * @return the bytecode info for this method, or null if it is
     * abstract or native.
     */
    public final BytecodeInfo getBytecodeInfo() {
	return code;
    }

    /**
     * Returns the import handler. The import handler should be informed
     * about all types we (or an expression in this method) use, so that
     * the corresponding class can be imported.
     * @return the import handler.
     */
    public final ImportHandler getImportHandler() {
	return imports;
    }

    /**
     * Registers a type at the import handler.  This should be called
     * if an expression needs to print the type name to the code.  The
     * corresponding class will be imported in that case (if used
     * often enough).
     * @param type the type that should be registered.
     */
    public final void useType(Type type) {
	imports.useType(type);
    }

    /**
     * Inserts a structured block to the beginning of the method.
     * This is called by transform constructors, to move the super
     * call from the real constructor to the constructor$xx method
     * (the jikes constructor).
     * @param insertBlock the structured block that should be inserted.
     */
    public void insertStructuredBlock(StructuredBlock insertBlock) {
	if (methodHeader != null) {
	    insertBlock.setJump(new Jump(FlowBlock.NEXT_BY_ADDR));
	    FlowBlock insertFlowBlock = new FlowBlock(this, 0);
	    insertFlowBlock.appendBlock(insertBlock, 0);
	    insertFlowBlock.setNextByAddr(methodHeader);
	    insertFlowBlock.doT2(methodHeader);
	    methodHeader = insertFlowBlock;
	} else {
	    throw new IllegalStateException();
	}
    }

    /**
     * Checks if this method is a constructor, i.e. getName() returns
     * "<init>" or "<clinit>".
     * @return true, iff this method is a real constructor.  
     */
    public final boolean isConstructor() {
        return isConstructor;
    }

    /**
     * Checks if this method is static.
     * @return true, iff this method is static.
     */
    public final boolean isStatic() {
        return minfo.isStatic();
    }

    /**
     * Checks if this method is synthetic, i.e. a synthetic attribute is
     * present.
     * @return true, iff this method is synthetic.
     */
    public final boolean isSynthetic() {
	return minfo.isSynthetic();
    }

    /**
     * Checks if this method is strictfp
     * @return true, iff this method is synthetic.
     */
    public final boolean isStrictFP() {
	return (minfo.getModifiers() & STRICTFP) != 0;
    }

    /**
     * Tells if this method is the constructor$xx method generated by jikes.
     * @param value true, iff this method is the jikes constructor.
     */
    public final void setJikesConstructor(MethodAnalyzer realConstr) {
	jikesConstructor = realConstr;
    }

    /**
     * Tells if this method is the block$xx method generated by jikes.
     * @param value true, iff this method is the jikes block initializer.
     */
    public final void setJikesBlockInitializer(boolean value) {
	isJikesBlockInitializer = value;
    }

    /**
     * Tells if this (constructor$xx) method has as first (implicit)
     * parameter the instance of the outer class.  
     * @param value true, this method has the implicit parameter.
     */
    public final void setHasOuterValue(boolean value) {
	hasJikesOuterValue = value;
    }

    /**
     * Tells if this constructor can be omited, since it is implicit.
     * @param value true, this method is the implicit constructor.
     */
    public final void setAnonymousConstructor(boolean value) {
	isAnonymousConstructor = value;
    }

    /**
     * Checks if this constructor can be omited, since it is implicit.
     * @return true, this method is the implicit constructor.
     */
    public final boolean isAnonymousConstructor() {
	return isAnonymousConstructor;
    }

    /**
     * Get the synthetic analyzer for this method.
     * @return the synthetic analyzer, or null if this method isn't
     * synthetic.
     */
    public final SyntheticAnalyzer getSynthetic() {
	return synth;
    }

    /**
     * Get the return type of this method.
     */
    public Type getReturnType() {
        return methodType.getReturnType();
    }

    /**
     * Get the class analyzer for the class containing this method.
     */
    public ClassAnalyzer getClassAnalyzer() {
	return classAnalyzer;
    }

    /**
     * Get the class info for the class containing this method.
     */
    public ClassInfo getClazz() {
        return classAnalyzer.clazz;
    }

    /**
     * Get the local info for a parameter.  This call is valid after
     * the analyze pass.
     * @param nr the index of the parameter (start by zero and
     * count the implicit this param for nonstatic method).
     * @return the local info for the specified parameter.
     * @see #getLocalInfo
     */
    public final LocalInfo getParamInfo(int nr) {
	return param[nr];
    }

    /**
     * Return the number of parameters for this method. This call is
     * valid after the analyze pass.
     */
    public final int getParamCount() {
	return param.length;
    }

    /**
     * Create a local info for a local variable located at an
     * instruction with the given address.
     * @param addr the address of the instruction using this local.
     *             the address of the next instruction for stores.
     * @param slot the slot, the local variable uses.
     * @return a new local info representing that local.
     */
    public LocalInfo getLocalInfo(int addr, int slot) {
        LocalInfo li = new LocalInfo(this, slot);
	if (lvt != null) {
	    LocalVarEntry entry = lvt.getLocal(slot, addr);
	    if (entry != null)
		li.addHint(entry.getName(), entry.getType());
	}
	allLocals.addElement(li);
        return li;
    }

    /**
     * Gets the complexity of this class.  Must be called after it has
     * been initialized.  This is used for a nice progress bar.
     */
    public double getComplexity() {
	if (code == null)
	    return 0.0;
	else
	    return code.getInstructions().size();
    }

    /**
     * Analyzes the code of this method.  This creates the
     * flow blocks (including methodHeader) and analyzes them.  
     */
    private void analyzeCode(ProgressListener pl, double done, double scale) 
    {
	int instrsPerStep = Integer.MAX_VALUE;
	if (GlobalOptions.verboseLevel > 0)
	    GlobalOptions.err.print(methodName+": ");

	if (pl != null) {
	    instrsPerStep = (int) ((code.getInstructions().size()
				    * STEP_COMPLEXITY) / (scale * 0.9));
	}

	/* The adjacent analyzation relies on this */
	DeadCodeAnalysis.removeDeadCode(code);
	Handler[] handlers = code.getExceptionHandlers();
	int returnCount;
        TransformExceptionHandlers excHandlers; 
	{
	    /* First create a FlowBlock for every block that has a 
	     * predecessor other than the previous instruction.
	     */
	    for (Iterator i = code.getInstructions().iterator(); 
		 i.hasNext(); ) {
		Instruction instr = (Instruction) i.next();
		if (instr.getPrevByAddr() == null
		    || instr.getPrevByAddr().doesAlwaysJump()
		    || instr.getPreds() != null)
		    instr.setTmpInfo(new FlowBlock(this, instr.getAddr()));
	    }

	    for (int i=0; i < handlers.length; i++) {
		Instruction instr = handlers[i].start;
		if (instr.getTmpInfo() == null)
		    instr.setTmpInfo(new FlowBlock(this, instr.getAddr()));
		/* end doesn't have a predecessor, but we must prevent
		 * it from being merged with the previous instructions.
		 */
		instr = handlers[i].end.getNextByAddr();
		if (instr.getTmpInfo() == null)
		    instr.setTmpInfo(new FlowBlock(this, instr.getAddr()));
		instr = handlers[i].catcher;
		if (instr.getTmpInfo() == null)
		    instr.setTmpInfo(new FlowBlock(this, instr.getAddr()));
	    }

            /* While we read the opcodes into FlowBlocks 
             * we try to combine sequential blocks, as soon as we
             * find two sequential instructions in a row, where the
             * second has no predecessors.
             */
            int mark = 1000;
	    int count = 0;
            FlowBlock lastBlock = null;
	    boolean   lastSequential = false;
	    for (Iterator i = code.getInstructions().iterator(); 
		 i.hasNext(); ) {
		Instruction instr = (Instruction) i.next();

		jode.flow.StructuredBlock block
		    = Opcodes.readOpcode(instr, this);

                if (GlobalOptions.verboseLevel > 0 && instr.getAddr() > mark) {
                    GlobalOptions.err.print('.');
                    mark += 1000;
                }
		if (++count >= instrsPerStep) {
		    done += count * scale / code.getInstructions().size();
		    pl.updateProgress(done, methodName);
		    count = 0;
		}
		  

                if (lastSequential && instr.getTmpInfo() == null
		    /* Only merge with previous block, if this is sequential, 
		     * too.  
		     * Why?  appendBlock only handles sequential blocks.
		     */
		    && !instr.doesAlwaysJump() && instr.getSuccs() == null) {
		    
                    lastBlock.appendBlock(block, instr.getLength());
                } else {

		    if (instr.getTmpInfo() == null)
			instr.setTmpInfo(new FlowBlock(this, instr.getAddr()));
		    FlowBlock flowBlock = (FlowBlock) instr.getTmpInfo();
		    flowBlock.appendBlock(block, instr.getLength());

		    if (lastBlock != null)
			lastBlock.setNextByAddr(flowBlock);

                    instr.setTmpInfo(lastBlock = flowBlock);
		    lastSequential = !instr.doesAlwaysJump()
			&& instr.getSuccs() == null;
                }
	    }

	    methodHeader = (FlowBlock)
		((Instruction) code.getInstructions().get(0)).getTmpInfo();
            excHandlers = new TransformExceptionHandlers();
            for (int i=0; i<handlers.length; i++) {
                Type type = null;
                FlowBlock start 
		    = (FlowBlock) handlers[i].start.getTmpInfo();
                int endAddr = handlers[i].end.getNextByAddr().getAddr();
                FlowBlock handler
		    = (FlowBlock) handlers[i].catcher.getTmpInfo();
                if (handlers[i].type != null)
                    type = Type.tClass(handlers[i].type);
                
                excHandlers.addHandler(start, endAddr, handler, type);
            }
        }
	for (Iterator i = code.getInstructions().iterator(); i.hasNext(); ) {
	    Instruction instr = (Instruction) i.next();
	    instr.setTmpInfo(null);
	}

        if (GlobalOptions.verboseLevel > 0)
            GlobalOptions.err.print('-');
            
        excHandlers.analyze();
        methodHeader.analyze();

	if ((Options.options & Options.OPTION_PUSH) == 0
	    && methodHeader.mapStackToLocal())
	    methodHeader.removePush();
	if ((Options.options & Options.OPTION_ONETIME) != 0)
	    methodHeader.removeOnetimeLocals();

	methodHeader.mergeParams(param);

	if (GlobalOptions.verboseLevel > 0)
	    GlobalOptions.err.println("");
	if (pl != null) {
	    done += 0.1 * scale;
	    pl.updateProgress(done, methodName);
	}
    } 

    /**
     * This is the first pass of the analyzation.  It will analyze the
     * code of this method, but not the method scoped classes.  
     */
    public void analyze(ProgressListener pl, double done, double scale) 
      throws ClassFormatError
    {
	if (pl != null)
	    pl.updateProgress(done, methodName);
	if (code != null) {
	    if ((Options.options & Options.OPTION_VERIFY) != 0) {
		CodeVerifier verifier
		    = new CodeVerifier(getClazz(), minfo, code);
		try {
		    verifier.verify();
		} catch (VerifyException ex) {
		    ex.printStackTrace(GlobalOptions.err);
		    throw new jode.AssertError("Verification error");
		}
	    }
	    
	    if ((Options.options & Options.OPTION_LVT) != 0) {
		LocalVariableInfo[] localvars = code.getLocalVariableTable();
		if (localvars != null)
		    lvt = new LocalVariableTable(code.getMaxLocals(), 
						 localvars);
	    }
	}

        Type[] paramTypes = getType().getParameterTypes();
	int paramCount = (isStatic() ? 0 : 1) + paramTypes.length;
	param = new LocalInfo[paramCount];

	int offset = 0;
	int slot = 0;
	if (!isStatic()) {
	    ClassInfo classInfo = classAnalyzer.getClazz();
	    LocalInfo thisLocal = getLocalInfo(0, slot++);
	    thisLocal.setExpression(new ThisOperator(classInfo, true));
	    param[offset++] = thisLocal;
	}

	for (int i=0; i< paramTypes.length; i++) {
	    param[offset] = getLocalInfo(0, slot);
	    param[offset].setType(paramTypes[i]);
	    slot += paramTypes[i].stackSize();
            offset++;
        }

        for (int i= 0; i< exceptions.length; i++)
            imports.useType(exceptions[i]);
    
        if (!isConstructor)
            imports.useType(methodType.getReturnType());

	if (code != null)
	    analyzeCode(pl, done, scale);
    }

    /**
     * This is the second pass of the analyzation.  It will analyze
     * the method scoped classes.  
     */
    public void analyzeInnerClasses()
      throws ClassFormatError
    {
	int serialnr = 0;
        Enumeration elts = anonConstructors.elements();
        while (elts.hasMoreElements()) {
	    InvokeOperator cop = (InvokeOperator) elts.nextElement();
	    analyzeInvokeOperator(cop);
	}
    }

    /**
     * This is the third and last pass of the analyzation.  It will analyze
     * the types and names of the local variables and where to declare them.
     * It will also determine where to declare method scoped local variables.
     */
    public void makeDeclaration(Set done) {
	if (innerAnalyzers != null) {
	    for (Enumeration enum = innerAnalyzers.elements();
		 enum.hasMoreElements(); ) {
		ClassAnalyzer classAna = (ClassAnalyzer) enum.nextElement();
		if (classAna.getParent() == this) {
		    OuterValues innerOV = classAna.getOuterValues();
		    for (int i=0; i < innerOV.getCount(); i++) {
			Expression value = innerOV.getValue(i);
			if (value instanceof OuterLocalOperator) {
			    LocalInfo li = ((OuterLocalOperator) 
					    value).getLocalInfo();
			    if (li.getMethodAnalyzer() == this)
				li.markFinal();
			}
		    }
		}
	    }
	}

        for (Enumeration enum = allLocals.elements();
	     enum.hasMoreElements(); ) {
            LocalInfo li = (LocalInfo)enum.nextElement();
            if (!li.isShadow())
                imports.useType(li.getType());
        }
	for (int i=0; i < param.length; i++) {
	    param[i].guessName();
	    Iterator doneIter = done.iterator();
	    while (doneIter.hasNext()) {
		Declarable previous = (Declarable) doneIter.next();
		if (param[i].getName().equals(previous.getName())) {
		    /* A name conflict happened. */
		    param[i].makeNameUnique();
		    break;
		}
	    }
	    done.add(param[i]);
	}
	
	if (code != null) {
	    methodHeader.makeDeclaration(done);
	    methodHeader.simplify();
	}
	for (int i=0; i < param.length; i++) {
	    done.remove(param[i]);
	    // remove the parameters, since we leave the scope
	}
    }

    /**
     * Tells if this method is synthetic or implicit or something else, so
     * that it doesn't have to be written to the source code.
     * @return true, iff it shouldn't be written to the source code.
     */
    public boolean skipWriting() {
	if (synth != null) {
	    // We don't need this class anymore (hopefully?)
	    if (synth.getKind() == synth.GETCLASS)
		return true;
	    if (synth.getKind() >= synth.ACCESSGETFIELD
		&& synth.getKind() <= synth.ACCESSDUPPUTSTATIC
		&& (Options.options & Options.OPTION_INNER) != 0
		&& (Options.options & Options.OPTION_ANON) != 0)
		return true;
	}

	if (jikesConstructor == this) {
	    // This is the first empty part of a jikes constructor
	    return true;
	}

	boolean declareAsConstructor = isConstructor;
	int skipParams = 0;
	if (isConstructor() && !isStatic()
	    && classAnalyzer.outerValues != null)
	    skipParams = classAnalyzer.outerValues.getCount();

	if (jikesConstructor != null) {
	    // This is the real part of a jikes constructor
	    declareAsConstructor = true;
	    skipParams = hasJikesOuterValue
		&& classAnalyzer.outerValues.getCount() > 0 ? 1 : 0;
	}

	if (isJikesBlockInitializer)
	    return true;

	/* The default constructor must be empty 
	 * and mustn't throw exceptions */
	if (getMethodHeader() == null
	    || !(getMethodHeader().getBlock() instanceof jode.flow.EmptyBlock)
	    || !getMethodHeader().hasNoJumps()
	    || exceptions.length > 0)
	    return false;

	if (declareAsConstructor
	    /* The access rights of default constructor should
	     * be public, iff the class is public, otherwise package.
	     * But this rule doesn't necessarily apply for anonymous
	     * classes...
	     */
	    && ((minfo.getModifiers()
		 & (Modifier.PROTECTED | Modifier.PUBLIC | Modifier.PRIVATE
		    | Modifier.SYNCHRONIZED | Modifier.STATIC
		    | Modifier.ABSTRACT | Modifier.NATIVE))
		== (getClassAnalyzer().getModifiers()
		    & (Modifier.PROTECTED | Modifier.PUBLIC))
		|| classAnalyzer.getName() == null)
	    && classAnalyzer.constructors.length == 1) {

	    // If the constructor doesn't take parameters (except outerValues)
	    // or if it is the anonymous constructor it can be removed.
	    if (methodType.getParameterTypes().length == skipParams
		|| isAnonymousConstructor)
		return true;
	}

        if (isConstructor() && isStatic())
            return true;

	return false;
    }
    
    /**
     * Dumps the source code for this method to the specified writer.
     * @param writer the tabbed print writer the code should be written to.
     * @exception IOException, if writer throws an exception.
     */
    public void dumpSource(TabbedPrintWriter writer) 
         throws IOException
    {
	boolean declareAsConstructor = isConstructor;
	int skipParams = 0;
	int modifiedModifiers = minfo.getModifiers();

	if (isConstructor() && !isStatic()
	    && (Options.options & Options.OPTION_CONTRAFO) != 0
	    && classAnalyzer.outerValues != null)
	    skipParams = classAnalyzer.outerValues.getCount();

	if (jikesConstructor != null) {
	    // This is the real part of a jikes constructor
	    declareAsConstructor = true;
	    skipParams = hasJikesOuterValue
		&& classAnalyzer.outerValues.getCount() > 0 ? 1 : 0;
	    // get the modifiers of the real constructor
	    modifiedModifiers = jikesConstructor.minfo.getModifiers();
	}

	if (minfo.isDeprecated()) {
	    writer.println("/**");
	    writer.println(" * @deprecated");
	    writer.println(" */");
	}

	writer.pushScope(this);

	/*
	 * JLS-1.0, section 9.4:
	 *
	 * For compatibility with older versions of Java, it is
	 * permitted but discouraged, as a matter of style, to
	 * redundantly specify the abstract modifier for methods
	 * declared in interfaces.
	 *
	 * Every method declaration in the body of an interface is
	 * implicitly public. It is permitted, but strongly
	 * discouraged as a matter of style, to redundantly specify
	 * the public modifier for interface methods.  We don't
	 * follow this second rule and mark this method explicitly
	 * as public.
	 */
	if (classAnalyzer.getClazz().isInterface())
	    modifiedModifiers &= ~Modifier.ABSTRACT;

	/* Don't ask me why, but jikes declares the static constructor
	 * as final.  Another compiler or obfuscator seems to declare
	 * it as public.  I remove every fancy modifier, now.
	 */
	if (isConstructor() && isStatic())
	    modifiedModifiers &= ~(Modifier.FINAL | Modifier.PUBLIC
				   | Modifier.PROTECTED | Modifier.PRIVATE);
	modifiedModifiers &= ~STRICTFP;

	writer.startOp(writer.NO_PAREN, 1);
	String delim = "";
	if (minfo.isSynthetic()) {
	    writer.print("/*synthetic*/");
	    delim = " ";
	}


	String modif = Modifier.toString(modifiedModifiers);
	if (modif.length() > 0) {
	    writer.print(delim + modif);
	    delim = " ";
	}
	if (isStrictFP()) {
	    /* The STRICTFP modifier is set.
	     * We handle it, since java.lang.reflect.Modifier is too dumb.
	     */

	    /* If STRICTFP is already set for class don't set it for method.
	     * And don't set STRICTFP for native methods or constructors.
	     */
	    if (!classAnalyzer.isStrictFP()
		&& !isConstructor()
		&& (modifiedModifiers & Modifier.NATIVE) == 0) {
		writer.print(delim + "strictfp");
		delim = " ";
	    }
	}

        if (isConstructor
	    && (isStatic()
		|| (classAnalyzer.getName() == null
		    && skipParams == methodType.getParameterTypes().length))) {
            /* static block or unnamed constructor */
        } else { 
	    writer.print(delim);
            if (declareAsConstructor)
		writer.print(classAnalyzer.getName());
            else {
                writer.printType(getReturnType());
		writer.print(" " + methodName);
	    }
	    writer.breakOp();
	    if ((Options.outputStyle & Options.GNU_SPACING) != 0)
		writer.print(" ");
            writer.print("(");
 	    writer.startOp(writer.EXPL_PAREN, 0);
	    int offset = skipParams + (isStatic() ? 0 : 1);
            for (int i = offset; i < param.length; i++) {
                if (i > offset) {
                    writer.print(", ");
		    writer.breakOp();
		}
		param[i].dumpDeclaration(writer);
            }
 	    writer.endOp();
            writer.print(")");
        }
        if (exceptions.length > 0) {
 	    writer.breakOp();
            writer.print(" throws ");
	    writer.startOp(writer.EXPL_PAREN, 2);
            for (int i= 0; i< exceptions.length; i++) {
                if (i > 0) {
                    writer.print(",");
		    writer.breakOp();
		    writer.print(" ");
		}
                writer.printType(exceptions[i]);
            }
	    writer.endOp();
        }
	writer.endOp();
        if (code != null) {
	    writer.openBraceNoIndent();
            writer.tab();
	    methodHeader.dumpSource(writer);
            writer.untab();
	    writer.closeBraceNoIndent();
        } else
            writer.println(";");
	writer.popScope();
    }

    /**
     * Checks if the variable set contains a local with the given name.
     * @return the local info the has the given name, or null if it doesn't
     * exists.
     */
    public LocalInfo findLocal(String name) {
        Enumeration enum = allLocals.elements();
        while (enum.hasMoreElements()) {
            LocalInfo li = (LocalInfo) enum.nextElement();
            if (li.getName().equals(name))
                return li;
        }
        return null;
    }

    /**
     * Checks if a method scoped class with the given name exists in this
     * method (not in a parent method).
     * @return the class analyzer with the given name, or null if it
     * doesn' exists.  
     */
    public ClassAnalyzer findAnonClass(String name) {
	if (innerAnalyzers != null) {
	    Enumeration enum = innerAnalyzers.elements();
	    while (enum.hasMoreElements()) {
		ClassAnalyzer classAna = (ClassAnalyzer) enum.nextElement();
		if (classAna.getParent() == this
		    && classAna.getName() != null
		    && classAna.getName().equals(name)) {
		    return classAna;
		}
	    }
	}
        return null;
    }

    /**
     * Checks if the specified object lies in this scope.
     * @param obj the object.
     * @param scopeType the type of this object.
     */
    public boolean isScopeOf(Object obj, int scopeType) {
	if (scopeType == METHODSCOPE
	    && obj instanceof ClassInfo) {
	    ClassAnalyzer ana = getClassAnalyzer((ClassInfo)obj);
	    if (ana != null)
		return ana.getParent() == this;
	}
	return false;
    }

    /**
     * Checks if the specified name conflicts with an object in this scope.
     * @param name the name to check.
     * @param scopeType the usage type of this name, AMBIGUOUSNAME if it is
     * ambiguous.
     */
    public boolean conflicts(String name, int usageType) {
	if (usageType == AMBIGUOUSNAME || usageType == LOCALNAME)
	    return findLocal(name) != null;
	if (usageType == AMBIGUOUSNAME || usageType == CLASSNAME)
	    return findAnonClass(name) != null;
	return false;
    }

    /**
     * Gets the parent scope, i.e. the class analyzer for the class containing
     * this method.
     * @XXX needed?
     */
    public ClassDeclarer getParent() {
	return getClassAnalyzer();
    }

    /**
     * Registers an anonymous constructor invokation.  This should be called
     * in the analyze or analyzeInner pass by invoke subexpressions.
     * @param cop the constructor invokation, that creates the method scoped
     * class.
     */
    public void addAnonymousConstructor(InvokeOperator cop) {
	anonConstructors.addElement(cop);
    }

    public void analyzeInvokeOperator(InvokeOperator cop) {
	ClassInfo clazz = (ClassInfo) cop.getClassInfo();
	ClassAnalyzer anonAnalyzer = getParent().getClassAnalyzer(clazz);
	
	if (anonAnalyzer == null) {
	    /* Create a new outerValues array corresponding to the
	     * first constructor invocation.
	     */
	    Expression[] outerValueArray;
	    Expression[] subExprs = cop.getSubExpressions();
	    outerValueArray = new Expression[subExprs.length-1];
	    
	    for (int j=0; j < outerValueArray.length; j++) {
		Expression expr = subExprs[j+1].simplify();
		if (expr instanceof CheckNullOperator)
		    expr = ((CheckNullOperator) 
			    expr).getSubExpressions()[0];
		if (expr instanceof ThisOperator) {
		    outerValueArray[j] = 
			new ThisOperator(((ThisOperator) expr).getClassInfo());
		    continue;
		}
		LocalInfo li = null;
		if (expr instanceof LocalLoadOperator) {
		    li = ((LocalLoadOperator) expr).getLocalInfo();
		    if (!li.isConstant())
			li = null;
		}
		if (expr instanceof OuterLocalOperator)
		    li = ((OuterLocalOperator) expr).getLocalInfo();
		
		if (li != null) {
		    outerValueArray[j] = new OuterLocalOperator(li);
		    continue;
		}
		
		Expression[] newOuter = new Expression[j];
		System.arraycopy(outerValueArray, 0, newOuter, 0, j);
		outerValueArray = newOuter;
		break;
	    }
	    anonAnalyzer = new ClassAnalyzer(this, clazz, imports,
					     outerValueArray);
	    addClassAnalyzer(anonAnalyzer);
	    anonAnalyzer.initialize();
	    anonAnalyzer.analyze(null, 0.0, 0.0);
	    anonAnalyzer.analyzeInnerClasses(null, 0.0, 0.0);
	} else {
	    /*
	     * Get the previously created outerValues and
	     * its length.  
	     */
	    OuterValues outerValues = anonAnalyzer.getOuterValues();
	    /*
	     * Merge the other constructor invocation and
	     * possibly shrink outerValues array.  
	     */
	    Expression[] subExprs = cop.getSubExpressions();
	    for (int j=0; j < outerValues.getCount(); j++) {
		if (j+1 < subExprs.length) {
		    Expression expr = subExprs[j+1].simplify();
		    if (expr instanceof CheckNullOperator)
			expr = ((CheckNullOperator) expr)
			    .getSubExpressions()[0];

		    if (outerValues.unifyOuterValues(j, expr))
			continue;
		    
		}
		outerValues.setCount(j);
		break;
	    }
	}

	if (usedAnalyzers == null)
	    usedAnalyzers = new ArrayList();
	usedAnalyzers.add(anonAnalyzer);
    }
    
    /**
     * Get the class analyzer for the given class info.  This searches
     * the method scoped/anonymous classes in this method and all
     * outer methods and the outer classes for the class analyzer.
     * @param cinfo the classinfo for which the analyzer is searched.
     * @return the class analyzer, or null if there is not an outer
     * class that equals cinfo, and not a method scope/inner class in
     * an outer method.
     */
    public ClassAnalyzer getClassAnalyzer(ClassInfo cinfo) {
	if (innerAnalyzers != null) {
	    Enumeration enum = innerAnalyzers.elements();
	    while (enum.hasMoreElements()) {
		ClassAnalyzer classAna = (ClassAnalyzer) enum.nextElement();
		if (classAna.getClazz().equals(cinfo)) {
		    if (classAna.getParent() != this) {
			ClassDeclarer declarer = classAna.getParent();
			while (declarer != this) {
			    if (declarer instanceof MethodAnalyzer)
				((MethodAnalyzer) declarer)
				    .innerAnalyzers.removeElement(classAna);
			    declarer = declarer.getParent();
			}
			classAna.setParent(this);
		    }
		    return classAna;
		}
	    }
	}
	return getParent().getClassAnalyzer(cinfo);
    }

    public void addClassAnalyzer(ClassAnalyzer clazzAna) {
	if (innerAnalyzers == null)
	    innerAnalyzers = new Vector();
	innerAnalyzers.addElement(clazzAna);
	getParent().addClassAnalyzer(clazzAna);
    }

    /**
     * We add the named method scoped classes to the declarables.
     */
    public void fillDeclarables(Collection used) {
	if (usedAnalyzers != null)
	    used.addAll(usedAnalyzers);
	if (innerAnalyzers != null) {
	    Enumeration enum = innerAnalyzers.elements();
	    while (enum.hasMoreElements()) {
		ClassAnalyzer classAna = (ClassAnalyzer) enum.nextElement();
		if (classAna.getParent() == this)
		    classAna.fillDeclarables(used);
	    }
	}
    }

    public boolean isMoreOuterThan(ClassDeclarer declarer) {
	ClassDeclarer ancestor = declarer;
	while (ancestor != null) {
	    if (ancestor == this)
		return true;
	    ancestor = ancestor.getParent();
	}
	return false;
    }

    public String toString() {
	return getClass().getName()+"["+getClazz()+"."+getName()+"]";
    }
}
