/**
 * Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *
 * 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.
 *
 * For more information: http://orientdb.com
 */
package com.orientechnologies.orient.jdbc;

import com.orientechnologies.orient.core.record.impl.OBlob;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.sql.Blob;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static java.util.Arrays.asList;

/**
 * @author Roberto Franchini (CELI Srl - franchini@celi.it)
 * @author Salvatore Piccione (TXT e-solutions SpA - salvo.picci@gmail.com)
 */
public class OrientBlob implements Blob {

  private final List<byte[]> binaryDataChunks;

  private long length;

  private byte[] currentChunk;

  private int currentChunkIndex;

  protected OrientBlob(OBlob binaryDataChunk) throws IllegalArgumentException {
    this(Collections.singletonList(binaryDataChunk));
  }

  protected OrientBlob(List<OBlob> binaryDataChunks) throws IllegalArgumentException {
    this.binaryDataChunks = new ArrayList<>(binaryDataChunks.size());
    for (OBlob binaryDataChunk : binaryDataChunks) {
      if (binaryDataChunk == null) {
        throw new IllegalArgumentException("The binary data chunks list cannot hold null chunks");
      } else if (binaryDataChunk.getSize() == 0) {
        throw new IllegalArgumentException("The binary data chunks list cannot hold empty chunks");
      } else {

        this.binaryDataChunks.add(binaryDataChunk.toStream());
      }
    }
    this.length = calculateLength();
  }

  /*
   * (non-Javadoc)
   *
   * @see java.sql.Blob#length()
   */
  public long length() throws SQLException {
    return this.length;
  }

  private long calculateLength() {
    long length = 0;
    for (byte[] binaryDataChunk : binaryDataChunks) {
      length += binaryDataChunk.length;
    }
    return length;
  }

  /*
   * (non-Javadoc)
   *
   * @see java.sql.Blob#getBytes(long, int)
   */
  public byte[] getBytes(long pos, int length) throws SQLException {
    if (pos < 1)
      throw new SQLException("The position of the first byte in the BLOB value to be " + "extracted cannot be less than 1");
    if (length < 0)
      throw new SQLException(
          "The number of the consecutive bytes in the BLOB value to " + "be extracted cannot be a negative number");

    int relativeIndex = this.getRelativeIndex(pos);

    ByteBuffer buffer = ByteBuffer.allocate(length);
    int j;
    for (j = 0; j < length; j++) {
      if (relativeIndex == currentChunk.length) {
        // go to the next chunk, if any...
        currentChunkIndex++;
        if (currentChunkIndex < binaryDataChunks.size()) {
          // the next chunk exists so we update the relative index and
          // the current chunk reference
          relativeIndex = 0;
          currentChunk = binaryDataChunks.get(currentChunkIndex);
        } else
          // exit from the loop: there are no more bytes to be read
          break;
      }
      buffer.put(currentChunk[relativeIndex]);
      relativeIndex++;
    }

    return buffer.array();
  }

  /**
   * Calculates the index within a binary chunk corresponding to the given absolute position within this BLOB
   *
   * @param pos
   * @return
   */
  private int getRelativeIndex(long pos) {
    int currentSize = 0;
    currentChunkIndex = 0;

    // loop until we find the chuks holding the given position
    while (pos >= (currentSize += binaryDataChunks.get(currentChunkIndex).length))
      currentChunkIndex++;

    currentChunk = binaryDataChunks.get(currentChunkIndex);
    currentSize -= currentChunk.length;
    // the position referred to the target binary chunk
    int relativePosition = (int) (pos - currentSize);
    // the index of the first byte to be returned
    return relativePosition - 1;
  }

  /*
   * (non-Javadoc)
   *
   * @see java.sql.Blob#getBinaryStream()
   */
  public InputStream getBinaryStream() throws SQLException {
    return new OrientBlobInputStream();
  }

  /*
   * (non-Javadoc)
   *
   * @see java.sql.Blob#position(byte[], long)
   */
  public long position(byte[] pattern, long start) throws SQLException {
    throw new SQLFeatureNotSupportedException();
  }

  /*
   * (non-Javadoc)
   *
   * @see java.sql.Blob#position(java.sql.Blob, long)
   */
  public long position(Blob pattern, long start) throws SQLException {
    throw new SQLFeatureNotSupportedException();
  }

  /*
   * (non-Javadoc)
   *
   * @see java.sql.Blob#setBytes(long, byte[])
   */
  public int setBytes(long pos, byte[] bytes) throws SQLException {
    throw new SQLFeatureNotSupportedException();
  }

  /*
   * (non-Javadoc)
   *
   * @see java.sql.Blob#setBytes(long, byte[], int, int)
   */
  public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException {
    throw new SQLFeatureNotSupportedException();
  }

  /*
   * (non-Javadoc)
   *
   * @see java.sql.Blob#setBinaryStream(long)
   */
  public OutputStream setBinaryStream(long pos) throws SQLException {
    throw new SQLFeatureNotSupportedException();
  }

  /*
   * (non-Javadoc)
   *
   * @see java.sql.Blob#truncate(long)
   */
  public void truncate(long len) throws SQLException {
    if (len < 0)
      throw new SQLException("The length of a BLOB cannot be a negtive number.");
    if (len < this.length)
      this.length = len;
  }

  /*
   * (non-Javadoc)
   *
   * @see java.sql.Blob#free()
   */
  public void free() throws SQLException {
    binaryDataChunks.clear();
  }

  /*
   * (non-Javadoc)
   *
   * @see java.sql.Blob#getBinaryStream(long, long)
   */
  public InputStream getBinaryStream(long pos, long length) throws SQLException {
    return new OrientBlobInputStream(pos, length);
  }

  private class OrientBlobInputStream extends InputStream {

    private long bytesToBeRead;

    private int positionInTheCurrentChunk;

    public OrientBlobInputStream() {
      bytesToBeRead = OrientBlob.this.length;
      positionInTheCurrentChunk = 0;
    }

    public OrientBlobInputStream(long pos, long length) {
      bytesToBeRead = length;
      positionInTheCurrentChunk = OrientBlob.this.getRelativeIndex(pos);
    }

    /*
     * (non-Javadoc)
     *
     * @see java.io.InputStream#read()
     */
    @Override
    public int read() throws IOException {
      if (bytesToBeRead > 0) {
        // BOUNDED READING

        // if all the bytes in the current binary chunk have been read,
        // we move to the next one
        if (positionInTheCurrentChunk == OrientBlob.this.currentChunk.length - 1) {
          // check if we've read all the available chunks
          if (OrientBlob.this.currentChunkIndex == OrientBlob.this.binaryDataChunks.size() - 1) {
            bytesToBeRead = 0;
            // we've read the last byte of the last binary chunk!
            return -1;
          } else {
            OrientBlob.this.currentChunk = OrientBlob.this.binaryDataChunks.get(++OrientBlob.this.currentChunkIndex);
            positionInTheCurrentChunk = 0;
          }
        }
        bytesToBeRead--;
        return OrientBlob.this.currentChunk[positionInTheCurrentChunk++];
      }
      return -1;
    }

  }
}


/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *  *
 *  *  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.
 *  *
 *  * For more information: http://orientdb.com
 *
 */
package com.orientechnologies.common.parser;

import java.util.Arrays;

/**
 * Abstract generic command to parse.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 *
 */
public abstract class OBaseParser {
  public String                   parserText;
  public String                   parserTextUpperCase;

  private transient StringBuilder parserLastWord            = new StringBuilder(256);
  private transient int           parserEscapeSequenceCount = 0;
  private transient int           parserCurrentPos          = 0;
  private transient int           parserPreviousPos         = 0;
  private transient char          parserLastSeparator       = ' ';

  public static int nextWord(final String iText, final String iTextUpperCase, int ioCurrentPosition, final StringBuilder ioWord,
      final boolean iForceUpperCase) {
    return nextWord(iText, iTextUpperCase, ioCurrentPosition, ioWord, iForceUpperCase, " =><(),");
  }

  public static int nextWord(final String iText, final String iTextUpperCase, int ioCurrentPosition, final StringBuilder ioWord,
      final boolean iForceUpperCase, final String iSeparatorChars) {
    ioWord.setLength(0);

    ioCurrentPosition = OStringParser.jumpWhiteSpaces(iText, ioCurrentPosition, -1);
    if (ioCurrentPosition < 0)
      return -1;

    getWordStatic(iForceUpperCase ? iTextUpperCase : iText, ioCurrentPosition, iSeparatorChars, ioWord);

    if (ioWord.length() > 0)
      ioCurrentPosition += ioWord.length();

    return ioCurrentPosition;
  }

  /**
   * @param iText
   *          Text where to search
   * @param iBeginIndex
   *          Begin index
   * @param iSeparatorChars
   *          Separators as a String of multiple characters
   * @param ioBuffer
   *          StringBuilder object with the word found
   */
  public static void getWordStatic(final CharSequence iText, int iBeginIndex, final String iSeparatorChars,
      final StringBuilder ioBuffer) {
    ioBuffer.setLength(0);

    char stringBeginChar = ' ';
    char c;

    for (int i = iBeginIndex; i < iText.length(); ++i) {
      c = iText.charAt(i);
      boolean found = false;
      for (int sepIndex = 0; sepIndex < iSeparatorChars.length(); ++sepIndex) {
        if (iSeparatorChars.charAt(sepIndex) == c) {
          // SEPARATOR AT THE BEGINNING: JUMP IT
          found = true;
          break;
        }
      }
      if (!found)
        break;

      iBeginIndex++;
    }

    for (int i = iBeginIndex; i < iText.length(); ++i) {
      c = iText.charAt(i);

      if (c == '\'' || c == '"' || c == '`') {
        if (stringBeginChar != ' ') {
          // CLOSE THE STRING?
          if (stringBeginChar == c) {
            // SAME CHAR AS THE BEGIN OF THE STRING: CLOSE IT AND PUSH
            stringBeginChar = ' ';
          }
        } else {
          // START STRING
          stringBeginChar = c;
        }
      } else if (stringBeginChar == ' ') {
        for (int sepIndex = 0; sepIndex < iSeparatorChars.length(); ++sepIndex) {
          if (iSeparatorChars.charAt(sepIndex) == c && ioBuffer.length() > 0) {
            // SEPARATOR (OUTSIDE A STRING): PUSH
            return;
          }
        }
      }

      ioBuffer.append(c);
    }
  }

