package me.zzp.ar;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;
import javax.sql.DataSource;
import me.zzp.ar.d.Dialect;
import me.zzp.ar.ex.DBOpenException;
import me.zzp.ar.ex.IllegalTableNameException;
import me.zzp.ar.ex.SqlExecuteException;
import me.zzp.ar.ex.TransactionException;
import me.zzp.ar.ex.UnsupportedDatabaseException;
import me.zzp.ar.pool.SingletonDataSource;
import me.zzp.util.Seq;

/**
 * 数据库对象。
 *
 * @since 1.0
 * @author redraiment
 */
public final class DB {
  private static final ServiceLoader<Dialect> dialects;

  static {
    dialects = ServiceLoader.load(Dialect.class);
  }

  public static DB open(String url) {
    return open(url, new Properties());
  }

  public static DB open(String url, String username, String password) {
    Properties info = new Properties();
    info.put("user", username);
    info.put("password", password);
    return open(url, info);
  }

  public static DB open(String url, Properties info) {
    try {
      return open(new SingletonDataSource(url, info));
    } catch (SQLException e) {
      throw new DBOpenException(e);
    }
  }

  public static DB open(DataSource pool) {
    try (Connection base = pool.getConnection()) {
      for (Dialect dialect : dialects) {
        if (dialect.accept(base)) {
          base.close();
          return new DB(pool, dialect);
        }
      }

      DatabaseMetaData meta = base.getMetaData();
      String version = String.format("%s %d.%d/%s", meta.getDatabaseProductName(),
                                                    meta.getDatabaseMajorVersion(),
                                                    meta.getDatabaseMinorVersion(),
                                                    meta.getDatabaseProductVersion());
      throw new UnsupportedDatabaseException(version);
    } catch (SQLException e) {
      throw new DBOpenException(e);
    }
  }

  private final DataSource pool;
  private final InheritableThreadLocal<Connection> base;
  private final Dialect dialect;
  private final Map<String, Map<String, Integer>> columns;
  private final Map<String, Map<String, Association>> relations;
  private final Map<String, Map<String, Lambda>> hooks;

  private DB(DataSource pool, Dialect dialect) {
    this.pool = pool;
    this.base = new InheritableThreadLocal<>();
    this.columns = new HashMap<>();
    this.relations = new HashMap<>();
    this.dialect = dialect;
    this.hooks = new HashMap<>();
  }

  private Connection getConnection() {
    try {
      return base.get() == null? pool.getConnection(): base.get();
    } catch (SQLException e) {
      throw new DBOpenException(e);
    }
  }

  void close(Connection c) {
    if (c != null && base.get() != c) {
      try {
        c.close();
      } catch (SQLException e) {
        throw new RuntimeException("close Connection fail", e);
      }
    }
  }

  void close(Statement s) {
    if (s != null) {
      try {
        Connection c = s.getConnection();
        s.close();
        close(c);
      } catch (SQLException e) {
        throw new RuntimeException("close Statement fail", e);
      }
    }
  }

  void close(ResultSet rs) {
    if (rs != null) {
      try {
        Statement s = rs.getStatement();
        rs.close();
        close(s);
      } catch (SQLException e) {
        throw new RuntimeException("close ResultSet fail", e);
      }
    }
  }

  public Set<String> getTableNames() {
    Set<String> tables = new HashSet();
    try (Connection c = pool.getConnection()) {
      DatabaseMetaData db = c.getMetaData();
      try (ResultSet rs = db.getTables(null, null, "%", new String[] {"TABLE"})) {
        while (rs.next()) {
          tables.add(rs.getString("table_name"));
        }
      }
    } catch (SQLException e) {
      throw new DBOpenException(e);
    }
    return tables;
  }

  public Set<Table> getTables() {
    Set<Table> tables = new HashSet();
    for (String name : getTableNames()) {
      tables.add(active(name));
    }
    return tables;
  }

  private Map<String, Integer> getColumns(String name) throws SQLException {
    if (!columns.containsKey(name)) {
      synchronized (columns) {
        if (!columns.containsKey(name)) {
          String catalog, schema, table;
          String[] patterns = name.split("\\.");
          if (patterns.length == 1) {
            catalog = null;
            schema = null;
            table = patterns[0];
          } else if (patterns.length == 2) {
            catalog = null;
            schema = patterns[0];
            table = patterns[1];
          } else if (patterns.length == 3) {
            catalog = patterns[0];
            schema = patterns[1];
            table = patterns[2];
          } else {
            throw new IllegalArgumentException(String.format("Illegal table name: %s", name));
          }

          Map<String, Integer> column = new LinkedHashMap<>();
          try (Connection c = pool.getConnection()) {
            DatabaseMetaData db = c.getMetaData();
            try (ResultSet rs = db.getColumns(catalog, schema, table, null)) {
              while (rs.next()) {
                String columnName = rs.getString("column_name");
                if (columnName.equalsIgnoreCase("id")
                    || columnName.equalsIgnoreCase("created_at")
                    || columnName.equalsIgnoreCase("updated_at")) {
                  continue;
                }
                column.put(parseKeyParameter(columnName), rs.getInt("data_type"));
              }
            }
          }
          columns.put(name, column);
        }
      }
    }
    return columns.get(name);
  }

  public Table active(String name) {
    name = dialect.getCaseIdentifier(name);

    if (!relations.containsKey(name)) {
      synchronized (relations) {
        if (!relations.containsKey(name)) {
          relations.put(name, new HashMap<String, Association>());
        }
      }
    }

    if (!hooks.containsKey(name)) {
      synchronized (hooks) {
        if (!hooks.containsKey(name)) {
          hooks.put(name, new HashMap<String, Lambda>());
        }
      }
    }

    try {
      return new Table(this, name, getColumns(name), relations.get(name), hooks.get(name));
    } catch (SQLException e) {
      throw new IllegalTableNameException(name, e);
    }
  }

  public PreparedStatement prepare(String sql, Object[] params, int[] types) {
    Connection c = getConnection();
    try {
      PreparedStatement call;
      if (sql.trim().toLowerCase().startsWith("insert")) {
        call = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
      } else {
        call = c.prepareStatement(sql);
      }

      if (params != null && params.length > 0) {
        for (int i = 0; i < params.length; i++) {
          if (params[i] != null) {
            call.setObject(i + 1, params[i]);
          } else {
            call.setNull(i + 1, types[i]);
          }
        }
      }
      return call;
    } catch (SQLException e) {
      throw new SqlExecuteException(sql, e);
    }
  }

  public void execute(String sql, Object[] params, int[] types) {
    PreparedStatement call = prepare(sql, params, types);
    try {
      call.executeUpdate();
    } catch (SQLException e) {
      throw new SqlExecuteException(sql, e);
    } finally {
      close(call);
    }
  }

  public void execute(String sql) {
    execute(sql, null, null);
  }

  public ResultSet query(String sql, Object... params) {
    try {
      PreparedStatement call = prepare(sql, params, null);
      return call.executeQuery();
    } catch (SQLException e) {
      throw new SqlExecuteException(sql, e);
    }
  }

  public Table createTable(String name, String... columns) {
    String template = "create table %s (id %s, %s, created_at timestamp, updated_at timestamp)";
    execute(String.format(template, name, dialect.getIdentity(), Seq.join(Arrays.asList(columns), ", ")));
    return active(name);
  }

  public void dropTable(String name) {
    execute(String.format("drop table if exists %s", name));
  }

  public void createIndex(String name, String table, String... columns) {
    execute(String.format("create index %s on %s(%s)", name, table, Seq.join(Arrays.asList(columns), ", ")));
  }

  public void dropIndex(String name, String table) {
    execute(String.format("drop index %s", name));
  }

  /* Transaction */
  public void batch(Runnable transaction) {
    // TODO: 不支持嵌套事务
    try (Connection c = pool.getConnection()) {
      boolean commit = c.getAutoCommit();
      try {
        c.setAutoCommit(false);
      } catch (SQLException e) {
        throw new TransactionException("transaction setAutoCommit(false)", e);
      }
      base.set(c);

      try {
        transaction.run();
      } catch (RuntimeException e) {
        try {
          c.rollback();
          c.setAutoCommit(commit);
        } catch (SQLException ex) {
          throw new TransactionException("transaction rollback: " + ex.getMessage(), e);
        }
        throw e;
      }

      try {
        c.commit();
      } catch (SQLException e) {
        throw new TransactionException("transaction commit", e);
      }
      c.setAutoCommit(commit);
    } catch (SQLException e) {
      throw new DBOpenException(e);
    } finally {
      base.set(null);
    }
  }

  public boolean tx(Runnable transaction) {
    try {
      batch(transaction);
    } catch (Throwable e) {
      return false;
    }
    return true;
  }

  /* Utility */
  public static Timestamp now() {
    return new Timestamp(System.currentTimeMillis());
  }

  static String parseKeyParameter(String name) {
    name = name.toLowerCase();
    if (name.endsWith(":")) {
      name = name.substring(0, name.length() - 1);
    }
    return name;
  }
}


package com.bah.lucene.blockcache;

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import com.googlecode.concurrentlinkedhashmap.EvictionListener;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

public class BlockCache implements Closeable {

  /**
   * true, if this platform supports unmapping mmapped files.
   */
  public static final boolean UNMAP_SUPPORTED;
  static {
    boolean v;
    try {
      Class.forName("sun.misc.Cleaner");
      Class.forName("java.nio.DirectByteBuffer").getMethod("cleaner");
      v = true;
    } catch (Exception e) {
      v = false;
    }
    UNMAP_SUPPORTED = v;
  }

  public static final int _128M = 134217728;
  public static final int _8K = 8192;

  private final ConcurrentMap<BlockCacheKey, BlockCacheLocation> _cache;
  private final ByteBuffer[] _slabs;
  private final BlockLocks[] _locks;
  private final AtomicInteger[] _lockCounters;
  private final int _blockSize = _8K;
  private final int _numberOfBlocksPerSlab;
  private final int _maxEntries;
  private final int _numberOfSlabs;
  private final boolean _directAllocation;
  private final ThreadLocal<ByteBuffer[]> _threadLocalSlabs = new ThreadLocal<ByteBuffer[]>() {
    @Override
    protected ByteBuffer[] initialValue() {
      return new ByteBuffer[_numberOfSlabs];
    }
  };

  //This turns the lazy bytebuffer allocation on or off.
  private final boolean lazy = true;

