package org.apache.bcel.generic;

/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and
 *    "Apache BCEL" must not be used to endorse or promote products
 *    derived from this software without prior written permission. For
 *    written permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    "Apache BCEL", nor may "Apache" appear in their name, without
 *    prior written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.*;
import java.util.ArrayList;
import java.util.Iterator;

/** 
 * Template class for building up a java class. May be initialized with an
 * existing java class (file).
 *
 * @see JavaClass
 * @version $Id: ClassGen.java,v 1.2 2006/08/22 15:33:21 andos Exp $
 * @author  <A HREF="mailto:markus.dahm@berlin.de">M. Dahm</A>
 */
public class ClassGen extends AccessFlags implements Cloneable {
  /**
	 * 
	 */
	private static final long serialVersionUID = 1030389262306646953L;
/* Corresponds to the fields found in a JavaClass object.
   */
  private String   class_name, super_class_name, file_name;
  private int      class_name_index = -1, superclass_name_index = -1;
  private int      major = Constants.MAJOR_1_1, minor = Constants.MINOR_1_1;

  private ConstantPoolGen cp; // Template for building up constant pool

  // ArrayLists instead of arrays to gather fields, methods, etc.
  private ArrayList<Field>   field_vec	 = new ArrayList<Field>();
  private ArrayList<Method>   method_vec    = new ArrayList<Method>();
  private ArrayList<Attribute>   attribute_vec = new ArrayList<Attribute>();
  private ArrayList<String>   interface_vec = new ArrayList<String>();

  /** Convenience constructor to set up some important values initially.
   *
   * @param class_name fully qualified class name
   * @param super_class_name fully qualified superclass name
   * @param file_name source file name
   * @param access_flags access qualifiers
   * @param interfaces implemented interfaces
   * @param cp constant pool to use
   */
  public ClassGen(String class_name, String super_class_name, String file_name,
		  int access_flags, String[] interfaces, ConstantPoolGen cp) {
    this.class_name       = class_name;
    this.super_class_name = super_class_name;
    this.file_name        = file_name;
    this.access_flags     = access_flags;
    this.cp               = cp;

    // Put everything needed by default into the constant pool and the vectors
    if(file_name != null)
      addAttribute(new SourceFile(cp.addUtf8("SourceFile"), 2,
				  cp.addUtf8(file_name), cp.getConstantPool()));

    class_name_index      = cp.addClass(class_name);
    superclass_name_index = cp.addClass(super_class_name);

    if(interfaces != null)
      for(int i=0; i < interfaces.length; i++)
	addInterface(interfaces[i]);
  }

  /** Convenience constructor to set up some important values initially.
   *
   * @param class_name fully qualified class name
   * @param super_class_name fully qualified superclass name
   * @param file_name source file name
   * @param access_flags access qualifiers
   * @param interfaces implemented interfaces
   */
  public ClassGen(String class_name, String super_class_name, String file_name,
		  int access_flags, String[] interfaces) {
    this(class_name, super_class_name, file_name, access_flags, interfaces,
	 new ConstantPoolGen());
  }

  /**
   * Initialize with existing class.
   * @param clazz JavaClass object (e.g. read from file)
   */
  public ClassGen(JavaClass clazz) {
    class_name_index      = clazz.getClassNameIndex();
    superclass_name_index = clazz.getSuperclassNameIndex();
    class_name            = clazz.getClassName();
    super_class_name      = clazz.getSuperclassName();
    file_name             = clazz.getSourceFileName();
    access_flags          = clazz.getAccessFlags();
    cp                    = new ConstantPoolGen(clazz.getConstantPool());
    major                 = clazz.getMajor();
    minor                 = clazz.getMinor();

    Attribute[] attributes = clazz.getAttributes();
    Method[]    methods    = clazz.getMethods();
    Field[]     fields     = clazz.getFields();
    String[]    interfaces = clazz.getInterfaceNames();
    
    for(int i=0; i < interfaces.length; i++)
      addInterface(interfaces[i]);

    for(int i=0; i < attributes.length; i++)
      addAttribute(attributes[i]);

    for(int i=0; i < methods.length; i++)
      addMethod(methods[i]);

    for(int i=0; i < fields.length; i++)
      addField(fields[i]);
  }