  public String getSyntax() {
    return "?";
  }

  /**
   * Returns the last separator encountered, otherwise returns a blank (' ').
   */
  public char parserGetLastSeparator() {
    return parserLastSeparator;
  }

  /**
   * Overwrites the last separator. To ignore it set it to blank (' ').
   */
  public void parserSetLastSeparator(final char iSeparator) {
    parserLastSeparator = iSeparator;
  }

  /**
   * Returns the cursor position before last parsing.
   *
   * @return Offset from the beginning
   */
  public int parserGetPreviousPosition() {
    return parserPreviousPos;
  }

  /**
   * Tells if the parsing has reached the end of the content.
   *
   * @return True if is ended, otherwise false
   */
  public boolean parserIsEnded() {
    return parserCurrentPos == -1;
  }

  /**
   * Returns the current cursor position.
   *
   * @return Offset from the beginning
   */
  public int parserGetCurrentPosition() {
    return parserCurrentPos;
  }

  /**
   * Returns the current character in the current cursor position
   *
   * @return The current character in the current cursor position. If the end is reached, then a blank (' ') is returned
   */
  public char parserGetCurrentChar() {
    if (parserCurrentPos < 0)
      return ' ';
    return parserText.charAt(parserCurrentPos);
  }

  /**
   * Returns the last parsed word.
   *
   * @return Last parsed word as String
   */
  public String parserGetLastWord() {
    return parserLastWord.toString();
  }

  public int getLastWordLength() {
    return parserLastWord.length() + parserEscapeSequenceCount;
  }

  /**
   * Throws a syntax error exception.
   *
   * @param iText
   *          Text about the problem.
   */
  protected abstract void throwSyntaxErrorException(final String iText);

  /**
   * Parses the next word. It returns the word parsed if any.
   *
   * @param iUpperCase
   *          True if must return UPPERCASE, otherwise false
   * @return The word parsed if any, otherwise null
   */
  protected String parserOptionalWord(final boolean iUpperCase) {
    parserPreviousPos = parserCurrentPos;

    parserNextWord(iUpperCase);
    if (parserLastWord.length() == 0)
      return null;
    return parserLastWord.toString();
  }

  /**
   * Parses the next word. If any word is parsed it's checked against the word array received as parameter. If the parsed word is
   * not enlisted in it a SyntaxError exception is thrown. It returns the word parsed if any.
   *
   * @param iUpperCase
   *          True if must return UPPERCASE, otherwise false
   * @return The word parsed if any, otherwise null
   */
  protected String parseOptionalWord(final boolean iUpperCase, final String... iWords) {
    parserNextWord(iUpperCase);

    if (iWords.length > 0) {
      if (parserLastWord.length() == 0)
        return null;

      boolean found = false;
      for (String w : iWords) {
        if (parserLastWord.toString().equals(w)) {
          found = true;
          break;
        }
      }

      if (!found)
        throwSyntaxErrorException("Found unexpected keyword '" + parserLastWord + "' while it was expected '"
            + Arrays.toString(iWords) + "'");
    }

    if (parserLastWord.length() > 1 && parserLastWord.charAt(0) == '`' && parserLastWord.charAt(parserLastWord.length() - 1) == '`') {
      return parserLastWord.substring(1, parserLastWord.length() - 1);
    }

    return parserLastWord.toString();
  }

  /**
   * Goes back to the previous position.
   *
   * @return The previous position
   */
  protected int parserGoBack() {
    parserCurrentPos = parserPreviousPos;
    return parserCurrentPos;
  }

  /**
   * Parses the next word. If no word is found an SyntaxError exception is thrown It returns the word parsed if any.
   *
   * @param iUpperCase
   *          True if must return UPPERCASE, otherwise false
   * @return The word parsed
   */
  protected String parserRequiredWord(final boolean iUpperCase) {
    return parserRequiredWord(iUpperCase, "Syntax error", null);
  }

  /**
   * Parses the next word. If no word is found an SyntaxError exception with the custom message received as parameter is thrown It
   * returns the word parsed if any.
   *
   * @param iUpperCase
   *          True if must return UPPERCASE, otherwise false
   * @param iCustomMessage
   *          Custom message to include in case of SyntaxError exception
   * @return The word parsed
   */
  protected String parserRequiredWord(final boolean iUpperCase, final String iCustomMessage) {
    return parserRequiredWord(iUpperCase, iCustomMessage, null);
  }

  /**
   * Parses the next word. If no word is found or the parsed word is not present in the word array received as parameter then a
   * SyntaxError exception with the custom message received as parameter is thrown. It returns the word parsed if any.
   *
   * @param iUpperCase
   *          True if must return UPPERCASE, otherwise false
   * @param iCustomMessage
   *          Custom message to include in case of SyntaxError exception
   * @param iSeparators
   *          Separator characters
   * @return The word parsed
   */
  protected String parserRequiredWord(final boolean iUpperCase, final String iCustomMessage, String iSeparators) {
    if (iSeparators == null)
      iSeparators = " ()=><,\r\n";

    parserNextWord(iUpperCase, iSeparators);
    if (parserLastWord.length() == 0)
      throwSyntaxErrorException(iCustomMessage);
    if (parserLastWord.charAt(0) == '`' && parserLastWord.charAt(parserLastWord.length() - 1) == '`') {
      return parserLastWord.substring(1, parserLastWord.length() - 1);
    }
    return parserLastWord.toString();
  }

  /**
   * Parses the next word. If no word is found or the parsed word is not present in the word array received as parameter then a
   * SyntaxError exception is thrown.
   *
   * @param iWords
   *          Array of expected keywords
   */
  protected void parserRequiredKeyword(final String... iWords) {
    parserNextWord(true, " \r\n,()");
    if (parserLastWord.length() == 0)
      throwSyntaxErrorException("Cannot find expected keyword '" + Arrays.toString(iWords) + "'");

    boolean found = false;
    for (String w : iWords) {
      if (parserLastWord.toString().equals(w)) {
        found = true;
        break;
      }
    }

    if (!found)
      throwSyntaxErrorException("Found unexpected keyword '" + parserLastWord + "' while it was expected '"
          + Arrays.toString(iWords) + "'");
  }

