/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.
 */
/*
 * $Id$
 */
package org.apache.xalan.templates;

import java.util.StringTokenizer;
import java.util.Vector;

import javax.xml.transform.TransformerException;

import org.apache.xalan.processor.StylesheetHandler;
import org.apache.xalan.res.XSLMessages;
import org.apache.xalan.res.XSLTErrorResources;
import org.apache.xml.utils.FastStringBuffer;
import org.apache.xml.utils.StringBufferPool;
import org.apache.xpath.XPath;
import org.apache.xpath.XPathContext;

/**
 * Class to hold an Attribute Value Template.
 * @xsl.usage advanced
 */
public class AVT implements java.io.Serializable, XSLTVisitable
{
    static final long serialVersionUID = 5167607155517042691L;
  
  /**
    *We are not going to use the object pool if USE_OBJECT_POOL == false.
  */
  private final static boolean USE_OBJECT_POOL = false; 
  
  /**
    * INIT_BUFFER_CHUNK_BITS is used to set initial size of
    * of the char m_array in FastStringBuffer if USE_OBJECT_POOL == false. 
    * size = 2^ INIT_BUFFER_CHUNK_BITS, INIT_BUFFER_CHUNK_BITS = 7 
    * corresponds size = 256. 
  */
  private final static int INIT_BUFFER_CHUNK_BITS = 8; 
  
  /**
   * If the AVT is not complex, just hold the simple string.
   * @serial
   */
  private String m_simpleString = null;

  /**
   * If the AVT is complex, hold a Vector of AVTParts.
   * @serial
   */
  private Vector m_parts = null;
  
   

  /**
   * The name of the attribute.
   * @serial
   */
  private String m_rawName;

  /**
   * Get the raw name of the attribute, with the prefix unprocessed.
   *
   * @return non-null reference to prefixed name.
   */
  public String getRawName()
  {
    return m_rawName;
  }

  /**
   * Get the raw name of the attribute, with the prefix unprocessed.
   *
   * @param rawName non-null reference to prefixed name.
   */
  public void setRawName(String rawName)
  {
    m_rawName = rawName;
  }

  /**
   * The name of the attribute.
   * @serial
   */
  private String m_name;

  /**
   * Get the local name of the attribute.
   *
   * @return non-null reference to name string.
   */
  public String getName()
  {
    return m_name;
  }

  /**
   * Set the local name of the attribute.
   *
   * @param name non-null reference to name string.
   */
  public void setName(String name)
  {
    m_name = name;
  }

  /**
   * The namespace URI of the owning attribute.
   * @serial
   */
  private String m_uri;

  /**
   * Get the namespace URI of the attribute.
   *
   * @return non-null reference to URI, "" if null namespace.
   */
  public String getURI()
  {
    return m_uri;
  }

  /**
   * Get the namespace URI of the attribute.
   *
   * @param uri non-null reference to URI, "" if null namespace.
   */
  public void setURI(String uri)
  {
    m_uri = uri;
  }

