/*
 * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.jsp.java;

import com.caucho.config.inject.InjectManager.ReferenceFactory;
import com.caucho.jsp.JspParseException;
import com.caucho.jsp.JspUtil;
import com.caucho.jsp.TagInstance;
import com.caucho.util.BeanUtil;
import com.caucho.vfs.WriteStream;
import com.caucho.xml.QName;

import javax.servlet.jsp.tagext.*;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Represents a custom tag.
 */
abstract public class GenericTag extends JspContainerNode
{
  private static final Logger log
    = Logger.getLogger(GenericTag.class.getName());
  private static final String DEFAULT_VAR_TYPE = "java.lang.String";

  private static final HashSet<String> _primTypes
    = new HashSet<String>();
  
  protected TagInstance _tag;
  protected TagInfo _tagInfo;
  protected Class<?> _tagClass;
  protected VariableInfo []_varInfo;

  private boolean _isDeclaringInstance;
  
  public GenericTag()
  {
  }
  
  public void setTagInfo(TagInfo tagInfo)
  {
    _tagInfo = tagInfo;
  }

  public TagInfo getTagInfo()
  {
    return _tagInfo;
  }

  @Override
  public boolean isJsp21()
  {
    return ! isPre21Taglib();
  }

  @Override
  public boolean isPre21Taglib()
  {
    if (_tagInfo == null)
      return false;
    
    TagLibraryInfo library = _tagInfo.getTagLibrary();

    if (library == null || library.getRequiredVersion() == null)
      return false;

    return "2.1".compareTo(library.getRequiredVersion()) > 0;
  }

  public TagInstance getTag()
  {
    return _tag;
  }

  /**
   * Returns the tag name for the current tag.
   */
  public String getCustomTagName()
  {
    return _tag.getId();
  }

  /**
   * Returns true if the tag is a simple tag.
   */
  public boolean isSimple()
  {
    return _tag.isSimpleTag();
  }

  /**
   * True if this is a jstl node.
   */
  @Override
  public boolean isJstl()
  {
    String uri = _tag.getTagInfo().getTagLibrary().getURI();
    
    return (JavaJspBuilder.JSTL_CORE_URI.equals(uri)
            || JavaJspBuilder.JSTL_EL_CORE_URI.equals(uri)
            || JavaJspBuilder.JSTL_FMT_URI.equals(uri)
            || JavaJspBuilder.JSTL_XML_URI.equals(uri)
            || JavaJspBuilder.JSTL_EL_XML_URI.equals(uri)
            || JavaJspBuilder.JSTL_SQL_URI.equals(uri)
            || JavaJspBuilder.JSTL_EL_SQL_URI.equals(uri));
  }

  public void setTagClass(Class cl)
  {
    _tagClass = cl;
  }

  public VariableInfo []getVarInfo()
  {
    return _varInfo;
  }

  /**
   * Returns the body content.
   */
  @Override
  public String getBodyContent()
  {
    return _tagInfo.getBodyContent();
  }
  
  /**
   * Adds a child node.
   */
  @Override
  public void addChild(JspNode node)
    throws JspParseException
  {
    if (! "empty".equals(getBodyContent()))
      super.addChild(node);
    else if (node instanceof JspAttribute) {
      super.addChild(node);
    }
    else if (node instanceof StaticText
             && ((StaticText) node).isWhitespace()) {
    }
    else {
      throw error(L.l("<{0}> must be empty.  Since <{0}> has a body-content of 'empty', it must not have any content.",
                      getTagName()));
    }
  }