  /**
   * Parses the next sequence of chars.
   *
   * @return The position of the word matched if any, otherwise -1 or an exception if iMandatory is true
   */
  protected int parserNextChars(final boolean iUpperCase, final boolean iMandatory, final String... iCandidateWords) {
    parserPreviousPos = parserCurrentPos;
    parserSkipWhiteSpaces();

    parserEscapeSequenceCount = 0;
    parserLastWord.setLength(0);

    final String[] processedWords = Arrays.copyOf(iCandidateWords, iCandidateWords.length);

    // PARSE THE CHARS
    final String text2Use = iUpperCase ? parserTextUpperCase : parserText;
    final int max = text2Use.length();

    parserCurrentPos = parserCurrentPos + parserTextUpperCase.length() - parserText.length();
    // PARSE TILL 1 CHAR AFTER THE END TO SIMULATE A SEPARATOR AS EOF
    for (int i = 0; parserCurrentPos <= max; ++i) {
      final char ch = parserCurrentPos < max ? text2Use.charAt(parserCurrentPos) : '\n';
      final boolean separator = ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t' || ch == '(';
      if (!separator)
        parserLastWord.append(ch);

      // CLEAR CANDIDATES
      int candidatesWordsCount = 0;
      int candidatesWordsPos = -1;
      for (int c = 0; c < processedWords.length; ++c) {
        final String w = processedWords[c];
        if (w != null) {
          final int wordSize = w.length();
          if ((separator && wordSize > i) || (!separator && (i > wordSize - 1 || w.charAt(i) != ch)))
            // DISCARD IT
            processedWords[c] = null;
          else {
            candidatesWordsCount++;
            if (candidatesWordsCount == 1)
              // REMEMBER THE POSITION
              candidatesWordsPos = c;
          }
        }
      }

      if (candidatesWordsCount == 1) {
        // ONE RESULT, CHECKING IF FOUND
        final String w = processedWords[candidatesWordsPos];
        if (w.length() == i + (separator ? 0 : 1) && !Character.isLetter(ch))
          // FOUND!
          return candidatesWordsPos;
      }

      if (candidatesWordsCount == 0 || separator)
        break;

      parserCurrentPos++;
    }

    if (iMandatory)
      throwSyntaxErrorException("Found unexpected keyword '" + parserLastWord + "' while it was expected '"
          + Arrays.toString(iCandidateWords) + "'");

    return -1;
  }

  /**
   * Parses optional keywords between the iWords. If a keyword is found but doesn't match with iWords then a SyntaxError is raised.
   *
   * @param iWords
   *          Optional words to match as keyword. If at least one is passed, then the check is made
   * @return true if a keyword was found, otherwise false
   */
  protected boolean parserOptionalKeyword(final String... iWords) {
    parserNextWord(true, " \r\n,");
    if (parserLastWord.length() == 0)
      return false;

    // FOUND: CHECK IF IT'S IN RANGE
    boolean found = iWords.length == 0;
    for (String w : iWords) {
      if (parserLastWord.toString().equals(w)) {
        found = true;
        break;
      }
    }

    if (!found)
      throwSyntaxErrorException("Found unexpected keyword '" + parserLastWord + "' while it was expected '"
          + Arrays.toString(iWords) + "'");

    return true;
  }

  /**
   * Skips not valid characters like spaces and line feeds.
   *
   * @return True if the string is not ended, otherwise false
   */
  protected boolean parserSkipWhiteSpaces() {
    if (parserCurrentPos == -1)
      return false;

    parserCurrentPos = OStringParser.jumpWhiteSpaces(parserText, parserCurrentPos, -1);
    return parserCurrentPos > -1;
  }

  /**
   * Overwrites the current cursor position.
   *
   * @param iPosition
   *          New position
   * @return True if the string is not ended, otherwise false
   */
  protected boolean parserSetCurrentPosition(final int iPosition) {
    parserCurrentPos = iPosition;
    if (parserCurrentPos >= parserText.length())
      // END OF TEXT
      parserCurrentPos = -1;
    return parserCurrentPos > -1;
  }

  /**
   * Sets the end of text as position
   */
  protected void parserSetEndOfText() {
    parserCurrentPos = -1;
  }

  /**
   * Moves the current cursor position forward or backward of iOffset characters
   *
   * @param iOffset
   *          Number of characters to move. Negative numbers means backwards
   * @return True if the string is not ended, otherwise false
   */
  protected boolean parserMoveCurrentPosition(final int iOffset) {
    if (parserCurrentPos < 0)
      return false;
    return parserSetCurrentPosition(parserCurrentPos + iOffset);
  }

  /**
   * Parses the next word.
   *
   * @param iForceUpperCase
   *          True if must return UPPERCASE, otherwise false
   */
  protected String parserNextWord(final boolean iForceUpperCase) {
    return parserNextWord(iForceUpperCase, " =><(),\r\n");
  }

  /**
   * Parses the next word.
   *
   * @param iForceUpperCase
   *          True if must return UPPERCASE, otherwise false
   * @param iSeparatorChars
   */
  protected String parserNextWord(final boolean iForceUpperCase, final String iSeparatorChars) {
    return parserNextWord(iForceUpperCase, iSeparatorChars, false);
  }

  protected String parserNextWord(final boolean iForceUpperCase, final String iSeparatorChars, boolean preserveEscapes) {
    parserPreviousPos = parserCurrentPos;
    parserLastWord.setLength(0);
    parserEscapeSequenceCount = 0;

    parserSkipWhiteSpaces();
    if (parserCurrentPos == -1)
      return null;

    char stringBeginChar = ' ';

    final String text2Use = iForceUpperCase ? parserTextUpperCase : parserText;

    while (parserCurrentPos < text2Use.length()) {
      final char c = text2Use.charAt(parserCurrentPos);
      boolean found = false;
      for (int sepIndex = 0; sepIndex < iSeparatorChars.length(); ++sepIndex) {
        if (iSeparatorChars.charAt(sepIndex) == c) {
          // SEPARATOR AT THE BEGINNING: JUMP IT
          found = true;
          break;
        }
      }
      if (!found)
        break;

      parserCurrentPos++;
    }

    try {
      int openParenthesis = 0;
      int openBracket = 0;
      int openGraph = 0;

      int escapePos = -1;

      for (; parserCurrentPos < text2Use.length(); parserCurrentPos++) {
        final char c = text2Use.charAt(parserCurrentPos);

        if (escapePos == -1 && c == '\\' && ((parserCurrentPos + 1) < text2Use.length())) {
          // ESCAPE CHARS

          if (openGraph == 0) {
            final char nextChar = text2Use.charAt(parserCurrentPos + 1);
            if(preserveEscapes){
              parserLastWord.append(c);
              parserLastWord.append(nextChar);
              parserCurrentPos++;
            }else {

              if (nextChar == 'u') {
                parserCurrentPos = OStringParser.readUnicode(text2Use, parserCurrentPos + 2, parserLastWord);
                parserEscapeSequenceCount += 5;
              } else {
                if (nextChar == 'n')
                  parserLastWord.append('\n');
                else if (nextChar == 'r')
                  parserLastWord.append('\r');
                else if (nextChar == 't')
                  parserLastWord.append('\t');
                else if (nextChar == 'b')
                  parserLastWord.append('\b');
                else if (nextChar == 'f')
                  parserLastWord.append('\f');
                else {
                  parserLastWord.append(nextChar);
                  parserEscapeSequenceCount++;
                }

                parserCurrentPos++;
              }
            }
            continue;
          } else
            escapePos = parserCurrentPos;
        }

        if (escapePos == -1 && (c == '\'' || c == '"')) {
          if (stringBeginChar != ' ') {
            // CLOSE THE STRING?
            if (stringBeginChar == c) {
              // SAME CHAR AS THE BEGIN OF THE STRING: CLOSE IT AND PUSH
              stringBeginChar = ' ';

              if (openBracket == 0 && openGraph == 0 && openParenthesis == 0) {
                parserCurrentPos++;
                parserLastWord.append(c);
                break;
              }
            }
          } else
            // START STRING
            stringBeginChar = c;
        }

        if (stringBeginChar == ' ') {
          if (openBracket == 0 && openGraph == 0 && openParenthesis == 0 && parserCheckSeparator(c, iSeparatorChars)) {
            // SEPARATOR FOUND!
            break;
          } else if (c == '(')
            openParenthesis++;
          else if (c == ')' && openParenthesis > 0)
            openParenthesis--;
          else if (c == '[')
            openBracket++;
          else if (c == ']' && openBracket > 0)
            openBracket--;
          else if (c == '{')
            openGraph++;
          else if (c == '}' && openGraph > 0)
            openGraph--;
        }

        if (escapePos != -1)
          parserEscapeSequenceCount++;

        if (escapePos != parserCurrentPos)
          escapePos = -1;

        parserLastWord.append(c);
      }

      // CHECK MISSING CHARACTER
      if (stringBeginChar != ' ')
        throw new IllegalStateException("Missing closed string character: '" + stringBeginChar + "', position: " + parserCurrentPos);
      if (openBracket > 0)
        throw new IllegalStateException("Missing closed braket character: ']', position: " + parserCurrentPos);
      if (openGraph > 0)
        throw new IllegalStateException("Missing closed graph character: '}', position: " + parserCurrentPos);
      if (openParenthesis > 0)
        throw new IllegalStateException("Missing closed parenthesis character: ')', position: " + parserCurrentPos);

    } finally {
      if (parserCurrentPos >= text2Use.length()) {
        // END OF TEXT
        parserCurrentPos = -1;
        parserLastSeparator = ' ';
      }
    }

    return parserLastWord.toString();
  }

  /**
   * Check for a separator
   *
   * @param c
   * @param iSeparatorChars
   * @return
   */
  private boolean parserCheckSeparator(final char c, final String iSeparatorChars) {
    for (int sepIndex = 0; sepIndex < iSeparatorChars.length(); ++sepIndex) {
      if (iSeparatorChars.charAt(sepIndex) == c) {
        parserLastSeparator = c;
        return true;
      }
    }
    return false;
  }
}


/*
  *
  *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
  *  *
  *  *  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.
  *  *
  *  * For more information: http://orientdb.com
  *
  */
package com.orientechnologies.orient.graph.sql.functions;

import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.command.OCommandExecutorAbstract;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.sql.functions.math.OSQLFunctionMathAbstract;
import com.tinkerpop.blueprints.Direction;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.blueprints.impls.orient.OrientBaseGraph;
import com.tinkerpop.blueprints.impls.orient.OrientVertex;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

/**
 * Abstract class to find paths between nodes.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 *
 */
public abstract class OSQLFunctionPathFinder extends OSQLFunctionMathAbstract {
  protected OrientBaseGraph         db;
  protected Set<OrientVertex>       unSettledNodes;
  protected Map<ORID, OrientVertex> predecessors;
  protected Map<ORID, Float>        distance;

  protected OrientVertex            paramSourceVertex;
  protected OrientVertex            paramDestinationVertex;
  protected Direction               paramDirection = Direction.OUT;
  protected OCommandContext         context;

  protected static final float      MIN            = 0f;

  public OSQLFunctionPathFinder(final String iName, final int iMinParams, final int iMaxParams) {
    super(iName, iMinParams, iMaxParams);
  }

  protected LinkedList<OrientVertex> execute(final OCommandContext iContext) {
    context = iContext;
    unSettledNodes = new HashSet<OrientVertex>();
    distance = new HashMap<ORID, Float>();
    predecessors = new HashMap<ORID, OrientVertex>();
    distance.put(paramSourceVertex.getIdentity(), MIN);
    unSettledNodes.add(paramSourceVertex);

    int maxDistances = 0;
    int maxSettled = 0;
    int maxUnSettled = 0;
    int maxPredecessors = 0;

    while (continueTraversing()) {
      final OrientVertex node = getMinimum(unSettledNodes);
      unSettledNodes.remove(node);
      findMinimalDistances(node);

      if (distance.size() > maxDistances)
        maxDistances = distance.size();
      if (unSettledNodes.size() > maxUnSettled)
        maxUnSettled = unSettledNodes.size();
      if (predecessors.size() > maxPredecessors)
        maxPredecessors = predecessors.size();

      if (!isVariableEdgeWeight() && distance.containsKey(paramDestinationVertex.getIdentity()))
        // FOUND
        break;

      if( !OCommandExecutorAbstract.checkInterruption(context) )
        break;
    }

    context.setVariable("maxDistances", maxDistances);
    context.setVariable("maxSettled", maxSettled);
    context.setVariable("maxUnSettled", maxUnSettled);
    context.setVariable("maxPredecessors", maxPredecessors);

    distance = null;

    return getPath();
  }

  protected boolean isVariableEdgeWeight() {
    return false;
  }

  /*
   * This method returns the path from the source to the selected target and NULL if no path exists
   */
  public LinkedList<OrientVertex> getPath() {
    final LinkedList<OrientVertex> path = new LinkedList<OrientVertex>();
    OrientVertex step = paramDestinationVertex;
    // Check if a path exists
    if (predecessors.get(step.getIdentity()) == null)
      return null;

    path.add(step);
    while (predecessors.get(step.getIdentity()) != null) {
      step = predecessors.get(step.getIdentity());
      path.add(step);
    }
    // Put it into the correct order
    Collections.reverse(path);
    return path;
  }

  public boolean aggregateResults() {
    return false;
  }

  @Override
  public Object getResult() {
    return getPath();
  }

  protected void findMinimalDistances(final OrientVertex node) {
    for (OrientVertex neighbor : getNeighbors(node)) {
      final float d = sumDistances(getShortestDistance(node), getDistance(node, neighbor));

      if (getShortestDistance(neighbor) > d) {
        distance.put(neighbor.getIdentity(), d);
        predecessors.put(neighbor.getIdentity(), node);
        unSettledNodes.add(neighbor);
      }
    }

  }

  protected Set<OrientVertex> getNeighbors(final Vertex node) {
    context.incrementVariable("getNeighbors");

    final Set<OrientVertex> neighbors = new HashSet<OrientVertex>();
    if (node != null) {
      for (Vertex v : node.getVertices(paramDirection)) {
        final OrientVertex ov = (OrientVertex) v;
        if (ov != null && isNotSettled(ov))
          neighbors.add(ov);
      }
    }
    return neighbors;
  }

