/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/*
 * IOJavaClassWriter.java
 *
 * Created on November 12, 2001, 4:59 PM
 */

package com.sun.jdo.spi.persistence.utility.generator.io;

import java.util.*;
import java.lang.reflect.Modifier;

import com.sun.jdo.spi.persistence.utility.StringHelper;
import com.sun.jdo.spi.persistence.utility.generator.JavaClassWriter;

/**
 * This implementation of the {@link JavaClassWriter} interface is based on 
 * simple {@link java.lang.StringBuffer} "println" type statements.
 * <p>
 * The order of the generated code in this implementation depends on the 
 * initialization.  The default order is to accept fields and methods in any
 * order and generate all fields together and all methods together (by member 
 * category).  Specifying the ordered parameter in the constructor will lead 
 * to a slightly different order: generation of fields and methods 
 * interspersed among each other in exactly the order they were added (no 
 * member categories).
 *
 * @author raccah
 */
public final class IOJavaClassWriter implements JavaClassWriter
{
	private static final String FIELD = "FIELD";				// NOI18N
	private static final String INITIALIZER = "INITIALIZER";	// NOI18N
	private static final String CONSTRUCTOR = "CONSTRUCTOR";	// NOI18N
	private static final String METHOD = "METHOD";				// NOI18N
	private static final String INNER_CLASS = "INNER_CLASS";	// NOI18N
	private static final String MIXED = "MIXED";				// NOI18N
	private static final String COMMA_SEPARATOR = ", ";			// NOI18N

	private boolean _maintainCategories;
	private String _superclass;
	private String _classDeclarationBlock;
	private List _interfaces = new ArrayList();
	private Map _members = new HashMap();

	/** Creates a new instance of IOJavaClassWriter which maintains the 
	 * order of member input but groups them by category.
	 * @see #IOJavaClassWriter(boolean)
	 */	
	public IOJavaClassWriter ()
	{
		this(true);
	}

	/** Creates a new instance of IOJavaClassWriter in which the order of the 
	 * generated code depends on the specified flag.
	 * @param maintainCategories If <code>true</code>, the order of members is 
	 * preserved within category groups.  If <code>false</code>, the 
	 * generation of fields and methods will be interspersed among each other 
	 * in exactly the order they were added with no member categories.
	 */	
	public IOJavaClassWriter (boolean maintainCategories)
	{
		_maintainCategories = maintainCategories;
	}

	/** Sets the information for the class declaration including modifiers,
	 * name, and comments.  Note that the name must not be fully qualified.
	 * @param modifiers The modifier flags for this class.
	 * @param className The (non-qualified) name of this class.
	 * @param comments The comments shown just above the class declaration.
	 * The comments are passed as an array so the line separators can be added
	 * by the implementation.  Note that not all implementations will choose
	 * to make use of this comment.
	 * @see java.lang.reflect.Modifier
	 */	
	public void setClassDeclaration (final int modifiers, 
		final String className, final String[] comments)
	{
		final FormattedWriter writerHelper =  new FormattedWriter();
		final String modifierString = Modifier.toString(modifiers);

		writerHelper.writeComments(comments);

		writerHelper.writeln(modifierString + ((modifierString.length() > 0)
			? " " : "") + "class " + className);	// NOI18N

		_classDeclarationBlock = writerHelper.toString();
	}

	/** Sets the superclass of this class.  Note that the name format must 
	 * be package style (that is - it can contain . but not / or $).
	 * @param name The name of the superclass.
	 */	
	public void setSuperclass (final String name)
	{
		_superclass = name;
	}

	/** Adds an interface to the list of those implemented by this class.
	 * @param name The name of the interface.
	 */	
	public void addInterface (final String name)
	{
		if (!StringHelper.isEmpty(name))
			_interfaces.add(name);
	}