  /**
   * Completes the element
   */
  @Override
  public void endElement()
    throws Exception
  {
    addTagDepend();
    
    Hashtable<String,Object> tags = new Hashtable<String,Object>();

    for (int i = 0; i < _attributeNames.size(); i++) {
      QName qName = _attributeNames.get(i);
      Object value = _attributeValues.get(i);
      String name = qName.getName();

      TagAttributeInfo attrInfo = getAttributeInfo(qName);
      Method method = getAttributeMethod(qName);
      
      Class type = null;

      if (method != null)
        type = method.getParameterTypes()[0];
      
      if (value instanceof JspAttribute) {
        JspAttribute attr = (JspAttribute) value;

        if (attr.isStatic()) {
          String textValue = attr.getStaticText();

          /*
          if (type != null)
            tags.put(name, staticStringToValue(type, textValue));
          else
            tags.put(name, textValue);
          */
          tags.put(name, textValue);
        }
        else
          tags.put(name, TagData.REQUEST_TIME_VALUE);
      }
      else if (value instanceof String && hasRuntimeAttribute((String) value))
        tags.put(name, TagData.REQUEST_TIME_VALUE);
      else
        tags.put(name, value);

      String typeName = null;

      boolean isFragment = false;

      if (attrInfo != null) {
        typeName = attrInfo.getTypeName();
        isFragment = attrInfo.isFragment();

        if (isFragment &&
            type != null && type.isAssignableFrom(JspFragment.class))
          typeName = JspFragment.class.getName();
      }
      else if (method != null)
        typeName = type.getName();

      if (! isFragment && ! JspFragment.class.getName().equals(typeName)) {
      }
      else if (value instanceof JspAttribute) {
        JspAttribute jspAttr = (JspAttribute) value;

        jspAttr.setJspFragment(true);
      }
    }

    TagData tagData = new TagData(tags);

    try {
      _varInfo = _tagInfo.getVariableInfo(tagData);
    } catch (Exception e) {
      throw error(e);
    }

    if (_varInfo == null)
      _varInfo = fillVariableInfo(_tagInfo.getTagVariableInfos(), tagData);

    TagExtraInfo tei = _tagInfo.getTagExtraInfo();
    ValidationMessage []messages;
    if (tei != null) {
      messages = tei.validate(tagData);

      _gen.addDepend(tei.getClass());

      if (messages != null && messages.length != 0) {
        throw error(messages[0].getMessage());
      }
    }
  }

  protected void addTagDepend()
  {
    if (_tagClass != null)
      _gen.addDepend(_tagClass);
  }
  
  /**
   * True if the node has scripting
   */
  @Override
  public boolean hasScripting()
  {
    if (super.hasScripting())
      return true;

    // Any conflicting values must be set each time.
    for (int i = 0; i < _attributeValues.size(); i++) {
      QName name = _attributeNames.get(i);
      Object value = _attributeValues.get(i);

      try {
        if (value instanceof String && hasRuntimeAttribute((String) value))
          return true;
      } catch (Throwable e) {
        log.log(Level.WARNING, e.toString(), e);
        return true;
      }
    }
    
    return false;
  }

  /**
   * True if the jsf-parent setting is required.
   */
  @Override
  public boolean isJsfParentRequired()
  {
    return true;
  }

  /**
   * Returns true if this instance declares the tag.
   */
  public boolean isDeclaringInstance()
  {
    return _isDeclaringInstance;
  }
  

  /**
   * Returns the variable containing the jsf component
   */
  @Override
  public String getJsfVar()
  {
    return null;
  }

  /**
   * Returns the variable containing the jsf body
   */
  @Override
  public String getJsfBodyVar()
  {
    return null;
  }
  
  @Override
  public void generateClassEpilogue(JspJavaWriter out)
    throws IOException
  {
    super.generateClassEpilogue(out);
    
    if (_tag.getAnalyzedTag().getHasInjection()
        && ! _tag.getAndSetInjectFactory(true)) {
      out.println();
      out.print("static ");
      out.printClass(ReferenceFactory.class);
      out.println(" _jsp_inject_" + _tag.getId());
      out.print("  = ");
      out.printClass(JspUtil.class);
      out.print(".getInjectFactory(");
      out.printClass(_tag.getTagClass());
      out.println(".class);");
    }
  }
    
