/*
 * Copyright 2004,2004 The Apache Software Foundation.
 * 
 * Licensed 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.
 */

package org.apache.bsf.util;

import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;

import org.apache.bsf.util.cf.CodeFormatter;

/**
 * A <code>CodeBuffer</code> object is used as a code repository for generated Java code.
 * It provides buffers which correspond to the various sections of a Java class.
 * 
 * @author   Matthew J. Duftler
 */
public class CodeBuffer
{
  private StringWriter fieldDeclSW       = new StringWriter(),
					   methodDeclSW      = new StringWriter(),
					   initializerSW     = new StringWriter(),
					   constructorSW     = new StringWriter(),
					   serviceMethodSW   = new StringWriter();

  private PrintWriter  fieldDeclPW       = new PrintWriter(fieldDeclSW),
					   methodDeclPW      = new PrintWriter(methodDeclSW),
					   initializerPW     = new PrintWriter(initializerSW),
					   constructorPW     = new PrintWriter(constructorSW),
					   serviceMethodPW   = new PrintWriter(serviceMethodSW);

  private Stack        symbolTableStack  = new Stack();
  private Hashtable    symbolTable       = new Hashtable(),
					   usedSymbolIndices = new Hashtable();

  private ObjInfo      finalStatementInfo;
  private CodeBuffer   parent;


  {
	symbolTableStack.push(symbolTable);
  }

  // New stuff...
  private Vector imports                 = new Vector(),
				 constructorArguments    = new Vector(),
				 constructorExceptions   = new Vector(),
				 serviceMethodExceptions = new Vector(),
				 implementsVector        = new Vector();
  private String packageName             = null,
				 className               = "Test",
				 serviceMethodName       = "exec",
				 extendsName             = null;
  private Class  serviceMethodReturnType = void.class;

