/*
 *
 *  *  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.tx;

import com.orientechnologies.common.concur.ONeedRetryException;
import com.orientechnologies.common.exception.OException;
import com.orientechnologies.orient.core.db.ODatabase.OPERATION_MODE;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.document.LatestVersionRecordReader;
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentEmbedded;
import com.orientechnologies.orient.core.db.document.RecordReader;
import com.orientechnologies.orient.core.db.document.SimpleRecordReader;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ORecordOperation;
import com.orientechnologies.orient.core.exception.ODatabaseException;
import com.orientechnologies.orient.core.exception.ORecordNotFoundException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.ORecordInternal;
import com.orientechnologies.orient.core.record.impl.ODirtyManager;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.storage.ORecordCallback;
import com.orientechnologies.orient.core.storage.OStorage;
import com.orientechnologies.orient.core.tx.OTransactionIndexChanges.OPERATION;

import java.util.*;

/**
 * No operation transaction.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class OTransactionNoTx extends OTransactionAbstract {
  public OTransactionNoTx(final ODatabaseDocumentInternal iDatabase, Map<ORID, LockedRecordMetadata> noTxLocks) {
    super(iDatabase);
    if (noTxLocks != null) {
      setLocks(noTxLocks);
    }
  }

  public void begin() {
  }

  public void commit() {
  }

  @Override
  public int getEntryCount() {
    return 0;
  }

  @Override
  public void commit(boolean force) {
  }

  public void rollback() {
  }

  @Deprecated
  public ORecord loadRecord(final ORID iRid, final ORecord iRecord, final String iFetchPlan, final boolean ignoreCache,
      final boolean loadTombstone, final OStorage.LOCKING_STRATEGY iLockingStrategy) {
    if (iRid.isNew())
      return null;

    return database
        .executeReadRecord((ORecordId) iRid, iRecord, -1, iFetchPlan, ignoreCache, !ignoreCache, loadTombstone, iLockingStrategy,
            new SimpleRecordReader(database.isPrefetchRecords()));
  }

  @Deprecated
  public ORecord loadRecord(final ORID iRid, final ORecord iRecord, final String iFetchPlan, final boolean ignoreCache,
      final boolean iUpdateCache, final boolean loadTombstone, final OStorage.LOCKING_STRATEGY iLockingStrategy) {
    if (iRid.isNew())
      return null;

    return database
        .executeReadRecord((ORecordId) iRid, iRecord, -1, iFetchPlan, ignoreCache, iUpdateCache, loadTombstone, iLockingStrategy,
            new SimpleRecordReader(database.isPrefetchRecords()));
  }

  public ORecord loadRecord(final ORID iRid, final ORecord iRecord, final String iFetchPlan, final boolean ignoreCache) {
    if (iRid.isNew())
      return null;

    return database.executeReadRecord((ORecordId) iRid, iRecord, -1, iFetchPlan, ignoreCache, !ignoreCache, false,
        OStorage.LOCKING_STRATEGY.NONE, new SimpleRecordReader(database.isPrefetchRecords()));
  }

  @Override
  public ORecord reloadRecord(ORID rid, ORecord record, String fetchPlan, boolean ignoreCache) {
    return reloadRecord(rid, record, fetchPlan, ignoreCache, true);
  }

  @Override
  public ORecord reloadRecord(ORID rid, ORecord record, String fetchPlan, boolean ignoreCache, boolean force) {
    if (rid.isNew())
      return null;

    final RecordReader recordReader;
    if (force) {
      recordReader = new SimpleRecordReader(database.isPrefetchRecords());
    } else {
      recordReader = new LatestVersionRecordReader();
    }

    final ORecord loadedRecord = database
        .executeReadRecord((ORecordId) rid, record, -1, fetchPlan, ignoreCache, !ignoreCache, false, OStorage.LOCKING_STRATEGY.NONE,
            recordReader);

    if (force) {
      return loadedRecord;
    } else {
      if (loadedRecord == null)
        return record;

      return loadedRecord;
    }
  }

  @Override
  public ORecord loadRecordIfVersionIsNotLatest(ORID rid, int recordVersion, String fetchPlan, boolean ignoreCache)
      throws ORecordNotFoundException {
    if (rid.isNew())
      return null;

    return database.executeReadRecord((ORecordId) rid, null, recordVersion, fetchPlan, ignoreCache, !ignoreCache, false,
        OStorage.LOCKING_STRATEGY.NONE, new LatestVersionRecordReader());
  }

  /**
   * Update the record.
   *
   * @param iRecord
   * @param iForceCreate
   * @param iRecordCreatedCallback
   * @param iRecordUpdatedCallback
   */
  public ORecord saveRecord(final ORecord iRecord, final String iClusterName, final OPERATION_MODE iMode, boolean iForceCreate,
      final ORecordCallback<? extends Number> iRecordCreatedCallback, ORecordCallback<Integer> iRecordUpdatedCallback) {
    try {

      return database.saveAll(iRecord, iClusterName, iMode, iForceCreate, iRecordCreatedCallback, iRecordUpdatedCallback);

    } catch (Exception e) {
      // REMOVE IT FROM THE CACHE TO AVOID DIRTY RECORDS
      final ORecordId rid = (ORecordId) iRecord.getIdentity();
      if (rid.isValid())
        database.getLocalCache().freeRecord(rid);

      if (e instanceof ONeedRetryException)
        throw (ONeedRetryException) e;

      throw OException.wrapException(
          new ODatabaseException("Error during saving of record" + (iRecord != null ? " with rid " + iRecord.getIdentity() : "")),
          e);
    }
  }

  @Override
  public OTransaction setIsolationLevel(final ISOLATION_LEVEL isolationLevel) {
    if (isolationLevel != ISOLATION_LEVEL.READ_COMMITTED)
      throw new IllegalArgumentException("Isolation level '" + isolationLevel + "' is not supported without an active transaction");
    return super.setIsolationLevel(isolationLevel);
  }

  /**
   * Deletes the record.
   */
  public void deleteRecord(final ORecord iRecord, final OPERATION_MODE iMode) {
    if (!iRecord.getIdentity().isPersistent())
      return;

    try {
      database.executeDeleteRecord(iRecord, iRecord.getVersion(), true, iMode, false);
    } catch (Exception e) {
      // REMOVE IT FROM THE CACHE TO AVOID DIRTY RECORDS
      final ORecordId rid = (ORecordId) iRecord.getIdentity();
      if (rid.isValid())
        database.getLocalCache().freeRecord(rid);

      if (e instanceof RuntimeException)
        throw (RuntimeException) e;
      throw OException.wrapException(
          new ODatabaseException("Error during deletion of record" + (iRecord != null ? " with rid " + iRecord.getIdentity() : "")),
          e);
    }
  }

  public Collection<ORecordOperation> getCurrentRecordEntries() {
    return null;
  }

  public Collection<ORecordOperation> getRecordOperations() {
    return null;
  }

  public List<ORecordOperation> getNewRecordEntriesByClass(final OClass iClass, final boolean iPolymorphic) {
    return null;
  }

  public List<ORecordOperation> getNewRecordEntriesByClusterIds(final int[] iIds) {
    return null;
  }

  public void clearRecordEntries() {
  }

  public int getRecordEntriesSize() {
    return 0;
  }

  public ORecord getRecord(final ORID rid) {
    return null;
  }

  public ORecordOperation getRecordEntry(final ORID rid) {
    return null;
  }

  public boolean isUsingLog() {
    return false;
  }

  @Override
  public void setCustomData(String iName, Object iValue) {

  }

  @Override
  public Object getCustomData(String iName) {
    return null;
  }

  public void setUsingLog(final boolean useLog) {
  }

  public ODocument getIndexChanges() {
    return null;
  }

  public void addIndexEntry(final OIndex<?> delegate, final String indexName, final OPERATION status, final Object key,
      final OIdentifiable value) {
    switch (status) {
    case CLEAR:
      delegate.clear();
      break;

    case PUT:
      delegate.put(key, value);
      break;

    case REMOVE:
      delegate.remove(key, value);
      break;
    }
  }

  @Override
  public void addChangedDocument(ODocument document) {
    // do nothing
  }

  public void clearIndexEntries() {
  }

  public OTransactionIndexChanges getIndexChanges(final String iName) {
    return null;
  }

  public int getId() {
    return 0;
  }

  public List<String> getInvolvedIndexes() {
    return null;
  }

  public void updateIdentityAfterCommit(ORID oldRid, ORID newRid) {
  }

  @Override
  public int amountOfNestedTxs() {
    return 0;
  }

  @Override
  public void rollback(boolean force, int commitLevelDiff) {
  }

  @Override
  public OTransactionIndexChanges getIndexChangesInternal(String indexName) {
    return null;
  }

  @Override
  public void internalRollback() {

  }
}


/*
 *
 *  *  Copyright 2016 OrientDB LTD (info(-at-)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.server.security;

import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.server.OServer;
import com.orientechnologies.orient.server.config.OServerConfigurationManager;
import com.orientechnologies.orient.server.config.OServerUserConfiguration;

import javax.security.auth.Subject;

/**
 * Provides an abstract implementation of OSecurityAuthenticator.
 *
 * @author S. Colin Leister
 *
 */
public abstract class OSecurityAuthenticatorAbstract implements OSecurityAuthenticator {
  private String                      name          = "";
  private boolean                     debug         = false;
  private boolean                     enabled       = true;
  private boolean                     caseSensitive = true;
  private OServer                     server;
  private OServerConfigurationManager serverConfig;