  protected OrientVertex getMinimum(final Set<OrientVertex> vertexes) {
    OrientVertex minimum = null;
    Float minimumDistance = null;
    for (OrientVertex vertex : vertexes) {
      if (minimum == null || getShortestDistance(vertex) < minimumDistance) {
        minimum = vertex;
        minimumDistance = getShortestDistance(minimum);
      }
    }
    return minimum;
  }

  protected boolean isNotSettled(final OrientVertex vertex) {
    return unSettledNodes.contains(vertex) || !distance.containsKey(vertex.getIdentity());
  }

  protected boolean continueTraversing() {
    return unSettledNodes.size() > 0;
  }

  protected float getShortestDistance(final OrientVertex destination) {
    if (destination == null)
      return Float.MAX_VALUE;

    final Float d = distance.get(destination.getIdentity());
    return d == null ? Float.MAX_VALUE : d;
  }

  protected float sumDistances(final float iDistance1, final float iDistance2) {
    return iDistance1 + iDistance2;
  }

  protected abstract float getDistance(final OrientVertex node, final OrientVertex target);
}


/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *  *
 *  *  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.
 *  *
 *  * For more information: http://orientdb.com
 *
 */
package com.orientechnologies.orient.core.sql;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest;
import com.orientechnologies.orient.core.command.OCommandRequest;
import com.orientechnologies.orient.core.command.OCommandRequestText;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.sql.parser.ODropClassStatement;

import java.util.Map;

/**
 * SQL DROP CLASS command: Drops a class from the database. Cluster associated are removed too if are used exclusively by the
 * deleting class.
 *
 * @author Luca Garulli
 */
@SuppressWarnings("unchecked")
public class OCommandExecutorSQLDropClass extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest {
  public static final String KEYWORD_DROP   = "DROP";
  public static final String KEYWORD_CLASS  = "CLASS";
  public static final String KEYWORD_UNSAFE = "UNSAFE";

  private String             className;
  private boolean            unsafe;
  private boolean            ifExists       = false;

  public OCommandExecutorSQLDropClass parse(final OCommandRequest iRequest) {
    final OCommandRequestText textRequest = (OCommandRequestText) iRequest;

    String queryText = textRequest.getText();
    String originalQuery = queryText;
    try {
      queryText = preParse(queryText, iRequest);
      textRequest.setText(queryText);
      final boolean strict = getDatabase().getStorage().getConfiguration().isStrictSql();
      if (strict) {
        this.className = ((ODropClassStatement) this.preParsedStatement).name.getStringValue();
        this.unsafe = ((ODropClassStatement) this.preParsedStatement).unsafe;
        this.ifExists = ((ODropClassStatement) this.preParsedStatement).ifExists;
      } else {
        oldParsing((OCommandRequestText) iRequest);
      }
    } finally {
      textRequest.setText(originalQuery);
    }

    return this;
  }

  private void oldParsing(OCommandRequestText iRequest) {
    init(iRequest);

    final StringBuilder word = new StringBuilder();

    int oldPos = 0;
    int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true);
    if (pos == -1 || !word.toString().equals(KEYWORD_DROP)) {
      throw new OCommandSQLParsingException("Keyword " + KEYWORD_DROP + " not found. Use " + getSyntax(), parserText, oldPos);
    }

    pos = nextWord(parserText, parserTextUpperCase, pos, word, true);
    if (pos == -1 || !word.toString().equals(KEYWORD_CLASS)) {
      throw new OCommandSQLParsingException("Keyword " + KEYWORD_CLASS + " not found. Use " + getSyntax(), parserText, oldPos);
    }

    pos = nextWord(parserText, parserTextUpperCase, pos, word, false);
    if (pos == -1) {
      throw new OCommandSQLParsingException("Expected <class>. Use " + getSyntax(), parserText, pos);
    }

    className = word.toString();

    pos = nextWord(parserText, parserTextUpperCase, pos, word, true);
    if (pos > -1 && KEYWORD_UNSAFE.equalsIgnoreCase(word.toString())) {
      unsafe = true;
    }
  }

  @Override
  public QUORUM_TYPE getQuorumType() {
    return QUORUM_TYPE.ALL;
  }

  @Override
  public long getDistributedTimeout() {
    final OClass cls = getDatabase().getMetadata().getSchema().getClass(className);
    if (className != null && cls != null)
      return getDatabase().getConfiguration().getValueAsLong(OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT) + (2 * cls.count());

    return getDatabase().getConfiguration().getValueAsLong(OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT);
  }

  /**
   * Execute the DROP CLASS.
   */
  public Object execute(final Map<Object, Object> iArgs) {
    if (className == null) {
      throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet");
    }

    final ODatabaseDocument database = getDatabase();
    if (ifExists && !database.getMetadata().getSchema().existsClass(className)) {
      return true;
    }
    final OClass cls = database.getMetadata().getSchema().getClass(className);
    if (cls == null) {
      return null;
    }

    final long records = cls.count(true);

    if (records > 0 && !unsafe) {
      // NOT EMPTY, CHECK IF CLASS IS OF VERTEX OR EDGES
      if (cls.isSubClassOf("V")) {
        // FOUND VERTEX CLASS
        throw new OCommandExecutionException("'DROP CLASS' command cannot drop class '" + className
            + "' because it contains Vertices. Use 'DELETE VERTEX' command first to avoid broken edges in a database, or apply the 'UNSAFE' keyword to force it");
      } else if (cls.isSubClassOf("E")) {
        // FOUND EDGE CLASS
        throw new OCommandExecutionException("'DROP CLASS' command cannot drop class '" + className
            + "' because it contains Edges. Use 'DELETE EDGE' command first to avoid broken vertices in a database, or apply the 'UNSAFE' keyword to force it");
      }
    }

    database.getMetadata().getSchema().dropClass(className);

    if (records > 0 && unsafe) {
      // NOT EMPTY, CHECK IF CLASS IS OF VERTEX OR EDGES
      if (cls.isSubClassOf("V")) {
        // FOUND VERTICES
        if (unsafe)
          OLogManager.instance().warn(this,
              "Dropped class '%s' containing %d vertices using UNSAFE mode. Database could contain broken edges", className,
              records);
      } else if (cls.isSubClassOf("E")) {
        // FOUND EDGES
        OLogManager.instance().warn(this,
            "Dropped class '%s' containing %d edges using UNSAFE mode. Database could contain broken vertices", className, records);
      }
    }

    return true;
  }

  @Override
  public String getSyntax() {
    return "DROP CLASS <class> [IF EXISTS] [UNSAFE]";
  }

  @Override
  public boolean involveSchema() {
    return true;
  }
}


/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *  *
 *  *  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.
 *  *
 *  * For more information: http://orientdb.com
 *
 */
package com.orientechnologies.orient.core.storage.impl.local;

import com.orientechnologies.common.exception.OException;
import com.orientechnologies.common.io.OIOUtils;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.serialization.types.OByteSerializer;
import com.orientechnologies.common.serialization.types.OIntegerSerializer;
import com.orientechnologies.common.serialization.types.OLongSerializer;
import com.orientechnologies.orient.core.config.OContextConfiguration;
import com.orientechnologies.orient.core.config.OStorageConfigurationImpl;
import com.orientechnologies.orient.core.exception.OSerializationException;
import com.orientechnologies.orient.core.exception.OStorageException;
import com.orientechnologies.orient.core.storage.disk.OLocalPaginatedStorage;
import com.orientechnologies.orient.core.storage.fs.OFileClassic;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.zip.CRC32;

/**
 * Handles the database configuration in one big record.
 */
public class OStorageConfigurationSegment extends OStorageConfigurationImpl {
  //This class uses "double write" pattern.
  //Whenever we want to update configuration, first we write data in backup file and make fsync. Then we write the same data
  //in primary file and make fsync. Then we remove backup file. So does not matter if we have error on any of this stages
  //we always will have consistent storage configuration.
  //Downside of this approach is the double overhead during write of configuration, but it was chosen to keep binary compatibility
  //between versions.

  /**
   * Name of primary file
   */
  private static final String NAME = "database.ocf";

  /**
   * Name of backup file which is used when we update storage configuration using double write pattern
   */
  private static final String BACKUP_NAME = "database.ocf2";

  private static final long ENCODING_FLAG_1 = 128975354756545L;
  private static final long ENCODING_FLAG_2 = 587138568122547L;
  private static final long ENCODING_FLAG_3 = 812587836547249L;

  private static final int  CRC_32_OFFSET  = 100;
  private static final byte FORMAT_VERSION = (byte) 42;

  private final String storageName;
  private final Path   storagePath;

  public OStorageConfigurationSegment(final OLocalPaginatedStorage storage) {
    super(storage, Charset.forName("UTF-8"));

    this.storageName = storage.getName();
    this.storagePath = storage.getStoragePath();
  }

  @Override
  public void delete() throws IOException {
    lock.acquireWriteLock();
    try {
      super.delete();

      clearConfigurationFiles();
    } finally {
      lock.releaseWriteLock();
    }
  }

  /**
   * Remove both backup and primary configuration files on delete
   *
   * @see #update()
   */
  private void clearConfigurationFiles() throws IOException {
    final Path file = storagePath.resolve(NAME);
    Files.deleteIfExists(file);

    final Path backupFile = storagePath.resolve(BACKUP_NAME);
    Files.deleteIfExists(backupFile);
  }

  @Override
  public void create() throws IOException {
    lock.acquireWriteLock();
    try {
      clearConfigurationFiles();

      super.create();
    } finally {
      lock.releaseWriteLock();
    }
  }

