/* gnu.classpath.tools.gjdoc.FieldDocImpl
   Copyright (C) 2001 Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath 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, or (at your option)
any later version.
 
GNU Classpath 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.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA. */

package gnu.classpath.tools.gjdoc;

import java.util.*;
import com.sun.javadoc.*;
import java.lang.reflect.Modifier;

import gnu.classpath.tools.gjdoc.expr.Evaluator;
import gnu.classpath.tools.gjdoc.expr.CircularExpressionException;
import gnu.classpath.tools.gjdoc.expr.IllegalExpressionException;

public class FieldDocImpl 
   extends MemberDocImpl 
   implements FieldDoc, Cloneable 
{

   private boolean isTransient;
   private boolean isVolatile;
   private String valueLiteral;
   private Object constantValue;
   private boolean constantValueEvaluated;

   private FieldDocImpl(ClassDoc containingClass,
                        PackageDoc containingPackage,
                        SourcePosition position) {

      super(containingClass,
	    containingPackage,
            position);
   }

   private static FieldDocImpl createFieldDoc(FieldDocImpl prototype,
                                              String fieldDef,
                                              String fieldValueLiteral)
   {
      if (null != fieldValueLiteral && fieldValueLiteral.length() == 0) {
         fieldValueLiteral = null;
      }

      try {
         FieldDocImpl fieldDoc=(FieldDocImpl)prototype.clone();
         String dimSuffix="";
         while (fieldDef.trim().endsWith("[")
                || fieldDef.trim().endsWith("]")) {
            fieldDef=fieldDef.trim();
            dimSuffix=fieldDef.charAt(fieldDef.length()-1)+dimSuffix;
            fieldDef=fieldDef.substring(0,fieldDef.length()-1);
         }
         
         fieldDoc.setTypeName(fieldDoc.getTypeName()+dimSuffix);
         fieldDoc.setName(fieldDef.trim());
         fieldDoc.setValueLiteral(fieldValueLiteral);
         return fieldDoc;
      }
      catch (CloneNotSupportedException e) {
         // should not happen
         e.printStackTrace();
         return null;
      }
   }

   public static Collection createFromSource(ClassDoc containingClass,
					     PackageDoc containingPackage,
					     char[] source, int startIndex, int endIndex) {
      
      List rcList=new ArrayList();

      FieldDocImpl fd=new FieldDocImpl(containingClass,
				       containingPackage,
                                       DocImpl.getPosition(containingClass, source, startIndex));

      int ndx=fd.parseModifiers(source, startIndex, endIndex);

      if (containingClass.isInterface()) {
         fd.accessLevel = ACCESS_PUBLIC;
      }

      final int STATE_FIELDNAME   = 1;
      final int STATE_FIELDVALUE  = 2;
      final int STATE_QUOTE       = 3;
      final int STATE_QUOTEBS     = 4;
      final int STATE_SQUOTE      = 5;
      final int STATE_SQUOTEBS    = 6;
      final int STATE_COMMENT     = 7;
      final int STATE_LINECOMMENT = 8;

      int lastFieldDefStart = ndx;
      int state = STATE_FIELDNAME;
      int prevState = state;

      int bracketCount = 0;

      StringBuffer fieldNameBuf = new StringBuffer();
      StringBuffer fieldValueLiteralBuf = new StringBuffer();

      for (int i=ndx; i<endIndex; ++i) {

	 char c = source[i];
         char nextChar = '\0';
         if (i + 1 < endIndex) {
            nextChar = source[i + 1];
         }
	 switch (state) {
         case STATE_FIELDNAME:
            if ('/' == c && '/' == nextChar) {
               prevState = state;
               state = STATE_LINECOMMENT;
            }
            else if ('/' == c && '*' == nextChar) {
               prevState = state;
               state = STATE_COMMENT;
            }
            else if (',' == c || ';' == c) {
               rcList.add(createFieldDoc(fd, fieldNameBuf.toString(), null));
               fieldNameBuf.setLength(0);
            }
            else if ('=' == c) {
               state = STATE_FIELDVALUE;
            }
            else if (!(' ' == c || '\n' == c || '\r' == c || '\t' == c)) {
               fieldNameBuf.append(c);
            }
            break;

	 case STATE_FIELDVALUE:
            if ('/' == c && '/' == nextChar) {
               prevState = state;
               state = STATE_LINECOMMENT;
            }
            else if ('/' == c && '*' == nextChar) {
               prevState = state;
               state = STATE_COMMENT;
            }
	    else if ('\"' == c) {
               prevState = state;
	       state = STATE_QUOTE;
               fieldValueLiteralBuf.append(c);
	    }
	    else if ('\'' == c) {
               prevState = state;
	       state = STATE_SQUOTE;
               fieldValueLiteralBuf.append(c);
	    }
	    else if ('{' == c || '(' == c) {
               ++ bracketCount;
               fieldValueLiteralBuf.append(c);
	    }
	    else if ('}' == c || ')' == c) {
               -- bracketCount;
               fieldValueLiteralBuf.append(c);
	    }
	    else if (0 == bracketCount && (',' == c || ';' == c)) {
               rcList.add(createFieldDoc(fd, fieldNameBuf.toString(), 
                                         fieldValueLiteralBuf.toString()));
               fieldNameBuf.setLength(0);
               fieldValueLiteralBuf.setLength(0);
	       state = STATE_FIELDNAME;
	    }
            else {
               fieldValueLiteralBuf.append(c);
            }
	    break;
	    
	 case STATE_QUOTE:
            fieldValueLiteralBuf.append(c);
	    if ('\\' == c) {
	       state = STATE_QUOTEBS;
	    }
	    else if ('\"' == c) {
	       state = prevState;
	    }
	    break;
	    
	 case STATE_SQUOTE:
            fieldValueLiteralBuf.append(c);
	    if ('\\' == c) {
	       state = STATE_SQUOTEBS;
	    }
	    else if ('\'' == c) {
	       state = prevState;
	    }
	    break;
	    
	 case STATE_QUOTEBS:
            fieldValueLiteralBuf.append(c);
	    state = STATE_QUOTE;
	    break;

	 case STATE_SQUOTEBS:
            fieldValueLiteralBuf.append(c);
	    state = STATE_SQUOTE;
	    break;

         case STATE_LINECOMMENT:
            if ('\n' == c) {
               state = prevState;
            }
            break;

         case STATE_COMMENT:
            if ('*' == c && '/' == nextChar) {
               ++ i;
               state = prevState;
            }
            break;
         }
      }
      
      if (fieldNameBuf.length() > 0) {
         rcList.add(createFieldDoc(fd, fieldNameBuf.toString(), 
                                   fieldValueLiteralBuf.toString()));
      }

      return rcList;
   }

   public boolean isField() {
      return true;
   } 

   public boolean isTransient() { return isTransient; }

   public boolean isVolatile() { return isVolatile; }

   public SerialFieldTag[] serialFieldTags() { return new SerialFieldTag[0]; }

   public int modifierSpecifier() {
      return super.modifierSpecifier()
	 | (isVolatile()?Modifier.VOLATILE:0)
	 | (isTransient()?Modifier.TRANSIENT:0)
	 ;
   }

   protected boolean processModifier(String word) {
      if (super.processModifier(word)) {
	 return true;
      }
      else if (word.equals("transient")) {
	 isTransient=true;
	 return true;	 
      }
      else if (word.equals("volatile")) {
	 isVolatile=true;
	 return true;	 
      }
      else {
	 return false;
      }
   }

   void resolve() {
      resolveTags();
   }

   public boolean hasSerialTag() {
      return true; //tagMap.get("serial")!=null;
   }

   public String toString() { return name(); }

   public Object constantValue() {
      return constantValue(new HashSet());
   }

   public Object constantValue(Set visitedFields) {
      if (!isStatic() 
          || !isFinal() 
          || (!type().isPrimitive() && !"java.lang.String".equals(type().qualifiedTypeName()))
          || type.dimension().length()>0 
          || null == valueLiteral) {

         return null;

      }
      else {
         if (!constantValueEvaluated) {

            visitedFields.add(this);

            String expression = "(" + type().typeName() + ")(" + valueLiteral + ")";
            try {
               this.constantValue = Evaluator.evaluate(expression, 
                                                       visitedFields,
                                                       (ClassDocImpl)containingClass());
            }
            catch (CircularExpressionException e) {
               // FIXME: This should use the error reporter
               System.err.println("WARNING: Cannot resolve expression for field " + containingClass.qualifiedTypeName() + "." + name() + ": " + e.getMessage());
            }
            catch (IllegalExpressionException ignore) {
            }
            constantValueEvaluated = true;
         }
         return this.constantValue;
      }
   }

   private static void appendCharString(StringBuffer result, char c, boolean inSingleCuotes)
   {
      switch (c) {
      case '\b': result.append("\\b"); break;
      case '\t': result.append("\\t"); break;
      case '\n': result.append("\\n"); break;
      case '\f': result.append("\\f"); break;
      case '\r': result.append("\\r"); break;
      case '\"': result.append("\\\""); break;
      case '\'': result.append(inSingleCuotes ? "\\'" : "'"); break;
      default:
         if (c >= 32 && c <= 127) {
            result.append(c);
         }
         else {
            result.append("\\u");
            String hexValue = Integer.toString((int)c, 16);
            int zeroCount = 4 - hexValue.length();
            for (int i=0; i<zeroCount; ++i) {
               result.append('0');
            }
            result.append(hexValue);
         }
      }
   }

   public String constantValueExpression() {
      Object value = constantValue();

      if (null == value) {
         return "null";
      }
      else if (value instanceof String) {
         StringBuffer result = new StringBuffer("\"");
         char[] chars = ((String)value).toCharArray();
         for (int i=0; i<chars.length; ++i) {
            appendCharString(result, chars[i], false);
         }
         result.append("\"");
         return result.toString();
      }
      else if (value instanceof Float) {
         return value.toString() + "f";
      }
      else if (value instanceof Long) {
         return value.toString() + "L";
      }
      else if (value instanceof Character) {
         StringBuffer result = new StringBuffer("'");
         appendCharString(result, ((Character)value).charValue(), false);
         result.append("'");
         return result.toString();
      }
      else /* if (value instanceof Double
               || value instanceof Integer
               || value instanceof Short
               || value instanceof Byte) */ {
         return value.toString();
      }
   }

   void setValueLiteral(String valueLiteral)
   {
      this.valueLiteral = valueLiteral;
   }

   public boolean isStatic()
   {
      return super.isStatic() || containingClass().isInterface();
   }

   public boolean isFinal()
   {
      return super.isFinal() || containingClass().isInterface();
   }
}