  /**
   * Generates code before the actual JSP.
   */
  @Override
  public void generatePrologue(JspJavaWriter out)
    throws Exception
  {
    for (int i = 0; i < _attributeNames.size(); i++) {
      QName name = _attributeNames.get(i);
      Object value = _attributeValues.get(i);
      
      if (! (value instanceof JspFragmentNode))
        continue;
      
      JspFragmentNode frag = (JspFragmentNode) value;
      
      TagAttributeInfo attribute = getAttributeInfo(name);
      String typeName = null;

      boolean isFragment = false;

      if (attribute != null && attribute.isFragment())
        isFragment = true;

      String fragmentClass = JspFragment.class.getName();
      
      if (attribute != null && fragmentClass.equals(attribute.getTypeName()))
        isFragment = true;

      Method method = getAttributeMethod(name);

      if (method != null) {
        typeName = method.getParameterTypes()[0].getName();
        if (fragmentClass.equals(typeName))
          isFragment = true;
      }

      if (isFragment)
        frag.generateFragmentPrologue(out);
    }
      
    TagInstance parent = getParent().getTag();

    boolean isBodyTag = BodyTag.class.isAssignableFrom(_tagClass);
    boolean isEmpty = isEmpty();
    boolean hasBodyContent = isBodyTag && ! isEmpty;

    _tag = parent.findTag(getQName(), _attributeNames,
                          hasBodyContent);

    if (_tag == null || ! _parseState.isRecycleTags()) {
      _tag = parent.addTag(_gen, getQName(), _tagInfo, _tagClass,
                           _attributeNames, _attributeValues,
                           hasBodyContent);

      if (! JspTagFileSupport.class.isAssignableFrom(_tagClass)) {
        out.printClass(_tagClass);
        out.println(" " + _tag.getId() + " = null;");
      }
      _isDeclaringInstance = true;

      /*
      if (SimpleTag.class.isAssignableFrom(_tagClass) && hasCustomTag())
        out.println("javax.servlet.jsp.tagext.Tag " + _tag.getId() + "_adapter = null;");
      */
    }
    else {
      // Any conflicting values must be set each time.
      for (int i = 0; i < _attributeNames.size(); i++) {
        QName name = _attributeNames.get(i);
        Object value = _attributeValues.get(i);
        
        _tag.addAttribute(name, value);
      }
    }

    if (_tag == null)
      throw new NullPointerException();

    /* already taken care of
    if (! isEmpty())
      _tag.setBodyContent(true);
    */

    generatePrologueDeclare(out);
    generatePrologueChildren(out);
  }

  @Override
  public void generatePrologueDeclare(JspJavaWriter out)
    throws Exception
  {
    // Any AT_END variables
    for (int i = 0; _varInfo != null && i < _varInfo.length; i++) {
      VariableInfo var = _varInfo[i];

      if (var == null) {
      }
      else if (! _gen.hasScripting()) {
      }
      else if ((var.getScope() == VariableInfo.AT_END
                || var.getScope() == VariableInfo.AT_BEGIN)
               && var.getDeclare()
               && ! _gen.isDeclared(var.getVarName())) {
        String className = var.getClassName();

        if (className == null
            || "".equals(className)
            || "null".equals(className))
          className = DEFAULT_VAR_TYPE;

        validateClass(className, var.getVarName());
        
        out.print(className + " " + var.getVarName() + " = ");

        _gen.addDeclared(var.getVarName());
        
        if ("byte".equals(var.getClassName())
            || "short".equals(var.getClassName())
            || "char".equals(var.getClassName())
            || "int".equals(var.getClassName())
            || "long".equals(var.getClassName())
            || "float".equals(var.getClassName())
            || "double".equals(var.getClassName()))
          out.println("0;");
        else if ("boolean".equals(var.getClassName()))
          out.println("false;");
        else
          out.println("null;");
      }
    }
  }