  public BlockCache(boolean directAllocation, long totalMemory) {
    this(directAllocation, totalMemory, _128M);
  }

  public BlockCache(boolean directAllocation, long totalMemory, int slabSize) {
    _numberOfBlocksPerSlab = slabSize / _blockSize;
    _numberOfSlabs = (int) (totalMemory / slabSize);
    _directAllocation = directAllocation;

    _slabs = new ByteBuffer[_numberOfSlabs];
    _locks = new BlockLocks[_numberOfSlabs];
    _lockCounters = new AtomicInteger[_numberOfSlabs];
    _maxEntries = (_numberOfBlocksPerSlab * _numberOfSlabs) - 1;
    for (int i = 0; i < _numberOfSlabs; i++) {
      if (!lazy) {
        if (_directAllocation) {
          _slabs[i] = ByteBuffer.allocateDirect(_numberOfBlocksPerSlab * _blockSize);
        } else {
          _slabs[i] = ByteBuffer.allocate(_numberOfBlocksPerSlab * _blockSize);
        }
      }
      _locks[i] = new BlockLocks(_numberOfBlocksPerSlab);
      _lockCounters[i] = new AtomicInteger();
    }

    EvictionListener<BlockCacheKey, BlockCacheLocation> listener = new EvictionListener<BlockCacheKey, BlockCacheLocation>() {
      @Override
      public void onEviction(BlockCacheKey key, BlockCacheLocation location) {
        releaseLocation(location);
      }
    };
    _cache = new ConcurrentLinkedHashMap.Builder<BlockCacheKey, BlockCacheLocation>()
        .maximumWeightedCapacity(_maxEntries).listener(listener).build();
  }

  private void releaseLocation(BlockCacheLocation location) {
    if (location == null) {
      return;
    }
    int slabId = location.getSlabId();
    int block = location.getBlock();
    location.setRemoved(true);
    _locks[slabId].clear(block);
    _lockCounters[slabId].decrementAndGet();
  }

  public boolean store(BlockCacheKey blockCacheKey, int blockOffset, byte[] data, int offset, int length) {
    if (length + blockOffset > _blockSize) {
      throw new RuntimeException("Buffer size exceeded, expecting max [" + _blockSize + "] got length [" + length
          + "] with blockOffset [" + blockOffset + "]");
    }
    BlockCacheLocation location = _cache.get(blockCacheKey);
    boolean newLocation = false;
    if (location == null) {
      newLocation = true;
      location = new BlockCacheLocation();
      if (!findEmptyLocation(location)) {
        return false;
      }
    }
    if (location.isRemoved()) {
      return false;
    }
    int slabId = location.getSlabId();
    int slabOffset = location.getBlock() * _blockSize;
    ByteBuffer slab = getSlab(slabId);
    slab.position(slabOffset + blockOffset);
    slab.put(data, offset, length);
    if (newLocation) {
      releaseLocation(_cache.put(blockCacheKey.clone(), location));
    }
    return true;
  }

  public boolean fetch(BlockCacheKey blockCacheKey, byte[] buffer, int blockOffset, int off, int length) {
    BlockCacheLocation location = _cache.get(blockCacheKey);
    if (location == null) {
      return false;
    }
    if (location.isRemoved()) {
      return false;
    }
    int slabId = location.getSlabId();
    int offset = location.getBlock() * _blockSize;
    location.touch();
    ByteBuffer slab = getSlab(slabId);
    slab.position(offset + blockOffset);
    slab.get(buffer, off, length);
    return true;
  }

  public boolean fetch(BlockCacheKey blockCacheKey, byte[] buffer) {
    checkLength(buffer);
    return fetch(blockCacheKey, buffer, 0, 0, _blockSize);
  }

  private boolean findEmptyLocation(BlockCacheLocation location) {
    // This is a tight loop that will try and find a location to
    // place the block before giving up
    for (int j = 0; j < 10; j++) {
      OUTER: for (int slabId = 0; slabId < _slabs.length; slabId++) {
        AtomicInteger bitSetCounter = _lockCounters[slabId];
        BlockLocks bitSet = _locks[slabId];
        if (bitSetCounter.get() == _numberOfBlocksPerSlab) {
          // if bitset is full
          continue OUTER;
        }
        // this check needs to spin, if a lock was attempted but not obtained
        // the rest of the slab should not be skipped
        int bit = bitSet.nextClearBit(0);
        INNER: while (bit != -1) {
          if (bit >= _numberOfBlocksPerSlab) {
            // bit set is full
            continue OUTER;
          }
          if (!bitSet.set(bit)) {
            // lock was not obtained
            // this restarts at 0 because another block could have been unlocked
            // while this was executing
            bit = bitSet.nextClearBit(0);
            continue INNER;
          } else {
            // lock obtained
            location.setSlabId(slabId);
            location.setBlock(bit);
            bitSetCounter.incrementAndGet();
            return true;
          }
        }
      }
    }
    return false;
  }

  private void checkLength(byte[] buffer) {
    if (buffer.length != _blockSize) {
      throw new RuntimeException("Buffer wrong size, expecting [" + _blockSize + "] got [" + buffer.length + "]");
    }
  }

  private ByteBuffer getSlab(int slabId) {
    if (!lazy) {
      return _slabs[slabId].duplicate();
    } else {
      ByteBuffer[] byteBuffers = _threadLocalSlabs.get();
      ByteBuffer byteBuffer = byteBuffers[slabId];
      if (byteBuffer == null) {
        synchronized (_slabs) {
          ByteBuffer bb = _slabs[slabId];
          if (bb == null) {
            if (_directAllocation) {
              bb = ByteBuffer.allocateDirect(_numberOfBlocksPerSlab * _blockSize);
            } else {
              bb = ByteBuffer.allocate(_numberOfBlocksPerSlab * _blockSize);
            }
            _slabs[slabId] = bb;
          }
          byteBuffer = bb.duplicate();
        }
        byteBuffers[slabId] = byteBuffer;
      }
      return byteBuffer;
    }
  }

  public int getSize() {
    if (_cache == null) {
      return 0;
    }
    return _cache.size();
  }

  public void close() throws IOException {
    for (ByteBuffer buffer : this._slabs) {
      freeBuffer(buffer);
    }
  }

  /**
   * This code was copied form MMAPDirectory in Lucene.
   */
  protected void freeBuffer(final ByteBuffer buffer) throws IOException {
    if (buffer == null) {
      return;
    }
    if (UNMAP_SUPPORTED) {
      try {
        AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
          @Override
          public Void run() throws Exception {
            final Method getCleanerMethod = buffer.getClass().getMethod("cleaner");
            getCleanerMethod.setAccessible(true);
            final Object cleaner = getCleanerMethod.invoke(buffer);
            if (cleaner != null) {
              cleaner.getClass().getMethod("clean").invoke(cleaner);
            }
            return null;
          }
        });
      } catch (PrivilegedActionException e) {
        final IOException ioe = new IOException("unable to unmap the mapped buffer");
        ioe.initCause(e.getCause());
        throw ioe;
      }
    }
  }
}


/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bah.lucene.blockcache_v2;

import java.io.Closeable;
import java.io.IOException;

import org.apache.lucene.store.IOContext;

public abstract class Cache implements Closeable {

  /**
   * Creates a new instance of CacheValue, the cache capacity should be used for
   * the given file.
   *
   * @param directory
   *          the directory.
   * @param fileName
   *          the file name.
   * @return the new CacheValue instance.
   */
  public CacheValue newInstance(CacheDirectory directory, String fileName) {
    return newInstance(directory, fileName, getCacheBlockSize(directory, fileName));
  }

  /**
   * Creates a new instance of CacheValue, the cache capacity should be used for
   * the given file.
   *
   * @param directory
   *          the directory.
   * @param fileName
   *          the file name.
   * @param cacheBlockSize
   *          the length of the {@link CacheValue}.
   * @return the new CacheValue instance.
   */
  public abstract CacheValue newInstance(CacheDirectory directory, String fileName, int cacheBlockSize);

  /**
   * Gets unique id for the given file. This is assumed to be unique even if the
   * file is deleted and recreated.
   *
   * @param directory
   *          the directory.
   * @param fileName
   *          the file name.
   * @return the file id.
   * @throws IOException
   */
  public abstract long getFileId(CacheDirectory directory, String fileName) throws IOException;

  /**
   * Get capacity of each cache entry for the given file.
   *
   * @param directory
   *          the directory.
   * @param fileName
   *          the file name.
   * @return the capacity.
   */
  public abstract int getCacheBlockSize(CacheDirectory directory, String fileName);

  /**
   * Gets buffer size of the buffer used while interacting with the underlying
   * directory.
   *
   * @param directory
   *          the directory.
   * @param fileName
   *          the file name.
   * @return the buffer size.
   */
  public abstract int getFileBufferSize(CacheDirectory directory, String fileName);

  /**
   * Checks whether file should be cached or not during reading.
   *
   * @param directory
   *          the directory.
   * @param fileName
   *          the file name.
   * @param context
   *          the IOContext from Lucene.
   * @return boolean.
   */
  public abstract boolean cacheFileForReading(CacheDirectory directory, String fileName, IOContext context);

  /**
   * Checks whether file should be cached or not during writing.
   *
   * @param directory
   *          the directory.
   * @param fileName
   *          the file name.
   * @param context
   *          the IOContext from Lucene.
   * @return boolean.
   */
  public abstract boolean cacheFileForWriting(CacheDirectory directory, String fileName, IOContext context);

  /**
   * Gets the cache value for the given key. Null if missing.
   *
   * @param key
   *          the key.
   * @return the cache value or null.
   */
  public abstract CacheValue get(CacheKey key);

  /**
   * Gets the cache value for the given key. Null if missing. NOTE: This method
   * will not effect the priority of the cache.
   *
   * @param key
   *          the key.
   * @return the cache value or null.
   */
  public abstract CacheValue getQuietly(CacheKey key);

  /**
   * Puts the cache entry into the cache.
   *
   * @param key
   *          the key.
   * @param value
   *          the value.
   */
  public abstract void put(CacheKey key, CacheValue value);

  /**
   * Removes the file from the cache.
   *
   * @param directory
   *          the directory.
   * @param fileName
   *          the file name.
   * @throws IOException
   */
  public abstract void removeFile(CacheDirectory directory, String fileName) throws IOException;

  /**
   * This is called when the CacheDirectory is finalized.
   *
   * @param directoryName
   *          the directory name.
   */
  public abstract void releaseDirectory(String directoryName);