  protected OServer getServer() {
    return server;
  }

  protected OServerConfigurationManager getServerConfig() {
    return serverConfig;
  }

  protected boolean isDebug() {
    return debug;
  }

  protected boolean isCaseSensitive() {
    return caseSensitive;
  }

  // OSecurityComponent
  public void active() {
  }

  // OSecurityComponent
  public void config(final OServer oServer, final OServerConfigurationManager serverCfg, final ODocument jsonConfig) {
    server = oServer;
    serverConfig = serverCfg;

    if (jsonConfig.containsField("name")) {
      name = jsonConfig.field("name");
    }

    if (jsonConfig.containsField("debug")) {
      debug = jsonConfig.field("debug");
    }

    if (jsonConfig.containsField("enabled")) {
      enabled = jsonConfig.field("enabled");
    }

    if (jsonConfig.containsField("caseSensitive")) {
      caseSensitive = jsonConfig.field("caseSensitive");
    }
  }

  // OSecurityComponent
  public void dispose() {
  }

  // OSecurityComponent
  public boolean isEnabled() {
    return enabled;
  }

  // OSecurityAuthenticator
  // databaseName may be null.
  public String getAuthenticationHeader(String databaseName) {
    String header;

    // Default to Basic.
    if (databaseName != null)
      header = "WWW-Authenticate: Basic realm=\"OrientDB db-" + databaseName + "\"";
    else
      header = "WWW-Authenticate: Basic realm=\"OrientDB Server\"";

    return header;
  }

  public Subject getClientSubject() {
    return null;
  }

  // Returns the name of this OSecurityAuthenticator.
  public String getName() {
    return name;
  }

  public OServerUserConfiguration getUser(final String username) {
    return null;
  }

  public boolean isAuthorized(final String username, final String resource) {
    return false;
  }

  public boolean isSingleSignOnSupported() {
    return false;
  }

  protected boolean isPasswordValid(final OServerUserConfiguration user) {
    return user != null && user.password != null && !user.password.isEmpty();
  }
}


/*
 *
 *  *  Copyright 2016 OrientDB LTD (info(at)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://www.orientdb.com
 *
 */
package com.orientechnologies.orient.server.security.authenticator;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.metadata.security.ORole;
import com.orientechnologies.orient.core.metadata.security.ORule;
import com.orientechnologies.orient.core.metadata.security.OSecurityUser;
import com.orientechnologies.orient.core.metadata.security.OUser;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.server.OServer;
import com.orientechnologies.orient.server.config.OServerConfigurationManager;
import com.orientechnologies.orient.server.config.OServerUserConfiguration;
import com.orientechnologies.orient.server.security.OSecurityAuthenticatorAbstract;

/**
 * Provides a default password authenticator.
 *
 * @author S. Colin Leister
 */
public class OSystemUserAuthenticator extends OSecurityAuthenticatorAbstract {

  // OSecurityComponent
  // Called once the Server is running.
  public void active() {
    OLogManager.instance().info(this, "OSystemUserAuthenticator is active");
  }

  // OSecurityComponent
  public void config(final OServer oServer, final OServerConfigurationManager serverCfg, final ODocument jsonConfig) {
    super.config(oServer, serverCfg, jsonConfig);

    try {
    } catch (Exception ex) {
      OLogManager.instance().error(this, "config()", ex);
    }
  }

  // OSecurityComponent
  // Called on removal of the authenticator.
  public void dispose() {
  }

  // OSecurityAuthenticator
  // Returns the actual username if successful, null otherwise.
  // This will authenticate username using the system database.
  public String authenticate(final String username, final String password) {
    String principal = null;

    try {
      if (getServer() != null) {
        // dbName parameter is null because we don't need to filter any roles for this.
        OUser user = getServer().getSecurity().getSystemUser(username, null);

        if (user != null && user.getAccountStatus() == OSecurityUser.STATUSES.ACTIVE) {
          if (user.checkPassword(password))
            principal = username;
        }
      }
    } catch (Exception ex) {
      OLogManager.instance().error(this, "authenticate()", ex);
    }

    return principal;
  }

  // OSecurityAuthenticator
  // If not supported by the authenticator, return false.
  // Checks to see if a
  public boolean isAuthorized(final String username, final String resource) {
    if (username == null || resource == null)
      return false;

    try {
      if (getServer() != null) {
        OUser user = getServer().getSecurity().getSystemUser(username, null);

        if (user != null && user.getAccountStatus() == OSecurityUser.STATUSES.ACTIVE) {
          ORole role = null;

          ORule.ResourceGeneric rg = ORule.mapLegacyResourceToGenericResource(resource);

          if (rg != null) {
            String specificResource = ORule.mapLegacyResourceToSpecificResource(resource);

            if (specificResource == null || specificResource.equals("*")) {
              specificResource = null;
            }

            role = user.checkIfAllowed(rg, specificResource, ORole.PERMISSION_EXECUTE);
          }

          return role != null;
        }
      }
    } catch (Exception ex) {
      OLogManager.instance().error(this, "isAuthorized()", ex);
    }

    return false;
  }

  // OSecurityAuthenticator
  public OServerUserConfiguration getUser(final String username) {
    OServerUserConfiguration userCfg = null;

    try {
      if (getServer() != null) {
        OUser user = getServer().getSecurity().getSystemUser(username, null);

        if (user != null && user.getAccountStatus() == OSecurityUser.STATUSES.ACTIVE) {
          userCfg = new OServerUserConfiguration(user.getName(), "", "");
        }
      }
    } catch (Exception ex) {
      OLogManager.instance().error(this, "getUser()", ex);
    }

    return userCfg;
  }
}


/*
      *
      *  *  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.server;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.Orient;

public class OServerShutdownHook extends Thread {
  private final OServer server;

  protected OServerShutdownHook(final OServer server) {
    this.server = server;
    Orient.instance().removeShutdownHook();
    Runtime.getRuntime().addShutdownHook(this);
  }

  /**
   * Catch the JVM exit and assure to shutdown the Orient Server.
   */
  @Override
  public void run() {
    if (server != null)
      if (!server.shutdown()) {
        // ALREADY IN SHUTDOWN, WAIT FOR 5 SEC MORE
        try {
          Thread.sleep(5000);
        } catch (InterruptedException e) {
        }
      }
  }

  public void cancel() {
    try {
      Runtime.getRuntime().removeShutdownHook(this);
    } catch (IllegalStateException e) {
    }
  }
}


/* Generated By:JavaCC: Do not edit this line. JavaCharStream.java Version 5.0 */
/* JavaCCOptions:STATIC=false,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
package com.orientechnologies.orient.core.sql.parser;

/**
 * An implementation of interface CharStream, where the stream is assumed to
 * contain only ASCII characters (with java-like unicode escape processing).
 */

public
class JavaCharStream implements CharStream
{
  /** Whether parser is static. */
  public static final boolean staticFlag = false;

  static final int hexval(char c) throws java.io.IOException {
    switch(c)
    {
       case '0' :
          return 0;
       case '1' :
          return 1;
       case '2' :
          return 2;
       case '3' :
          return 3;
       case '4' :
          return 4;
       case '5' :
          return 5;
       case '6' :
          return 6;
       case '7' :
          return 7;
       case '8' :
          return 8;
       case '9' :
          return 9;

       case 'a' :
       case 'A' :
          return 10;
       case 'b' :
       case 'B' :
          return 11;
       case 'c' :
       case 'C' :
          return 12;
       case 'd' :
       case 'D' :
          return 13;
       case 'e' :
       case 'E' :
          return 14;
       case 'f' :
       case 'F' :
          return 15;
    }

    throw new java.io.IOException(); // Should never come here
  }

/** Position in buffer. */
  public int bufpos = -1;
  int bufsize;
  int available;
  int tokenBegin;
  protected int bufline[];
  protected int bufcolumn[];

  protected int column = 0;
  protected int line = 1;

  protected boolean prevCharIsCR = false;
  protected boolean prevCharIsLF = false;

  protected java.io.Reader inputStream;

  protected char[] nextCharBuf;
  protected char[] buffer;
  protected int maxNextCharInd = 0;
  protected int nextCharInd = -1;
  protected int inBuf = 0;
  protected int tabSize = 8;

  protected void setTabSize(int i) { tabSize = i; }
  protected int getTabSize(int i) { return tabSize; }

  protected void ExpandBuff(boolean wrapAround)
  {
    char[] newbuffer = new char[bufsize + 2048];
    int newbufline[] = new int[bufsize + 2048];
    int newbufcolumn[] = new int[bufsize + 2048];

    try
    {
      if (wrapAround)
      {
        System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
        System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos);
        buffer = newbuffer;

        System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
        System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos);
        bufline = newbufline;

        System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
        System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos);
        bufcolumn = newbufcolumn;

        bufpos += (bufsize - tokenBegin);
    }
    else
    {
        System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
        buffer = newbuffer;

        System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
        bufline = newbufline;

        System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
        bufcolumn = newbufcolumn;

        bufpos -= tokenBegin;
      }
    }
    catch (Throwable t)
    {
      throw new Error(t.getMessage(), t);
    }

    available = (bufsize += 2048);
    tokenBegin = 0;
  }

  protected void FillBuff() throws java.io.IOException
  {
    int i;
    if (maxNextCharInd == 4096)
      maxNextCharInd = nextCharInd = 0;

    try {
      if ((i = inputStream.read(nextCharBuf, maxNextCharInd,
                                          4096 - maxNextCharInd)) == -1)
      {
        inputStream.close();
        throw new java.io.IOException();
      }
      else
         maxNextCharInd += i;
      return;
    }
    catch(java.io.IOException e) {
      if (bufpos != 0)
      {
        --bufpos;
        backup(0);
      }
      else
      {
        bufline[bufpos] = line;
        bufcolumn[bufpos] = column;
      }
      throw e;
    }
  }

  protected char ReadByte() throws java.io.IOException
  {
    if (++nextCharInd >= maxNextCharInd)
      FillBuff();

    return nextCharBuf[nextCharInd];
  }