  /**
   * Construct an AVT by parsing the string, and either
   * constructing a vector of AVTParts, or simply hold
   * on to the string if the AVT is simple.
   *
   * @param handler non-null reference to StylesheetHandler that is constructing.
   * @param uri non-null reference to URI, "" if null namespace.
   * @param name  non-null reference to name string.
   * @param rawName prefixed name.
   * @param stringedValue non-null raw string value.
   *
   * @throws javax.xml.transform.TransformerException
   */
  public AVT(StylesheetHandler handler, String uri, String name, 
             String rawName, String stringedValue,
             ElemTemplateElement owner)
          throws javax.xml.transform.TransformerException
  {

    m_uri = uri;
    m_name = name;
    m_rawName = rawName;

    StringTokenizer tokenizer = new StringTokenizer(stringedValue, "{}\"\'",
                                  true);
    int nTokens = tokenizer.countTokens();

    if (nTokens < 2)
    {
      m_simpleString = stringedValue;  // then do the simple thing
    }
    else
    {
      FastStringBuffer buffer = null;
      FastStringBuffer exprBuffer = null;
      if(USE_OBJECT_POOL){
        buffer = StringBufferPool.get(); 
        exprBuffer = StringBufferPool.get();
      }else{
        buffer = new FastStringBuffer(6);
        exprBuffer = new FastStringBuffer(6);
      }
      try
      {
        m_parts = new Vector(nTokens + 1);

        String t = null;  // base token
        String lookahead = null;  // next token
        String error = null;  // if non-null, break from loop

        while (tokenizer.hasMoreTokens())
        {
          if (lookahead != null)
          {
            t = lookahead;
            lookahead = null;
          }
          else
            t = tokenizer.nextToken();

          if (t.length() == 1)
          {
            switch (t.charAt(0))
            {
            case ('\"') :
            case ('\'') :
            {

              // just keep on going, since we're not in an attribute template
              buffer.append(t);

              break;
            }
            case ('{') :
            {

              try
              {
                // Attribute Value Template start
                lookahead = tokenizer.nextToken();

                if (lookahead.equals("{"))
                {

                  // Double curlys mean escape to show curly
                  buffer.append(lookahead);

                  lookahead = null;

                  break;  // from switch
                }

                /*
                else if(lookahead.equals("\"") || lookahead.equals("\'"))
                {
                // Error. Expressions can not begin with quotes.
                error = "Expressions can not begin with quotes.";
                break; // from switch
                }
                */
                else
                {
                  if (buffer.length() > 0)
                  {
                    m_parts.addElement(new AVTPartSimple(buffer.toString()));
                    buffer.setLength(0);
                  }

                  exprBuffer.setLength(0);

                  while (null != lookahead)
                  {
                    if (lookahead.length() == 1)
                    {
                      switch (lookahead.charAt(0))
                      {
                      case '\'' :
                      case '\"' :
                        {

                          // String start
                          exprBuffer.append(lookahead);

                          String quote = lookahead;

                          // Consume stuff 'till next quote
                          lookahead = tokenizer.nextToken();

                          while (!lookahead.equals(quote))
                          {
                            exprBuffer.append(lookahead);

                            lookahead = tokenizer.nextToken();
                          }

                          exprBuffer.append(lookahead);

                          lookahead = tokenizer.nextToken();

                          break;
                        }
                      case '{' :
                        {

                          // What's another curly doing here?
                          error = XSLMessages.createMessage(
                                                            XSLTErrorResources.ER_NO_CURLYBRACE, null);  //"Error: Can not have \"{\" within expression.";
                          
                          lookahead = null;  // breaks out of inner while loop

                          break;
                        }
                      case '}' :
                        {

                          // Proper close of attribute template.
                          // Evaluate the expression.
                          buffer.setLength(0);

                          XPath xpath =
                                       handler.createXPath(exprBuffer.toString(), owner);

                          m_parts.addElement(new AVTPartXPath(xpath));

                          lookahead = null;  // breaks out of inner while loop

                          break;
                        }
                      default :
                        {

                          // part of the template stuff, just add it.
                          exprBuffer.append(lookahead);

                          lookahead = tokenizer.nextToken();
                        }
                      }  // end inner switch
                    }  // end if lookahead length == 1
                    else
                    {

                      // part of the template stuff, just add it.
                      exprBuffer.append(lookahead);

                      lookahead = tokenizer.nextToken();
                    }
                  }  // end while(!lookahead.equals("}"))

                  if (error != null)
                  {
                    break;  // from inner while loop
                  }
                }

                break;
              }
              catch (java.util.NoSuchElementException ex)
              {
                error = XSLMessages.createMessage(XSLTErrorResources.ER_ILLEGAL_ATTRIBUTE_VALUE, new Object[]{ name, stringedValue }); 
                break;
              }
            }
            case ('}') :
            {
              lookahead = tokenizer.nextToken();

              if (lookahead.equals("}"))
              {

                // Double curlys mean escape to show curly
                buffer.append(lookahead);

                lookahead = null;  // swallow
              }
              else
              {

                // Illegal, I think...
                try
                {
                  handler.warn(XSLTErrorResources.WG_FOUND_CURLYBRACE, null);  //"Found \"}\" but no attribute template open!");
                }
                catch (org.xml.sax.SAXException se)
                {
                  throw new TransformerException(se);
                }

                buffer.append("}");

                // leave the lookahead to be processed by the next round.
              }

              break;
            }
            default :
            {

              // Anything else just add to string.
              buffer.append(t);
            }
            }  // end switch t
          }  // end if length == 1
          else
          {

            // Anything else just add to string.
            buffer.append(t);
          }

          if (null != error)
          {
            try
            {
              handler.warn(XSLTErrorResources.WG_ATTR_TEMPLATE,
                           new Object[]{ error });  //"Attr Template, "+error);
            }
            catch (org.xml.sax.SAXException se)
            {
              throw new TransformerException(se);
            }

            break;
          }
        }  // end while(tokenizer.hasMoreTokens())

        if (buffer.length() > 0)
        {
          m_parts.addElement(new AVTPartSimple(buffer.toString()));
          buffer.setLength(0);
        }
      }
      finally
      {
        if(USE_OBJECT_POOL){
             StringBufferPool.free(buffer);
             StringBufferPool.free(exprBuffer);
         }else{
            buffer = null;
            exprBuffer = null;
         };
      }
    }  // end else nTokens > 1

    if (null == m_parts && (null == m_simpleString))
    {

      // Error?
      m_simpleString = "";
    }
  }