  /**
   * Determines if the reader should be quiet or not.
   *
   * @param directory
   *          the directory.
   * @param fileName
   *          the file name.
   * @return boolean.
   */
  public abstract boolean shouldBeQuiet(CacheDirectory directory, String fileName);

}


package me.zzp.ar;

import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import me.zzp.ar.ex.IllegalFieldNameException;
import me.zzp.ar.ex.SqlExecuteException;
import me.zzp.ar.sql.SqlBuilder;
import me.zzp.ar.sql.TSqlBuilder;
import me.zzp.util.Seq;

/**
 * 表对象。
 *
 * @since 1.0
 * @author redraiment
 */
public final class Table {
  final DB dbo;
  final String name;
  final Map<String, Integer> columns;
  final Map<String, Association> relations;
  final Map<String, Lambda> hooks;
  final String primaryKey;

  private String foreignTable;
  private final Map<String, Integer> foreignKeys = new HashMap<>();

  Table(DB dbo, String name, Map<String, Integer> columns, Map<String, Association> relations, Map<String, Lambda> hooks) {
    this.dbo = dbo;
    this.name = name;
    this.columns = columns;
    this.relations = relations;
    this.hooks = hooks;
    this.primaryKey = name.concat(".id");
  }

  /**
   * 继承给定的JavaBean，扩展Record对象的get和set方法。
   *
   * @param bean 希望被继承的JavaBean
   * @return 返回Table自身
   * @since 2.3
   */
  public Table extend(Object bean) {
    Class<?> type = bean.getClass();
    for (Method method : type.getDeclaredMethods()) {
      Class<?> returnType = method.getReturnType();
      Class<?>[] params = method.getParameterTypes();
      String key = method.getName();

      if (params.length == 2
          && key.length() > 3
          && (key.startsWith("get") || key.startsWith("set"))
          && params[0].isAssignableFrom(Record.class)
          && params[1].isAssignableFrom(Object.class)
          && Object.class.isAssignableFrom(returnType)) {
        key = key.replaceAll("(?=[A-Z])", "_").toLowerCase();
        hooks.put(key, new Lambda(bean, method));
      }
    }

    return this;
  }

  public Map<String, Integer> getColumns() {
    return Collections.unmodifiableMap(columns);
  }

  /* Association */
  private Association assoc(String name, boolean onlyOne, boolean ancestor) {
    name = DB.parseKeyParameter(name);
    Association assoc = new Association(relations, name, onlyOne, ancestor);
    relations.put(name, assoc);
    return assoc;
  }

  public Association belongsTo(String name) {
    return assoc(name, true, false);
  }

  public Association hasOne(String name) {
    return assoc(name, true, true);
  }

  public Association hasMany(String name) {
    return assoc(name, false, true);
  }

  public Association hasAndBelongsToMany(String name) {
    return assoc(name, false, false);
  }

  private String[] getForeignKeys() {
    List<String> conditions = new ArrayList<>();
    for (Map.Entry<String, Integer> e : foreignKeys.entrySet()) {
      conditions.add(String.format("%s.%s = %d", name, e.getKey(), e.getValue()));
    }
    return conditions.toArray(new String[0]);
  }

  public Table constrain(String key, int id) {
    foreignKeys.put(DB.parseKeyParameter(key), id);
    return this;
  }

  public Table join(String table) {
    this.foreignTable = table;
    return this;
  }

  /* CRUD */
  public Record create(Object... args) {
    Map<String, Object> data = new HashMap<>();
    data.putAll(foreignKeys);
    for (int i = 0; i < args.length; i += 2) {
      String key = DB.parseKeyParameter(args[i].toString());
      if (!columns.containsKey(key)) {
        throw new IllegalFieldNameException(key);
      }
      Object value = args[i + 1];
      data.put(key, value);
    }

    String[] fields = new String[data.size() + 2];
    int[] types = new int[data.size() + 2];
    Object[] values = new Object[data.size() + 2];
    int index = 0;
    for (Map.Entry<String, Object> e : data.entrySet()) {
      fields[index] = e.getKey();
      types[index] = columns.get(e.getKey());
      values[index] = e.getValue();
      index++;
    }
    Seq.assignAt(fields, Seq.array(-2, -1), "created_at", "updated_at");
    Seq.assignAt(types, Seq.array(-2, -1), Types.TIMESTAMP, Types.TIMESTAMP);
    Seq.assignAt(values, Seq.array(-2, -1), DB.now(), DB.now());

    SqlBuilder sql = new TSqlBuilder();
    sql.insert().into(name).values(fields);
    PreparedStatement call = dbo.prepare(sql.toString(), values, types);
    try {
      int id = 0;
      if (call.executeUpdate() > 0) {
        ResultSet rs = call.getGeneratedKeys();
        if (rs != null && rs.next()) {
          id = rs.getInt(1);
          rs.close();
        }
      }
      return id > 0 ? find(id) : null;
    } catch (SQLException e) {
      throw new SqlExecuteException(sql.toString(), e);
    } finally {
      dbo.close(call);
    }
  }

  /**
   * 根据现有的Record创建新的Record.
   * 为跨数据库之间导数据提供便捷接口；同时也方便根据模板创建多条相似的纪录。
   * @param o Record对象
   * @return 根据参数创建的新的Record对象
   */
  public Record create(Record o) {
    List<Object> params = new LinkedList<>();
    for (String key : columns.keySet()) {
      if (!foreignKeys.containsKey(key)) {
        params.add(key);
        params.add(o.get(key));
      }
    }
    return create(params.toArray());
  }

  public void update(Record record) {
    String[] fields = new String[columns.size() + 1];
    int[] types = new int[columns.size() + 1];
    Object[] values = new Object[columns.size() + 1];
    int index = 0;
    for (String column : columns.keySet()) {
      fields[index] = column;
      types[index] = columns.get(column);
      values[index] = record.get(column);
      index++;
    }

    fields[columns.size()] = "updated_at";
    types[columns.size()] = Types.TIMESTAMP;
    values[columns.size()] = DB.now();

    SqlBuilder sql = new TSqlBuilder();
    sql.update(name).set(fields).where(String.format("%s = %d", primaryKey, record.getInt("id")));
    dbo.execute(sql.toString(), values, types);
  }

  public void delete(Record record) {
    int id = record.get("id");
    SqlBuilder sql = new TSqlBuilder();
    sql.delete().from(name).where(String.format("%s = %d", primaryKey, id));
    dbo.execute(sql.toString());
  }

  public void purge() {
    // TODO: need enhancement
    for (Record record : all()) {
      delete(record);
    }
  }

  List<Record> query(SqlBuilder sql, Object... args) {
    List<Record> records = new LinkedList<>();
    ResultSet rs = dbo.query(sql.toString(), args);
    try {
      ResultSetMetaData meta = rs.getMetaData();
      while (rs.next()) {
        Map<String, Object> values = new LinkedHashMap<>();
        for (int i = 1; i <= meta.getColumnCount(); i++) {
          String label = DB.parseKeyParameter(meta.getColumnLabel(i));
          values.put(label, rs.getObject(label));
        }
        records.add(new Record(this, values));
      }
    } catch (SQLException e) {
      throw new SqlExecuteException(sql.toString(), e);
    } finally {
      dbo.close(rs);
    }
    return records;
  }

  public Query select(String... columns) {
    Query sql = new Query(this);
    if (columns == null || columns.length == 0) {
      sql.select(String.format("%s.*", name));
    } else {
      sql.select(columns);
    }
    sql.from(name);
    if (foreignTable != null && !foreignTable.isEmpty()) {
      sql.join(foreignTable);
    }
    if (!foreignKeys.isEmpty()) {
      for (String condition : getForeignKeys()) {
        sql.where(condition);
      }
    }
    return sql.orderBy(primaryKey);
  }

  public Record first() {
    return select().limit(1).one();
  }

  public Record first(String condition, Object... args) {
    return select().where(condition).limit(1).one(args);
  }

  public Record last() {
    return select().orderBy(primaryKey.concat(" desc")).limit(1).one();
  }

  public Record last(String condition, Object... args) {
    return select().where(condition).orderBy(primaryKey.concat(" desc")).limit(1).one(args);
  }

  public Record find(int id) {
    return first(primaryKey.concat(" = ?"), id);
  }

  /**
   * 根据指定列，返回符合条件的第一条记录.
   * @param key 要匹配的列名
   * @param value 要匹配的值
   * @return 返回符合条件的第一条记录
   */
  public Record findA(String key, Object value) {
    key = DB.parseKeyParameter(key);
    if (value != null) {
      return first(key.concat(" = ?"), value);
    } else {
      return first(key.concat(" is null"));
    }
  }

  public List<Record> findBy(String key, Object value) {
    key = DB.parseKeyParameter(key);
    if (value != null) {
      return where(key.concat(" = ?"), value);
    } else {
      return where(key.concat(" is null"));
    }
  }

  public List<Record> all() {
    return select().all();
  }

  public List<Record> where(String condition, Object... args) {
    return select().where(condition).all(args);
  }

  public List<Record> paging(int page, int size) {
    return select().limit(size).offset(page * size).all();
  }
}


package me.zzp.ar.d;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import me.zzp.ar.ex.DBOpenException;

/**
 * HyperSQL方言。
 *
 * @since 1.0
 * @author redraiment
 */
public class HyperSQLDialect implements Dialect {
  /**
   * 判断当前数据库的名称里是否包含hsql（忽略大小写）。
   *
   * @param c 数据库连接
   * @return 如果数据库名称包含hsql，则返回true；否则返回false。
   */
  @Override
  public boolean accept(Connection c) {
    try {
      DatabaseMetaData d = c.getMetaData();
      String name = d.getDatabaseProductName(); // HSQL Database Engine
      return name.toLowerCase().contains("hsql");
    } catch (SQLException e) {
      throw new DBOpenException(e);
    }
  }

  /**
   * 返回HyperSQL中定义自增长的整型主键语句。
   *
   * @return integer primary key generated always as identity (start with 1, increment by 1)。
   */
  @Override
  public String getIdentity() {
    return "integer primary key generated always as identity (start with 1, increment by 1)";
  }

  /**
   * 将标识转换成为大写。
   *
   * @param identifier 待转换的标识。
   * @return 大写形式的标识。
   */
  @Override
  public String getCaseIdentifier(String identifier) {
    return identifier.toUpperCase();
  }
}