  @Override
  public OStorageConfigurationImpl load(final OContextConfiguration configuration) throws OSerializationException {
    lock.acquireWriteLock();
    try {
      initConfiguration(configuration);

      final Path file = storagePath.resolve(NAME);
      final Path backupFile = storagePath.resolve(BACKUP_NAME);

      if (Files.exists(file)) {
        if (readData(file)) {
          return this;
        }

        OLogManager.instance()
            .warnNoDb(this, "Main storage configuration file %s is broken in storage %s, try to read from backup file %s", file,
                storageName, backupFile);

        if (Files.exists(backupFile)) {
          if (readData(backupFile)) {
            return this;
          }

          OLogManager.instance().errorNoDb(this, "Backup configuration file %s is broken too", null);
          throw new OStorageException("Invalid format for configuration file " + file + " for storage" + storageName);
        } else {
          OLogManager.instance().errorNoDb(this, "Backup configuration file %s does not exist", null, backupFile);
          throw new OStorageException("Invalid format for configuration file " + file + " for storage" + storageName);
        }
      } else if (Files.exists(backupFile)) {
        OLogManager.instance().warn(this, "Seems like previous update to the storage '%s' configuration was finished incorrectly, "
            + "main configuration file %s is absent, reading from backup", backupFile, file);

        if (readData(backupFile)) {
          return this;
        }

        OLogManager.instance().errorNoDb(this, "Backup configuration file %s is broken", null, backupFile);
        throw new OStorageException("Invalid format for configuration file " + backupFile + " for storage" + storageName);
      } else {
        throw new OStorageException("Can not find configuration file for storage " + storageName);
      }
    } catch (IOException e) {
      throw OException
          .wrapException(new OSerializationException("Cannot load database configuration. The database seems corrupted"), e);
    } finally {
      lock.releaseWriteLock();
    }
  }

  @Override
  public void update() throws OSerializationException {
    lock.acquireWriteLock();
    try {
      final Charset utf8 = Charset.forName("UTF-8");
      final byte[] buffer = toStream(utf8);

      final ByteBuffer byteBuffer = ByteBuffer.allocate(buffer.length + OIntegerSerializer.INT_SIZE);
      byteBuffer.putInt(buffer.length);
      byteBuffer.put(buffer);

      try {
        if (!Files.exists(storagePath)) {
          Files.createDirectories(storagePath);
        }

        final Path backupFile = storagePath.resolve(BACKUP_NAME);
        Files.deleteIfExists(backupFile);

        try (FileChannel channel = FileChannel.open(backupFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)) {
          writeConfigFile(buffer, byteBuffer, channel);
        }

        final Path file = storagePath.resolve(NAME);
        Files.deleteIfExists(file);

        try (FileChannel channel = FileChannel.open(file, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)) {
          writeConfigFile(buffer, byteBuffer, channel);
        }

        Files.delete(backupFile);
      } catch (Exception e) {
        throw OException.wrapException(new OSerializationException("Error on update storage configuration"), e);
      }
    } finally {
      lock.releaseWriteLock();
    }
  }

  private void writeConfigFile(byte[] buffer, ByteBuffer byteBuffer, FileChannel channel) throws IOException {
    final ByteBuffer versionBuffer = ByteBuffer.allocate(1);
    versionBuffer.put(FORMAT_VERSION);

    versionBuffer.position(0);
    OIOUtils.writeByteBuffer(versionBuffer, channel, OFileClassic.VERSION_OFFSET);

    final ByteBuffer crc32buffer = ByteBuffer.allocate(OIntegerSerializer.INT_SIZE);
    final CRC32 crc32 = new CRC32();
    crc32.update(buffer);
    crc32buffer.putInt((int) crc32.getValue());

    crc32buffer.position(0);
    OIOUtils.writeByteBuffer(crc32buffer, channel, CRC_32_OFFSET);

    channel.force(true);

    byteBuffer.position(0);
    OIOUtils.writeByteBuffer(byteBuffer, channel, OFileClassic.HEADER_SIZE);

    channel.force(true);
  }

  private boolean readData(Path file) throws IOException {
    final ByteBuffer byteBuffer;
    final byte fileVersion;
    final int crc32content;

    try (final FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) {
      //file header + size of content + at least one byte of content
      if (channel.size() < OFileClassic.HEADER_SIZE + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE) {
        return false;
      }

      final ByteBuffer versionBuffer = ByteBuffer.allocate(1);
      OIOUtils.readByteBuffer(versionBuffer, channel, OFileClassic.VERSION_OFFSET, true);
      versionBuffer.position(0);

      fileVersion = versionBuffer.get();
      if (fileVersion >= 42) {
        final ByteBuffer crc32buffer = ByteBuffer.allocate(OIntegerSerializer.INT_SIZE);
        OIOUtils.readByteBuffer(crc32buffer, channel, CRC_32_OFFSET, true);

        crc32buffer.position(0);
        crc32content = crc32buffer.getInt();
      } else {
        crc32content = 0;
      }

      byteBuffer = ByteBuffer.allocate((int) channel.size() - OFileClassic.HEADER_SIZE);
      OIOUtils.readByteBuffer(byteBuffer, channel, OFileClassic.HEADER_SIZE, true);
    }

    byteBuffer.position(0);
    final int size = byteBuffer.getInt();//size of string which contains database configuration
    byte[] buffer = new byte[size];

    byteBuffer.get(buffer);

    if (fileVersion < 42) {
      if (byteBuffer.limit() >= size + 2 * OIntegerSerializer.INT_SIZE + 3 * OLongSerializer.LONG_SIZE) {
        final long encodingFagOne = byteBuffer.getLong();
        final long encodingFagTwo = byteBuffer.getLong();
        final long encodingFagThree = byteBuffer.getLong();

        final Charset streamEncoding;

        //those flags are added to distinguish between old version of configuration file and new one.
        if (encodingFagOne == ENCODING_FLAG_1 && encodingFagTwo == ENCODING_FLAG_2 && encodingFagThree == ENCODING_FLAG_3) {
          final byte[] utf8Encoded = "UTF-8".getBytes(Charset.forName("UTF-8"));

          final int encodingNameLength = byteBuffer.getInt();

          if (encodingNameLength == utf8Encoded.length) {
            final byte[] binaryEncodingName = new byte[encodingNameLength];
            byteBuffer.get(binaryEncodingName);

            final String encodingName = new String(binaryEncodingName, "UTF-8");

            if (encodingName.equals("UTF-8")) {
              streamEncoding = Charset.forName("UTF-8");
            } else {
              return false;
            }

            try {
              fromStream(buffer, 0, buffer.length, streamEncoding);
            } catch (Exception e) {
              OLogManager.instance().errorNoDb(this, "Error during reading of configuration %s of storage %s", e, file, storageName);
              return false;
            }

          } else {
            return false;
          }
        } else {
          try {
            fromStream(buffer, 0, buffer.length, Charset.defaultCharset());
          } catch (Exception e) {
            OLogManager.instance().errorNoDb(this, "Error during reading of configuration %s of storage %s", e, file, storageName);
            return false;
          }
        }
      } else {
        try {
          fromStream(buffer, 0, buffer.length, Charset.defaultCharset());
        } catch (Exception e) {
          OLogManager.instance().errorNoDb(this, "Error during reading of configuration %s of storage %s", e, file, storageName);
          return false;
        }
      }
    } else {
      CRC32 crc32 = new CRC32();
      crc32.update(buffer);

      if (crc32content != (int) crc32.getValue()) {
        return false;
      }

      try {
        fromStream(buffer, 0, buffer.length, Charset.forName("UTF-8"));
      } catch (Exception e) {
        OLogManager.instance().errorNoDb(this, "Error during reading of configuration %s of storage %s", e, file, storageName);
        return false;
      }
    }

    return true;
  }
}


/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *  *
 *  *  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.
 *  *
 *  * For more information: http://orientdb.com
 *
 */
package com.orientechnologies.orient.object.db;

import java.io.Serializable;
import java.util.*;
import javassist.util.proxy.Proxy;
import javassist.util.proxy.ProxyObject;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.db.object.OLazyObjectListInterface;
import com.orientechnologies.orient.core.db.object.OObjectLazyMultivalueElement;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.object.enhancement.OObjectEntityEnhancer;
import com.orientechnologies.orient.object.enhancement.OObjectEntitySerializer;
import com.orientechnologies.orient.object.enhancement.OObjectProxyMethodHandler;