	/** Adds a field to the list of those declared by this class.  Note 
	 * that the type format must be package style (that is - it can contain
	 * . but not / or $).
	 * @param name The name of the field.
	 * @param modifiers The modifier flags for this field.
	 * @param type A string representing the type of this field.
	 * @param initialValue A string representing the initial value of
	 * this field.
	 * @param comments The comments shown just above the declaration of
	 * this field.  The comments are passed as an array so the line
	 * separators can be added by the implementation.  Note that not all
	 * implementations will choose to make use of this comment.
	 * @see java.lang.reflect.Modifier
	 */ 
	public void addField (final String name, final int modifiers, String type,
		final String initialValue, final String[] comments)
	{
		final FormattedWriter writerHelper = new FormattedWriter();
		final String fieldString = 
			Modifier.toString(modifiers) + ' ' + type + ' ' + name;

		writerHelper.writeComments(comments);
		writerHelper.writeln(fieldString + ((initialValue != null) ?
			(" = " + initialValue) : "") + ';');		// NOI18N

		getMemberList(FIELD).add(writerHelper.toString());
	}

	/** Adds an initializer to this class.
	 * @param isStatic True if this is a static initializer, false otherwise.
	 * @param body The implementation block of the initializer.  The body of
	 * the implementation is passed as an array so the line separators can
	 * be added by the implementation.
	 * @param comments The comments shown just above the initializer block.
	 * The comments are passed as an array so the line separators can be added
	 * by the implementation.  Note that not all implementations will choose
	 * to make use of this comment.
	 */	
	public void addInitializer (boolean isStatic, String[] body,
		String[] comments)
	{
		final FormattedWriter writerHelper = new FormattedWriter();
		final int n = (body != null ? body.length : 0);

		writerHelper.writeComments(comments);

		// header
		writerHelper.writeln(isStatic ? "static" : "");		// NOI18N
		writerHelper.writeln("{");							// NOI18N

		// implementation
		for (int i = 0; i < n; i++)
			writerHelper.writeln(1, body[i]);

		// end
		writerHelper.writeln("}");							// NOI18N

		getMemberList(INITIALIZER).add(writerHelper.toString());
	}

	/** Adds a constructor to this class.  Note that the type format in the 
	 * parameter type strings must be package style (that is - it can contain
	 * . but not / or $).
	 * @param name The name of the constructor - should be the same as the
	 * name of the class.
	 * @param modifiers The modifier flags for this constructor.
	 * @param parameterNames A list of parameter names.
	 * @param parameterTypes A list of parameter types.
	 * @param exceptions A list of exceptions.
	 * @param body The implementation block of the constructor.  The body of
	 * the implementation is passed as an array so the line separators can
	 * be added by the implementation.
	 * @param comments The comments shown just above the constructor.  The
	 * comments are passed as an array so the line separators can be added
	 * by the implementation.  Note that not all implementations will choose
	 * to make use of this comment.
	 * @see java.lang.reflect.Modifier
	 */	
	public void addConstructor (final String name, final int modifiers,
		final String[] parameterNames, final String[] parameterTypes,
		final String[] exceptions, final String[] body, final String[] comments)
	{
		addMethod(name, modifiers, null, parameterNames, parameterTypes, 
			exceptions, body, comments, getMemberList(CONSTRUCTOR));
	}

	/** Adds a method to this class.  Note that the type format in the 
	 * return type and parameter type strings must be package style 
	 * (that is - it can contain . but not / or $).
	 * @param name The name of the method.
	 * @param modifiers The modifier flags for this method.
	 * @param returnType A string representing the return type of this method.
	 * @param parameterNames A list of parameter names.
	 * @param parameterTypes A list of parameter types.
	 * @param exceptions A list of exceptions.
	 * @param body The implementation block of the method.  The body of
	 * the implementation is passed as an array so the line separators can
	 * be added by the implementation.
	 * @param comments The comments shown just above the method.  The
	 * comments are passed as an array so the line separators can be added
	 * by the implementation.  Note that not all implementations will choose
	 * to make use of this comment.
	 * @see java.lang.reflect.Modifier
	 */	
	public void addMethod (final String name, final int modifiers,
		final String returnType, final String[] parameterNames,
		final String[] parameterTypes, final String[] exceptions,
		final String[] body, final String[] comments)
	{
		addMethod(name, modifiers, returnType, parameterNames, parameterTypes, 
			exceptions, body, comments, getMemberList(METHOD));
	}

	/** Adds an inner class to this class.
	 * @param classWriter The definition of the inner class.
	 */	
	public void addClass (final JavaClassWriter classWriter)
	{
		if (classWriter != null)
			getMemberList(INNER_CLASS).add(classWriter);
	}

