/**
 * Redistribution and use of this software and associated documentation ("Software"), with or
 * without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
 * must also contain a copy of this document.
 *
 * 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 name "Exolab" must not be used to endorse or promote products derived from this Software
 * without prior written permission of Intalio, Inc. For written permission, please contact
 * info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
 * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
 * Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``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 INTALIO, INC. 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.
 *
 * Copyright 2001-2002 (C) Intalio, Inc. All Rights Reserved.
 */
package org.exolab.javasource;

import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Vector;

/**
 * A representation of the Java Source code for a Java Interface. This is a useful utility when
 * creating in memory source code. The code in this package was modeled after the Java Reflection
 * API as much as possible to reduce the learning curve.
 *
 * @author <a href="mailto:skopp AT riege DOT de">Martin Skopp</a>
 * @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a>
 * @version $Revision$ $Date: 2005-02-26 17:30:28 -0700 (Sat, 26 Feb 2005) $
 */
public final class JInterface extends JStructure {

  /** The fields for this JInterface. */
  private final Map<String, JField> _fields = new LinkedHashMap<String, JField>();

  /** The list of methods of this JInterface. */
  private final Vector<JMethodSignature> _methods = new Vector<JMethodSignature>();

  /**
   * Creates a new JInterface with the given name.
   *
   * @param name The name of the JInterface.
   */
  public JInterface(final String name) {
    super(name);

    // -- initialize default Java doc
    getJDocComment().appendComment("Interface " + getLocalName() + ".");
  }

  /**
   * {@inheritDoc}
   */
  public void addImport(final String className) {
    if (className == null || className.isEmpty()) {
      return;
    }
    addImportInternal(className);
  }

  /**
   * Adds the given JMember to this Jinterface.
   * <p>
   * This method is implemented by subclasses and should only accept the proper types for the
   * subclass otherwise an IllegalArgumentException will be thrown.
   *
   * @param jMember The JMember to add to this JStructure.
   */
  public void addMember(final JMember jMember) {
    if (jMember == null) {
      throw new IllegalArgumentException("argument 'jMember' may not be null.");
    }
    if (jMember instanceof JField) {
      addField((JField) jMember);
    } else {
      throw new IllegalArgumentException("invalid member for JInterface: " + jMember);
    }
  }

  /**
   * Returns an array of all the JFields of this Jinterface.
   *
   * @return An array of all the JFields of this Jinterface.
   */
  public JField[] getFields() {
    return _fields.values().toArray(new JField[_fields.size()]);
  }

  /**
   * Returns the field with the given name, or null if no field was found with the given name.
   *
   * @param name The name of the field to return.
   * @return The field with the given name, or null if no field was found with the given name.
   */
  public JField getField(final String name) {
    return _fields.get(name);
  }

  /**
   * Adds the given JField to this Jinterface.
   * <p>
   * This method is implemented by subclasses and should only accept the proper fields for the
   * subclass otherwise an IllegalArgumentException will be thrown. For example a JInterface will
   * only accept static fields.
   *
   * @param jField The JField to add.
   */
  public void addField(final JField jField) {
    if (jField == null) {
      throw new IllegalArgumentException("argument 'jField' cannot be null");
    }

    String name = jField.getName();

    // -- check for duplicate field name
    if ((_fields != null) && (_fields.get(name) != null)) {
      String err = "duplicate name found: " + name;
      throw new IllegalArgumentException(err);
    }

    // -- check for proper modifiers
    JModifiers modifiers = jField.getModifiers();
    if (!modifiers.isStatic()) {
      String err = "Fields added to a JInterface must be static.";
      throw new IllegalArgumentException(err);
    }
    if (modifiers.isPrivate()) {
      String err = "Fields added to a JInterface must not be private.";
      throw new IllegalArgumentException(err);
    }

    _fields.put(name, jField);

    // if member is of a type not imported by this class
    // then add import
    JType type = jField.getType();
    while (type.isArray()) {
      type = ((JArrayType) type).getComponentType();
    }
    if (!type.isPrimitive()) {
      addImport(((JClass) type).getName());
    }

    // ensure annotation classes are imported
    addImport(jField.getAnnotations());
  }

  /**
   * Returns an array of all the JMethodSignatures of this JInterface.
   *
   * @return An array of all the JMethodSignatures of this JInterface.
   */
  public JMethodSignature[] getMethods() {
    return _methods.toArray(new JMethodSignature[_methods.size()]);
  }

  /**
   * Returns the JMethodSignature with the given name and occuring at or after the given starting
   * index.
   *
   * @param name The name of the JMethodSignature to return.
   * @param startIndex The starting index to begin searching from.
   * @return The JMethodSignature, or null if not found.
   */
  public JMethodSignature getMethod(final String name, final int startIndex) {
    for (JMethodSignature jMethod : _methods) {
      if (jMethod.getName().equals(name)) {
        return jMethod;
      }
    }
    return null;
  }

  /**
   * Returns the JMethodSignature at the given index.
   *
   * @param index The index of the JMethodSignature to return.
   * @return The JMethodSignature at the given index.
   */
  public JMethodSignature getMethod(final int index) {
    return _methods.elementAt(index);
  }