@SuppressWarnings({ "unchecked" })
public class OObjectLazyList<TYPE> extends ArrayList<TYPE> implements OLazyObjectListInterface<TYPE>,
    OObjectLazyMultivalueElement<List<TYPE>>, Serializable {
  private static final long         serialVersionUID = -1665952780303555865L;
  private ProxyObject               sourceRecord;
  private final List<OIdentifiable> recordList;
  private String                    fetchPlan;
  private boolean                   converted        = false;
  private boolean                   convertToRecord  = true;
  private final boolean             orphanRemoval;

  public OObjectLazyList(final Object iSourceRecord, final List<OIdentifiable> iRecordList, final boolean orphanRemoval) {
    this.sourceRecord = iSourceRecord instanceof ProxyObject ? (ProxyObject) iSourceRecord : null;
    this.recordList = iRecordList;
    this.orphanRemoval = orphanRemoval;
    for (int i = 0; i < iRecordList.size(); i++) {
      super.add(i, null);
    }
  }

  public OObjectLazyList(final Object iSourceRecord, final List<OIdentifiable> iRecordList,
      final Collection<? extends TYPE> iSourceList, final boolean orphanRemoval) {
    this.sourceRecord = iSourceRecord instanceof ProxyObject ? (ProxyObject) iSourceRecord : null;
    this.recordList = iRecordList;
    this.orphanRemoval = orphanRemoval;
    for (int i = 0; i < iRecordList.size(); i++) {
      super.add(i, null);
    }
    addAll(iSourceList);
  }

  public Iterator<TYPE> iterator() {
    return new OObjectLazyListIterator<TYPE>(this, sourceRecord);
  }

  public Spliterator<TYPE> spliterator() {
    return Spliterators.spliterator(this, Spliterator.ORDERED);
  }

  public boolean contains(final Object o) {
    if (o instanceof Proxy)
      return recordList.contains(OObjectEntitySerializer.getDocument((Proxy) o));
    else if (o instanceof OIdentifiable)
      return recordList.contains(o);
    convertAll();
    return super.contains(o);
  }

  public boolean add(TYPE element) {
    boolean dirty = false;
    OIdentifiable record;
    if (element instanceof OIdentifiable) {
      record = (OIdentifiable) element;
      if (converted)
        converted = false;
      if (recordList.add(record)) {
        setDirty();
        if (orphanRemoval && record != null && sourceRecord != null)
          ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().remove(record.getIdentity());
        return true;
      }
    } else if (element instanceof Proxy) {
      record = (OIdentifiable) OObjectEntitySerializer.getDocument((Proxy) element);
      if (orphanRemoval && record != null && sourceRecord != null)
        ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().remove(record.getIdentity());
      dirty = recordList.add(record);
    } else {
      element = (TYPE) OObjectEntitySerializer.serializeObject(element, getDatabase());
      record = (OIdentifiable) OObjectEntitySerializer.getDocument((Proxy) element);
      if (orphanRemoval && record != null && sourceRecord != null)
        ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().remove(record.getIdentity());
      dirty = recordList.add(record);
    }
    if (dirty)
      setDirty();
    return super.add(element);
  }

  public void add(int index, TYPE element) {
    setDirty();
    OIdentifiable record;
    if (element instanceof OIdentifiable) {
      record = (OIdentifiable) element;
      if (converted)
        converted = false;
      if (orphanRemoval && record != null && sourceRecord != null)
        ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().remove(record.getIdentity());
      recordList.add(index, record);
      return;
    } else if (element instanceof Proxy) {
      record = (OIdentifiable) OObjectEntitySerializer.getDocument((Proxy) element);
      if (orphanRemoval && record != null && sourceRecord != null)
        ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().remove(record.getIdentity());
      recordList.add(index, record);
    } else {
      element = (TYPE) OObjectEntitySerializer.serializeObject(element, getDatabase());
      record = (OIdentifiable) OObjectEntitySerializer.getDocument((Proxy) element);
      if (orphanRemoval && record != null && sourceRecord != null)
        ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().remove(record.getIdentity());
      recordList.add(index, record);
    }
    super.add(index, element);
  }

  public TYPE get(final int index) {
    TYPE o = (TYPE) super.get(index);
    if (o == null) {
      OIdentifiable record = (OIdentifiable) recordList.get(index);
      if (record == null || record.getRecord() == null) {
        OLogManager.instance().warn(
            this,
            "Record " + ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getDoc().getIdentity()
                + " references a deleted instance");
        return null;
      }
      o = (TYPE) OObjectEntityEnhancer.getInstance().getProxiedInstance(((ODocument) record.getRecord()).getClassName(),
          getDatabase().getEntityManager(), (ODocument) record.getRecord(), sourceRecord);
      super.set(index, o);
    }
    return o;
  }

  public int indexOf(final Object o) {
    if (o instanceof Proxy)
      return recordList.indexOf(OObjectEntitySerializer.getDocument((Proxy) o));
    else if (o instanceof OIdentifiable)
      return recordList.indexOf(o);
    convertAll();
    return super.indexOf(o);
  }

  public int lastIndexOf(final Object o) {
    if (o instanceof Proxy)
      return recordList.lastIndexOf(OObjectEntitySerializer.getDocument((Proxy) o));
    else if (o instanceof OIdentifiable)
      return recordList.lastIndexOf(o);
    convertAll();
    return super.lastIndexOf(o);
  }

  public Object[] toArray() {
    convertAll();
    return super.toArray();
  }

  public <T> T[] toArray(final T[] a) {
    convertAll();
    return super.toArray(a);
  }

  public int size() {
    return recordList.size();
  }

  public boolean isEmpty() {
    return recordList.isEmpty();
  }

  public boolean remove(Object o) {
    setDirty();
    if (o instanceof OIdentifiable) {
      int elementIndex = recordList.indexOf(o);
      elementIndex = elementIndex > -1 ? elementIndex : recordList.indexOf(((OIdentifiable) o).getRecord());
      if (elementIndex > -1 && indexLoaded(elementIndex))
        super.remove(elementIndex);
      if (orphanRemoval && sourceRecord != null)
        ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().add(((OIdentifiable) o).getIdentity());
      return recordList.remove(o);
    } else if (o instanceof Proxy) {
      OIdentifiable record = (OIdentifiable) OObjectEntitySerializer.getDocument((Proxy) o);
      if (orphanRemoval && record != null && sourceRecord != null)
        ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().add(record.getIdentity());
      recordList.remove(record);
    } else {
      OIdentifiable record = getDatabase().getRecordByUserObject(o, false);
      if (orphanRemoval && record != null && sourceRecord != null)
        ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().add(record.getIdentity());
      recordList.remove(record);
    }
    return super.remove(o);
  }

  public boolean containsAll(Collection<?> c) {
    for (Object o : c) {
      if (!contains(o))
        return false;
    }
    return true;
  }

  public boolean addAll(Collection<? extends TYPE> c) {
    boolean dirty = false;
    for (TYPE element : c) {
      dirty |= add(element);
    }
    if (dirty)
      setDirty();
    return dirty;
  }

  public boolean addAll(int index, Collection<? extends TYPE> c) {
    for (TYPE element : c) {
      add(index, element);
      index++;
    }
    if (c.size() > 0)
      setDirty();
    return c.size() > 0;
  }

  public boolean removeAll(Collection<?> c) {
    boolean dirty = true;
    for (Object o : c) {
      dirty = remove(o) || dirty;
    }
    if (dirty)
      setDirty();
    return dirty;
  }

  public boolean retainAll(Collection<?> c) {
    boolean modified = false;
    Iterator<TYPE> e = iterator();
    while (e.hasNext()) {
      Object value = e.next();
      if (!c.contains(value)) {
        remove(value);
        modified = true;
      }
    }
    return modified;
  }

  public void clear() {
    setDirty();
    if (orphanRemoval && sourceRecord != null)
      for (OIdentifiable value : recordList)
        ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().add(value.getIdentity());
    recordList.clear();
    super.clear();
  }

  public TYPE set(int index, TYPE element) {
    OIdentifiable record;
    if (element instanceof OIdentifiable) {
      record = (OIdentifiable) element;
      if (converted)
        converted = false;
      if (orphanRemoval && record != null && sourceRecord != null)
        ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().remove(record.getIdentity());
      recordList.set(index, record);
    } else if (element instanceof Proxy) {
      record = (OIdentifiable) OObjectEntitySerializer.getDocument((Proxy) element);
      if (orphanRemoval && record != null && sourceRecord != null)
        ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().remove(record.getIdentity());
      recordList.set(index, record);
    } else {
      element = (TYPE) OObjectEntitySerializer.serializeObject(element, getDatabase());
      record = getDatabase().getRecordByUserObject(element, false);
      if (orphanRemoval && record != null && sourceRecord != null)
        ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().remove(record.getIdentity());
      recordList.add(index, record);
    }
    setDirty();
    return (TYPE) super.set(index, element);
  }

  public TYPE remove(int index) {
    TYPE element;
    OIdentifiable record = recordList.remove(index);
    if (indexLoaded(index)) {
      if (orphanRemoval && record != null && sourceRecord != null)
        ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().add(record.getIdentity());
      element = (TYPE) super.remove(index);
    } else {
      if (orphanRemoval && record != null && sourceRecord != null)
        ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getOrphans().add(record.getIdentity());
      element = (TYPE) OObjectEntityEnhancer.getInstance().getProxiedInstance(((ODocument) record.getRecord()).getClassName(),
          getDatabase().getEntityManager(), (ODocument) record.getRecord(), sourceRecord);
    }
    setDirty();
    return element;
  }

  public ListIterator<TYPE> listIterator() {
    return (ListIterator<TYPE>) super.listIterator();
  }

  public ListIterator<TYPE> listIterator(int index) {
    return (ListIterator<TYPE>) super.listIterator(index);
  }

  public List<TYPE> subList(int fromIndex, int toIndex) {
    return (List<TYPE>) super.subList(fromIndex, toIndex);
  }

  public String getFetchPlan() {
    return fetchPlan;
  }

  public OObjectLazyList<TYPE> setFetchPlan(final String fetchPlan) {
    this.fetchPlan = fetchPlan;
    return this;
  }

  public boolean isConvertToRecord() {
    return convertToRecord;
  }

  public void setConvertToRecord(boolean convertToRecord) {
    this.convertToRecord = convertToRecord;
  }

  public boolean isConverted() {
    return converted;
  }

  public void detach() {
    convertAll();
  }

  public void detach(boolean nonProxiedInstance) {
    convertAll();
  }

  protected void convertAll() {
    if (converted || !convertToRecord)
      return;

    for (int i = 0; i < size(); ++i)
      convert(i);

    converted = true;
  }

  public void setDirty() {
    if (sourceRecord != null)
      ((OObjectProxyMethodHandler) sourceRecord.getHandler()).setDirty();
  }

  @Override
  public List<TYPE> getNonOrientInstance() {
    List<TYPE> list = new ArrayList<TYPE>();
    list.addAll(this);
    return list;
  }

  @Override
  public Object getUnderlying() {
    return recordList;
  }

  /**
   * Convert the item requested.
   *
   * @param iIndex
   *          Position of the item to convert
   */
  private void convert(final int iIndex) {
    if (converted || !convertToRecord)
      return;

    Object o = super.get(iIndex);
    if (o == null) {

      final ODatabaseDocument database = getDatabase().getUnderlying();

      o = recordList.get(iIndex);
      ODocument doc;
      if (o instanceof ORID) {
        doc = database.load((ORID) o, fetchPlan);
      } else {
        doc = (ODocument) o;
      }
      if (o == null) {
        OLogManager.instance().warn(
            this,
            "Record " + ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getDoc().getIdentity()
                + " references a deleted instance");
        return;
      }
      super.set(
          iIndex,
          (TYPE) OObjectEntityEnhancer.getInstance().getProxiedInstance(doc.getClassName(), getDatabase().getEntityManager(), doc,
              sourceRecord));
    }
  }

  public void detachAll(boolean nonProxiedInstance, Map<Object, Object> alreadyDetached, Map<Object, Object> lazyObjects) {
    convertAndDetachAll(nonProxiedInstance, alreadyDetached, lazyObjects);
  }

  protected void convertAndDetachAll(boolean nonProxiedInstance, Map<Object, Object> alreadyDetached, Map<Object, Object> lazyObjects) {
    if (converted || !convertToRecord)
      return;

    for (int i = 0; i < size(); ++i)
      convertAndDetachAll(i, nonProxiedInstance, alreadyDetached, lazyObjects);

    converted = true;
  }

  private void convertAndDetachAll(final int iIndex, boolean nonProxiedInstance, Map<Object, Object> alreadyDetached, Map<Object, Object> lazyObjects) {
    if (converted || !convertToRecord)
      return;

    Object o = super.get(iIndex);
    if (o == null) {

      final ODatabaseDocument database = getDatabase().getUnderlying();

      o = recordList.get(iIndex);
      ODocument doc;
      if (o instanceof ORID) {
        doc = database.load((ORID) o, fetchPlan);
      } else {
        doc = (ODocument) o;
      }
      if (o == null) {
        OLogManager.instance().warn(
            this,
            "Record " + ((OObjectProxyMethodHandler) sourceRecord.getHandler()).getDoc().getIdentity()
                + " references a deleted instance");
        return;
      }
      o = OObjectEntityEnhancer.getInstance().getProxiedInstance(doc.getClassName(), getDatabase().getEntityManager(), doc,
          sourceRecord);
      o = ((OObjectDatabaseTx) getDatabase()).detachAll(o, nonProxiedInstance, alreadyDetached, lazyObjects);
      super.set(iIndex, (TYPE) o);
    }
  }

  protected OObjectDatabaseTx getDatabase() {
    return (OObjectDatabaseTx) OLazyCollectionUtil.getDatabase();
  }

  protected boolean indexLoaded(int iIndex) {
    return super.get(iIndex) != null;
  }

  @Override
  public String toString() {
    return super.toString();
  }
}