/** @return starting character for token. */
  public char BeginToken() throws java.io.IOException
  {
    if (inBuf > 0)
    {
      --inBuf;

      if (++bufpos == bufsize)
        bufpos = 0;

      tokenBegin = bufpos;
      return buffer[bufpos];
    }

    tokenBegin = 0;
    bufpos = -1;

    return readChar();
  }

  protected void AdjustBuffSize()
  {
    if (available == bufsize)
    {
      if (tokenBegin > 2048)
      {
        bufpos = 0;
        available = tokenBegin;
      }
      else
        ExpandBuff(false);
    }
    else if (available > tokenBegin)
      available = bufsize;
    else if ((tokenBegin - available) < 2048)
      ExpandBuff(true);
    else
      available = tokenBegin;
  }

  protected void UpdateLineColumn(char c)
  {
    column++;

    if (prevCharIsLF)
    {
      prevCharIsLF = false;
      line += (column = 1);
    }
    else if (prevCharIsCR)
    {
      prevCharIsCR = false;
      if (c == '\n')
      {
        prevCharIsLF = true;
      }
      else
        line += (column = 1);
    }

    switch (c)
    {
      case '\r' :
        prevCharIsCR = true;
        break;
      case '\n' :
        prevCharIsLF = true;
        break;
      case '\t' :
        column--;
        column += (tabSize - (column % tabSize));
        break;
      default :
        break;
    }

    bufline[bufpos] = line;
    bufcolumn[bufpos] = column;
  }

/** Read a character. */
  public char readChar() throws java.io.IOException
  {
    if (inBuf > 0)
    {
      --inBuf;

      if (++bufpos == bufsize)
        bufpos = 0;

      return buffer[bufpos];
    }

    char c;

    if (++bufpos == available)
      AdjustBuffSize();

    if ((buffer[bufpos] = c = ReadByte()) == '\\')
    {
      UpdateLineColumn(c);

      int backSlashCnt = 1;

      for (;;) // Read all the backslashes
      {
        if (++bufpos == available)
          AdjustBuffSize();

        try
        {
          if ((buffer[bufpos] = c = ReadByte()) != '\\')
          {
            UpdateLineColumn(c);
            // found a non-backslash char.
            if ((c == 'u') && ((backSlashCnt & 1) == 1))
            {
              if (--bufpos < 0)
                bufpos = bufsize - 1;

              break;
            }

            backup(backSlashCnt);
            return '\\';
          }
        }
        catch(java.io.IOException e)
        {
	  // We are returning one backslash so we should only backup (count-1)
          if (backSlashCnt > 1)
            backup(backSlashCnt-1);

          return '\\';
        }

        UpdateLineColumn(c);
        backSlashCnt++;
      }

      // Here, we have seen an odd number of backslash's followed by a 'u'
      try
      {
        while ((c = ReadByte()) == 'u')
          ++column;

        buffer[bufpos] = c = (char)(hexval(c) << 12 |
                                    hexval(ReadByte()) << 8 |
                                    hexval(ReadByte()) << 4 |
                                    hexval(ReadByte()));

        column += 4;
      }
      catch(java.io.IOException e)
      {
        throw new Error("Invalid escape character at line " + line +
 " column " + column, e);
      }

      if (backSlashCnt == 1)
        return c;
      else
      {
        backup(backSlashCnt - 1);
        return '\\';
      }
    }
    else
    {
      UpdateLineColumn(c);
      return c;
    }
  }

  @Deprecated
  /**
   * @deprecated
   * @see #getEndColumn
   */
  public int getColumn() {
    return bufcolumn[bufpos];
  }

  @Deprecated
  /**
   * @deprecated
   * @see #getEndLine
   */
  public int getLine() {
    return bufline[bufpos];
  }

/** Get end column. */
  public int getEndColumn() {
    return bufcolumn[bufpos];
  }

/** Get end line. */
  public int getEndLine() {
    return bufline[bufpos];
  }

/** @return column of token start */
  public int getBeginColumn() {
    return bufcolumn[tokenBegin];
  }

/** @return line number of token start */
  public int getBeginLine() {
    return bufline[tokenBegin];
  }

/** Retreat. */
  public void backup(int amount) {

    inBuf += amount;
    if ((bufpos -= amount) < 0)
      bufpos += bufsize;
  }

/** Constructor. */
  public JavaCharStream(java.io.Reader dstream,
                 int startline, int startcolumn, int buffersize)
  {
    inputStream = dstream;
    line = startline;
    column = startcolumn - 1;

    available = bufsize = buffersize;
    buffer = new char[buffersize];
    bufline = new int[buffersize];
    bufcolumn = new int[buffersize];
    nextCharBuf = new char[4096];
  }

/** Constructor. */
  public JavaCharStream(java.io.Reader dstream,
                                        int startline, int startcolumn)
  {
    this(dstream, startline, startcolumn, 4096);
  }

/** Constructor. */
  public JavaCharStream(java.io.Reader dstream)
  {
    this(dstream, 1, 1, 4096);
  }
/** Reinitialise. */
  public void ReInit(java.io.Reader dstream,
                 int startline, int startcolumn, int buffersize)
  {
    inputStream = dstream;
    line = startline;
    column = startcolumn - 1;

    if (buffer == null || buffersize != buffer.length)
    {
      available = bufsize = buffersize;
      buffer = new char[buffersize];
      bufline = new int[buffersize];
      bufcolumn = new int[buffersize];
      nextCharBuf = new char[4096];
    }
    prevCharIsLF = prevCharIsCR = false;
    tokenBegin = inBuf = maxNextCharInd = 0;
    nextCharInd = bufpos = -1;
  }

/** Reinitialise. */
  public void ReInit(java.io.Reader dstream,
                                        int startline, int startcolumn)
  {
    ReInit(dstream, startline, startcolumn, 4096);
  }

/** Reinitialise. */
  public void ReInit(java.io.Reader dstream)
  {
    ReInit(dstream, 1, 1, 4096);
  }
/** Constructor. */
  public JavaCharStream(java.io.InputStream dstream, String encoding, int startline,
  int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
  {
    this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
  }

/** Constructor. */
  public JavaCharStream(java.io.InputStream dstream, int startline,
  int startcolumn, int buffersize)
  {
    this(new java.io.InputStreamReader(dstream), startline, startcolumn, 4096);
  }

/** Constructor. */
  public JavaCharStream(java.io.InputStream dstream, String encoding, int startline,
                        int startcolumn) throws java.io.UnsupportedEncodingException
  {
    this(dstream, encoding, startline, startcolumn, 4096);
  }

/** Constructor. */
  public JavaCharStream(java.io.InputStream dstream, int startline,
                        int startcolumn)
  {
    this(dstream, startline, startcolumn, 4096);
  }

/** Constructor. */
  public JavaCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException
  {
    this(dstream, encoding, 1, 1, 4096);
  }

/** Constructor. */
  public JavaCharStream(java.io.InputStream dstream)
  {
    this(dstream, 1, 1, 4096);
  }

/** Reinitialise. */
  public void ReInit(java.io.InputStream dstream, String encoding, int startline,
  int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
  {
    ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
  }

/** Reinitialise. */
  public void ReInit(java.io.InputStream dstream, int startline,
  int startcolumn, int buffersize)
  {
    ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
  }
/** Reinitialise. */
  public void ReInit(java.io.InputStream dstream, String encoding, int startline,
                     int startcolumn) throws java.io.UnsupportedEncodingException
  {
    ReInit(dstream, encoding, startline, startcolumn, 4096);
  }
/** Reinitialise. */
  public void ReInit(java.io.InputStream dstream, int startline,
                     int startcolumn)
  {
    ReInit(dstream, startline, startcolumn, 4096);
  }
/** Reinitialise. */
  public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException
  {
    ReInit(dstream, encoding, 1, 1, 4096);
  }

/** Reinitialise. */
  public void ReInit(java.io.InputStream dstream)
  {
    ReInit(dstream, 1, 1, 4096);
  }

  /** @return token image as String */
  public String GetImage()
  {
    if (bufpos >= tokenBegin)
      return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
    else
      return new String(buffer, tokenBegin, bufsize - tokenBegin) +
                              new String(buffer, 0, bufpos + 1);
  }

  /** @return suffix */
  public char[] GetSuffix(int len)
  {
    char[] ret = new char[len];

    if ((bufpos + 1) >= len)
      System.arraycopy(buffer, bufpos - len + 1, ret, 0, len);
    else
    {
      System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0,
                                                        len - bufpos - 1);
      System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1);
    }

    return ret;
  }

  /** Set buffers back to null when finished. */
  public void Done()
  {
    nextCharBuf = null;
    buffer = null;
    bufline = null;
    bufcolumn = null;
  }

  /**
   * Method to adjust line and column numbers for the start of a token.
   */
  public void adjustBeginLineColumn(int newLine, int newCol)
  {
    int start = tokenBegin;
    int len;

    if (bufpos >= tokenBegin)
    {
      len = bufpos - tokenBegin + inBuf + 1;
    }
    else
    {
      len = bufsize - tokenBegin + bufpos + 1 + inBuf;
    }

    int i = 0, j = 0, k = 0;
    int nextColDiff = 0, columnDiff = 0;

    while (i < len && bufline[j = start % bufsize] == bufline[k = ++start % bufsize])
    {
      bufline[j] = newLine;
      nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
      bufcolumn[j] = newCol + columnDiff;
      columnDiff = nextColDiff;
      i++;
    }

    if (i < len)
    {
      bufline[j] = newLine++;
      bufcolumn[j] = newCol + columnDiff;

      while (i++ < len)
      {
        if (bufline[j = start % bufsize] != bufline[++start % bufsize])
          bufline[j] = newLine++;
        else
          bufline[j] = newLine;
      }
    }

    line = bufline[j];
    column = bufcolumn[j];
  }

}
/* JavaCC - OriginalChecksum=846f18867a6b03b776ece99d4ba5c6d8 (do not edit this line) */


