/FileExtraction/Java_unseen/dlitz_resin/modules/quercus/src/com/caucho/quercus/lib/UnserializeReader.java
/*
 * Copyright (c) 1998-2011 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.quercus.lib;

import com.caucho.quercus.env.*;
import com.caucho.util.L10N;
import com.caucho.util.LruCache;

import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Logger;

public final class UnserializeReader {
  private static final L10N L = new L10N(UnserializeReader.class);
  private static final Logger log
    = Logger.getLogger(UnserializeReader.class.getName());

  private static final LruCache<StringKey,StringValue> _keyCache
    = new LruCache<StringKey,StringValue>(4096);

  private final char []_buffer;
  private final int _length;

  private int _index;
  private StringKey _key = new StringKey();
  
  private ArrayList<Value> _valueList
    = new ArrayList<Value>();
  
  private ArrayList<Boolean> _referenceList
    = new ArrayList<Boolean>();
  
  private boolean _useReference;

  public UnserializeReader(StringValue s)
    throws IOException
  {
    _buffer = s.toCharArray();
    _length = _buffer.length;
    
    if (s.indexOf("R:") >= 0
        || s.indexOf("r:") >= 0)
      initReferenceList();
  }

  public UnserializeReader(String s)
     throws IOException
  {
    _buffer = s.toCharArray();
    _length = _buffer.length;
    
    if (s.indexOf("R:") >= 0
        || s.indexOf("r:") >= 0)
      initReferenceList();
  }

  boolean useReference()
  {
    return _useReference;
  }

  public Value unserialize(Env env)
    throws IOException
  {
    int ch = read();

    switch (ch) {
    case 'b':
      {
        expect(':');
        long v = readInt();
        expect(';');
        
        Value value = v == 0 ? BooleanValue.FALSE : BooleanValue.TRUE;
        
        if (_useReference)
          value = createReference(value);
        
        return value;
      }

    case 's':
    case 'S':
      {
        expect(':');
        int len = (int) readInt();
        expect(':');
        expect('"');

        if (! isValidString(len)) {
          env.notice(L.l("expected string length of {0}", len));
          return BooleanValue.FALSE;
        }
        
        Value value = readStringValue(env, len);

        expect('"');
        expect(';');
        
        if (_useReference)
          value = createReference(value);

        return value;
      }
    case 'u':
    case 'U':
      {
        expect(':');
        int len = (int) readInt();
        expect(':');
        expect('"');

        if (! isValidString(len)) {
          env.notice(L.l("expected string length of {0}", len));
          return BooleanValue.FALSE;
        }
        
        Value value = readUnicodeValue(env, len);

        expect('"');
        expect(';');
        
        if (_useReference)
          value = createReference(value);

        return value;
      }

    case 'i':
      {
        expect(':');

        long l = readInt();

        expect(';');
        
        Value value = LongValue.create(l); 
        
        if (_useReference)
          value = createReference(value);

        return value;
      }

    case 'd':
      {
        expect(':');

        StringBuilder sb = new StringBuilder();
        for (ch = read(); ch >= 0 && ch != ';'; ch = read()) {
          sb.append((char) ch);
        }

        if (ch != ';')
          throw new IOException(L.l("expected ';'"));

        Value value = new DoubleValue(Double.parseDouble(sb.toString()));
        
        if (_useReference)
          value = createReference(value);
        
        return value;
      }

    case 'a':
      {
        expect(':');
        int len = (int) readInt();
        expect(':');
        expect('{');
        
        Value array = new ArrayValueImpl(len);

        if (_useReference)
          array = createReference(array);
        
        for (int i = 0; i < len; i++) {
          Value key = unserializeKey(env);
          Value value = unserialize(env);

          array.put(key, value);
        }

        expect('}');
          
        return array;
      }

    case 'O':
      {
        expect(':');
        int len = (int) readInt();
        expect(':');
        expect('"');

        if (! isValidString(len))
          return BooleanValue.FALSE;
        
        String className = readString(len);

        expect('"');
        expect(':');
        int count = (int) readInt();
        expect(':');
        expect('{');

        QuercusClass qClass = env.findClass(className);
        Value obj;

        if (qClass != null)
          obj = qClass.createObject(env);
        else {
          log.fine(L.l("{0} is an undefined class in unserialize",
                   className));
          
          obj = env.createIncompleteObject(className);
        }
        
        Value ref = null;
        
        if (_useReference)
          ref = createReference(obj);

        for (int i = 0; i < count; i++) {
          StringValue key = unserializeKey(env).toStringValue();
          
          FieldVisibility visibility = FieldVisibility.PUBLIC;

          if (key.length() > 3 && key.charAt(0) == 0) {
            switch (key.charAt(1)) {
              case 'A': // 0x41
                visibility = FieldVisibility.PRIVATE;
                break;
              case '*': // 0x2A
                visibility = FieldVisibility.PROTECTED;
                break;
              default:
                throw new IOException(
                    L.l("field visibility modifier is not valid: 0x{0}",
                                          Integer.toHexString(key.charAt(2))));
            }
            
            if (key.charAt(2) != 0) {
              throw new IOException(
                  L.l("end of field visibility modifier is not valid: 0x{0}",
                                        Integer.toHexString(key.charAt(2))));
            }
            
            key = key.substring(3);
          }
          
          Value value = unserialize(env);

          obj.initField(key, value, visibility);
        }

        expect('}');
        
        if (ref != null)
          return ref;
        else
          return obj;
      }

    case 'N':
      {
        expect(';');
        
        Value value = NullValue.NULL;
        
        if (_useReference)
          value = createReference(value);

        return value;
      }
    case 'R':
      {
        expect(':');

        int index = (int) readInt();

        expect(';');
        
        if (index - 1 >= _valueList.size()) {
          throw new IOException(
              L.l("reference out of range: {0}, size {1}, index {2}",
                                    index - 1, _valueList.size(), _index));
          //return BooleanValue.FALSE;
        }
        
        Value ref = _valueList.get(index - 1);

        return ref;
      }
    case 'r':
      {
        expect(':');

        int index = (int) readInt();

        expect(';');
        
        if (index - 1 >= _valueList.size()) {
          throw new IOException(
              L.l("reference out of range: {0}, size {1}, index {2}",
                                    index - 1, _valueList.size(), _index));
          //return BooleanValue.FALSE;
        }
        
        Value value = _valueList.get(index - 1).copy();
        
        if (_useReference)
          value = createReference(value);
        
        return value;
        
      }

    default:
      throw new IOException(
          L.l("option not recognized '{0}' (0x{1}) at index {2}",
                                String.valueOf((char) ch),
                                Integer.toHexString(ch),
                                _index));
      
      //return BooleanValue.FALSE;
    }
  }
  
  public Value createReference(Value value)
  {
    if (_referenceList.get(_valueList.size()) == Boolean.FALSE) {
      
      _valueList.add(value);
      return value;
    }
    else {
      Var var = new Var(value);
      
      _valueList.add(var);
      return var;
    }
  }

  private void initReferenceList()
    throws IOException
  {
    populateReferenceList();
    
    _index = 0;
  }
  
  private void populateReferenceList()
    throws IOException
  {
    int ch = read();

    switch (ch) {
      case 'b':
      {
        _referenceList.add(Boolean.FALSE);
        
        expect(':');
        for (ch = read(); ch >= 0 && ch != ';'; ch = read()) {
        }
        
        return;
      }

      case 's':
      case 'S':
      {
        _referenceList.add(Boolean.FALSE);
        
        expect(':');
        int len = (int) readInt();
        expect(':');
        expect('"');

        _index += len;

        expect('"');
        expect(';');
        
        return;
      }
      case 'u':
      case 'U':
      {
        _referenceList.add(Boolean.FALSE);
        
        expect(':');
        int len = (int) readInt();
        expect(':');
        expect('"');

        _index += len;

        expect('"');
        expect(';');
        
        return;
      }

      case 'i':
      {
        _referenceList.add(Boolean.FALSE);
        
        expect(':');

        for (ch = read(); ch >= 0 && ch != ';'; ch = read()) {
        }
        
        return;
      }

      case 'd':
      {
        _referenceList.add(Boolean.FALSE);
        
        expect(':');

        for (ch = read(); ch >= 0 && ch != ';'; ch = read()) {
        }
        
        return;
      }

      case 'a':
      {
        _referenceList.add(Boolean.FALSE);
        
        expect(':');
        int len = (int) readInt();
        expect(':');
        expect('{');

        for (int i = 0; i < len; i++) {
          switch (read()) {
          case 's':
          case 'S':
            {
              expect(':');
              int keyLen = (int) readInt();
              expect(':');
              expect('"');

              _index += keyLen;

              expect('"');
              expect(';');
              
              break;
              
            }

          case 'i':
            {
              expect(':');

              for (ch = read(); ch >= 0 && ch != ';'; ch = read()) {
              }
              
              break;
            }
          }

          populateReferenceList();
        }

        expect('}');
        
        return;
      }

      case 'O':
      {
        _referenceList.add(Boolean.FALSE);
        
        expect(':');
        int len = (int) readInt();
        expect(':');
        expect('"');

        _index += len;

        expect('"');
        expect(':');
        int count = (int) readInt();
        expect(':');
        expect('{');

        for (int i = 0; i < count; i++) {
          switch (read()) {
            case 's':
            case 'S':
            {
              expect(':');
              int keyLen = (int) readInt();
              expect(':');
              expect('"');

              _index += keyLen;

              expect('"');
              expect(';');
                
              break;
            }

            case 'i':
            {
              expect(':');

              for (ch = read(); ch >= 0 && ch != ';'; ch = read()) {
              }
                
              break;
            }
          }

          populateReferenceList();
        }

        expect('}');
        
        return;
      }

      case 'N':
      {
        _referenceList.add(Boolean.FALSE);
        
        expect(';');
        
        return;
      }

      case 'R':
      {
        _referenceList.add(Boolean.FALSE);
        
        _useReference = true;
        
        expect(':');

        int value = (int) readInt();

        expect(';');
        
        _referenceList.set(value - 1, Boolean.TRUE);
        
        return;
      }
      
      case 'r':
      {
        _referenceList.add(Boolean.FALSE);
        
        _useReference = true;

        expect(':');

        int value = (int) readInt();

        expect(';');
        
        return;
      }
    }
  }

  public Value unserializeKey(Env env)
    throws IOException
  {
    int ch = read();

    switch (ch) {
    case 's':
    case 'S':
      {
        expect(':');
        int len = (int) readInt();
        expect(':');
        expect('"');

        StringValue v;

        if (len < 32) {
          _key.init(_buffer, _index, len);

          v = _keyCache.get(_key);

          if (v != null) {
            _index += len;
          }
          else {
            StringKey key = new StringKey(_buffer, _index, len);

            v = readStringValue(env, len);

            _keyCache.put(key, v);
          }
        }
        else {
          v = readStringValue(env, len);
        }

        expect('"');
        expect(';');

        return v;
      }

    case 'i':
      {
        expect(':');

        long value = readInt();

        expect(';');

        return LongValue.create(value);
      }

    default:
      return BooleanValue.FALSE;
    }
  }

  private String unserializeString()
    throws IOException
  {
    int ch = read();
    
    if (ch != 's' && ch != 'S') {
      throw new IOException(L.l("expected 's' at '{1}' (0x{2})",
                                String.valueOf((char) ch),
                                Integer.toHexString(ch)));
    }

    expect(':');
    int len = (int) readInt();
    expect(':');
    expect('"');

    String s = readString(len);

    expect('"');
    expect(';');

    return s;
  }

  public final void expect(int expectCh)
    throws IOException
  {
    if (_length <= _index)
      throw new IOException(L.l("expected '{0}' at end of string",
                                String.valueOf((char) expectCh)));

    int ch = _buffer[_index++];

    if (ch != expectCh) {
      String context = String.valueOf((char) ch);
      
      if (_index - 2 >= 0)
        context = _buffer[_index - 2] + context;
      if (_index < _buffer.length)
        context += _buffer[_index];
      
      throw new IOException(
          L.l("expected '{0}' at '{1}' (0x{2}) (context '{3}', index {4})",
                                String.valueOf((char) expectCh),
                                String.valueOf((char) ch),
                                Integer.toHexString(ch),
                                context,
                                _index));
    }
  }

  public final long readInt()
  {
    int ch = read();

    long sign = 1;
    long value = 0;

    if (ch == '-') {
      sign = -1;
      ch = read();
    }
    else if (ch == '+') {
      ch = read();
    }

    for (; '0' <= ch && ch <= '9'; ch = read()) {
      value = 10 * value + ch - '0';
    }

    unread();

    return sign * value;
  }
  
  public final boolean isValidString(int len)
  {
    if (_index + len >= _buffer.length)
      return false;
    
    return true;
  }

  public final String readString(int len)
  {
    String s = new String(_buffer, _index, len);

    _index += len;

    return s;
  }

  public final StringValue readStringValue(Env env, int len)
  {
    StringValue s = env.createString(_buffer, _index, len);

    _index += len;

    return s;
  }
  
  public final StringValue readUnicodeValue(Env env, int len)
  {
    StringValue s = new UnicodeBuilderValue(_buffer, _index, len);

    _index += len;

    return s;
  }

  public final int read()
  {
    if (_index < _length)
      return _buffer[_index++];
    else
      return -1;
  }

  public final int read(char []buffer, int offset, int length)
  {
    System.arraycopy(_buffer, _index, buffer, offset, length);

    _index += length;

    return length;
  }

  public final void unread()
  {
    _index--;
  }

  public final static class StringKey
  {
    char []_buffer;
    int _offset;
    int _length;

    StringKey()
    {
    }

    StringKey(char []buffer, int offset, int length)
    {
      _buffer = new char[length];
      System.arraycopy(buffer, offset, _buffer, 0, length);
      _offset = 0;
      _length = length;
    }

    void init(char []buffer, int offset, int length)
    {
      _buffer = buffer;
      _offset = offset;
      _length = length;
    }

    public int hashCode()
    {
      char []buffer = _buffer;
      int offset = _offset;
      int end = offset + _length;
      int hash = 17;

      for (; offset < end; offset++)
        hash = 65521 * hash + buffer[offset];

      return hash;
    }

    public boolean equals(Object o)
    {
      if (! (o instanceof StringKey))
        return false;

      StringKey key = (StringKey) o;

      int length = _length;

      if (length != key._length)
        return false;

      char []aBuf = _buffer;
      char []bBuf = key._buffer;

      int aOffset = _offset;
      int bOffset = key._offset;

      int aEnd = aOffset + length;

      while (aOffset < aEnd) {
        if (aBuf[aOffset++] != bBuf[bOffset++])
          return false;
      }

      return true;
    }
  }
}