package com.bah.lucene.buffer;

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

import java.io.EOFException;
import java.io.IOException;

import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.store.IndexOutput;

/** Base implementation class for buffered {@link IndexInput}. */
public abstract class ReusedBufferedIndexInput extends IndexInput {

  // START Changed code from normal BufferedIndexInput
  @Override
  public final void close() throws IOException {
    closeInternal();
    BufferStore.putBuffer(buffer);
    buffer = null;
  }

  protected abstract void closeInternal() throws IOException;

  private void refill() throws IOException {
    long start = bufferStart + bufferPosition;
    long end = start + bufferSize;
    if (end > length()) // don't read past EOF
      end = length();
    int newLength = (int) (end - start);
    if (newLength <= 0)
      throw new EOFException("read past EOF: " + this);

    if (buffer == null) {
      buffer = BufferStore.takeBuffer(bufferSize);
      seekInternal(bufferStart);
    }
    readInternal(buffer, 0, newLength);
    bufferLength = newLength;
    bufferStart = start;
    bufferPosition = 0;
  }

  // The normal read buffer size defaults to 1024, but
  // increasing this during merging seems to yield
  // performance gains. However we don't want to increase
  // it too much because there are quite a few
  // BufferedIndexInputs created during merging. See
  // LUCENE-888 for details.
  /**
   * A buffer size for merges set to 8192, 4k in Lucene
   */
  public static final int MERGE_BUFFER_SIZE = 8192;

  //END Changed code from normal BufferedIndexInput

  /** Default buffer size set to 1024 */
  public static final int BUFFER_SIZE = 1024;

  private int bufferSize = BUFFER_SIZE;

  protected byte[] buffer;

  private long bufferStart = 0; // position in file of buffer
  private int bufferLength = 0; // end of valid bytes
  private int bufferPosition = 0; // next byte to read

  @Override
  public final byte readByte() throws IOException {
    if (bufferPosition >= bufferLength)
      refill();
    return buffer[bufferPosition++];
  }

  public ReusedBufferedIndexInput(String resourceDesc) {
    this(resourceDesc, BUFFER_SIZE);
  }

  public ReusedBufferedIndexInput(String resourceDesc, IOContext context) {
    this(resourceDesc, bufferSize(context));
  }

  /** Inits BufferedIndexInput with a specific bufferSize */
  public ReusedBufferedIndexInput(String resourceDesc, int bufferSize) {
    super(resourceDesc);
    checkBufferSize(bufferSize);
    this.bufferSize = bufferSize;
  }

  /** Returns buffer size. @see #setBufferSize */
  public final int getBufferSize() {
    return bufferSize;
  }

  private void checkBufferSize(int bufferSize) {
    if (bufferSize <= 0)
      throw new IllegalArgumentException("bufferSize must be greater than 0 (got " + bufferSize + ")");
  }

  @Override
  public final void readBytes(byte[] b, int offset, int len) throws IOException {
    readBytes(b, offset, len, true);
  }

  @Override
  public final void readBytes(byte[] b, int offset, int len, boolean useBuffer) throws IOException {

    if (len <= (bufferLength - bufferPosition)) {
      // the buffer contains enough data to satisfy this request
      if (len > 0) // to allow b to be null if len is 0...
        System.arraycopy(buffer, bufferPosition, b, offset, len);
      bufferPosition += len;
    } else {
      // the buffer does not have enough data. First serve all we've got.
      int available = bufferLength - bufferPosition;
      if (available > 0) {
        System.arraycopy(buffer, bufferPosition, b, offset, available);
        offset += available;
        len -= available;
        bufferPosition += available;
      }
      // and now, read the remaining 'len' bytes:
      if (useBuffer && len < bufferSize) {
        // If the amount left to read is small enough, and
        // we are allowed to use our buffer, do it in the usual
        // buffered way: fill the buffer and copy from it:
        refill();
        if (bufferLength < len) {
          // Throw an exception when refill() could not read len bytes:
          System.arraycopy(buffer, 0, b, offset, bufferLength);
          throw new EOFException("read past EOF: " + this);
        } else {
          System.arraycopy(buffer, 0, b, offset, len);
          bufferPosition = len;
        }
      } else {
        // The amount left to read is larger than the buffer
        // or we've been asked to not use our buffer -
        // there's no performance reason not to read it all
        // at once. Note that unlike the previous code of
        // this function, there is no need to do a seek
        // here, because there's no need to reread what we
        // had in the buffer.
        long after = bufferStart + bufferPosition + len;
        if (after > length())
          throw new EOFException("read past EOF: " + this);
        readInternal(b, offset, len);
        bufferStart = after;
        bufferPosition = 0;
        bufferLength = 0; // trigger refill() on read
      }
    }
  }

  @Override
  public final short readShort() throws IOException {
    if (2 <= (bufferLength - bufferPosition)) {
      return (short) (((buffer[bufferPosition++] & 0xFF) << 8) | (buffer[bufferPosition++] & 0xFF));
    } else {
      return super.readShort();
    }
  }

  @Override
  public final int readInt() throws IOException {
    if (4 <= (bufferLength - bufferPosition)) {
      return ((buffer[bufferPosition++] & 0xFF) << 24) | ((buffer[bufferPosition++] & 0xFF) << 16) | ((buffer[bufferPosition++] & 0xFF) << 8) | (buffer[bufferPosition++] & 0xFF);
    } else {
      return super.readInt();
    }
  }

  @Override
  public final long readLong() throws IOException {
    if (8 <= (bufferLength - bufferPosition)) {
      final int i1 = ((buffer[bufferPosition++] & 0xff) << 24) | ((buffer[bufferPosition++] & 0xff) << 16) | ((buffer[bufferPosition++] & 0xff) << 8)
          | (buffer[bufferPosition++] & 0xff);
      final int i2 = ((buffer[bufferPosition++] & 0xff) << 24) | ((buffer[bufferPosition++] & 0xff) << 16) | ((buffer[bufferPosition++] & 0xff) << 8)
          | (buffer[bufferPosition++] & 0xff);
      return (((long) i1) << 32) | (i2 & 0xFFFFFFFFL);
    } else {
      return super.readLong();
    }
  }

  @Override
  public final int readVInt() throws IOException {
    if (5 <= (bufferLength - bufferPosition)) {
      byte b = buffer[bufferPosition++];
      if (b >= 0)
        return b;
      int i = b & 0x7F;
      b = buffer[bufferPosition++];
      i |= (b & 0x7F) << 7;
      if (b >= 0)
        return i;
      b = buffer[bufferPosition++];
      i |= (b & 0x7F) << 14;
      if (b >= 0)
        return i;
      b = buffer[bufferPosition++];
      i |= (b & 0x7F) << 21;
      if (b >= 0)
        return i;
      b = buffer[bufferPosition++];
      // Warning: the next ands use 0x0F / 0xF0 - beware copy/paste errors:
      i |= (b & 0x0F) << 28;
      if ((b & 0xF0) == 0)
        return i;
      throw new IOException("Invalid vInt detected (too many bits)");
    } else {
      return super.readVInt();
    }
  }

  @Override
  public final long readVLong() throws IOException {
    if (9 <= bufferLength - bufferPosition) {
      byte b = buffer[bufferPosition++];
      if (b >= 0)
        return b;
      long i = b & 0x7FL;
      b = buffer[bufferPosition++];
      i |= (b & 0x7FL) << 7;
      if (b >= 0)
        return i;
      b = buffer[bufferPosition++];
      i |= (b & 0x7FL) << 14;
      if (b >= 0)
        return i;
      b = buffer[bufferPosition++];
      i |= (b & 0x7FL) << 21;
      if (b >= 0)
        return i;
      b = buffer[bufferPosition++];
      i |= (b & 0x7FL) << 28;
      if (b >= 0)
        return i;
      b = buffer[bufferPosition++];
      i |= (b & 0x7FL) << 35;
      if (b >= 0)
        return i;
      b = buffer[bufferPosition++];
      i |= (b & 0x7FL) << 42;
      if (b >= 0)
        return i;
      b = buffer[bufferPosition++];
      i |= (b & 0x7FL) << 49;
      if (b >= 0)
        return i;
      b = buffer[bufferPosition++];
      i |= (b & 0x7FL) << 56;
      if (b >= 0)
        return i;
      throw new IOException("Invalid vLong detected (negative values disallowed)");
    } else {
      return super.readVLong();
    }
  }

  /**
   * Expert: implements buffer refill. Reads bytes from the current position in
   * the input.
   *
   * @param b
   *          the array to read bytes into
   * @param offset
   *          the offset in the array to start storing bytes
   * @param length
   *          the number of bytes to read
   */
  protected abstract void readInternal(byte[] b, int offset, int length) throws IOException;

  @Override
  public final long getFilePointer() {
    return bufferStart + bufferPosition;
  }

  @Override
  public final void seek(long pos) throws IOException {
    if (pos >= bufferStart && pos < (bufferStart + bufferLength))
      bufferPosition = (int) (pos - bufferStart); // seek within buffer
    else {
      bufferStart = pos;
      bufferPosition = 0;
      bufferLength = 0; // trigger refill() on read()
      seekInternal(pos);
    }
  }

  /**
   * Expert: implements seek. Sets current position in this file, where the next
   * {@link #readInternal(byte[],int,int)} will occur.
   *
   * @see #readInternal(byte[],int,int)
   */
  protected abstract void seekInternal(long pos) throws IOException;

  @Override
  public ReusedBufferedIndexInput clone() {
    ReusedBufferedIndexInput clone = (ReusedBufferedIndexInput) super.clone();

    clone.buffer = null;
    clone.bufferLength = 0;
    clone.bufferPosition = 0;
    clone.bufferStart = getFilePointer();

    return clone;
  }

  /**
   * Flushes the in-memory bufer to the given output, copying at most
   * numBytes.
   *

   * NOTE: this method does not refill the buffer, however it does
   * advance the buffer position.
   *
   * @return the number of bytes actually flushed from the in-memory buffer.
   */
  protected final int flushBuffer(IndexOutput out, long numBytes) throws IOException {
    int toCopy = bufferLength - bufferPosition;
    if (toCopy > numBytes) {
      toCopy = (int) numBytes;
    }
    if (toCopy > 0) {
      out.writeBytes(buffer, bufferPosition, toCopy);
      bufferPosition += toCopy;
    }
    return toCopy;
  }