  public CodeBuffer()
  {
  }
  public CodeBuffer(CodeBuffer parent)
  {
	this.parent = parent;
  }
  public void addConstructorArgument(ObjInfo arg)
  {
	constructorArguments.addElement(arg);
  }
  public void addConstructorException(String exceptionName)
  {
	if (!constructorExceptions.contains(exceptionName))
	{
	  constructorExceptions.addElement(exceptionName);
	}
  }
  public void addConstructorStatement(String statement)
  {
	constructorPW.println(statement);
  }
  public void addFieldDeclaration(String statement)
  {
	fieldDeclPW.println(statement);
  }
  public void addImplements(String importName)
  {
	if (!implementsVector.contains(importName))
	{
	  implementsVector.addElement(importName);
	}
  }
  public void addImport(String importName)
  {
	if (!imports.contains(importName))
	{
	  imports.addElement(importName);
	}
  }
  public void addInitializerStatement(String statement)
  {
	initializerPW.println(statement);
  }
  public void addMethodDeclaration(String statement)
  {
	methodDeclPW.println(statement);
  }
  public void addServiceMethodException(String exceptionName)
  {
	if (!serviceMethodExceptions.contains(exceptionName))
	{
	  serviceMethodExceptions.addElement(exceptionName);
	}
  }
  public void addServiceMethodStatement(String statement)
  {
	serviceMethodPW.println(statement);
  }
  // Used internally by merge(...).
  private void appendIfNecessary(PrintWriter pw, StringBuffer buf)
  {
	if (buf.length() > 0)
	{
	  pw.print(buf.toString());
	}
  }
  public String buildNewSymbol(String prefix)
  {
	Integer nextNum = getSymbolIndex(prefix);

	if (nextNum == null)
	{
	  nextNum = new Integer(0);
	}

	int    iNextNum = nextNum.intValue();
	String symbol   = prefix + "_" + iNextNum;

	while (getSymbol(symbol) != null)
	{
	  iNextNum++;
	  symbol = prefix + "_" + iNextNum;
	}

	putSymbolIndex(prefix, new Integer(iNextNum + 1));

	return symbol;
  }
  public void clearSymbolTable()
  {
	symbolTable       = new Hashtable();
	symbolTableStack  = new Stack();
	symbolTableStack.push(symbolTable);

	usedSymbolIndices = new Hashtable();
  }
  public String getClassName()
  {
	return className;
  }
  public Vector getConstructorArguments()
  {
	return constructorArguments;
  }
  public StringBuffer getConstructorBuffer()
  {
	constructorPW.flush();

	return constructorSW.getBuffer();
  }
  public Vector getConstructorExceptions()
  {
	return constructorExceptions;
  }
  public String getExtends()
  {
	return extendsName;
  }
  public StringBuffer getFieldBuffer()
  {
	fieldDeclPW.flush();

	return fieldDeclSW.getBuffer();
  }
  public ObjInfo getFinalServiceMethodStatement()
  {
	return finalStatementInfo;
  }
  public Vector getImplements()
  {
	return implementsVector;
  }
  public Vector getImports()
  {
	return imports;
  }
  public StringBuffer getInitializerBuffer()
  {
	initializerPW.flush();

	return initializerSW.getBuffer();
  }
  public StringBuffer getMethodBuffer()
  {
	methodDeclPW.flush();

	return methodDeclSW.getBuffer();
  }
  public String getPackageName()
  {
	return packageName;
  }
  public StringBuffer getServiceMethodBuffer()
  {
	serviceMethodPW.flush();

	return serviceMethodSW.getBuffer();
  }
  public Vector getServiceMethodExceptions()
  {
	return serviceMethodExceptions;
  }
  public String getServiceMethodName()
  {
	return serviceMethodName;
  }
  public Class getServiceMethodReturnType()
  {
	if (finalStatementInfo != null)
	{
	  return finalStatementInfo.objClass;
	}
	else if (serviceMethodReturnType != null)
	{
	  return serviceMethodReturnType;
	}
	else
	{
	  return void.class;
	}
  }
  public ObjInfo getSymbol(String symbol)
  {
	ObjInfo ret = (ObjInfo)symbolTable.get(symbol);

	if (ret == null && parent != null)
	  ret = parent.getSymbol(symbol);

	return ret;
  }
  Integer getSymbolIndex(String prefix)
  {
	if (parent != null)
	{
	  return parent.getSymbolIndex(prefix);
	}
	else
	{
	  return (Integer)usedSymbolIndices.get(prefix);
	}
  }
  public Hashtable getSymbolTable()
  {
	return symbolTable;
  }
  public void merge(CodeBuffer otherCB)
  {
	Vector otherImports = otherCB.getImports();

	for (int i = 0; i < otherImports.size(); i++)
	{
	  addImport((String)otherImports.elementAt(i));
	}

	appendIfNecessary(fieldDeclPW,     otherCB.getFieldBuffer());
	appendIfNecessary(methodDeclPW,    otherCB.getMethodBuffer());
	appendIfNecessary(initializerPW,   otherCB.getInitializerBuffer());
	appendIfNecessary(constructorPW,   otherCB.getConstructorBuffer());
	appendIfNecessary(serviceMethodPW, otherCB.getServiceMethodBuffer());

	ObjInfo oldRet = getFinalServiceMethodStatement();

	if (oldRet != null && oldRet.isExecutable())
	{
	  addServiceMethodStatement(oldRet.objName + ";");
	}

	setFinalServiceMethodStatement(otherCB.getFinalServiceMethodStatement());
  }
  public void popSymbolTable()
  {
	symbolTableStack.pop();
	symbolTable = (Hashtable)symbolTableStack.peek();
  }
  public void print(PrintWriter out, boolean formatOutput)
  {
	if (formatOutput)
	{
	  new CodeFormatter().formatCode(new StringReader(toString()), out);
	}
	else
	{
	  out.print(toString());
	}

	out.flush();
  }
  public void pushSymbolTable()
  {
	symbolTable = (Hashtable)symbolTableStack.push(new ScriptSymbolTable(symbolTable));
  }
  public void putSymbol(String symbol, ObjInfo obj)
  {
	symbolTable.put(symbol, obj);
  }
  void putSymbolIndex(String prefix, Integer index)
  {
	if (parent != null)
	{
	  parent.putSymbolIndex(prefix, index);
	}
	else
	{
	  usedSymbolIndices.put(prefix, index);
	}
  }
  public void setClassName(String className)
  {
	this.className = className;
  }
  public void setExtends(String extendsName)
  {
	this.extendsName = extendsName;
  }
  public void setFinalServiceMethodStatement(ObjInfo finalStatementInfo)
  {
	this.finalStatementInfo = finalStatementInfo;
  }
  public void setPackageName(String packageName)
  {
	this.packageName = packageName;
  }
  public void setServiceMethodName(String serviceMethodName)
  {
	this.serviceMethodName = serviceMethodName;
  }
  public void setServiceMethodReturnType(Class serviceMethodReturnType)
  {
	this.serviceMethodReturnType = serviceMethodReturnType;
  }
  public void setSymbolTable(Hashtable symbolTable)
  {
	this.symbolTable = symbolTable;
  }
  public boolean symbolTableIsStacked()
  {
	return (symbolTable instanceof ScriptSymbolTable);
  }
  public String toString()
  {
	StringWriter sw  = new StringWriter();
	PrintWriter  pw  = new PrintWriter(sw);
	ObjInfo      ret = finalStatementInfo;

	if (packageName != null && !packageName.equals(""))
	{
	  pw.println("package " + packageName + ";");
	  pw.println();
	}

	if (imports.size() > 0)
	{
	  for (int i = 0; i < imports.size(); i++)
	  {
		pw.println("import " + imports.elementAt(i) + ";");
	  }

	  pw.println();
	}

	pw.println("public class " + className +
			   (extendsName != null && !extendsName.equals("")
				? " extends " + extendsName
				: "") +
			   (implementsVector.size() > 0
				? " implements " +
				  StringUtils.getCommaListFromVector(implementsVector)
				: "")
			  );
	pw.println("{");

	pw.print(getFieldBuffer().toString());

	StringBuffer buf = getInitializerBuffer();

	if (buf.length() > 0)
	{
	  pw.println();
	  pw.println("{");
	  pw.print(buf.toString());
	  pw.println("}");
	}

	buf = getConstructorBuffer();

	if (buf.length() > 0)
	{
	  pw.println();
	  pw.println("public " + className + "(" +
				 (constructorArguments.size() > 0
				  ? StringUtils.getCommaListFromVector(constructorArguments)
				  : ""
				 ) + ")" +
				 (constructorExceptions.size() > 0
				  ? " throws " +
					StringUtils.getCommaListFromVector(constructorExceptions)
				  : ""
				 )
				);
	  pw.println("{");
	  pw.print(buf.toString());
	  pw.println("}");
	}

	buf = getServiceMethodBuffer();

	if (buf.length() > 0 || ret != null)
	{
	  pw.println();
	  pw.println("public " +
				  StringUtils.getClassName(getServiceMethodReturnType()) + " " +
				  serviceMethodName + "()" +
				 (serviceMethodExceptions.size() > 0
				  ? " throws " +
					StringUtils.getCommaListFromVector(serviceMethodExceptions)
				  : ""
				 )
				);
	  pw.println("{");

	  pw.print(buf.toString());

	  if (ret != null)
	  {
		if (ret.isValueReturning())
		{
		  pw.println();
		  pw.println("return " + ret.objName + ";");
		}
		else if (ret.isExecutable())
		{
		  pw.println(ret.objName + ";");
		}
	  }

	  pw.println("}");
	}

	pw.print(getMethodBuffer().toString());

	pw.println("}");

	pw.flush();

	return sw.toString();
  }
}