/*
 *
 *  *  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.serializer.binary.impl.index;

import com.orientechnologies.common.serialization.types.OBinarySerializer;
import com.orientechnologies.common.serialization.types.OIntegerSerializer;
import com.orientechnologies.common.serialization.types.ONullSerializer;
import com.orientechnologies.common.util.OCommonConst;
import com.orientechnologies.orient.core.index.OCompositeKey;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.serialization.serializer.binary.OBinarySerializerFactory;
import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges;

import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;

/**
 * Serializer that is used for serialization of {@link OCompositeKey} keys in index.
 *
 * @author Andrey Lomakin (a.lomakin-at-orientdb.com)
 * @since 29.07.11
 */
public class OCompositeKeySerializer implements OBinarySerializer<OCompositeKey> {

  public static final OCompositeKeySerializer INSTANCE = new OCompositeKeySerializer();
  public static final byte                    ID       = 14;

  public int getObjectSize(OCompositeKey compositeKey, Object... hints) {
    final OType[] types = getKeyTypes(hints);

    final List<Object> keys = compositeKey.getKeys();

    int size = 2 * OIntegerSerializer.INT_SIZE;

    final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance();
    for (int i = 0; i < keys.size(); i++) {
      final Object key = keys.get(i);

      if (key != null) {
        final OType type;
        if (types.length > i)
          type = types[i];
        else
          type = OType.getTypeByClass(key.getClass());

        size += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + factory.getObjectSerializer(type).getObjectSize(key);
      } else {
        size += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + ONullSerializer.INSTANCE.getObjectSize(null);
      }
    }

    return size;
  }

  public void serialize(OCompositeKey compositeKey, byte[] stream, int startPosition, Object... hints) {
    final OType[] types = getKeyTypes(hints);

    final List<Object> keys = compositeKey.getKeys();
    final int keysSize = keys.size();

    final int oldStartPosition = startPosition;

    startPosition += OIntegerSerializer.INT_SIZE;

    OIntegerSerializer.INSTANCE.serializeLiteral(keysSize, stream, startPosition);

    startPosition += OIntegerSerializer.INT_SIZE;

    final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance();

    for (int i = 0; i < keys.size(); i++) {
      final Object key = keys.get(i);

      OBinarySerializer<Object> binarySerializer;
      if (key != null) {
        final OType type;
        if (types.length > i)
          type = types[i];
        else
          type = OType.getTypeByClass(key.getClass());

        binarySerializer = factory.getObjectSerializer(type);
      } else
        binarySerializer = ONullSerializer.INSTANCE;

      stream[startPosition] = binarySerializer.getId();
      startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE;

      binarySerializer.serialize(key, stream, startPosition);
      startPosition += binarySerializer.getObjectSize(key);
    }

    OIntegerSerializer.INSTANCE.serializeLiteral((startPosition - oldStartPosition), stream, oldStartPosition);
  }

  @SuppressWarnings("unchecked")
  public OCompositeKey deserialize(byte[] stream, int startPosition) {
    final OCompositeKey compositeKey = new OCompositeKey();

    startPosition += OIntegerSerializer.INT_SIZE;

    final int keysSize = OIntegerSerializer.INSTANCE.deserializeLiteral(stream, startPosition);
    startPosition += OIntegerSerializer.INSTANCE.getObjectSize(keysSize);

    final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance();
    for (int i = 0; i < keysSize; i++) {
      final byte serializerId = stream[startPosition];
      startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE;

      OBinarySerializer<Object> binarySerializer = (OBinarySerializer<Object>) factory.getObjectSerializer(serializerId);
      final Object key = binarySerializer.deserialize(stream, startPosition);
      compositeKey.addKey(key);

      startPosition += binarySerializer.getObjectSize(key);
    }

    return compositeKey;
  }

  public int getObjectSize(byte[] stream, int startPosition) {
    return OIntegerSerializer.INSTANCE.deserializeLiteral(stream, startPosition);
  }

  public byte getId() {
    return ID;
  }

  public int getObjectSizeNative(byte[] stream, int startPosition) {
    return OIntegerSerializer.INSTANCE.deserializeNative(stream, startPosition);
  }

  public void serializeNativeObject(OCompositeKey compositeKey, byte[] stream, int startPosition, Object... hints) {
    final OType[] types = getKeyTypes(hints);

    final List<Object> keys = compositeKey.getKeys();
    final int keysSize = keys.size();

    final int oldStartPosition = startPosition;

    startPosition += OIntegerSerializer.INT_SIZE;

    OIntegerSerializer.INSTANCE.serializeNative(keysSize, stream, startPosition);

    startPosition += OIntegerSerializer.INT_SIZE;

    final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance();

    for (int i = 0; i < keys.size(); i++) {
      final Object key = keys.get(i);
      OBinarySerializer<Object> binarySerializer;
      if (key != null) {
        final OType type;
        if (types.length > i)
          type = types[i];
        else
          type = OType.getTypeByClass(key.getClass());

        binarySerializer = factory.getObjectSerializer(type);
      } else
        binarySerializer = ONullSerializer.INSTANCE;

      stream[startPosition] = binarySerializer.getId();
      startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE;

      binarySerializer.serializeNativeObject(key, stream, startPosition);
      startPosition += binarySerializer.getObjectSize(key);
    }

    OIntegerSerializer.INSTANCE.serializeNative((startPosition - oldStartPosition), stream, oldStartPosition);
  }

  public OCompositeKey deserializeNativeObject(byte[] stream, int startPosition) {
    final OCompositeKey compositeKey = new OCompositeKey();

    startPosition += OIntegerSerializer.INT_SIZE;

    final int keysSize = OIntegerSerializer.INSTANCE.deserializeNative(stream, startPosition);
    startPosition += OIntegerSerializer.INSTANCE.getObjectSize(keysSize);

    final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance();
    for (int i = 0; i < keysSize; i++) {
      final byte serializerId = stream[startPosition];
      startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE;

      @SuppressWarnings("unchecked")
      OBinarySerializer<Object> binarySerializer = (OBinarySerializer<Object>) factory.getObjectSerializer(serializerId);
      final Object key = binarySerializer.deserializeNativeObject(stream, startPosition);
      compositeKey.addKey(key);

      startPosition += binarySerializer.getObjectSize(key);
    }

    return compositeKey;
  }

  private OType[] getKeyTypes(Object[] hints) {
    final OType[] types;

    if (hints != null && hints.length > 0)
      types = (OType[]) hints;
    else
      types = OCommonConst.EMPTY_TYPES_ARRAY;
    return types;
  }

  public boolean isFixedLength() {
    return false;
  }

  public int getFixedLength() {
    return 0;
  }