  /**
   * Generates the XML text representation for the tag validation.
   *
   * @param os write stream to the generated XML.
   */
  @Override
  public void printXml(WriteStream os)
    throws IOException
  {
    TagInfo tag = getTagInfo();

    String prefix = tag.getTagLibrary().getPrefixString();
    String uri = tag.getTagLibrary().getURI();
    
    String name = prefix + ':' + tag.getTagName();

    os.print("<" + name);
    
    os.print(" xmlns:" + prefix + "=\"" + uri + "\"");
    
    printJspId(os);

    for (int i = 0; i < _attributeNames.size(); i++) {
      QName attrName = _attributeNames.get(i);
      Object value = _attributeValues.get(i);

      if (value instanceof String) {
        String string = (String) value;

        printXmlAttribute(os, attrName.getName(), string);
      }
    }

    os.print(">");

    printXmlChildren(os);

    os.print("</" + name + ">");
  }

  /**
   * Generates the code for a custom tag.
   *
   * @param out the output writer for the generated java.
   */
  abstract public void generate(JspJavaWriter out)
    throws Exception;

  protected void fillAttributes(JspJavaWriter out, String name)
    throws Exception
  {
    TagAttributeInfo attrs[] = _tagInfo.getAttributes();

    // clear any attributes mentioned in the taglib that aren't set
    for (int i = 0; attrs != null && i < attrs.length; i++) {
      int p = getAttributeIndex(attrs[i].getName());
      
      if (p < 0 && attrs[i].isRequired()) {
        throw error(L.l("required attribute '{0}' missing from <{1}>",
                        attrs[i].getName(),
                        getTagName()));
      }
    }

    boolean isDynamic = DynamicAttributes.class.isAssignableFrom(_tagClass);
    
    // fill all mentioned attributes
    for (int i = 0; i < _attributeNames.size(); i++) {
      QName attrName = _attributeNames.get(i);
      Object value = _attributeValues.get(i);
      
      TagAttributeInfo attribute = getAttributeInfo(attrName);
      
      if (attrs != null && attribute == null && ! isDynamic)
        throw error(L.l("unexpected attribute '{0}' in <{1}>",
                        attrName.getName(), getTagName()));

      if (_tag.getAttribute(attrName) != null)
        continue;

      boolean isFragment = false;

      if (attribute != null) {
        isFragment = (attribute.isFragment()
                      || JspFragment.class.getName().equals(attribute.getTypeName()));
      }

      if (value instanceof JspAttribute
          && ((JspAttribute) value).isJspFragment())
        isFragment = true;

      generateSetAttribute(out, name, attrName, value,
                           attribute == null || attribute.canBeRequestTime(),
                           isFragment, attribute);
    }
  }

  private TagAttributeInfo getAttributeInfo(QName attrName)
  {
    TagAttributeInfo attrs[] = _tagInfo.getAttributes();

    int j = 0;
    for (j = 0; attrs != null && j < attrs.length; j++) {
      if (isNameMatch(attrs[j].getName(), attrName))
        return attrs[j];
    }

    return null;
  }

  private int getAttributeIndex(String name)
  {
    for (int i = 0; i < _attributeNames.size(); i++) {
      QName attrName = _attributeNames.get(i);

      if (isNameMatch(name, attrName))
        return i;
    }

    return -1;
  }

  private boolean isNameMatch(String defName, QName attrName)
  {
    if (defName.equals(attrName.getName())) {
      return true;
    }
    else if (defName.equals(attrName.getLocalName()) &&
             attrName.getPrefix().equals(getQName().getPrefix())) {
      return true;
    }
    else
      return false;
  }