  /**
   * Returns default buffer sizes for the given {@link IOContext}
   */
  public static int bufferSize(IOContext context) {
    switch (context.context) {
    case MERGE:
      return MERGE_BUFFER_SIZE;
    default:
      return BUFFER_SIZE;
    }
  }

}


package me.zzp.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/**
 * 数组和List相关的工具方法。
 *
 * @since 1.0
 * @author redraiment
 */
public final class Seq {
  /**
   * 将一系列对象组装成数组。
   *
   * @param <E> 元素类型。
   * @param args 多个同类型的元素。
   * @return 返回由这些元素组成的数组。
   */
  public static <E> E[] array(E... args) {
    return args;
  }

  /**
   * 将一系列对象组装成List。
   *
   * @param <E> 元素类型。
   * @param args 多个同类型的元素。
   * @return 返回由这些元素组成的List。
   */
  public static <E> List<E> list(E... args) {
    List<E> list = new ArrayList<>();
    list.addAll(Arrays.asList(args));
    return list;
  }

  /**
   * 追加任意多个新int到int数组末尾。
   *
   * @param a 现有int数组。
   * @param b 任意多个新int。
   * @return 返回组装好的新int数组。
   */
  public static int[] concat(int[] a, int... b) {
    int[] c = new int[a.length + b.length];
    System.arraycopy(a, 0, c, 0, a.length);
    System.arraycopy(b, 0, c, a.length, b.length);
    return c;
  }

  /**
   * 追加任意多个新元素到数组末尾。
   *
   * @param <E> 元素的类型。
   * @param a 现有数组。
   * @param b 任意多个新元素。
   * @return 返回组装好的新数组。
   */
  public static <E> E[] concat(E[] a, E... b) {
    return merge(a, b);
  }

  /**
   * 将数组连接成字符串。
   *
   * @param delimiter 分隔符。
   * @param args 任意多个元素。
   * @return 连接后的字符串。
   */
  public static String join(String delimiter, Object... args) {
    return join(Arrays.asList(args), delimiter);
  }

  /**
   * 将容器里的元素连接成字符串。
   *
   * @param list 包含任意多个元素的容器。
   * @param delimiter 分隔符。
   * @return 连接后的字符串。
   */
  public static String join(Collection<?> list, String delimiter) {
    if (list == null || list.isEmpty()) {
      return "";
    }
    if (delimiter == null) {
      delimiter = "";
    }

    StringBuilder s = new StringBuilder();
    boolean first = true;
    for (Object e : list) {
      if (first) {
        first = false;
      } else {
        s.append(delimiter);
      }
      s.append(e);
    }
    return s.toString();
  }

  /**
   * 合并两个数组。
   *
   * @param <E> 数组元素的类型。
   * @param a 前半部分数组。
   * @param b 后半部分数组。
   * @return 合并后的数组。
   */
  public static <E> E[] merge(E[] a, E[] b) {
    List<E> list = merge(Arrays.asList(a), Arrays.asList(b));
    return list.toArray(a);
  }

  /**
   * 合并两个List。
   *
   * @param <E> List元素的类型。
   * @param a 前半部分List。
   * @param b 后半部分List。
   * @return 合并后的List。
   */
  public static <E> List<E> merge(List<E> a, List<E> b) {
    List<E> list = new ArrayList<>();
    list.addAll(a);
    list.addAll(b);
    return list;
  }

  /**
   * 从数组中删除所有与给定对象equals的元素。
   *
   * @param <E> 元素类型。
   * @param a 给定数组。
   * @param e 给定对象
   * @return 去除e之后的数组。
   */
  public static <E> E[] remove(E[] a, E e) {
    List<E> list = remove(Arrays.asList(a), e);
    return (E[])list.toArray();
  }

  /**
   * 从List中删除所有与给定对象equals的元素。
   *
   * @param <E> 元素类型。
   * @param a 给定List。
   * @param e 给定对象
   * @return 去除e之后的List。
   */
  public static <E> List<E> remove(List<E> a, E e) {
    List<E> list = new ArrayList<>();
    for (E o : a) {
      if (!o.equals(e)) {
        list.add(o);
      }
    }
    return list;
  }

  /**
   * 根据给定的下标，选出多个元素，并组成新的数组。
   *
   * @param <E> 元素类型。
   * @param a 给定数组。
   * @param indexes 任意多个要获取的元素下标。
   * @return 根据下标获得的元素组成的新数组。
   */
  public static <E> E[] valuesAt(E[] a, int... indexes) {
    List<E> list = valuesAt(Arrays.asList(a), indexes);
    return (E[])list.toArray();
  }

  /**
   * 根据给定的下标，选出多个元素，并组成新的List。
   *
   * @param <E> 元素类型。
   * @param from 给定List。
   * @param indexes 任意多个要获取的元素下标。
   * @return 根据下标获得的元素组成的新List。
   */
  public static <E> List<E> valuesAt(List<E> from, int... indexes) {
    List<E> list = new ArrayList<>();
    for (int i : indexes) {
      if (0 <= i && i < from.size()) {
        list.add(from.get(i));
      } else if (-from.size() <= i && i < 0) {
        list.add(from.get(from.size() + i));
      } else {
        list.add(null);
      }
    }
    return list;
  }

  /**
   * 同时给int数组中多个位置同时赋值。
   *
   * @param a 给定int数组。
   * @param indexes 待赋值的位置下标。
   * @param values 与下标一一对应的int值。
   * @return 返回赋值后的新int数组。
   */
  public static int[] assignAt(int[] a, Integer[] indexes, int... values) {
    if (indexes.length != values.length) {
      throw new IllegalArgumentException(String.format("index.length(%d) != values.length(%d)", indexes.length, values.length));
    }
    for (int i = 0; i < indexes.length; i++) {
      int index = indexes[i];
      if (0 <= index && index < a.length) {
        a[index] = values[i];
      } else if (-a.length <= index && index < 0) {
        a[a.length + index] = values[i];
      } else {
        throw new ArrayIndexOutOfBoundsException(index);
      }
    }
    return a;
  }

  /**
   * 同时给数组中多个位置同时赋值。
   *
   * @param <E> 数组元素类型。
   * @param a 给定数组。
   * @param indexes 待赋值的位置下标。
   * @param values 与下标一一对应的值。
   * @return 返回赋值后的新数组。
   */
  public static <E> E[] assignAt(E[] a, Integer[] indexes, E... values) {
    if (indexes.length != values.length) {
      throw new IllegalArgumentException(String.format("index.length(%d) != values.length(%d)", indexes.length, values.length));
    }
    for (int i = 0; i < indexes.length; i++) {
      int index = indexes[i];
      if (0 <= index && index < a.length) {
        a[index] = values[i];
      } else if (-a.length <= index && index < 0) {
        a[a.length + index] = values[i];
      } else {
        throw new ArrayIndexOutOfBoundsException(index);
      }
    }
    return a;
  }

  /**
   * 根据规定格式，对容器中的每个元素进行格式化，并返回格式化后的结果。
   *
   * @param from 包含任意多个元素的容器。
   * @param format 格式化模板，与printf兼容。
   * @return 格式化后的新列表。
   */
  public static List<String> map(Collection<?> from, String format) {
    List<String> to = new ArrayList<>(from.size());
    for (Object e : from) {
      to.add(String.format(format, e));
    }
    return to;
  }

  /**
   * 拆分容器，每份至多包含n个元素，将每堆元素连接成一个独立字符串。
   * @param from 包含任意多个元素的容器。
   * @param n 子元素个数。
   * @param delimiter 分隔符。
   * @return 拆分后的字符串List。
   */
  public static List<String> partition(Collection<String> from, int n, String delimiter) {
    List<String> to = new ArrayList<>();
    List<String> buffer = new ArrayList<>(n);
    for (String e : from) {
      buffer.add(e);
      if (buffer.size() >= n) {
        to.add(join(buffer, delimiter));
        buffer.clear();
      }
    }
    return to;
  }
}


/*
 * Copyright 2017 dmfs GmbH
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.dmfs.iterators.utils;

import org.dmfs.iterators.ArrayIterator;
import org.dmfs.iterators.EmptyIterator;
import org.dmfs.iterators.SingletonIterator;
import org.dmfs.iterators.decorators.Filtered;
import org.dmfs.iterators.decorators.Serialized;
import org.dmfs.iterators.filters.NonNull;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * An open addressing {@link Set} implementation. While insert performance is mostly comparable to the default {@link
 * HashSet} implementation, this set aims to be much more memory efficient.
 *

 * This class is not thread safe.
 *
 * @param <E>
 *         The type of the elements in the set.
 *
 * @author Marten Gajda
 */
public final class SlimSet<E> implements Set<E>, Cloneable
{
    /**
     * Indicates that this set contains a null value. Since null values are not hashable, we handle them separately.
     */
    private boolean mContainsNull;

    /**
     * The number of elements in this set.
     */
    private int mSize;

    /**
     * The backing array that contains all the values.
     */
    private Object[] mArray;

    /**
     * The calculated hash value. The value is updated on-the-fly whenever an element is added or removed.
     */
    private int mHashCode;

    /**
     * The configured load factor. The backing array is resized whenever mSize/mArray.length > mLoadFactor
     */
    private final float mLoadFactor;

    /**
     * Default constructor.
     */
    public SlimSet()
    {
        this(16);
    }

    /**
     * Constructor to create a set that can take at least the given number of elements before it needs to be rehashed.
     *
     * @param size
     *         The number of elements that the set can hold before it needs to be resized and rehashed.
     */
    public SlimSet(final int size)
    {
        this(size, 0.75f);
    }

    /**
     * Constructor to create a set that can take at least the given number of elements before it needs to be rehashed.
     * This constructor also allows to specify the load factor of the set.
     *
     * @param size
     *         The number of elements that the set can hold before it needs to be resized and rehashed.
     * @param foadFactor
     *         The load factor if this set.
     */
    public SlimSet(final int size, final float foadFactor)
    {
        this(new Object[(int) Math.max(size / foadFactor, 16)], 0, false, 0, foadFactor);
    }

    /**
     * Creates a set from the elements of another {@link Collection}.
     *
     * @param other
     *         The other collection that contains the initial values of this set.
     */
    public SlimSet(final Collection<E> other)
    {
        this((other.size() * 3) / 2 /* leave some more room to allow adding elements without having to rehash immediately */);
        addAll(other);
    }