	/** Returns a string representation of this object.
	 * @return The string representation of the generated class.
	 */	
	public String toString ()
	{
		final FormattedWriter writerHelper =  new FormattedWriter();

		writeClassDeclaration(writerHelper);		// class declaration
		writeMembers(writerHelper);					// members
		writerHelper.writeln("}");		// NOI18N	// closing
		writerHelper.writeln();

		return writerHelper.toString();
	}

	private void addMethod (final String name, final int modifiers,
		final String returnType, final String[] parameterNames,
		final String[] parameterTypes, final String[] exceptions,
		final String[] body, final String[] comments, List methodList)
	{
		final String signature = createMethodSignature(name, modifiers, 
			returnType, parameterNames, parameterTypes, exceptions);
		final FormattedWriter writerHelper =  new FormattedWriter();
		final int n = (body != null ? body.length : 0);

		writerHelper.writeComments(comments);

		// signature==null if we have an instance initializer
		if (signature.length() > 0)
			writerHelper.writeln(signature);

		writerHelper.writeln("{");					// NOI18N

		// implementation
		for (int i = 0; i < n; i++)
			writerHelper.writeln(1, body[i]);

		// end
		writerHelper.writeln("}");					// NOI18N

		methodList.add(writerHelper.toString());
	}

	static private String createMethodSignature (final String name,
		final int modifiers, String returnType, final String[] parameterNames,
		final String[] parameterTypes, final String[] exceptions)
	{
		int i, count = (parameterNames != null ? parameterNames.length : 0);
		final FormattedWriter writerHelper =  new FormattedWriter();

		if (modifiers != 0)
			writerHelper.write(Modifier.toString(modifiers) + ' ');

		writerHelper.write(((returnType != null) ? 
			returnType + " " : "") + name);			// NOI18N

		// parameters
		writerHelper.write(" (");						// NOI18N

		for (i = 0; i < count; i++)
		{
			writeListElement(i, count, parameterTypes[i] + ' ' + 
				parameterNames[i], writerHelper);
		}
		writerHelper.write(")");						// NOI18N

		// exceptions
		count = (exceptions != null ? exceptions.length : 0);
		if (count > 0)
		{
			writerHelper.writeln();
			writerHelper.write(1, "throws ");				// NOI18N

			for (i = 0; i < count; i++)
				writeListElement(i, count, exceptions[i], writerHelper);
		}

		return writerHelper.toString();
	}

	static private void writeListElement (int i, int count, String string, 
		FormattedWriter writerHelper)
	{
		int indent = ((i == 0) ? 0 : 1);

		if (i == (count - 1))
			writerHelper.write(indent, string);	
		else
			writerHelper.writeln(indent, string + COMMA_SEPARATOR);
	}

	private List getMemberList (String memberType)
	{
		Object memberList = null;

		if (!_maintainCategories)
			memberType = MIXED;

		memberList = _members.get(memberType);
		if (memberList == null)
		{
			memberList = new ArrayList();

			_members.put(memberType, memberList);
		}

		return (List)memberList;
	}

	private void writeClassDeclaration (FormattedWriter writerHelper)
	{
		// class declaration block
		writerHelper.write(_classDeclarationBlock);

		// extends
		if (_superclass != null)
			writerHelper.writeln(1, "extends " + _superclass);	// NOI18N

		// implements
		if ((_interfaces != null) &&  (_interfaces.size() > 0))
		{
			writerHelper.write(1, "implements ");				// NOI18N
			writerHelper.write(StringHelper.arrayToSeparatedList(
				_interfaces, COMMA_SEPARATOR));
			writerHelper.writeln();
		}

		writerHelper.writeln("{");								// NOI18N
	}

	private void writeMembers (FormattedWriter writerHelper)
	{
		if (_maintainCategories)
		{
			writerHelper.writeList(1, getMemberList(FIELD));
			writerHelper.writeList(1, getMemberList(INITIALIZER));
			writerHelper.writeList(1, getMemberList(CONSTRUCTOR));
			writerHelper.writeList(1, getMemberList(METHOD));
			writerHelper.writeList(1, getMemberList(INNER_CLASS));
		}
		else
			writerHelper.writeList(1, getMemberList(MIXED), true);
	}
}