  /**
   * Sets an attribute for a tag
   *
   * @param info the tag's introspected information
   * @param name the tag's Java variable name
   * @param attrName the attribute name to set
   * @param value the new value of the tag.
   */
  void generateSetAttribute(JspJavaWriter out,
                            String name, QName attrName, Object value,
                            boolean allowRtexpr, boolean isFragment,
                            TagAttributeInfo attrInfo)
    throws Exception
  {
    Method method = getAttributeMethod(attrName);

    boolean isDynamic = DynamicAttributes.class.isAssignableFrom(_tagClass);
    
    if (method != null) {
      // jsp/18cq
      if (Modifier.isStatic(method.getModifiers()))
        throw error(L.l("attribute '{0}' may not be a static method.",
                        method.getName()));

      generateSetParameter(out, name, value, method,
                           allowRtexpr, "pageContext", false, isFragment, attrInfo);
    }
    else if (! isDynamic) {
      throw error(L.l("attribute '{0}' in tag '{1}' has no corresponding set method in tag class '{2}'",
                  attrName.getName(), getTagName(), _tagClass.getName()));
    }
    else if (isFragment) {
      String uri = attrName.getNamespaceURI();
      String local = attrName.getLocalName();

      out.print(name + ".setDynamicAttribute(");

      if (uri == null)
        out.print("null, ");
      else
        out.print("\"" + escapeJavaString(uri) + "\", ");
      
      JspFragmentNode frag = (JspFragmentNode) value;
      out.print("\"" + escapeJavaString(local) + "\", ");
      out.print(frag.generateValue());
      out.println(");");
    }
    else {
      String uri = attrName.getNamespaceURI();
      String local = attrName.getLocalName();
      
      out.print(name + ".setDynamicAttribute(");

      if (uri == null)
        out.print("null, ");
      else
        out.print("\"" + escapeJavaString(uri) + "\", ");
      
      out.print("\"" + escapeJavaString(local) + "\", ");
      out.print(generateRTValue(Object.class, value));
      out.println(");");
    }
  }

  private Method getAttributeMethod(QName attrName)
    throws Exception
  {
    Method method = null;
    
    try {
      BeanInfo info = Introspector.getBeanInfo(_tagClass);

      if (info != null)
        method = BeanUtil.getSetMethod(info, attrName.getLocalName());

      if (method != null)
        return method;
    } catch (Exception e) {
      log.log(Level.FINER, e.toString(), e);
    }

    /*
    try {
      method = BeanUtil.getSetMethod(_tagClass, attrName.getLocalName());

      if (method != null)
        return method;
    } catch (Throwable e) {
      log.log(Level.FINER, e.toString(), e);
    }
    */

    return method;
  }

  /**
   * Returns true if there is a tag variable declaration matching the scope.
   */
  protected boolean hasVarDeclaration(int scope)
    throws Exception
  {
    for (int i = 0; _varInfo != null && i < _varInfo.length; i++) {
      VariableInfo var = _varInfo[i];
      
      if (var != null && var.getScope() == scope)
        return true;
    }

    return false;
  }

  /**
   * Prints a tag variable declaration.  Only the variables matching the
   * scope will be printed.
   *
   * @param out the stream to the java code.
   * @param scope the variable scope to print
   */
  protected void printVarDeclaration(JspJavaWriter out, int scope)
    throws Exception
  {
    for (int i = 0; _varInfo != null && i < _varInfo.length; i++) {
      VariableInfo var = _varInfo[i];
      
      if (var != null) {
        printVarDeclare(out, scope, var);
        printVarAssign(out, scope, var);
      }
    }
  }

  /**
   * Prints a tag variable declaration.  Only the variables matching the
   * scope will be printed.
   *
   * @param out the stream to the java code.
   * @param scope the variable scope to print
   */
  protected void printVarDeclare(JspJavaWriter out, int scope)
    throws Exception
  {
    for (int i = 0; _varInfo != null && i < _varInfo.length; i++) {
      VariableInfo var = _varInfo[i];

      if (var != null)
        printVarDeclare(out, scope, var);
    }
  }

  /**
   * Prints a tag variable declaration.  Only the variables matching the
   * scope will be printed.
   *
   * @param out the stream to the java code.
   * @param scope the variable scope to print
   */
  protected void printVarAssign(JspJavaWriter out, int scope)
    throws Exception
  {
    for (int i = 0; _varInfo != null && i < _varInfo.length; i++) {
      VariableInfo var = _varInfo[i];

      if (var != null)
        printVarAssign(out, scope, var);
    }
  }