  /**
   * Get the AVT as the original string.
   *
   * @return The AVT as the original string
   */
  public String getSimpleString()
  {

    if (null != m_simpleString){
      return m_simpleString;
    }
    else if (null != m_parts){
     final FastStringBuffer buf = getBuffer();
     String out = null;

    int n = m_parts.size();
    try{
      for (int i = 0; i < n; i++){
        AVTPart part = (AVTPart) m_parts.elementAt(i);
        buf.append(part.getSimpleString());
      }
      out = buf.toString();
    }finally{
      if(USE_OBJECT_POOL){
         StringBufferPool.free(buf);
     }else{
        buf.setLength(0); 
     };
    }
    return out;
  }else{
      return "";
  }
}

  /**
   * Evaluate the AVT and return a String.
   *
   * @param xctxt Te XPathContext to use to evaluate this.
   * @param context The current source tree context.
   * @param nsNode The current namespace context (stylesheet tree context).
   *
   * @return The AVT evaluated as a string
   *
   * @throws javax.xml.transform.TransformerException
   */
  public String evaluate(
          XPathContext xctxt, int context, org.apache.xml.utils.PrefixResolver nsNode)
            throws javax.xml.transform.TransformerException
  {
    if (null != m_simpleString){
        return m_simpleString;
    }else if (null != m_parts){
      final FastStringBuffer buf =getBuffer();
      String out = null;
      int n = m_parts.size();
      try{
        for (int i = 0; i < n; i++){
          AVTPart part = (AVTPart) m_parts.elementAt(i);  
          part.evaluate(xctxt, buf, context, nsNode);
        }
       out = buf.toString();
      }finally{
          if(USE_OBJECT_POOL){
             StringBufferPool.free(buf);
         }else{
           buf.setLength(0); 
         }
      }
     return out;
    }else{
      return "";
    }
  }

  /**
   * Test whether the AVT is insensitive to the context in which
   *  it is being evaluated. This is intended to facilitate
   *  compilation of templates, by allowing simple AVTs to be
   *  converted back into strings.
   *
   *  Currently the only case we recognize is simple strings.
   * ADDED 9/5/2000 to support compilation experiment
   *
   * @return True if the m_simpleString member of this AVT is not null
   */
  public boolean isContextInsensitive()
  {
    return null != m_simpleString;
  }

  /**
   * Tell if this expression or it's subexpressions can traverse outside
   * the current subtree.
   *
   * @return true if traversal outside the context node's subtree can occur.
   */
  public boolean canTraverseOutsideSubtree()
  {

    if (null != m_parts)
    {
      int n = m_parts.size();

      for (int i = 0; i < n; i++)
      {
        AVTPart part = (AVTPart) m_parts.elementAt(i);

        if (part.canTraverseOutsideSubtree())
          return true;
      }
    }

    return false;
  }
  
  /**
   * This function is used to fixup variables from QNames to stack frame 
   * indexes at stylesheet build time.
   * @param vars List of QNames that correspond to variables.  This list 
   * should be searched backwards for the first qualified name that 
   * corresponds to the variable reference qname.  The position of the 
   * QName in the vector from the start of the vector will be its position 
   * in the stack frame (but variables above the globalsTop value will need 
   * to be offset to the current stack frame).
   */
  public void fixupVariables(java.util.Vector vars, int globalsSize)
  {
    if (null != m_parts)
    {
      int n = m_parts.size();

      for (int i = 0; i < n; i++)
      {
        AVTPart part = (AVTPart) m_parts.elementAt(i);

        part.fixupVariables(vars, globalsSize);
      }
    }
  }
  
  /**
   * @see XSLTVisitable#callVisitors(XSLTVisitor)
   */
  public void callVisitors(XSLTVisitor visitor)
  {
  	if(visitor.visitAVT(this) && (null != m_parts))
  	{
      int n = m_parts.size();

      for (int i = 0; i < n; i++)
      {
        AVTPart part = (AVTPart) m_parts.elementAt(i);

        part.callVisitors(visitor);
      }  		
  	}
  }


  /**
   * Returns true if this AVT is simple
   */
  public boolean isSimple() {
  	return m_simpleString != null;
  }
  
  private final FastStringBuffer getBuffer(){
    if(USE_OBJECT_POOL){
      return StringBufferPool.get();
    }else{
      return new FastStringBuffer(INIT_BUFFER_CHUNK_BITS);
    }
  }
}