/*
  *
  *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
  *  *
  *  *  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.
  *  *
  *  * For more information: http://orientdb.com
  *
  */
package com.orientechnologies.orient.core.serialization;

import com.orientechnologies.common.util.OArrays;

import java.io.IOException;
import java.io.InputStream;

/**
 * Class to parse and write buffers in very fast way.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 *
 */
public class OMemoryInputStream extends InputStream {
  private byte[] buffer;
  private int    offset = 0;
  private int    length;
  private int    position;

  public OMemoryInputStream() {
  }

  public OMemoryInputStream(final byte[] iBuffer) {
    setSource(iBuffer);
  }

  public OMemoryInputStream(final byte[] iBuffer, final int iOffset, final int iLength) {
    setSource(iBuffer, iOffset, iLength);
  }

  public byte[] getAsByteArrayFixed(final int iSize) throws IOException {
    if (position >= length)
      return null;

    final byte[] portion = OArrays.copyOfRange(buffer, position, position + iSize);
    position += iSize;

    return portion;
  }

  @Override
  public int available() throws IOException {
    return length - position;
  }

  /**
   * Browse the stream but just return the begin of the byte array. This is used to lazy load the information only when needed.
   *
   */
  public int getAsByteArrayOffset() {
    if (position >= length)
      return -1;

    final int begin = position;

    final int size = OBinaryProtocol.bytes2int(buffer, position);
    position += OBinaryProtocol.SIZE_INT + size;

    return begin;
  }

  @Override
  public int read() throws IOException {
    if (position >= length)
      return -1;

    return buffer[position++] & 0xFF;
  }

  @Override
  public int read(final byte[] b) throws IOException {
    return read(b, 0, b.length);
  }

  @Override
  public int read(final byte[] b, final int off, final int len) throws IOException {
    if (position >= length)
      return -1;

    int newLen;
    if (position + len >= length)
      newLen = length - position;
    else
      newLen = len;

    if (off + newLen >= b.length)
      newLen = b.length - off;

    if (newLen <= 0)
      return 0;

    System.arraycopy(buffer, position, b, off, newLen);
    position += newLen;

    return newLen;
  }

  public byte[] getAsByteArray(int iOffset) throws IOException {
    if (buffer == null || iOffset >= length)
      return null;

    final int size = OBinaryProtocol.bytes2int(buffer, iOffset);

    if (size == 0)
      return null;

    iOffset += OBinaryProtocol.SIZE_INT;

    return OArrays.copyOfRange(buffer, iOffset, iOffset + size);
  }

  public byte[] getAsByteArray() throws IOException {
    if (position >= length)
      return null;

    final int size = OBinaryProtocol.bytes2int(buffer, position);
    position += OBinaryProtocol.SIZE_INT;

    final byte[] portion = OArrays.copyOfRange(buffer, position, position + size);
    position += size;

    return portion;
  }

  public boolean getAsBoolean() throws IOException {
    return buffer[position++] == 1;
  }

  public char getAsChar() throws IOException {
    final char value = OBinaryProtocol.bytes2char(buffer, position);
    position += OBinaryProtocol.SIZE_CHAR;
    return value;
  }

  public byte getAsByte() throws IOException {
    return buffer[position++];
  }

  public long getAsLong() throws IOException {
    final long value = OBinaryProtocol.bytes2long(buffer, position);
    position += OBinaryProtocol.SIZE_LONG;
    return value;
  }

  public int getAsInteger() throws IOException {
    final int value = OBinaryProtocol.bytes2int(buffer, position);
    position += OBinaryProtocol.SIZE_INT;
    return value;
  }

  public short getAsShort() throws IOException {
    final short value = OBinaryProtocol.bytes2short(buffer, position);
    position += OBinaryProtocol.SIZE_SHORT;
    return value;
  }

  public void close() {
    buffer = null;
  }

  public byte peek() {
    return buffer[position];
  }

  public void setSource(final byte[] iBuffer) {
    buffer = iBuffer;
    position = 0;
    offset = 0;
    length = iBuffer.length;
  }

  public void setSource(final byte[] iBuffer, final int iOffset, final int iLength) {
    buffer = iBuffer;
    position = iOffset;
    offset = iOffset;
    length = iLength + iOffset;
  }

  public OMemoryInputStream jump(final int iOffset) {
    position += iOffset;
    return this;
  }

  public byte[] copy() {
    if (buffer == null)
      return null;

    final int size = position > 0 ? position : buffer.length;

    final byte[] copy = new byte[size];
    System.arraycopy(buffer, 0, copy, 0, size);
    return copy;
  }

  public int getVariableSize() throws IOException {
    if (position >= length)
      return -1;

    final int size = OBinaryProtocol.bytes2int(buffer, position);
    position += OBinaryProtocol.SIZE_INT;

    return size;
  }

  public int getSize() {
    return buffer.length;
  }

  public int getPosition() {
    return position;
  }

  @Override
  public synchronized void reset() throws IOException {
    position = offset;
  }
}


/*
 * Copyright 2015-2015 the original author or authors.
 *
 * 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
 *
 *      https://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.springframework.cloud.security.oauth2.client.feign;

import java.util.Arrays;

import feign.RequestInterceptor;
import feign.RequestTemplate;

import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.http.AccessTokenRequiredException;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
import org.springframework.security.oauth2.client.token.AccessTokenProvider;
import org.springframework.security.oauth2.client.token.AccessTokenProviderChain;
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider;
import org.springframework.security.oauth2.common.OAuth2AccessToken;

/**
 * Pre-defined custom RequestInterceptor for Feign Requests. It uses the
 * {@link OAuth2ClientContext OAuth2ClientContext} provided from the environment and
 * construct a new header on the request before it is made by Feign.
 *
 * @author Joao Pedro Evangelista
 */
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {

	/**
	 * The name of the token.
	 */
	public static final String BEARER = "Bearer";

	/**
	 * The name of the header.
	 */
	public static final String AUTHORIZATION = "Authorization";

	private final OAuth2ClientContext oAuth2ClientContext;

	private final OAuth2ProtectedResourceDetails resource;

	private final String tokenType;

	private final String header;

	private AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(
			Arrays.<AccessTokenProvider>asList(new AuthorizationCodeAccessTokenProvider(),
					new ImplicitAccessTokenProvider(),
					new ResourceOwnerPasswordAccessTokenProvider(),
					new ClientCredentialsAccessTokenProvider()));

	/**
	 * Default constructor which uses the provided OAuth2ClientContext and Bearer tokens
	 * within Authorization header.
	 * @param oAuth2ClientContext provided context
	 * @param resource type of resource to be accessed
	 */
	public OAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext,
			OAuth2ProtectedResourceDetails resource) {
		this(oAuth2ClientContext, resource, BEARER, AUTHORIZATION);
	}

	/**
	 * Fully customizable constructor for changing token type and header name, in cases of
	 * Bearer and Authorization is not the default such as "bearer", "authorization".
	 * @param oAuth2ClientContext current oAuth2 Context
	 * @param resource type of resource to be accessed
	 * @param tokenType type of token e.g. "token", "Bearer"
	 * @param header name of the header e.g. "Authorization", "authorization"
	 */
	public OAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext,
			OAuth2ProtectedResourceDetails resource, String tokenType, String header) {
		this.oAuth2ClientContext = oAuth2ClientContext;
		this.resource = resource;
		this.tokenType = tokenType;
		this.header = header;
	}

	/**
	 * Create a template with the header of provided name and extracted extract.
	 *
	 * @see RequestInterceptor#apply(RequestTemplate)
	 */
	@Override
	public void apply(RequestTemplate template) {
		template.header(header, extract(tokenType));
	}

	/**
	 * Extracts the token extract id the access token exists or returning an empty extract
	 * if there is no one on the context it may occasionally causes Unauthorized response
	 * since the token extract is empty.
	 * @param tokenType type name of token
	 * @return token value from context if it exists otherwise empty String
	 */
	protected String extract(String tokenType) {
		OAuth2AccessToken accessToken = getToken();
		return String.format("%s %s", tokenType, accessToken.getValue());
	}

	/**
	 * Extract the access token within the request or try to acquire a new one by
	 * delegating it to {@link #acquireAccessToken()}.
	 * @return valid token
	 */
	public OAuth2AccessToken getToken() {

		OAuth2AccessToken accessToken = oAuth2ClientContext.getAccessToken();
		if (accessToken == null || accessToken.isExpired()) {
			try {
				accessToken = acquireAccessToken();
			}
			catch (UserRedirectRequiredException e) {
				oAuth2ClientContext.setAccessToken(null);
				String stateKey = e.getStateKey();
				if (stateKey != null) {
					Object stateToPreserve = e.getStateToPreserve();
					if (stateToPreserve == null) {
						stateToPreserve = "NONE";
					}
					oAuth2ClientContext.setPreservedState(stateKey, stateToPreserve);
				}
				throw e;
			}
		}
		return accessToken;
	}

	/**
	 * Try to acquire the token using a access token provider.
	 * @return valid access token
	 * @throws UserRedirectRequiredException in case the user needs to be redirected to an
	 * approval page or login page
	 */
	protected OAuth2AccessToken acquireAccessToken()
			throws UserRedirectRequiredException {
		AccessTokenRequest tokenRequest = oAuth2ClientContext.getAccessTokenRequest();
		if (tokenRequest == null) {
			throw new AccessTokenRequiredException(
					"Cannot find valid context on request for resource '"
							+ resource.getId() + "'.",
					resource);
		}
		String stateKey = tokenRequest.getStateKey();
		if (stateKey != null) {
			tokenRequest.setPreservedState(
					oAuth2ClientContext.removePreservedState(stateKey));
		}
		OAuth2AccessToken existingToken = oAuth2ClientContext.getAccessToken();
		if (existingToken != null) {
			oAuth2ClientContext.setAccessToken(existingToken);
		}
		OAuth2AccessToken obtainableAccessToken;
		obtainableAccessToken = accessTokenProvider.obtainAccessToken(resource,
				tokenRequest);
		if (obtainableAccessToken == null || obtainableAccessToken.getValue() == null) {
			throw new IllegalStateException(
					" Access token provider returned a null token, which is illegal according to the contract.");
		}
		oAuth2ClientContext.setAccessToken(obtainableAccessToken);
		return obtainableAccessToken;
	}

	public void setAccessTokenProvider(AccessTokenProvider accessTokenProvider) {
		this.accessTokenProvider = accessTokenProvider;
	}

}