  /**
   * @return the (finally) built up Java class object.
   */
  public JavaClass getJavaClass() {
    int[]        interfaces = getInterfaces();
    Field[]      fields     = getFields();
    Method[]     methods    = getMethods();
    Attribute[]  attributes = getAttributes();

    // Must be last since the above calls may still add something to it
    ConstantPool cp         = this.cp.getFinalConstantPool();
    
    return new JavaClass(class_name_index, superclass_name_index,
			 file_name, major, minor, access_flags,
			 cp, interfaces, fields, methods, attributes);
  }

  /**
   * Add an interface to this class, i.e., this class has to implement it.
   * @param name interface to implement (fully qualified class name)
   */
  public void addInterface(String name) {
    interface_vec.add(name);
  }

  /**
   * Remove an interface from this class.
   * @param name interface to remove (fully qualified name)
   */
  public void removeInterface(String name) {
    interface_vec.remove(name);
  }

  /**
   * @return major version number of class file
   */
  public int  getMajor()      { return major; }

  /** Set major version number of class file, default value is 45 (JDK 1.1)
   * @param major major version number
   */
  public void setMajor(int major) {
    this.major = major;
  }    

  /** Set minor version number of class file, default value is 3 (JDK 1.1)
   * @param minor minor version number
   */
  public void setMinor(int minor) {
    this.minor = minor;
  }    

  /**
   * @return minor version number of class file
   */
  public int  getMinor()      { return minor; }

  /**
   * Add an attribute to this class.
   * @param a attribute to add
   */
  public void addAttribute(Attribute a)    { attribute_vec.add(a); }

  /**
   * Add a method to this class.
   * @param m method to add
   */
  public void addMethod(Method m)          { method_vec.add(m); }

  /**
   * Convenience method.
   *
   * Add an empty constructor to this class that does nothing but calling super().
   * @param access rights for constructor
   */
  public void addEmptyConstructor(int access_flags) {
    InstructionList il = new InstructionList();
    il.append(InstructionConstants.THIS); // Push `this'
    il.append(new INVOKESPECIAL(cp.addMethodref(super_class_name,
						"<init>", "()V")));
    il.append(InstructionConstants.RETURN);

    MethodGen mg = new MethodGen(access_flags, Type.VOID, Type.NO_ARGS, null,
		       "<init>", class_name, il, cp);
    mg.setMaxStack(1);
    addMethod(mg.getMethod());
  }

  /**
   * Add a field to this class.
   * @param f field to add
   */
  public void addField(Field f)            { field_vec.add(f); }

  public boolean containsField(Field f)    { return field_vec.contains(f); }
  
  /** @return field object with given name, or null
   */
  public Field containsField(String name) {
    for(Iterator e=field_vec.iterator(); e.hasNext(); ) {
      Field f = (Field)e.next();
      if(f.getName().equals(name))
	return f;
    }

    return null;
  }

  /** @return method object with given name and signature, or null
   */
  public Method containsMethod(String name, String signature) {
    for(Iterator e=method_vec.iterator(); e.hasNext();) {
      Method m = (Method)e.next();
      if(m.getName().equals(name) && m.getSignature().equals(signature))
	return m;
    }

    return null;
  }

  /**
   * Remove an attribute from this class.
   * @param a attribute to remove
   */
  public void removeAttribute(Attribute a) { attribute_vec.remove(a); }

  /**
   * Remove a method from this class.
   * @param m method to remove
   */
  public void removeMethod(Method m)       { method_vec.remove(m); }

  /** Replace given method with new one. If the old one does not exist
   * add the new_ method to the class anyway.
   */
  public void replaceMethod(Method old, Method new_) {
    if(new_ == null)
      throw new ClassGenException("Replacement method must not be null");

    int i = method_vec.indexOf(old);

    if(i < 0)
      method_vec.add(new_);
    else
      method_vec.set(i, new_);
  }