  /**
   * Returns the VariableInfo corresponding the to tag vars and the tag
   * data.  Mainly, this means looking up the variable names from the
   * attributes for the name-from-attribute.
   *
   * @param tagVars the implicit tag variables for the tag
   * @param tagData the parsed tag attributes
   *
   * @return an array of filled VariableInfo
   */
  protected VariableInfo []fillVariableInfo(TagVariableInfo []tagVars,
                                            TagData tagData)
    throws JspParseException
  {
    if (tagVars == null)
      return null;

    VariableInfo []vars = new VariableInfo[tagVars.length];

    for (int i = 0; i < tagVars.length; i++) {
      TagVariableInfo tagVar = tagVars[i];

      String name = null;
      
      String attributeName = tagVar.getNameFromAttribute();

      if (attributeName != null) {
        Object value = tagData.getAttribute(attributeName);
        
        if (value != null && ! (value instanceof String)) {
          throw error(L.l("tag variable '{0}' may not be a request time attribute",
                          attributeName));
        }
        
        name = tagData.getAttributeString(attributeName);
      }

      if (name == null || "".equals(name) || "null".equals(name))
        name = null;

      if (tagVar.getNameGiven() != null) {
        if (name != null)
          throw error(L.l("name-given='{0}' conflicts with name-from-attribute='{1}' because the attribute value is the same as name-given.",
                          tagVar.getNameGiven(),
                          attributeName));
        
        
        name = tagVar.getNameGiven();
      }
      else if (name == null)
        continue;

      vars[i] = new VariableInfo(name, tagVar.getClassName(),
                                 tagVar.getDeclare(), tagVar.getScope());
    }

    return vars;
  }

  /**
   * Prints a tag variable declaration.  Only the variables matching the
   * scope will be printed.
   *
   * @param out the stream to the java code.
   * @param scope the variable scope to print
   */
  protected void printVarDeclare(JspJavaWriter out,
                                 int scope,
                                 VariableInfo var)
    throws Exception
  {
    if (! _gen.hasScripting()
        || var == null
        || var.getVarName() == null
        || "".equals(var.getVarName())
        || "null".equals(var.getVarName()))
      return;
    
    if (var.getScope() == scope
        || var.getScope() == VariableInfo.AT_BEGIN) {
      if (var.getVarName() == null)
        throw error(L.l("tag variable expects a name"));

      String className = var.getClassName();

      if (className == null
          || "".equals(className)
          || "null".equals(className))
        className = DEFAULT_VAR_TYPE;

      /*
      if (var.getClassName() == null)
        throw error(L.l("tag variable '{0}' expects a classname",
                        var.getVarName()));
      */

      validateVarName(var.getVarName());

      // jsp/107r
      if (var.getDeclare()
          && var.getScope() == scope
          && (var.getScope() == VariableInfo.NESTED && hasScripting()
              || var.getScope() == VariableInfo.AT_BEGIN)
                 && ! varAlreadyDeclared(var.getVarName())) {
        validateClass(className, var.getVarName());
        
        out.println(className + " " + var.getVarName() + ";");
      }
    }
  }

  /**
   * Prints a tag variable declaration.  Only the variables matching the
   * scope will be printed.
   *
   * @param out the stream to the java code.
   * @param scope the variable scope to print
   */
  protected void printVarAssign(JspJavaWriter out, int scope, VariableInfo var)
    throws Exception
  {
    if ("".equals(var.getVarName())
        || "null".equals(var.getVarName()))
      return;
    
    if (var.getScope() == scope
        || var.getScope() == VariableInfo.AT_BEGIN) {
      if (var.getVarName() == null)
        throw error(L.l("tag variable expects a name"));

      String className = var.getClassName();

      if (className == null || className.equals("null"))
        className = DEFAULT_VAR_TYPE;
      
      /*
      if (var.getClassName() == null)
        throw error(L.l("tag variable '{0}' expects a classname",
                        var.getVarName()));
      */

      validateVarName(var.getVarName());

      if (! _gen.hasScripting()) {
      }
      else if (var.getScope() != VariableInfo.NESTED || hasScripting()) {
        out.setLocation(_filename, _startLine);
        out.print(var.getVarName() + " = ");
        String v = "pageContext.findAttribute(\"" + var.getVarName() + "\")";
        convertParameterValue(out, className, v);
        out.println(";");
      }

      try {
        _gen.addBeanClass(var.getVarName(), className);
      } catch (Exception e) {
        Throwable cause = e.getCause();

        if (cause == null)
          cause = e;

        throw error(L.l("'{0}' is an unknown class for tag variable '{1}' in <{2}>",
                        className, var.getVarName(), getTagInfo().getTagName()),
                        cause);
      }
    }
  }