    /**
     * Creates a set from the values in an array.
     *
     * @param values
     *         An array holding the initial values of the set.
     */
    public SlimSet(final E[] values)
    {
        this((values.length * 3) / 2/* leave some more room to allow adding elements without having to rehash immediately */);
        for (E value : values)
        {
            add(value);
        }
    }

    private SlimSet(final Object[] objects, final int size, final boolean containsNull, final int hash, final float lf)
    {
        if (lf <= 0 || lf >= 1)
        {
            throw new IllegalArgumentException(String.format("The load factor %f is not a number between 0 and 1", lf));
        }

        mArray = objects;
        mSize = size;
        mContainsNull = containsNull;
        mHashCode = hash;
        mLoadFactor = lf;
    }

    @Override
    public boolean contains(final Object object)
    {
        if (object == null)
        {
            return mContainsNull;
        }

        // the element is contained if the element at the predicted position is not null
        return mArray[predictedPosition(mArray, object, object.hashCode())] != null;
    }

    @Override
    public boolean add(final E object)
    {
        if (object == null)
        {
            if (mContainsNull)
            {
                // null is already in the set
                return false;
            }

            // "add" null value
            mSize++;
            mContainsNull = true;
            return true;
        }

        if (mArray.length * mLoadFactor <= mSize)
        {
            // need a resize
            mArray = rehash(mArray, mArray.length * 2 /* always double the array */);
        }

        int hash = object.hashCode();
        int i = predictedPosition(mArray, object, hash);

        if (mArray[i] != null)
        {
            // the predicted position is already taken, which means the element is already in the set
            return false;
        }

        // update the set
        mArray[i] = object;
        mSize++;
        mHashCode ^= hash;
        return true;
    }

    @Override
    public boolean isEmpty()
    {
        return mSize == 0;
    }

    @Override
    public int size()
    {
        return mSize;
    }

    /**
     * Rebuild the backing array with a different size.
     *
     * @param values
     *         The values to put into the new array.
     * @param newSize
     *         The size of the new array.
     *
     * @return The new backing array.
     */
    private static Object[] rehash(final Object[] values, final int newSize)
    {
        Object[] newArray = new Object[newSize];

        for (Object value : values)
        {
            if (value == null)
            {
                continue;
            }

            newArray[predictedPosition(newArray, value, value.hashCode())] = value;
        }

        return newArray;
    }

    /**
     * Calculates the position of the given element in the given array. The value at the calculated position will be
     * null if the element doesn't exist in the array, otherwise the position will point to the element.
     *
     * @param array
     * @param object
     *
     * @return
     */
    private static int predictedPosition(final Object[] array, final Object object, final int hash)
    {
        int arraySize = array.length;
        int i = Math.abs(hash) % arraySize;

        Object element = array[i];
        while (element != null) // the load factor guarantees that there is always at least one free slot
        {
            if (element.equals(object))
            {
                // already in the array
                return i;
            }
            // the position is taken, try the next slot
            i = (i + 1) % arraySize;
            element = array[i];
        }
        return i;
    }

    @Override
    public boolean addAll(final Collection<? extends E> c)
    {
        if (c instanceof Set && c.size() * 4 > size() * 3)
        {
            // prepare to take at least c.size() additional unique elements
            mArray = rehash(mArray, (int) (c.size() / mLoadFactor) + size());
        }

        boolean result = false;
        for (E element : c)
        {
            result |= add(element);
        }
        return result;
    }

    @Override
    public void clear()
    {
        mArray = new Object[mArray.length];
        mSize = 0;
        mContainsNull = false;
        mHashCode = 0;
    }

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

    @SuppressWarnings("unchecked")
    @Override
    public Iterator<E> iterator()
    {
        if (mSize == 0)
        {
            return EmptyIterator.instance();
        }

        if (mSize == 1 && mContainsNull)
        {
            return new SingletonIterator<E>(null);
        }

        Iterator<E> result = new Filtered<E>(new ArrayIterator<E>((E[]) mArray.clone()),
                NonNull.<E>instance());
        if (mContainsNull)
        {
            return new Serialized<>(result, new SingletonIterator<E>(null));
        }
        return result;
    }

    @Override
    public boolean remove(Object object)
    {
        if (object == null)
        {
            if (!mContainsNull)
            {
                return false;
            }

            mSize--;
            mContainsNull = true;
            return true;
        }

        int hash = object.hashCode();

        int i = predictedPosition(mArray, object, hash);

        if (mArray[i] == null)
        {
            return false;
        }

        mHashCode ^= hash;
        mArray[i] = null;
        mSize--;
        return true;
    }

    @Override
    public boolean removeAll(Collection<?> c)
    {
        boolean result = false;
        for (Object element : c)
        {
            result |= remove(element);
        }
        return result;
    }

    @Override
    public boolean retainAll(Collection<?> c)
    {
        int originalCount = mSize;
        if (mContainsNull && !c.contains(null))
        {
            mContainsNull = false;
            --mSize;
        }

        for (int i = 0, count = mArray.length; i < count; ++i)
        {
            Object element = mArray[i];
            if (element == null)
            {
                continue;
            }
            if (!c.contains(element))
            {
                // element is not in c, remove it from the set
                mHashCode ^= element.hashCode();
                mArray[i] = null;
                --mSize;
            }
        }
        return mSize != originalCount;
    }

    @Override
    public Object[] toArray()
    {
        return mArray.clone();
    }

    @Override
    public <T> T[] toArray(T[] a)
    {
        System.arraycopy(mArray, 0, a, 0, Math.min(a.length, mArray.length));
        return a;
    }

    @Override
    protected SlimSet<E> clone()
    {
        return new SlimSet<E>(mArray.clone(), mSize, mContainsNull, mHashCode, mLoadFactor);
    }

    @Override
    public int hashCode()
    {
        return mContainsNull ? mHashCode ^ 7654321 : mHashCode;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (obj == this)
        {
            return true;
        }

        if (!(obj instanceof Set))
        {
            return false;
        }

        if (obj instanceof SlimSet && obj.hashCode() != hashCode())
        {
            // we can rule out SlimSets with different hash codes
            return false;
        }

        // otherwise we assume that two sets are equal if they contain the same entries.

        Set<?> other = (Set<?>) obj;

        return other.size() == mSize && other.containsAll(this);
    }
}


/*
 * Copyright 2017 dmfs GmbH
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.dmfs.iterators;

import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * An {@link Iterator} that iterates the elements of a CharSequence of a comma (or other character) separated value list
 * .
 *

 * Example:
 *

 *

 *
 * 	Iterator<CharSequence> i = new Split("a, b,def,123", ',');
 * 	i.next(); // returns "a"
 * 	i.next(); // returns " b"
 * 	i.next(); // returns "def"
 * 	i.next(); // returns "123"
 * 	i.hasNext(); // false
 *
 *

 *

 * Iterating an empty CharSequence or a CharSequence without (unquoted) separators will return exactly one element.
 *

 * Example:
 *

 *

 *
 * 	Iterator<CharSequence> i = new Split("", ',');
 * 	i.next(); // returns ""
 * 	i.hasNext(); // false
 *
 *

 *
 * @author Marten Gajda
 */
public final class Split extends AbstractBaseIterator<CharSequence>
{
    private final CharSequence mValue;
    private final char mSeparator;

    private int mLastSeparatorPos = -1;
    private int mNextSeparatorPos = -1;

    /**
     * Creates an {@link Iterator} that iterates all segments of the given CharSequence which are separated by the given
     * separator.
     *
     * @param value
     *         The CharSequence that contains a list of values.
     * @param separator
     *         The separator that separates the values.
     */
    public Split(CharSequence value, char separator)
    {
        mValue = value;
        mSeparator = separator;
    }

    @Override
    public boolean hasNext()
    {
        if (mNextSeparatorPos == -1)
        {
            findNextSeparator();
        }
        return mLastSeparatorPos < mValue.length();
    }

    @Override
    public CharSequence next()
    {
        if (mLastSeparatorPos >= mValue.length())
        {
            throw new NoSuchElementException("Last element has already been iterated.");
        }
        if (mNextSeparatorPos == -1)
        {
            findNextSeparator();
        }
        CharSequence result = mValue.subSequence(mLastSeparatorPos + 1, mNextSeparatorPos);
        findNextSeparator();
        return result;
    }

    /**
     * Move {@link #mNextSeparatorPos} to the next (unquoted) separator (or the end of the {@link CharSequence} if no
     * other separator exists in {@link #mValue}).
     */
    private void findNextSeparator()
    {
        mLastSeparatorPos = mNextSeparatorPos;
        while (++mNextSeparatorPos < mValue.length())
        {
            char c = mValue.charAt(mNextSeparatorPos);
            if (c == mSeparator)
            {
                return;
            }
        }
    }
}


/*
 * Copyright 2017 dmfs GmbH
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.dmfs.iterators;

import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * An {@link Iterator} that iterates the elements of a string of a comma (or other character) separated value list .
 *

 * Example:
 *

 *

 *
 * 	Iterator<String> i = new CsvIterator<String>("a, b,def,123", ',');
 * 	i.next(); // returns "a"
 * 	i.next(); // returns " b"
 * 	i.next(); // returns "def"
 * 	i.next(); // returns "123"
 * 	i.hasNext(); // false
 *
 *

 *

 * Separators between quote characters (") will be ignored.
 *

 * Example:
 *

 *

 *
 * 	Iterator<String> i = new CsvIterator<String>("\"a, b\",\"def,123\"", ',');
 * 	i.next(); // returns "a , b"
 * 	i.next(); // returns "def,123"
 * 	i.hasNext(); // false
 *
 *

 *

 * Iterating an empty String or a string without (unquoted) separators will return exactly one element.
 *

 * Example:
 *

 *

 *
 * 	Iterator<String> i = new CsvIterator<String>("", ',');
 * 	i.next(); // returns ""
 * 	i.hasNext(); // false
 *
 * 	Iterator<String> i2 = new CsvIterator<String>("\"abc,def\"", ',');
 * 	i2.next(); // returns "abc,def"
 * 	i2.hasNext(); // false
 *
 *

 *
 * @author Marten Gajda
 * @deprecated in favor of {@link UnquotedSplit}, to be removed in version 2.0
 */
@Deprecated
public final class CsvIterator implements Iterator<String>
{
    private final String mValue;
    private final char mSeparator;

    private int mLastSeparatorPos = -1;
    private int mNextSeparatorPos = -1;