/*
 * Copyright 2012-2015 the original author or authors.
 *
 * 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
 *
 *      https://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.springframework.cloud.security.oauth2.client;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;

/**
 * Convenience class for relaying an access token from the {@link SecurityContext} to the
 * {@link OAuth2ClientContext}. If successful then subsequent calls to an
 * {@link OAuth2RestTemplate} using the context contained here will use the same access
 * token. This is mostly useful for relaying calls to a resource server downstream to
 * other resource servers. If the access token expires there is no way to refresh it, so
 * expect an exception from downstream (propagating it to the caller is the best strategy,
 * so they can refresh it and try again).
 *
 * @author Dave Syer
 *
 */
public class AccessTokenContextRelay {

	private OAuth2ClientContext context;

	public AccessTokenContextRelay(OAuth2ClientContext context) {
		this.context = context;
	}

	/**
	 * Attempt to copy an access token from the security context into the oauth2 context.
	 * @return true if the token was copied
	 */
	public boolean copyToken() {
		if (context.getAccessToken() == null) {
			Authentication authentication = SecurityContextHolder.getContext()
					.getAuthentication();
			if (authentication != null) {
				Object details = authentication.getDetails();
				if (details instanceof OAuth2AuthenticationDetails) {
					OAuth2AuthenticationDetails holder = (OAuth2AuthenticationDetails) details;
					String token = holder.getTokenValue();
					DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(
							token);
					String tokenType = holder.getTokenType();
					if (tokenType != null) {
						accessToken.setTokenType(tokenType);
					}
					context.setAccessToken(accessToken);
					return true;
				}
			}
		}
		return false;
	}

}


/* Copyright 2013-2015 www.snakerflow.com.
 *
 * 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.snaker.engine.spring;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snaker.engine.Context;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;

/**
 * spring的服务查找实现
 * @author yuqs
 * @since 1.5
 */
public class SpringContext implements Context {
	private static final Logger log = LoggerFactory.getLogger(SpringContext.class);
	/**
	 * Spring context
	 */
	private ApplicationContext applicationContext;

	private DefaultListableBeanFactory beanFactory;

	/**
	 * 根据spring的上下文构造SpringContext
	 * @param ctx 上下文
	 */
	public SpringContext(ApplicationContext ctx) {
		this.applicationContext = ctx;
		beanFactory = (DefaultListableBeanFactory)ctx.getAutowireCapableBeanFactory();
	}

	@SuppressWarnings("unchecked")
	public <T> T find(Class<T> clazz) {
		String[] names = applicationContext.getBeanNamesForType(clazz);
		if (names.length > 1 && log.isWarnEnabled()) {
			log.warn("重复定义类型:" + clazz);
		}

		if (names.length >= 1) {
		  return (T) applicationContext.getBean(names[0]);
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	public <T> List<T> findList(Class<T> clazz) {
		String[] names = applicationContext.getBeanNamesForType(clazz);
		List<T> beans = new ArrayList<T>();
		for(String name : names) {
			beans.add((T)applicationContext.getBean(name));
		}
		return beans;
	}

	public <T> T findByName(String name, Class<T> clazz) {
		return applicationContext.getBean(name, clazz);
	}

	/**
	 * spring不支持向applicationContext中直接添加对象
	 */
	public void put(String name, Object object) {
		log.warn("spring不支持向applicationContext中直接添加对象");
	}

	/**
	 * 向spring添加bean的定义
	 */
	public void put(String name, Class<?> clazz) {
		BeanDefinition definition = new RootBeanDefinition(clazz);
		beanFactory.registerBeanDefinition(name, definition);
	}

	/**
	 * 判断是否存在服务
	 */
	public boolean exist(String name) {
		return applicationContext.containsBean(name);
	}
}


/* Copyright 2013-2015 www.snakerflow.com.
 *
 * 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.snaker.engine.parser.impl;

import org.snaker.engine.model.FieldModel;
import org.snaker.engine.model.NodeModel;
import org.snaker.engine.model.TaskModel;
import org.snaker.engine.parser.AbstractNodeParser;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.List;

/**
 * 任务节点解析类
 * @author yuqs
 * @since 1.0
 */
public class TaskParser extends AbstractNodeParser {
	/**
	 * 由于任务节点需要解析form、assignee属性，这里覆盖抽象类方法实现
	 */
	protected void parseNode(NodeModel node, Element element) {
		TaskModel task = (TaskModel)node;
		task.setForm(element.getAttribute(ATTR_FORM));
		task.setAssignee(element.getAttribute(ATTR_ASSIGNEE));
		task.setExpireTime(element.getAttribute(ATTR_EXPIRETIME));
		task.setAutoExecute(element.getAttribute(ATTR_AUTOEXECUTE));
		task.setCallback(element.getAttribute(ATTR_CALLBACK));
		task.setReminderTime(element.getAttribute(ATTR_REMINDERTIME));
		task.setReminderRepeat(element.getAttribute(ATTR_REMINDERREPEAT));
		task.setPerformType(element.getAttribute(ATTR_PERFORMTYPE));
		task.setTaskType(element.getAttribute(ATTR_TASKTYPE));
		task.setAssignmentHandler(element.getAttribute(ATTR_ASSIGNEE_HANDLER));
        NodeList fieldList = element.getElementsByTagName(ATTR_FIELD);
        List<FieldModel> fields = new ArrayList<FieldModel>();
        for(int i = 0; i < fieldList.getLength(); i++) {
            Element item = (Element)fieldList.item(i);
            FieldModel fieldModel = new FieldModel();
            fieldModel.setName(item.getAttribute(ATTR_NAME));
            fieldModel.setDisplayName(item.getAttribute(ATTR_DISPLAYNAME));
            fieldModel.setType(item.getAttribute(ATTR_TYPE));
            NodeList attrList = item.getElementsByTagName(ATTR_ATTR);
            for(int j = 0; j < attrList.getLength(); j++) {
                Node attr = attrList.item(j);
                fieldModel.addAttr(((Element) attr).getAttribute(ATTR_NAME),
                        ((Element) attr).getAttribute(ATTR_VALUE));
            }
            fields.add(fieldModel);
        }
        task.setFields(fields);
	}

	/**
	 * 产生TaskModel模型对象
	 */
	protected NodeModel newModel() {
		return new TaskModel();
	}
}


/* Copyright 2013-2015 www.snakerflow.com.
 *
 * 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.snaker.engine.helper;

import java.util.Date;
import java.util.Map;

import org.joda.time.DateTime;

/**
 * 日期帮助类
 * @author yuqs
 * @since 1.0
 */
public class DateHelper {
	private static final String DATE_FORMAT_DEFAULT = "yyyy-MM-dd HH:mm:ss";

	/**
	 * 返回标准格式的当前时间
	 * @return
	 */
	public static String getTime() {
		return new DateTime().toString(DATE_FORMAT_DEFAULT);
	}

	/**
	 * 解析日期时间对象
	 * @param date
	 * @return
	 */
	public static String parseTime(Object date) {
		if(date == null) return null;
		if(date instanceof Date) {
			return new DateTime((Date)date).toString(DATE_FORMAT_DEFAULT);
		} else if(date instanceof String) {
			return String.valueOf(date);
		}
		return "";
	}

	/**
	 * 对时限数据进行处理
	 * 1、运行时设置的date型数据直接返回
	 * 2、模型设置的需要特殊转换成date类型
	 * 3、运行时设置的转换为date型
	 * @param args 运行时参数
	 * @param parameter 模型参数
	 * @return Date类型
	 */
	public static Date processTime(Map<String, Object> args, String parameter) {
		if(StringHelper.isEmpty(parameter)) return null;
		Object data = args.get(parameter);
		if(data == null) data = parameter;

		Date result = null;
		if(data instanceof Date) {
			return (Date)data;
		} else if(data instanceof Long) {
			return new Date((Long)data);
		} else if(data instanceof String) {
			//TODO 1.4-dev ignore
		}
		return result;
	}
}


/* Copyright 2012-2014 the original author or authors.
 *
 * 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.snaker.jfinal.access;

import java.sql.Connection;
import java.sql.SQLException;

import com.jfinal.plugin.activerecord.Config;
import org.snaker.engine.access.jdbc.JdbcAccess;

import org.snaker.jfinal.JfinalHelper;

/**
 * jfinal的数据访问实现类
 * 主要重构getConnection方法
 * 从jfinal的threadlocal中获取数据连接，事务统一由jfinal管理
 * @author yuqs
 * @since 2.0
 */
public class JfinalAccess extends JdbcAccess {
	/**
	 * 从jfinal的threadlocal中获取数据库连接
	 */
	protected Connection getConnection() throws SQLException {
        Config config = JfinalHelper.getConfig();
		Connection conn = config.getThreadLocalConnection();
		if(conn == null) {
			conn = config.getConnection();
            conn.setAutoCommit(true);
		}
		return conn;
	}
}