  /**
   * Adds the given JMethodSignature to this Jinterface.
   *
   * @param jMethodSig The JMethodSignature to add.
   */
  public void addMethod(final JMethodSignature jMethodSig) {
    if (jMethodSig == null) {
      String err = "The JMethodSignature cannot be null.";
      throw new IllegalArgumentException(err);
    }

    // -- check method name and signatures *add later*

    // -- keep method list sorted for esthetics when printing
    // -- START SORT :-)
    boolean added = false;
    JModifiers modifiers = jMethodSig.getModifiers();
    for (int i = 0; i < _methods.size(); i++) {
      JMethodSignature tmp = _methods.elementAt(i);
      // -- first compare modifiers
      if (tmp.getModifiers().isProtected() && !modifiers.isProtected()) {
        _methods.insertElementAt(jMethodSig, i);
        added = true;
        break;
      }
      // -- compare names
      if (jMethodSig.getName().compareTo(tmp.getName()) < 0) {
        _methods.insertElementAt(jMethodSig, i);
        added = true;
        break;
      }
    }
    // -- END SORT
    if (!added) {
      _methods.add(jMethodSig);
    }

    // -- check parameter packages to make sure we have them
    // -- in our import list

    String[] pkgNames = jMethodSig.getParameterClassNames();
    for (String pkgName : pkgNames) {
      addImport(pkgName);
    }
    // -- check return type to make sure it's included in the
    // -- import list
    JType jType = jMethodSig.getReturnType();
    if (jType != null) {
      while (jType.isArray()) {
        jType = ((JArrayType) jType).getComponentType();
      }
      if (!jType.isPrimitive()) {
        addImport(jType.getName());
      }
    }
    // -- check exceptions
    JClass[] exceptions = jMethodSig.getExceptions();
    for (JClass exc : exceptions) {
      addImport(exc.getName());
    }
    // -- ensure method and parameter annotations imported
    addImport(jMethodSig.getAnnotations());
    JParameter[] params = jMethodSig.getParameters();
    for (JParameter param : params) {
      addImport(param.getAnnotations());
    }
  }

  /**
   * {@inheritDoc}
   * 
   * @deprecated Please use the Velocity-template based approach instead.
   * @see SourceGenerator#setJClassPrinterType(String)
   */
  public void print(final JSourceWriter jsw) {
    print(jsw, false);
  }

  /**
   * Prints the source code for this JInterface to the given JSourceWriter.
   *
   * @param jsw The JSourceWriter to print to. Must not be null.
   * @param classOnly If true, generates the class body without the class header, package
   *        declaration, or imports.
   * 
   * @deprecated Please use the Velocity-template based approach instead.
   * @see SourceGenerator#setJClassPrinterType(String)
   */
  public void print(final JSourceWriter jsw, final boolean classOnly) {
    if (jsw == null) {
      throw new IllegalArgumentException("argument 'jsw' should not be null.");
    }

    StringBuilder buffer = new StringBuilder(100);

    if (!classOnly) {
      printHeader(jsw);
      printPackageDeclaration(jsw);
      printImportDeclarations(jsw);
    }

    getJDocComment().print(jsw);

    getAnnotatedElementHelper().printAnnotations(jsw);

    JModifiers modifiers = getModifiers();
    if (modifiers.isPrivate()) {
      buffer.append("private ");
    } else if (modifiers.isPublic()) {
      buffer.append("public ");
    }

    if (modifiers.isAbstract()) {
      buffer.append("abstract ");
    }

    buffer.append("interface ").append(getLocalName()).append(' ');
    if (getInterfaceCount() > 0) {
      Enumeration<String> enumeration = getInterfaces();
      boolean endl = false;
      if (getInterfaceCount() > 1) {
        jsw.writeln(buffer.toString());
        buffer.setLength(0);
        endl = true;
      }
      buffer.append("extends ");
      while (enumeration.hasMoreElements()) {
        buffer.append(enumeration.nextElement());
        if (enumeration.hasMoreElements()) {
          buffer.append(", ");
        }
      }
      if (endl) {
        jsw.writeln(buffer.toString());
        buffer.setLength(0);
      } else {
        buffer.append(' ');
      }
    }

    buffer.append('{');
    jsw.writeln(buffer.toString());
    buffer.setLength(0);
    jsw.writeln();

    jsw.indent();

    // -- declare static members

    if (!_fields.isEmpty()) {
      jsw.writeln();
      jsw.writeln("  //--------------------------/");
      jsw.writeln(" //- Class/Member Variables -/");
      jsw.writeln("//--------------------------/");
      jsw.writeln();
    }

    for (JField jField : _fields.values()) {

      // -- print Java comment
      JDocComment comment = jField.getComment();
      if (comment != null) {
        comment.print(jsw);
      }

      // -- annotations
      jField.printAnnotations(jsw);

      // -- print member
      jsw.write(jField.getModifiers().toString());
      jsw.write(' ');

      JType type = jField.getType();
      String typeName = type.toString();
      // -- for esthetics use short name in some cases
      if (typeName.equals(toString())) {
        typeName = type.getLocalName();
      }
      jsw.write(typeName);
      jsw.write(' ');
      jsw.write(jField.getName());

      String init = jField.getInitString();
      if (init != null) {
        jsw.write(" = ");
        jsw.write(init);
      }

      jsw.writeln(';');
      jsw.writeln();
    }

    // -- print method signatures

    if (!_methods.isEmpty()) {
      jsw.writeln();
      jsw.writeln("  //-----------/");
      jsw.writeln(" //- Methods -/");
      jsw.writeln("//-----------/");
      jsw.writeln();
    }

    for (JMethodSignature signature : _methods) {
      signature.print(jsw);
      jsw.writeln(';');
    }

    jsw.unindent();
    jsw.writeln('}');
    jsw.flush();
    jsw.close();
  }

}