    /**
     * Creates an {@link Iterator} that iterates all elements of the given string which are separated by the given
     * separator.
     *
     * @param value
     *         The string that contains a list of values.
     * @param separator
     *         The separator that separates the values.
     */
    public CsvIterator(String value, char separator)
    {
        mValue = value;
        mSeparator = separator;
    }

    @Override
    public boolean hasNext()
    {
        if (mNextSeparatorPos == -1)
        {
            findNextSeparator();
        }
        return mLastSeparatorPos < mValue.length();
    }

    @Override
    public String next()
    {
        if (mLastSeparatorPos >= mValue.length())
        {
            throw new NoSuchElementException("Last element has already been iterated.");
        }
        if (mNextSeparatorPos == -1)
        {
            findNextSeparator();
        }
        String result = mValue.substring(mLastSeparatorPos + 1, mNextSeparatorPos);
        findNextSeparator();
        return result;
    }

    @Override
    public void remove()
    {
        throw new UnsupportedOperationException("Remove not supported by this iterator");
    }

    /**
     * Move {@link #mNextSeparatorPos} to the next (unquoted) separator (or the end of the {@link String} if no other
     * separator exists in {@link #mValue}).
     */
    private void findNextSeparator()
    {
        boolean isQuoted = false;
        mLastSeparatorPos = mNextSeparatorPos;
        while (++mNextSeparatorPos < mValue.length())
        {
            char c = mValue.charAt(mNextSeparatorPos);
            if (c == mSeparator)
            {
                if (!isQuoted)
                {
                    return;
                }
                // else: ignore quoted speparator
            }
            else if (c == '"')
            {
                isQuoted = !isQuoted;
            }
        }
    }

}


/**
 * *****************************************************************************
 * Copyright C 2015, The Pistoia Alliance
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************
 */
package org.helm.notation2.parser.notation.polymer;

import java.io.IOException;
import java.util.List;

import org.helm.notation2.parser.exceptionparser.HELM1ConverterException;
import org.helm.notation2.parser.exceptionparser.NotationException;
import org.helm.notation2.parser.exceptionparser.SimplePolymerSectionException;
import org.jdom2.JDOMException;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

/**
 * PolymerElements
 *
 * @author hecht
 */
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "classType")
public abstract class PolymerElements {

	@JsonIgnore
	public List<MonomerNotation> listMonomerNotations = null;

	protected HELMEntity entity;

	public PolymerElements() {

	}

	public PolymerElements(HELMEntity entity) {
		this.entity = entity;
	}

	public void setListOfElements(List<MonomerNotation> monomers) {
		this.listMonomerNotations = monomers;
	}

	/**
	 * method to get a list of all elements
	 *
	 * @return List{@link MonomerNotation} list of all monomer notations
	 */
	public abstract List<MonomerNotation> getListOfElements();

	public HELMEntity getEntity() {
		return this.entity;
	}

	/**
	 * method to add a monomer to the polymer
	 *
	 * @param str
	 *            Monomer
	 * @throws SimplePolymerSectionException Simple Polymer Section is not valid
	 * @throws NotationException if HELM notation is not valid
	 */
	public abstract void addMonomerNotation(String str)
			throws SimplePolymerSectionException, NotationException;

	/**
	 * method to get the current monomer
	 *
	 * @return current Monomer
	 */
	@JsonIgnore
	public abstract MonomerNotation getCurrentMonomerNotation();

	/**
	 * method to change the current Monomer
	 *
	 * @param not
	 *            MonomerNotation
	 */
	public abstract void changeMonomerNotation(MonomerNotation not);

	/**
	 * method to generate a valid HELM2 of this object
	 *
	 * @return valid HELM2
	 */
	public String toHELM2() {
		StringBuilder notation = new StringBuilder();
		for (int i = 0; i < listMonomerNotations.size(); i++) {
			notation.append(listMonomerNotations.get(i).toHELM2() + ".");

		}

		notation.setLength(notation.length() - 1);
		return notation.toString();
	}

	/**
	 * @return PolymerElments in HELM format
	 * @throws HELM1ConverterException if one single object can not be downgraded to HELM1-Format
	 *
	 */
	public String toHELM() throws HELM1ConverterException {
		StringBuilder notation = new StringBuilder();
		for (int i = 0; i < listMonomerNotations.size(); i++) {
			notation.append(listMonomerNotations.get(i).toHELM() + ".");

		}

		notation.setLength(notation.length() - 1);
		return notation.toString();
	}

}


/**
 * *****************************************************************************
 * Copyright C 2015, The Pistoia Alliance
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************
 */
package org.helm.notation2.parser.connectionsection;

import org.helm.notation2.parser.State;
import org.helm.notation2.parser.StateMachineParser;
import org.helm.notation2.parser.exceptionparser.ConnectionSectionException;
import org.helm.notation2.parser.exceptionparser.NotationException;
import org.helm.notation2.parser.groupingsection.GroupingParser;
import org.helm.notation2.parser.notation.connection.ConnectionNotation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ConnectionsParser class to parse the ID of the first partner of the
 * connection
 *
 * @author hecht
 */
public class ConnectionsParser implements State {
  private StateMachineParser _parser;

  private String sourcepolymerid = "";

  private int parenthesisCounterOpen = 0;

  private int parenthesisCounterClose = 0;

  private static final Logger LOG = LoggerFactory.getLogger(ConnectionsParser.class);

  /**
   * Constructs with the state machine parser
   *
   * @param pParser current parser
   */
  public ConnectionsParser(StateMachineParser pParser) {
    _parser = pParser;
  }

  /**
   * {@inheritDoc}
   *
   * @throws NotationException if connection is not valid
   */
  @Override
  public void doAction(char cha) throws ConnectionSectionException, NotationException {

    /* list of connections is empty */
    if (cha == '$') {
      if (sourcepolymerid == "" && _parser.notationContainer.getListOfConnections().size() == 0) {
        LOG.info("Connection section is empty:");
        LOG.info("Transition to group section:");
        _parser.setState(new GroupingParser(_parser));

      } else {
        LOG.error("Missing target polymer ID in connection section:");
        throw new ConnectionSectionException("Missing target polymer id in connection section. Source of connection is " + sourcepolymerid);
      }
    } /* target polymer ID is starting */ else if (cha == ',' && checkBracketsParenthesis()) {
      if (_parser.checkPolymeridConnection(sourcepolymerid)) {
        LOG.info("Target polymer ID is read:");
        _parser.notationContainer.addConnection(new ConnectionNotation(sourcepolymerid));
        _parser.setState(new ConnectionsReadSecondIDParser(_parser));
      } else {
        LOG.error("Source polymer ID is not correct in the connection section: " + sourcepolymerid);
        throw new ConnectionSectionException("Source polymer id is not correct in the connection section: "
            + sourcepolymerid);
      }

    } else {
      sourcepolymerid += cha;
      if (cha == '(') {
        parenthesisCounterOpen += 1;
      }
      if (cha == ')') {
        parenthesisCounterClose += 1;
      }

    }
  }

  /**
   * method to check if all open brackets are closed
   *
   * @return boolean
   */
  private boolean checkBracketsParenthesis() {
    if (parenthesisCounterOpen == parenthesisCounterClose) {
      return true;
    }
    return false;
  }
}


/**
 * *****************************************************************************
 * Copyright C 2015, The Pistoia Alliance
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************
 */
package org.helm.notation2.parser.notation.grouping;

import java.text.DecimalFormat;

import org.helm.notation2.parser.exceptionparser.NotationException;

/**
 * GroupingOr
 *
 * @author hecht
 */
public class GroupingOr extends GroupingAmbiguity {

  private GroupingElement element;

  /**
   * Constructs with the given String representing a list of grouping elements. A possibility is attached to each
   * element
   *
   * @param str list of grouping elements
   * @throws NotationException if notation is not valid
   */
  public GroupingOr(String str) throws NotationException {
    /* Or elements are separated by , */
    String[] parts = str.split(",");
    boolean interval = false;
    for (int i = 0; i < parts.length; ++i) {
      /* Possibility can be defined -> default is 100/number of elements */
      String[] item = parts[i].split(":");
      double probability = 1;
      double probability2 = 1;
      interval = false;
      boolean isDefault = true;
      if (item.length > 1) {
        /* an interval can be there or not */
        if (item[1].split("-").length > 1) {
          interval = true;
          isDefault = false;
          probability = Double.parseDouble(item[1].split("-")[0]);
          probability2 = Double.parseDouble(item[1].split("-")[1]);
        } else {
          if ((Math.abs(Double.parseDouble(item[1]) - 1.0) > 0.00000001)) {
            probability = Double.parseDouble(item[1]);
            isDefault = false;
          }

          /* unknown ratio */
          if (item[1].contains("?")) {
            probability = -1;
          }

        }
      }

      if (!(interval)) {
        element = new GroupingElement(item[0], probability, isDefault);
      } else {
        element = new GroupingElement(item[0], probability, probability2);
      }

      addElement(element);

    }

  }

  /**
   * {@inheritDoc}
   */
  public String toHELM2() {
    DecimalFormat d = new DecimalFormat("#.##");
    StringBuilder notation = new StringBuilder();
    for (int i = 0; i < elements.size(); i++) {
      if (!(elements.get(i).isDefaultValue())) {
        String value = d.format(elements.get(i).getValue().get(0)).replace(',', '.');
        if (elements.get(i).getValue().size() > 1) {
          value += "-" + d.format(elements.get(i).getValue().get(1)).replace(',', '.');
        }
        notation.append(elements.get(i).getID() + ":" + value + ",");
      } else {
        notation.append(elements.get(i).getID() + ",");
      }
    }
    notation.setLength(notation.length() - 1);
    return notation.toString();
  }

}


/**
 * *****************************************************************************
 * Copyright C 2015, The Pistoia Alliance
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************
 */
package org.helm.notation2.parser.annotationsection;

import org.helm.notation2.parser.FinalState;
import org.helm.notation2.parser.State;
import org.helm.notation2.parser.StateMachineParser;
import org.helm.notation2.parser.exceptionparser.AnnotationSectionException;
import org.helm.notation2.parser.notation.annotation.AnnotationNotation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * AnnotationsParser class to read all characters in the last section,
 * annotation section
 *
 * @author hecht
 */
public class AnnotationsParser implements State {

  private StateMachineParser _parser;

  private static final Logger LOG = LoggerFactory.getLogger(AnnotationsParser.class);

  private String annotation = "";

  private int bracketCounterOpen = 0;

  private int bracketCounterClose = 0;