  @Override
  public OCompositeKey preprocess(OCompositeKey value, Object... hints) {
    if (value == null) {
      return null;
    }

    final OType[] types = getKeyTypes(hints);

    final List<Object> keys = value.getKeys();
    final OCompositeKey compositeKey = new OCompositeKey();

    final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance();
    for (int i = 0; i < keys.size(); i++) {
      Object key = keys.get(i);

      if (key != null) {
        final OType type;
        if (types.length > i)
          type = types[i];
        else {
          type = OType.getTypeByClass(key.getClass());
        }

        OBinarySerializer<Object> keySerializer = factory.getObjectSerializer(type);
        if (key instanceof Map && !(type == OType.EMBEDDEDMAP || type == OType.LINKMAP) && ((Map) key).size() == 1 && ((Map) key)
            .keySet().iterator().next().getClass().isAssignableFrom(type.getDefaultJavaType())) {
          key = ((Map) key).keySet().iterator().next();
        }
        compositeKey.addKey(keySerializer.preprocess(key));
      } else {
        compositeKey.addKey(key);
      }
    }

    return compositeKey;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void serializeInByteBufferObject(OCompositeKey object, ByteBuffer buffer, Object... hints) {
    final OType[] types = getKeyTypes(hints);

    final List<Object> keys = object.getKeys();
    final int keysSize = keys.size();

    final int oldStartOffset = buffer.position();
    buffer.position(oldStartOffset + OIntegerSerializer.INT_SIZE);

    buffer.putInt(keysSize);
    final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance();

    for (int i = 0; i < keys.size(); i++) {
      final Object key = keys.get(i);

      OBinarySerializer<Object> binarySerializer;
      if (key != null) {
        final OType type;
        if (types.length > i)
          type = types[i];
        else
          type = OType.getTypeByClass(key.getClass());

        binarySerializer = factory.getObjectSerializer(type);
      } else
        binarySerializer = ONullSerializer.INSTANCE;

      buffer.put(binarySerializer.getId());
      binarySerializer.serializeInByteBufferObject(key, buffer);
    }

    final int finalPosition = buffer.position();
    final int serializedSize = buffer.position() - oldStartOffset;

    buffer.position(oldStartOffset);
    buffer.putInt(serializedSize);

    buffer.position(finalPosition);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public OCompositeKey deserializeFromByteBufferObject(ByteBuffer buffer) {
    final OCompositeKey compositeKey = new OCompositeKey();

    buffer.position(buffer.position() + OIntegerSerializer.INT_SIZE);
    final int keysSize = buffer.getInt();

    final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance();
    for (int i = 0; i < keysSize; i++) {
      final byte serializerId = buffer.get();
      @SuppressWarnings("unchecked")
      OBinarySerializer<Object> binarySerializer = (OBinarySerializer<Object>) factory.getObjectSerializer(serializerId);
      final Object key = binarySerializer.deserializeFromByteBufferObject(buffer);
      compositeKey.addKey(key);
    }

    return compositeKey;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getObjectSizeInByteBuffer(ByteBuffer buffer) {
    return buffer.getInt();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public OCompositeKey deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) {
    final OCompositeKey compositeKey = new OCompositeKey();

    offset += OIntegerSerializer.INT_SIZE;

    final int keysSize = walChanges.getIntValue(buffer, offset);
    offset += OIntegerSerializer.INT_SIZE;

    final OBinarySerializerFactory factory = OBinarySerializerFactory.getInstance();
    for (int i = 0; i < keysSize; i++) {
      final byte serializerId = walChanges.getByteValue(buffer, offset);
      offset += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE;

      @SuppressWarnings("unchecked")
      OBinarySerializer<Object> binarySerializer = (OBinarySerializer<Object>) factory.getObjectSerializer(serializerId);
      final Object key = binarySerializer.deserializeFromByteBufferObject(buffer, walChanges, offset);
      compositeKey.addKey(key);

      offset += binarySerializer.getObjectSize(key);
    }

    return compositeKey;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) {
    return walChanges.getIntValue(buffer, offset);
  }
}


/*
 *
 *  *  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.index.sbtreebonsai.local;

import java.io.IOException;

import com.orientechnologies.common.serialization.types.OLongSerializer;
import com.orientechnologies.orient.core.storage.cache.OCacheEntry;
import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurablePage;

/**
 * A base class for bonsai buckets. Bonsai bucket size is usually less than page size and one page could contain multiple bonsai
 * buckets.
 *
 * Adds methods to read and write bucket pointers.
 *
 * @see com.orientechnologies.orient.core.storage.index.sbtreebonsai.local.OBonsaiBucketPointer
 * @see com.orientechnologies.orient.core.storage.index.sbtreebonsai.local.OSBTreeBonsai
 *
 * @author Artem Orobets (enisher-at-gmail.com)
 */
public class OBonsaiBucketAbstract extends ODurablePage {
  public OBonsaiBucketAbstract(OCacheEntry cacheEntry) {
    super(cacheEntry);
  }

  /**
   * Write a bucket pointer to specific location.
   *
   * @param pageOffset
   *          where to write
   * @param value
   *          - pointer to write
   * @throws IOException
   */
  protected void setBucketPointer(int pageOffset, OBonsaiBucketPointer value) throws IOException {
    setLongValue(pageOffset, value.getPageIndex());
    setIntValue(pageOffset + OLongSerializer.LONG_SIZE, value.getPageOffset());
  }

  /**
   * Read bucket pointer from page.
   *
   * @param offset
   *          where the pointer should be read from
   * @return bucket pointer
   */
  protected OBonsaiBucketPointer getBucketPointer(int offset) {
    final long pageIndex = getLongValue(offset);
    final int pageOffset = getIntValue(offset + OLongSerializer.LONG_SIZE);
    return new OBonsaiBucketPointer(pageIndex, pageOffset);
  }
}


package com.orientechnologies.orient.core.metadata.schema;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.util.OArrays;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.OScenarioThreadLocal;
import com.orientechnologies.orient.core.exception.ODatabaseException;
import com.orientechnologies.orient.core.exception.OSchemaException;
import com.orientechnologies.orient.core.metadata.security.ORole;
import com.orientechnologies.orient.core.metadata.security.ORule;
import com.orientechnologies.orient.core.metadata.security.OSecurityUser;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.storage.OAutoshardedStorage;

import java.util.*;
import java.util.concurrent.Callable;

/**
 * Created by tglman on 14/06/17.
 */
public class OClassEmbedded extends OClassImpl {
  protected OClassEmbedded(OSchemaShared iOwner, String iName) {
    super(iOwner, iName);
  }

  protected OClassEmbedded(OSchemaShared iOwner, String iName, int[] iClusterIds) {
    super(iOwner, iName, iClusterIds);
  }

  protected OClassEmbedded(OSchemaShared iOwner, ODocument iDocument, String iName) {
    super(iOwner, iDocument, iName);
  }

  public OProperty addProperty(final String propertyName, final OType type, final OType linkedType, final OClass linkedClass,
      final boolean unsafe) {
    if (type == null)
      throw new OSchemaException("Property type not defined.");

    if (propertyName == null || propertyName.length() == 0)
      throw new OSchemaException("Property name is null or empty");

    final ODatabaseDocumentInternal database = getDatabase();
    validatePropertyName(propertyName);
    if (database.getTransaction().isActive()) {
      throw new OSchemaException("Cannot create property '" + propertyName + "' inside a transaction");
    }

    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    if (linkedType != null)
      OPropertyImpl.checkLinkTypeSupport(type);

    if (linkedClass != null)
      OPropertyImpl.checkSupportLinkedClass(type);

    acquireSchemaWriteLock();
    try {
      return (OProperty) OScenarioThreadLocal.executeAsDistributed(new Callable<OProperty>() {
        @Override
        public OProperty call() throws Exception {
          return addPropertyInternal(propertyName, type, linkedType, linkedClass, unsafe);
        }
      });

    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OClassImpl setEncryption(final String iValue) {
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setEncryptionInternal(database, iValue);
    } finally {
      releaseSchemaWriteLock();
    }
    return this;
  }

  @Override
  public OClass setClusterSelection(final String value) {
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setClusterSelectionInternal(value);
      return this;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public void setClusterSelectionInternal(final String clusterSelection) {
    // AVOID TO CHECK THIS IN LOCK TO AVOID RE-GENERATION OF IMMUTABLE SCHEMAS
    if (this.clusterSelection.getName().equals(clusterSelection))
      // NO CHANGES
      return;

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      this.clusterSelection = owner.getClusterSelectionFactory().newInstance(clusterSelection);
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OClassImpl setCustom(final String name, final String value) {
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setCustomInternal(name, value);
      return this;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public void clearCustom() {
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      clearCustomInternal();

    } finally {
      releaseSchemaWriteLock();
    }
  }

  protected void clearCustomInternal() {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      customFields = null;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  @Override
  public OClass setSuperClasses(final List<? extends OClass> classes) {
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);
    if (classes != null) {
      List<OClass> toCheck = new ArrayList<OClass>(classes);
      toCheck.add(this);
      checkParametersConflict(toCheck);
    }
    acquireSchemaWriteLock();
    try {
      setSuperClassesInternal(classes);
    } finally {
      releaseSchemaWriteLock();
    }
    return this;
  }

  protected OClass removeBaseClassInternal(final OClass baseClass) {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      if (subclasses == null)
        return this;

      if (subclasses.remove(baseClass))
        removePolymorphicClusterIds((OClassImpl) baseClass);

      return this;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  @Override
  public OClass addSuperClass(final OClass superClass) {
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);
    checkParametersConflict(superClass);
    acquireSchemaWriteLock();
    try {
      addSuperClassInternal(database, superClass);
    } finally {
      releaseSchemaWriteLock();
    }
    return this;
  }

  protected void addSuperClassInternal(ODatabaseDocumentInternal database, final OClass superClass) {
    acquireSchemaWriteLock();
    try {
      final OClassImpl cls;

      if (superClass instanceof OClassAbstractDelegate)
        cls = (OClassImpl) ((OClassAbstractDelegate) superClass).delegate;
      else
        cls = (OClassImpl) superClass;

      if (cls != null) {

        // CHECK THE USER HAS UPDATE PRIVILEGE AGAINST EXTENDING CLASS
        final OSecurityUser user = database.getUser();
        if (user != null)
          user.allow(ORule.ResourceGeneric.CLASS, cls.getName(), ORole.PERMISSION_UPDATE);

        if (superClasses.contains(superClass)) {
          throw new OSchemaException(
              "Class: '" + this.getName() + "' already has the class '" + superClass.getName() + "' as superclass");
        }

        cls.addBaseClass(this);
        superClasses.add(cls);
      }
    } finally {
      releaseSchemaWriteLock();
    }
  }

  @Override
  public OClass removeSuperClass(OClass superClass) {
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);
    acquireSchemaWriteLock();
    try {
      removeSuperClassInternal(superClass);

    } finally {
      releaseSchemaWriteLock();
    }
    return this;
  }

  protected void removeSuperClassInternal(final OClass superClass) {
    acquireSchemaWriteLock();
    try {
      final OClassImpl cls;

      if (superClass instanceof OClassAbstractDelegate)
        cls = (OClassImpl) ((OClassAbstractDelegate) superClass).delegate;
      else
        cls = (OClassImpl) superClass;

      if (superClasses.contains(cls)) {
        if (cls != null)
          cls.removeBaseClassInternal(this);

        superClasses.remove(superClass);
      }
    } finally {
      releaseSchemaWriteLock();
    }
  }

  protected void setSuperClassesInternal(final List<? extends OClass> classes) {
    List<OClassImpl> newSuperClasses = new ArrayList<OClassImpl>();
    OClassImpl cls;
    for (OClass superClass : classes) {
      if (superClass instanceof OClassAbstractDelegate)
        cls = (OClassImpl) ((OClassAbstractDelegate) superClass).delegate;
      else
        cls = (OClassImpl) superClass;

      if (newSuperClasses.contains(cls)) {
        throw new OSchemaException("Duplicated superclass '" + cls.getName() + "'");
      }

      newSuperClasses.add(cls);
    }

    List<OClassImpl> toAddList = new ArrayList<OClassImpl>(newSuperClasses);
    toAddList.removeAll(superClasses);
    List<OClassImpl> toRemoveList = new ArrayList<OClassImpl>(superClasses);
    toRemoveList.removeAll(newSuperClasses);

    for (OClassImpl toRemove : toRemoveList) {
      toRemove.removeBaseClassInternal(this);
    }
    for (OClassImpl addTo : toAddList) {
      addTo.addBaseClass(this);
    }
    superClasses.clear();
    superClasses.addAll(newSuperClasses);
  }

  public OClass setName(final String name) {
    if (getName().equals(name))
      return this;
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);
    final Character wrongCharacter = OSchemaShared.checkClassNameIfValid(name);
    OClass oClass = database.getMetadata().getSchema().getClass(name);
    if (oClass != null) {
      String error = String.format("Cannot rename class %s to %s. A Class with name %s exists", this.name, name, name);
      throw new OSchemaException(error);
    }
    if (wrongCharacter != null)
      throw new OSchemaException(
          "Invalid class name found. Character '" + wrongCharacter + "' cannot be used in class name '" + name + "'");
    acquireSchemaWriteLock();
    try {
      setNameInternal(database, name);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setNameInternal(ODatabaseDocumentInternal database, final String name) {
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);
    acquireSchemaWriteLock();
    try {
      checkEmbedded();
      final String oldName = this.name;
      owner.changeClassName(database, this.name, name, this);
      this.name = name;
      renameCluster(oldName, this.name);
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public void setDefaultClusterId(final int defaultClusterId) {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();
      this.defaultClusterId = defaultClusterId;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  protected OClass addClusterIdInternal(ODatabaseDocumentInternal database, final int clusterId) {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      owner.checkClusterCanBeAdded(clusterId, this);

      for (int currId : clusterIds)
        if (currId == clusterId)
          // ALREADY ADDED
          return this;

      clusterIds = OArrays.copyOf(clusterIds, clusterIds.length + 1);
      clusterIds[clusterIds.length - 1] = clusterId;
      Arrays.sort(clusterIds);

      addPolymorphicClusterId(clusterId);

      if (defaultClusterId == NOT_EXISTENT_CLUSTER_ID)
        defaultClusterId = clusterId;

      ((OSchemaEmbedded) owner).addClusterForClass(database, clusterId, this);
      return this;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OClass setShortName(String shortName) {
    if (shortName != null) {
      shortName = shortName.trim();
      if (shortName.isEmpty())
        shortName = null;
    }
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setShortNameInternal(database, shortName);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setShortNameInternal(ODatabaseDocumentInternal database, final String iShortName) {
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      String oldName = null;

      if (this.shortName != null)
        oldName = this.shortName;

      owner.changeClassName(database, oldName, iShortName, this);

      this.shortName = iShortName;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  protected OPropertyImpl createPropertyInstance(ODocument p) {
    return new OPropertyEmbedded(this, p);
  }

  public OPropertyImpl addPropertyInternal(final String name, final OType type, final OType linkedType, final OClass linkedClass,
      final boolean unsafe) {
    if (name == null || name.length() == 0)
      throw new OSchemaException("Found property name null");

    if (!unsafe)
      checkPersistentPropertyType(getDatabase(), name, type);

    final OPropertyEmbedded prop;

    // This check are doubled because used by sql commands
    if (linkedType != null)
      OPropertyImpl.checkLinkTypeSupport(type);

    if (linkedClass != null)
      OPropertyImpl.checkSupportLinkedClass(type);

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      if (properties.containsKey(name))
        throw new OSchemaException("Class '" + this.name + "' already has property '" + name + "'");

      OGlobalProperty global = owner.findOrCreateGlobalProperty(name, type);

      prop = createPropertyInstance(global);

      properties.put(name, prop);

      if (linkedType != null)
        prop.setLinkedTypeInternal(linkedType);
      else if (linkedClass != null)
        prop.setLinkedClassInternal(linkedClass);
    } finally {
      releaseSchemaWriteLock();
    }

    if (prop != null && !unsafe)
      fireDatabaseMigration(getDatabase(), name, type);

    return prop;
  }

  protected OPropertyEmbedded createPropertyInstance(OGlobalProperty global) {
    return new OPropertyEmbedded(this, global);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public OClass truncateCluster(String clusterName) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.CLASS, ORole.PERMISSION_DELETE, name);

    acquireSchemaReadLock();
    try {
      final ODatabaseDocumentInternal database = getDatabase();
      truncateClusterInternal(clusterName, database);
    } finally {
      releaseSchemaReadLock();
    }

    return this;
  }

  public OClass setStrictMode(final boolean isStrict) {
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setStrictModeInternal(isStrict);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setStrictModeInternal(final boolean iStrict) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      this.strictMode = iStrict;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OClass setDescription(String iDescription) {
    if (iDescription != null) {
      iDescription = iDescription.trim();
      if (iDescription.isEmpty())
        iDescription = null;
    }
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setDescriptionInternal(iDescription);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setDescriptionInternal(final String iDescription) {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();
      this.description = iDescription;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OClass addClusterId(final int clusterId) {
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    if (isAbstract()) {
      throw new OSchemaException("Impossible to associate a cluster to an abstract class class");
    }

    acquireSchemaWriteLock();
    try {
      addClusterIdInternal(database, clusterId);
    } finally {
      releaseSchemaWriteLock();
    }
    return this;
  }

  public OClass removeClusterId(final int clusterId) {
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    if (clusterIds.length == 1 && clusterId == clusterIds[0])
      throw new ODatabaseException(" Impossible to remove the last cluster of class '" + getName() + "' drop the class instead");

    acquireSchemaWriteLock();
    try {
      removeClusterIdInternal(database, clusterId);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected OClass removeClusterIdInternal(ODatabaseDocumentInternal database, final int clusterToRemove) {

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      boolean found = false;
      for (int clusterId : clusterIds) {
        if (clusterId == clusterToRemove) {
          found = true;
          break;
        }
      }

      if (found) {
        final int[] newClusterIds = new int[clusterIds.length - 1];
        for (int i = 0, k = 0; i < clusterIds.length; ++i) {
          if (clusterIds[i] == clusterToRemove)
            // JUMP IT
            continue;

          newClusterIds[k] = clusterIds[i];
          k++;
        }
        clusterIds = newClusterIds;

        removePolymorphicClusterId(clusterToRemove);
      }

      if (defaultClusterId == clusterToRemove) {
        if (clusterIds.length >= 1)
          defaultClusterId = clusterIds[0];
        else
          defaultClusterId = NOT_EXISTENT_CLUSTER_ID;
      }

      ((OSchemaEmbedded) owner).removeClusterForClass(database, clusterToRemove, this);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  public void dropProperty(final String propertyName) {
    final ODatabaseDocumentInternal database = getDatabase();
    if (database.getTransaction().isActive())
      throw new IllegalStateException("Cannot drop a property inside a transaction");

    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_DELETE);

    acquireSchemaWriteLock();
    try {
      if (!properties.containsKey(propertyName))
        throw new OSchemaException("Property '" + propertyName + "' not found in class " + name + "'");

      OScenarioThreadLocal.executeAsDistributed((Callable<OProperty>) () -> {
        dropPropertyInternal(database, propertyName);
        return null;
      });

    } finally {
      releaseSchemaWriteLock();
    }
  }

  protected void dropPropertyInternal(ODatabaseDocumentInternal database, final String iPropertyName) {
    if (database.getTransaction().isActive())
      throw new IllegalStateException("Cannot drop a property inside a transaction");
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_DELETE);

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      final OProperty prop = properties.remove(iPropertyName);

      if (prop == null)
        throw new OSchemaException("Property '" + iPropertyName + "' not found in class " + name + "'");
    } finally {
      releaseSchemaWriteLock();
    }
  }

  @Override
  public OClass addCluster(final String clusterNameOrId) {
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    if (isAbstract()) {
      throw new OSchemaException("Impossible to associate a cluster to an abstract class class");
    }

    acquireSchemaWriteLock();
    try {
      final int clusterId = owner.createClusterIfNeeded(database, clusterNameOrId);
      addClusterIdInternal(database, clusterId);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  public OClass setOverSize(final float overSize) {
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);
    acquireSchemaWriteLock();
    try {
      setOverSizeInternal(database, overSize);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setOverSizeInternal(ODatabaseDocumentInternal database, final float overSize) {
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      this.overSize = overSize;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OClass setAbstract(boolean isAbstract) {
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setAbstractInternal(database, isAbstract);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setCustomInternal(final String name, final String value) {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      if (customFields == null)
        customFields = new HashMap<String, String>();
      if (value == null || "null".equalsIgnoreCase(value))
        customFields.remove(name);
      else
        customFields.put(name, value);
    } finally {
      releaseSchemaWriteLock();
    }
  }

  protected void setAbstractInternal(ODatabaseDocumentInternal database, final boolean isAbstract) {
    database.checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      if (isAbstract) {
        // SWITCH TO ABSTRACT
        if (defaultClusterId != NOT_EXISTENT_CLUSTER_ID) {
          // CHECK
          if (count() > 0)
            throw new IllegalStateException("Cannot set the class as abstract because contains records.");

          tryDropCluster(defaultClusterId);
          for (int clusterId : getClusterIds()) {
            tryDropCluster(clusterId);
            removePolymorphicClusterId(clusterId);
            ((OSchemaEmbedded) owner).removeClusterForClass(database, clusterId, this);
          }

          setClusterIds(new int[] { NOT_EXISTENT_CLUSTER_ID });

          defaultClusterId = NOT_EXISTENT_CLUSTER_ID;
        }
      } else {
        if (!abstractClass)
          return;

        int clusterId = database.getClusterIdByName(name);
        if (clusterId == -1)
          clusterId = database.addCluster(name);

        this.defaultClusterId = clusterId;
        this.clusterIds[0] = this.defaultClusterId;
        this.polymorphicClusterIds = Arrays.copyOf(clusterIds, clusterIds.length);
        for (OClass clazz : getAllSubclasses()) {
          if (clazz instanceof OClassImpl) {
            addPolymorphicClusterIds((OClassImpl) clazz);
          } else {
            OLogManager.instance().warn(this, "Warning: cannot set polymorphic cluster IDs for class " + name);
          }
        }
      }

      this.abstractClass = isAbstract;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  private void tryDropCluster(final int defaultClusterId) {
    if (name.toLowerCase(Locale.ENGLISH).equals(getDatabase().getClusterNameById(defaultClusterId))) {
      // DROP THE DEFAULT CLUSTER CALLED WITH THE SAME NAME ONLY IF EMPTY
      if (getDatabase().getClusterRecordSizeById(defaultClusterId) == 0)
        getDatabase().getStorage().dropCluster(defaultClusterId, 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.paginated.atomicoperations;

import com.orientechnologies.common.concur.lock.OInterruptedException;
import com.orientechnologies.common.concur.lock.OOneEntryPerKeyLockManager;
import com.orientechnologies.common.exception.OException;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.util.OPair;
import com.orientechnologies.orient.core.OOrientListenerAbstract;
import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.exception.ODatabaseException;
import com.orientechnologies.orient.core.storage.cache.OReadCache;
import com.orientechnologies.orient.core.storage.cache.OWriteCache;
import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage;
import com.orientechnologies.orient.core.storage.impl.local.paginated.OStorageTransaction;
import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent;
import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OLogSequenceNumber;
import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.ONonTxOperationPerformedWALRecord;
import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OOperationUnitId;
import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWriteAheadLog;
import com.orientechnologies.orient.core.tx.OTransactionInternal;

import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.LockSupport;

/**
 * @author Andrey Lomakin (a.lomakin-at-orientdb.com)
 * @since 12/3/13
 */
public class OAtomicOperationsManager implements OAtomicOperationsMangerMXBean {

  private volatile boolean trackAtomicOperations = OGlobalConfiguration.TX_TRACK_ATOMIC_OPERATIONS.getValueAsBoolean();

  private final LongAdder atomicOperationsCount = new LongAdder();

  private final AtomicInteger freezeRequests = new AtomicInteger();

  private final ConcurrentMap<Long, FreezeParameters> freezeParametersIdMap = new ConcurrentHashMap<>();
  private final AtomicLong                            freezeIdGen           = new AtomicLong();

  private final AtomicReference<WaitingListNode> waitingHead = new AtomicReference<>();
  private final AtomicReference<WaitingListNode> waitingTail = new AtomicReference<>();

  private static volatile ThreadLocal<OAtomicOperation> currentOperation = new ThreadLocal<>();

  static {
    Orient.instance().registerListener(new OOrientListenerAbstract() {
      @Override
      public void onStartup() {
        if (currentOperation == null) {
          currentOperation = new ThreadLocal<>();
        }
      }

      @Override
      public void onShutdown() {
        currentOperation = null;
      }
    });
  }

  private final OAbstractPaginatedStorage          storage;
  private final OWriteAheadLog                     writeAheadLog;
  private final OOneEntryPerKeyLockManager<String> lockManager = new OOneEntryPerKeyLockManager<>(true, -1,
      OGlobalConfiguration.COMPONENTS_LOCK_CACHE.getValueAsInteger());
  private final OReadCache                         readCache;
  private final OWriteCache                        writeCache;

  private final Map<OOperationUnitId, OPair<String, StackTraceElement[]>> activeAtomicOperations = new ConcurrentHashMap<>();

  public OAtomicOperationsManager(OAbstractPaginatedStorage storage) {
    this.storage = storage;
    this.writeAheadLog = storage.getWALInstance();
    this.readCache = storage.getReadCache();
    this.writeCache = storage.getWriteCache();
  }

  /**
   * @see #startAtomicOperation(String, boolean)
   */
  public OAtomicOperation startAtomicOperation(ODurableComponent durableComponent, boolean trackNonTxOperations)
      throws IOException {
    if (durableComponent != null) {
      return startAtomicOperation(durableComponent.getLockName(), trackNonTxOperations);
    }

    return startAtomicOperation((String) null, trackNonTxOperations);
  }

  /**
   * Starts atomic operation inside of current thread. If atomic operation has been already started, current atomic operation
   * instance will be returned. All durable components have to call this method at the beginning of any data modification
   * operation.
   *
In current implementation of atomic operation, each component which is participated in atomic operation is hold under
   * exclusive lock till atomic operation will not be completed (committed or rolled back).
   *
If other thread is going to read data from component it has to acquire read lock inside of atomic operation manager {@link
   * #acquireReadLock(ODurableComponent)}, otherwise data consistency will be compromised.
   *
Atomic operation may be delayed if start of atomic operations is prohibited by call of {@link
   * #freezeAtomicOperations(Class, String)} method. If mentioned above method is called then execution of current method will be
   * stopped till call of {@link #releaseAtomicOperations(long)} method or exception will be thrown. Concrete behaviour depends on
   * real values of parameters of {@link #freezeAtomicOperations(Class, String)} method.
   *
   * @param trackNonTxOperations If this flag set to true then special record {@link ONonTxOperationPerformedWALRecord}
   *                             will be added to WAL in case of atomic operation is started outside of active storage transaction.
   *                             During storage restore procedure this record is monitored and if given record is present then
   *                             rebuild of all indexes is performed.
   * @param lockName             Name of lock (usually name of component) which is going participate in atomic operation.
   *
   * @return Instance of active atomic operation.
   */
  public OAtomicOperation startAtomicOperation(String lockName, boolean trackNonTxOperations) throws IOException {
    OAtomicOperation operation = currentOperation.get();
    if (operation != null) {
      operation.incrementCounter();

      if (lockName != null) {
        acquireExclusiveLockTillOperationComplete(operation, lockName);
      }

      return operation;
    }

    atomicOperationsCount.increment();

    while (freezeRequests.get() > 0) {
      assert freezeRequests.get() >= 0;

      atomicOperationsCount.decrement();

      throwFreezeExceptionIfNeeded();

      final Thread thread = Thread.currentThread();

      addThreadInWaitingList(thread);

      if (freezeRequests.get() > 0) {
        LockSupport.park(this);
      }

      atomicOperationsCount.increment();
    }

    assert freezeRequests.get() >= 0;

    final boolean useWal = useWal();
    final OOperationUnitId unitId = OOperationUnitId.generateId();
    final OLogSequenceNumber lsn = useWal ? writeAheadLog.logAtomicOperationStartRecord(true, unitId) : null;

    operation = new OAtomicOperation(lsn, unitId, readCache, writeCache, storage.getId());
    currentOperation.set(operation);

    if (trackAtomicOperations) {
      final Thread thread = Thread.currentThread();
      activeAtomicOperations.put(unitId, new OPair<>(thread.getName(), thread.getStackTrace()));
    }

    if (useWal && trackNonTxOperations && storage.getStorageTransaction() == null) {
      writeAheadLog.log(new ONonTxOperationPerformedWALRecord());
    }

    if (lockName != null) {
      acquireExclusiveLockTillOperationComplete(operation, lockName);
    }

    try {
      storage.checkReadOnlyConditions();
    } catch (RuntimeException | Error e) {
      final Iterator<String> lockedObjectIterator = operation.lockedObjects().iterator();

      while (lockedObjectIterator.hasNext()) {
        final String lockedObject = lockedObjectIterator.next();
        lockedObjectIterator.remove();

        lockManager.releaseLock(this, lockedObject, OOneEntryPerKeyLockManager.LOCK.EXCLUSIVE);
      }

      throw e;
    }

    return operation;
  }

  public static void alarmClearOfAtomicOperation() {
    final OAtomicOperation current = currentOperation.get();

    if (current != null) {
      currentOperation.set(null);
    }
  }

  private void addThreadInWaitingList(Thread thread) {
    final WaitingListNode node = new WaitingListNode(thread);

    while (true) {
      final WaitingListNode last = waitingTail.get();

      if (waitingTail.compareAndSet(last, node)) {
        if (last == null) {
          waitingHead.set(node);
        } else {
          last.next = node;
          last.linkLatch.countDown();
        }

        break;
      }
    }
  }

  private WaitingListNode cutWaitingList() {
    while (true) {
      final WaitingListNode tail = waitingTail.get();
      final WaitingListNode head = waitingHead.get();

      if (tail == null) {
        return null;
      }

      //head is null but tail is not null we are in the middle of addition of item in the list
      if (head == null) {
        //let other thread to make it's work
        Thread.yield();
        continue;
      }

      if (head == tail) {
        return new WaitingListNode(head.item);
      }

      if (waitingHead.compareAndSet(head, tail)) {
        WaitingListNode node = head;

        node.waitTillAllLinksWillBeCreated();

        while (node.next != tail) {
          node = node.next;

          node.waitTillAllLinksWillBeCreated();
        }

        node.next = new WaitingListNode(tail.item);

        return head;
      }
    }
  }

  public long freezeAtomicOperations(Class<? extends OException> exceptionClass, String message) {

    final long id = freezeIdGen.incrementAndGet();

    freezeRequests.incrementAndGet();
    freezeParametersIdMap.put(id, new FreezeParameters(message, exceptionClass));

    while (atomicOperationsCount.sum() > 0) {
      Thread.yield();
    }

    return id;
  }

  public boolean isFrozen() {
    return freezeRequests.get() > 0;
  }

  public void releaseAtomicOperations(long id) {
    if (id >= 0) {
      final FreezeParameters freezeParameters = freezeParametersIdMap.remove(id);
      if (freezeParameters == null) {
        throw new IllegalStateException("Invalid value for freeze id " + id);
      }
    }

    final Map<Long, FreezeParameters> freezeParametersMap = new HashMap<>(freezeParametersIdMap);
    final long requests = freezeRequests.decrementAndGet();

    if (requests == 0) {
      for (Long freezeId : freezeParametersMap.keySet()) {
        freezeParametersIdMap.remove(freezeId);
      }

      WaitingListNode node = cutWaitingList();

      while (node != null) {
        LockSupport.unpark(node.item);
        node = node.next;
      }
    }
  }

  private void throwFreezeExceptionIfNeeded() {
    for (FreezeParameters freezeParameters : this.freezeParametersIdMap.values()) {
      if (freezeParameters.exceptionClass != null) {
        if (freezeParameters.message != null) {
          try {
            final Constructor<? extends OException> mConstructor = freezeParameters.exceptionClass.getConstructor(String.class);
            throw mConstructor.newInstance(freezeParameters.message);
          } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | InvocationTargetException ie) {
            OLogManager.instance().error(this, "Can not create instance of exception " + freezeParameters.exceptionClass
                + " with message will try empty constructor instead", ie);
            throwFreezeExceptionWithoutMessage(freezeParameters);
          }
        } else {
          throwFreezeExceptionWithoutMessage(freezeParameters);
        }

      }
    }
  }

  private void throwFreezeExceptionWithoutMessage(FreezeParameters freezeParameters) {
    try {
      throw freezeParameters.exceptionClass.newInstance();
    } catch (InstantiationException | IllegalAccessException ie) {
      OLogManager.instance().error(this, "Can not create instance of exception " + freezeParameters.exceptionClass
          + " will park thread instead of throwing of exception", ie);
    }
  }

  public static OAtomicOperation getCurrentOperation() {
    return currentOperation.get();
  }

  /**
   * Ends the current atomic operation on this manager.
   *
   * @param rollback {@code true} to indicate a rollback, {@code false} for successful commit.
   *
   * @return the LSN produced by committing the current operation or {@code null} if no commit was done.
   */
  public OLogSequenceNumber endAtomicOperation(boolean rollback) throws IOException {
    final OAtomicOperation operation = currentOperation.get();

    if (operation == null) {
      OLogManager.instance().error(this, "There is no atomic operation active", null);
      throw new ODatabaseException("There is no atomic operation active");
    }

    int counter = operation.getCounter();
    operation.decrementCounter();

    assert counter > 0;

    final OLogSequenceNumber lsn;
    try {
      if (rollback) {
        operation.rollback();
      }

      if (counter == 1) {
        try {
          final boolean useWal = useWal();

          if (!operation.isRollback()) {
            lsn = operation.commitChanges(useWal ? writeAheadLog : null);
          } else {
            lsn = null;
          }

          if (trackAtomicOperations) {
            activeAtomicOperations.remove(operation.getOperationUnitId());
          }
        } finally {
          final Iterator<String> lockedObjectIterator = operation.lockedObjects().iterator();

          while (lockedObjectIterator.hasNext()) {
            final String lockedObject = lockedObjectIterator.next();
            lockedObjectIterator.remove();

            lockManager.releaseLock(this, lockedObject, OOneEntryPerKeyLockManager.LOCK.EXCLUSIVE);
          }

          currentOperation.set(null);
        }
      } else {
        lsn = null;
      }
    } catch (Error e) {
      final OAbstractPaginatedStorage st = storage;
      if (st != null) {
        st.handleJVMError(e);
      }

      counter = 1;
      throw e;
    } finally {
      if (counter == 1) {
        atomicOperationsCount.decrement();
      }
    }

    return lsn;
  }

  public void ensureThatComponentsUnlocked() {
    final OAtomicOperation operation = currentOperation.get();
    if (operation != null) {
      final Iterator<String> lockedObjectIterator = operation.lockedObjects().iterator();

      while (lockedObjectIterator.hasNext()) {
        final String lockedObject = lockedObjectIterator.next();
        lockedObjectIterator.remove();

        lockManager.releaseLock(this, lockedObject, OOneEntryPerKeyLockManager.LOCK.EXCLUSIVE);
      }
    }
  }

  /**
   * Acquires exclusive lock with the given lock name in the given atomic operation.
   *
   * @param operation the atomic operation to acquire the lock in.
   * @param lockName  the lock name to acquire.
   */
  public void acquireExclusiveLockTillOperationComplete(OAtomicOperation operation, String lockName) {
    if (operation.containsInLockedObjects(lockName)) {
      return;
    }

    lockManager.acquireLock(lockName, OOneEntryPerKeyLockManager.LOCK.EXCLUSIVE);
    operation.addLockedObject(lockName);
  }

  /**
   * Acquires exclusive lock in the active atomic operation running on the current thread for the {@code durableComponent}.
   */
  public void acquireExclusiveLockTillOperationComplete(ODurableComponent durableComponent) {
    final OAtomicOperation operation = currentOperation.get();
    assert operation != null;
    acquireExclusiveLockTillOperationComplete(operation, durableComponent.getLockName());
  }

  public void acquireReadLock(ODurableComponent durableComponent) {
    assert durableComponent.getLockName() != null;

    lockManager.acquireLock(durableComponent.getLockName(), OOneEntryPerKeyLockManager.LOCK.SHARED);
  }

  public void releaseReadLock(ODurableComponent durableComponent) {
    assert durableComponent.getName() != null;
    assert durableComponent.getLockName() != null;

    lockManager.releaseLock(this, durableComponent.getLockName(), OOneEntryPerKeyLockManager.LOCK.SHARED);
  }

  @Override
  public void trackAtomicOperations() {
    activeAtomicOperations.clear();
    trackAtomicOperations = true;
  }

  @Override
  public void doNotTrackAtomicOperations() {
    trackAtomicOperations = false;
    activeAtomicOperations.clear();
  }

  @Override
  public String dumpActiveAtomicOperations() {
    if (!trackAtomicOperations) {
      activeAtomicOperations.clear();
    }

    @SuppressWarnings("resource")
    final StringWriter writer = new StringWriter();
    writer.append("List of active atomic operations: \r\n");
    writer.append("------------------------------------------------------------------------------------------------\r\n");
    for (Map.Entry<OOperationUnitId, OPair<String, StackTraceElement[]>> entry : activeAtomicOperations.entrySet()) {
      writer.append("Operation unit id :").append(entry.getKey().toString()).append("\r\n");
      writer.append("Started at thread : ").append(entry.getValue().getKey()).append("\r\n");
      writer.append("Stack trace of method which started this operation : \r\n");

      StackTraceElement[] stackTraceElements = entry.getValue().getValue();
      for (int i = 1; i < stackTraceElements.length; i++) {
        writer.append("\tat ").append(stackTraceElements[i].toString()).append("\r\n");
      }

      writer.append("\r\n\r\n");
    }
    writer.append("-------------------------------------------------------------------------------------------------\r\n");
    return writer.toString();
  }

  private static final class FreezeParameters {
    private final String                      message;
    private final Class<? extends OException> exceptionClass;

    FreezeParameters(String message, Class<? extends OException> exceptionClass) {
      this.message = message;
      this.exceptionClass = exceptionClass;
    }
  }

  private static final class WaitingListNode {
    /**
     * Latch which indicates that all links are created between add and existing list elements.
     */
    private final CountDownLatch linkLatch = new CountDownLatch(1);

    private final    Thread          item;
    private volatile WaitingListNode next;

    WaitingListNode(Thread item) {
      this.item = item;
    }

    void waitTillAllLinksWillBeCreated() {
      try {
        linkLatch.await();
      } catch (InterruptedException e) {
        throw OException.wrapException(
            new OInterruptedException("Thread was interrupted while was waiting for completion of 'waiting linked list' operation"),
            e);
      }
    }
  }

  private boolean useWal() {
    if (writeAheadLog == null) {
      return false;
    }

    final OStorageTransaction storageTransaction = storage.getStorageTransaction();
    if (storageTransaction == null) {
      return true;
    }

    final OTransactionInternal clientTx = storageTransaction.getClientTx();
    return clientTx == null || clientTx.isUsingLog();

  }
}