  /** Replace given field with new one. If the old one does not exist
   * add the new_ field to the class anyway.
   */
  public void replaceField(Field old, Field new_) {
    if(new_ == null)
      throw new ClassGenException("Replacement method must not be null");

    int i = field_vec.indexOf(old);

    if(i < 0)
      field_vec.add(new_);
    else
      field_vec.set(i, new_);
  }

  /**
   * Remove a field to this class.
   * @param f field to remove
   */
  public void removeField(Field f)         { field_vec.remove(f); }

  public String getClassName()      { return class_name; }
  public String getSuperclassName() { return super_class_name; }
  public String getFileName()       { return file_name; }

  public void setClassName(String name) {
    class_name = name.replace('/', '.');
    class_name_index = cp.addClass(name);
  }

  public void setSuperclassName(String name) {
    super_class_name = name.replace('/', '.');
    superclass_name_index = cp.addClass(name);
  }

  public Method[] getMethods() {
    Method[] methods = new Method[method_vec.size()];
    method_vec.toArray(methods);
    return methods;
  }

  public void setMethods(Method[] methods) {
    method_vec.clear();
    for(int m=0; m<methods.length; m++)
      addMethod(methods[m]);
  }

  public void setMethodAt(Method method, int pos) {
    method_vec.set(pos, method);
  }

  public Method getMethodAt(int pos) {
    return (Method)method_vec.get(pos);
  }

  public String[] getInterfaceNames() {
    int      size = interface_vec.size();
    String[] interfaces = new String[size];

    interface_vec.toArray(interfaces);
    return interfaces;
  }

  public int[] getInterfaces() {
    int   size = interface_vec.size();
    int[] interfaces = new int[size];

    for(int i=0; i < size; i++)
      interfaces[i] = cp.addClass((String)interface_vec.get(i));

    return interfaces;
  }

  public Field[] getFields() {
    Field[] fields = new Field[field_vec.size()];
    field_vec.toArray(fields);
    return fields;
  }

  public Attribute[] getAttributes() {
    Attribute[] attributes = new Attribute[attribute_vec.size()];
    attribute_vec.toArray(attributes);
    return attributes;
  }

  public ConstantPoolGen getConstantPool() { return cp; }
  public void setConstantPool(ConstantPoolGen constant_pool) {
    cp = constant_pool;
  }    

  public void setClassNameIndex(int class_name_index) {
    this.class_name_index = class_name_index;
    class_name = cp.getConstantPool().
      getConstantString(class_name_index, Constants.CONSTANT_Class).replace('/', '.');
  }

  public void setSuperclassNameIndex(int superclass_name_index) {
    this.superclass_name_index = superclass_name_index;
    super_class_name = cp.getConstantPool().
      getConstantString(superclass_name_index, Constants.CONSTANT_Class).replace('/', '.');
  }

  public int getSuperclassNameIndex() { return superclass_name_index; }    

  public int getClassNameIndex()   { return class_name_index; }

  private ArrayList<ClassObserver> observers;

  /** Add observer for this object.
   */
  public void addObserver(ClassObserver o) {
    if(observers == null)
      observers = new ArrayList<ClassObserver>();

    observers.add(o);
  }

  /** Remove observer for this object.
   */
  public void removeObserver(ClassObserver o) {
    if(observers != null)
      observers.remove(o);
  }

  /** Call notify() method on all observers. This method is not called
   * automatically whenever the state has changed, but has to be
   * called by the user after he has finished editing the object.
   */
  public void update() {
    if(observers != null)
      for(Iterator e = observers.iterator(); e.hasNext(); )
	((ClassObserver)e.next()).notify(this);
  }

  public Object clone() {
    try {
      return super.clone();
    } catch(CloneNotSupportedException e) {
      System.err.println(e);
      return null;
    }
  }
}