  private int parenthesisCounterOpen = 0;

  private int parenthesisCounterClose = 0;

  private int curlyBracketCounterOpen = 0;

  private int curlyBracketCounterClose = 0;

  /**
   * Constructor with the state machine parser
   *
   * @param pParser current StateMachineParser
   */
  public AnnotationsParser(StateMachineParser pParser) {
    _parser = pParser;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void doAction(char cha) throws AnnotationSectionException {
    /* list of attributes is finished */
    if (cha == '$') {
      if (checkBracketsParenthesis()) {
        LOG.info("Annotation section is finished:");
        if (annotation != "") {
          _parser.notationContainer.addAnnotation(new AnnotationNotation(annotation));
        }
        LOG.info("Transition to FinalState:");
        _parser.setState(new FinalState());
      } else {
        LOG.info("Annotation section is not valid: " + annotation);
        throw new AnnotationSectionException("Annotation section is not valid: " + annotation);
      }
    } else if (cha == '|') {
      if (checkBracketsParenthesis()) {
        LOG.info(" new annotation is starting");
        if (annotation != "") {
          _parser.notationContainer.addAnnotation(new AnnotationNotation(annotation));
          _parser.setState(new AnnotationsParser(_parser));
        } else {
          LOG.info("Annotation section is not valid: ");
          throw new AnnotationSectionException("Annotation section is not valid: ");
        }
      }
    } else {
      annotation += cha;
      if (cha == '{') {
        curlyBracketCounterOpen++;
      }
      if (cha == '}') {
        curlyBracketCounterClose++;
      }
      if (cha == '[') {
        bracketCounterOpen++;
      }
      if (cha == ']') {
        bracketCounterClose++;
      }
      if (cha == '(') {
        parenthesisCounterOpen++;
      }
      if (cha == ')') {
        parenthesisCounterClose++;
      }
    }

  }

  /**
   * method to check if all open brackets are closed
   *
   * @return true if all open brackets are close, false otherwise
   */
  private boolean checkBracketsParenthesis() {
    LOG.debug("Check of brackets in the annotation section:");
    if (bracketCounterOpen == bracketCounterClose
        && parenthesisCounterOpen == parenthesisCounterClose
        && curlyBracketCounterOpen == curlyBracketCounterClose) {
      return true;

    }
    return false;
  }
}


/**
 * *****************************************************************************
 * Copyright C 2015, The Pistoia Alliance
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************
 */
package org.helm.notation2.parser.notation.connection;

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.helm.notation2.parser.exceptionparser.HELM1ConverterException;
import org.helm.notation2.parser.exceptionparser.NotationException;
import org.helm.notation2.parser.notation.ValidationMethod;
import org.helm.notation2.parser.notation.polymer.HELMEntity;
import org.jdom2.JDOMException;

import com.fasterxml.jackson.annotation.JsonIgnore;

/**
 * ConnectionNotation class to save each connection
 *
 * @author hecht
 */
public final class ConnectionNotation {
  private HELMEntity sourceId;

  private HELMEntity targetId;

  private String sourceUnit;

  private String targetUnit;

  private String rGroupSource;

  private String rGroupTarget;

  private String annotation;

  private boolean isAnnotationHere = false;

  public ConnectionNotation() {

  }

  /**
   * Constructs with the given Source ID a Connection Notation
   *
   * @param str source ID of the connection
   * @throws NotationException if notation is not valid
   */
  public ConnectionNotation(String str) throws NotationException {
    this.sourceId = ValidationMethod.decideWhichEntity(str);
  }

  /**
   * Constructs with the given Source Entity and the given target ID
   *
   * @param firstID Entity of the Source ID
   * @param secondID Target ID
   * @throws NotationException if notation is not valid
   */
  public ConnectionNotation(HELMEntity firstID, String secondID) throws NotationException {
    this.sourceId = firstID;
    this.targetId = ValidationMethod.decideWhichEntity(secondID);
  }

  /**
   * Constructs with the two given Entities, Source ID and Target ID, and the
   * given details about the connection
   *
   * @param firstID Entity of the Source ID
   * @param secondID Entity of the Target ID
   * @param details connection details
   * @throws NotationException if notation is not valid
   */
  public ConnectionNotation(HELMEntity firstID, HELMEntity secondID, String details) throws NotationException {
    this.sourceId = firstID;
    this.targetId = secondID;
    addDetails(details);
  }

  /**
   * Constructs a Connection Notation with an annotation
   *
   * @param firstID Entity of the Source ID
   * @param secondID Entity of the Target ID
   * @param sourceUnit MonomerNotation of the Source
   * @param targetUnit MonomerNotation of the Target
   * @param rGroupSource RGroup of the Source
   * @param rGroupTarget RGroup of the Target
   * @param annotation new annotation
   */
  public ConnectionNotation(HELMEntity firstID, HELMEntity secondID, String sourceUnit, String targetUnit,
      String rGroupSource, String rGroupTarget, String annotation) {
    this.sourceId = firstID;
    this.targetId = secondID;
    this.sourceUnit = sourceUnit;
    this.targetUnit = targetUnit;
    this.rGroupSource = rGroupSource;
    this.rGroupTarget = rGroupTarget;
    addAnnotations(annotation);
  }

  /**
   * method to set the details of the current connection
   *
   * @param method to add connection detail
   * @throws NotationException
   */
  private void addDetails(String str) throws NotationException{
    String[] parts = str.split("-");

    /* MonomerUnit */
    sourceUnit = parts[0].split(":")[0].toUpperCase();
    targetUnit = parts[1].split(":")[0].toUpperCase();

    /* R-group */

    rGroupSource = parts[0].split(":")[1];
    rGroupTarget = parts[1].split(":")[1];

    Pattern r = Pattern.compile("R\\d", Pattern.CASE_INSENSITIVE);
    Pattern pair = Pattern.compile("pair", Pattern.CASE_INSENSITIVE);

    Matcher mR = r.matcher(rGroupSource);
    Matcher mPair = pair.matcher(rGroupTarget);
    if(mR.matches()){
    	rGroupSource = rGroupSource.toUpperCase();
    }else{
    	rGroupSource = rGroupSource.toLowerCase();
    }

    if(mPair.matches()){
    	rGroupTarget = rGroupTarget.toLowerCase();
    }else{
    	rGroupTarget = rGroupTarget.toUpperCase();
    }
  }

  /**
   * method to set the inline annotations
   *
   * @param str new annotation
   */
  private void addAnnotations(String str) {
    annotation = str;
    isAnnotationHere = true;
  }

  /**
   * method to check if an annotation is there or not
   *
   * @return true if an annotation is there, false otherwise
   */
  @JsonIgnore
  public boolean isAnnotationTrue() {
    return isAnnotationHere;
  }

  /**
   * method to get the annotation of the current connection
   *
   * @return Annotation
   */
  public String getAnnotation() {
    return annotation;
  }

  /**
   * method to get the source ID of the current connection
   *
   * @return Source ID
   */
  public HELMEntity getSourceId() {
    return sourceId;
  }

  /**
   * method to get the target ID of the current connection
   *
   * @return the target HELMEntity of the current connection
   */
  public HELMEntity getTargetId() {
    return targetId;
  }

  /**
   * method to get the Source Unit of the current connection
   *
   * @return source unit
   */
  public String getSourceUnit() {
    return sourceUnit;
  }

  /**
   * method to get the Target Unit of the current connection
   *
   * @return target unit
   */
  public String getTargetUnit() {
    return targetUnit;
  }

  /**
   * method to get the r Group of the source in the current connection
   *
   * @return rGroupSource
   */
  public String getrGroupSource() {
    return rGroupSource;
  }

  /**
   * method to get the r Group of the target in the current connection
   *
   * @return rGroupTarget
   */
  public String getrGroupTarget() {
    return rGroupTarget;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
    if (isAnnotationTrue()) {
      return "SourceID: " + sourceId + "\nTargetID: " + targetId + "\nDetails: " + "\nSourceUnit: " + sourceUnit
          + " RGroup: "
          + rGroupSource + "\nTargetUnit: " + targetUnit + " RGroup: " + rGroupTarget + "\nAnnotation: "
          + annotation;
    } else {
      return "SourceID: " + sourceId + "\nTargetID: " + targetId + "\nDetails: " + "\nSourceUnit: "
          + sourceUnit.toString()
          + " RGroup: "
          + rGroupSource + "\nTargetUnit: " + targetUnit.toString() + " RGroup: " + rGroupTarget;
    }

  }

  /**
   * method to get a valid HELM2 of the connection notation
   *
   * @return valid HELM2
   */
  public String toHELM2() {
    if (isAnnotationTrue()) {
      return sourceId.getId() + "," + targetId.getId() + "," + sourceUnit + ":"
          + rGroupSource + "-" + targetUnit + ":" + rGroupTarget + "\"" + annotation + "\"";
    } else {
      return sourceId.getId() + "," + targetId.getId() + "," + sourceUnit + ":"
          + rGroupSource + "-" + targetUnit + ":" + rGroupTarget;
    }
  }

  public String toHELM() throws HELM1ConverterException {
    String connectiondetails = sourceUnit + ":"
        + rGroupSource + "-" + targetUnit + ":" + rGroupTarget;
    String text = sourceId.getId() + "," + targetId.getId() + "," + connectiondetails;
    if (!(connectiondetails.matches("\\d+:R\\d+-\\d+:R\\d+") || connectiondetails.matches("\\d+:pair-\\d+:pair"))) {
      throw new HELM1ConverterException("Can't be downgraded to the HELM1-format");
    }
    if (connectiondetails.matches("\\d+:pair-\\d+:pair")) {
      text = "";
    }
    return text;
  }

  public String toReverseHELM() throws HELM1ConverterException {
    String connectiondetails = targetUnit + ":"
        + rGroupTarget + "-" + sourceUnit + ":" + rGroupSource;
    String text = targetId.getId() + "," + sourceId.getId() + "," + connectiondetails;
    if (!(connectiondetails.matches("\\d+:R\\d-\\d+:R\\d") || connectiondetails.matches("\\d+:pair-\\d+:pair"))) {
      throw new HELM1ConverterException("Can't be downgraded to the HELM1-format");
    }

    if (connectiondetails.matches("\\d+:pair-\\d+:pair")) {
      text = "";
    }

    return text;

  }

  public void setAnnotation(String notation) {
    annotation = notation;
    if (notation != null) {
      isAnnotationHere = true;
    }
  }

}