  private void validateVarName(String name)
    throws JspParseException
  {
    if (! Character.isJavaIdentifierStart(name.charAt(0)))
      throw error(L.l("tag variable '{0}' is an illegal Java identifier.", name));

    for (int i = 0; i < name.length(); i++) {
      if (! Character.isJavaIdentifierPart(name.charAt(i)))
        throw error(L.l("tag variable '{0}' is an illegal Java identifier.", name));
    }
  }

  /**
   * Returns true if the variable has been declared.
   */
  private boolean varAlreadyDeclared(String varName)
  {
    if (_gen.isDeclared(varName))
      return true;
    
    for (JspNode node = getParent();
         node != null;
         node = node.getParent()) {
      if (! (node instanceof GenericTag))
        continue;
      if (node instanceof JspFragmentNode)
        break;

      GenericTag tag = (GenericTag) node;
      
      VariableInfo []varInfo = tag.getVarInfo();

      for (int i = 0; varInfo != null && i < varInfo.length; i++) {
        if (varInfo[i] == null)
          continue;
        else if (varInfo[i].getVarName().equals(varName))
          return true;
      }
    }

    return false;
  }
  
  /**
   * Returns true if the tag instance can be reused
   */
  protected boolean isReuse()
  {
    return _gen.getRecycleTags();
  }

  /**
   * Returns true if the tag instance has been declared
   */
  protected boolean isDeclared()
  {
    if (! _gen.getRecycleTags())
      return false;

    JspNode parent = getParent();

    if (! (parent instanceof JspRoot)
        && ! (parent instanceof JspTop)
        && ! (parent instanceof GenericTag)
        && ! (parent instanceof JspAttribute))
      return false;

    boolean isDeclared = false;

    ArrayList<JspNode> siblings = getParent().getChildren();
    for (int i = 0; i < siblings.size(); i++) {
      JspNode node = siblings.get(i);

      if (node == this) {
        return isDeclared;
      }

      if (hasScriptlet(node)) {
        return false;
      }

      if (node instanceof GenericTag) {
        GenericTag customTag = (GenericTag) node;

        if (customTag.getTag() == getTag())
          isDeclared = true;
      }
    }

    return isDeclared;
  }

  /**
   * Returns true if the node or one of its children is a scriptlet
   */
  protected boolean hasScriptlet(JspNode node)
  {
    if (node instanceof JspScriptlet || node instanceof JspExpression)
      return true;

    ArrayList<JspNode> children = node.getChildren();

    if (children == null)
      return false;

    for (int i = 0; i < children.size(); i++) {
      JspNode child = children.get(i);

      if (hasScriptlet(child))
        return true;
    }

    return false;
  }

  /**
   * Checks that the given class is a valid variable class.
   */
  protected void validateClass(String className, String varName)
    throws JspParseException
  {
    try {
      if (_primTypes.contains(className))
        return;
      else if (className.endsWith("[]")) {
        validateClass(className.substring(0, className.length() - 2), varName);
        return;
      }

      Class<?> cl = _gen.getBeanClass(className);
    } catch (ClassNotFoundException e) {
      throw error(L.l("'{0}' is an unknown class for tag variable '{1}'.",
                      className, varName));
    }
  }

  static {
    _primTypes.add("boolean");
    _primTypes.add("byte");
    _primTypes.add("short");
    _primTypes.add("int");
    _primTypes.add("long");
    _primTypes.add("float");
    _primTypes.add("double");
    _primTypes.add("char");
  }
}
