package org.boon.slumberdb.stores;

import org.boon.Lists;
import org.boon.Logger;
import org.boon.Pair;
import org.boon.core.Sys;
import org.boon.slumberdb.service.config.DataStoreConfig;
import org.boon.slumberdb.service.config.DataStoreServerConfig;
import org.boon.slumberdb.service.config.ReplicationDataStoreConfig;
import org.boon.slumberdb.service.protocol.Action;
import org.boon.slumberdb.service.protocol.requests.*;
import org.boon.slumberdb.service.results.ErrorResult;
import org.boon.slumberdb.service.results.StatCount;
import org.boon.slumberdb.service.server.ServiceMethod;
import org.boon.slumberdb.stores.log.AsyncFileWriterDataStore;
import org.boon.slumberdb.stores.mysql.MySQLDataStore;
import org.boon.slumberdb.stores.replicate.ReplicationDataStore;

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

import static org.boon.Boon.configurableLogger;
import static org.boon.Boon.puts;

/**
 * Remaining tasks:
 * Created by Richard on 6/27/14.
 */
public class MasterDataStore implements DataStore {

    private Logger logger = configurableLogger(MasterDataStore.class);

    private DataOutputQueue transferQueue;
    private LevelDBDataStore levelDBDataStore;
    private MySQLDataStore mySQLDataStore;
    private ConcurrentMapDataStore mapDataStore = new ConcurrentMapDataStore();
    private EndOfTheLineMapStore endOfTheLineMapStore;
    private AsyncFileWriterDataStore logStore = new AsyncFileWriterDataStore();
    private ReplicationDataStore replicationDataStore = new ReplicationDataStore();
    private List<DataStore> dataStoreList;
    private DataStoreConfig dataStoreConfig;
    private boolean mySQLReadOnly;

    public void init(DataOutputQueue dataOutputQueue, StartupMode mode) {
        init(dataOutputQueue, mode, null, null);
    }

    public void init(DataOutputQueue dataOutputQueue) {
        init(dataOutputQueue, StartupMode.LEVELDB_AND_MYSQL, null, null);
    }

    public void init(DataOutputQueue dataOutputQueue, DataStoreServerConfig inDataStoreServerConfig) {
        init(dataOutputQueue, inDataStoreServerConfig.startupMode(), null, inDataStoreServerConfig.replicationDataStoreConfig());
    }

    public void init(DataOutputQueue dataOutputQueue, StartupMode mode, DataStoreConfig inDataStoreConfig, ReplicationDataStoreConfig inReplicationDataStoreConfig) {
        this.dataStoreConfig = inDataStoreConfig == null ? DataStoreConfig.load() : inDataStoreConfig;
        puts("Master - Data Store Config", this.dataStoreConfig);

        logStore.init(this.dataStoreConfig);

        if (mode == StartupMode.LEVELDB_AND_MYSQL) {
            transferQueue = dataOutputQueue;
            endOfTheLineMapStore = new EndOfTheLineMapStore(transferQueue);
            mySQLDataStore = new MySQLDataStore();
            levelDBDataStore = new LevelDBDataStore();

            dataStoreList = Lists.list(logStore, mapDataStore, levelDBDataStore, mySQLDataStore);
            mapDataStore.init(this.dataStoreConfig, transferQueue, levelDBDataStore);
            levelDBDataStore.init(this.dataStoreConfig, transferQueue, mySQLDataStore);

            try {
                mySQLDataStore.init(this.dataStoreConfig, transferQueue, endOfTheLineMapStore);
            } catch (Exception ex) {
                logger.error(ex, "Unable to connect to MySQL, proceeding without MySQL, no data will be forwarded to MySQL");
            }
        }

        else if (mode == StartupMode.LEVELDB) {

            transferQueue = dataOutputQueue;
            endOfTheLineMapStore = new EndOfTheLineMapStore(transferQueue);
            // NO mySQLDataStore
            levelDBDataStore = new LevelDBDataStore();

            dataStoreList = Lists.list(mapDataStore, logStore, levelDBDataStore);
            mapDataStore.init(this.dataStoreConfig, transferQueue, levelDBDataStore);
            levelDBDataStore.init(this.dataStoreConfig, transferQueue, endOfTheLineMapStore);
        }

        else if (mode == StartupMode.MYSQL) {

            transferQueue = dataOutputQueue;
            endOfTheLineMapStore = new EndOfTheLineMapStore(transferQueue);
            mySQLDataStore = new MySQLDataStore();
            // NO levelDBDataStore

            dataStoreList = Lists.list(logStore, mySQLDataStore, mapDataStore);
            mapDataStore.init(this.dataStoreConfig, transferQueue, mySQLDataStore);
            mySQLDataStore.init(this.dataStoreConfig, transferQueue, endOfTheLineMapStore);
        }

        else if (mode == StartupMode.MYSQL_READONLY) {

            transferQueue = dataOutputQueue;
            endOfTheLineMapStore = new EndOfTheLineMapStore(transferQueue);
            mySQLDataStore = new MySQLDataStore();

            dataStoreList = Lists.list(logStore, mySQLDataStore, mapDataStore);

            mapDataStore.init(this.dataStoreConfig, transferQueue, mySQLDataStore);
            mySQLDataStore.init(this.dataStoreConfig, transferQueue, endOfTheLineMapStore);
            mySQLReadOnly = true;
        }

        else if (mode == StartupMode.NO_BACKING_DB) {

            transferQueue = dataOutputQueue;
            endOfTheLineMapStore = new EndOfTheLineMapStore(transferQueue);
            // NO mySQLDataStore
            // NO levelDBDataStore

            dataStoreList = Lists.list(mapDataStore, logStore);
            mapDataStore.init(this.dataStoreConfig, transferQueue, endOfTheLineMapStore);
        }

        if (inReplicationDataStoreConfig != null) {
            replicationDataStore = new ReplicationDataStore();
            replicationDataStore.init(inReplicationDataStoreConfig);
            dataStoreList.add(replicationDataStore);
        }
    }

    @ServiceMethod
    public void setSource(SetRequest request) {
        _set(request.source(), request);
    }

    /*
        THIS IS THE DELEGATE FOR SETTING DATA. For instance
        set(SetRequest request) and setSource(SetRequest request)
        will both call this method instead of processing the set themself,
        therefore providing a common and consistent way to handle the set - SFF
    */
    protected void _set(DataStoreSource source, SetRequest request) {
        switch (source) {
            case MEMORY:
                _setMemory(request);
                break;
            case LOCAL_DB:
                _setLocal(request, true);
                break;
            case REMOTE_DB:
                _setRemote(request, true);
                break;
            case TRANSACTION_LOG:
                _setTransactionLog(request);
                break;
            case LOCAL_STORES:
                _setMemory(request);
                _setLocal(request, true);
                break;
            case ALL:
                _setTransactionLog(request);
                _setReplication(request);
                _setMemory(request);
                _setLocal(request, false);
                _setRemote(request, false);
                break;
            case REPLICATION:
                _setMemory(request);
                _setLocal(request, false);
                _setRemote(request, false);
                break;
            default:
                queueInvalidSource(request);
                logger.error("Master Data Store:: Unable to handle Set Source", request);
        }
    }

    private void _setMemory(SetRequest request) {
        mapDataStore.set(request);
    }

    private void _setLocal(SetRequest request, boolean required) {
        if (levelDBDataStore != null) {
            levelDBDataStore.set(request);
        }
        else if (required) {
            queueInvalidSource(request);
        }
    }

    private void _setRemote(SetRequest request, boolean required) {
        if (mySQLDataStore != null && !mySQLReadOnly) {
            mySQLDataStore.set(request);
        }
        else if (required) {
            queueInvalidSource(request);
        }
    }

    private void _setTransactionLog(SetRequest request) {
        logStore.set(request);
    }

    private void _setReplication(SetRequest request) {
        if (replicationDataStore != null) {
            replicationDataStore.set(request);
        }
    }

    @ServiceMethod
    public void getSource(GetRequest request) {
        switch (request.source()) {
            case MEMORY:
                mapDataStore.get(request);
                break;
            case LOCAL_DB:
                if (levelDBDataStore != null) {
                    levelDBDataStore.get(request);
                }
                else {
                    queueInvalidSource(request);
                }
                break;
            case REMOTE_DB:
                if (mySQLDataStore != null) {
                    mySQLDataStore.get(request);
                }
                else {
                    queueInvalidSource(request);
                }
                break;
            case TRANSACTION_LOG:
                queueInvalidSource(request);
                logger.error("Master Data Store:: Improper Get Source", request);
                break;
            default:
                queueInvalidSource(request);
                logger.error("Master Data Store:: Unable to handle Get Source", request);
        }
    }

    @ServiceMethod
    public void removeFromSource(RemoveRequest request) {
        switch (request.source()) {
            case MEMORY:
                mapDataStore.remove(request);
                break;
            case LOCAL_DB:
                if (levelDBDataStore != null) {
                    levelDBDataStore.remove(request);
                }
                else {
                    queueInvalidSource(request);
                }
                break;
            case REMOTE_DB:
                if (mySQLDataStore != null) {
                    mySQLDataStore.remove(request);
                }
                else {
                    queueInvalidSource(request);
                }
                break;
            case TRANSACTION_LOG:
                logStore.remove(request);
                break;
            default:
                logger.error("Master Data Store:: Unable to handle REMOVE Source", request);

        }
    }

    @ServiceMethod
    @Override
    public void clearStats() {
        logStore.clearStats();
        if (replicationDataStore != null) {
            replicationDataStore.clearStats();
        }
        mapDataStore.clearStats();
        if (mySQLDataStore != null) {
            mySQLDataStore.clearStats();
        }
        if (levelDBDataStore != null) {
            levelDBDataStore.clearStats();
        }
    }

    @ServiceMethod
    @Override
    public void sendStats(long now) {
        logStore.sendStats(now);
        if (replicationDataStore != null) {
            replicationDataStore.sendStats(now);
        }
        mapDataStore.sendStats(now);
        if (mySQLDataStore != null) {
            mySQLDataStore.sendStats(now);
        }
        if (levelDBDataStore != null) {
            levelDBDataStore.sendStats(now);
        }

        StatCount statCount;

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG dbReaderCount",
                dataStoreConfig.dbReaderCount());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG dbWriterCount",
                dataStoreConfig.dbWriterCount());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG levelDBBatchReaderCount",
                dataStoreConfig.levelDBBatchReaderCount());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG checkStatusEveryIntervalMS",
                dataStoreConfig.checkStatusEveryIntervalMS());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG dbMaxReadBatch",
                dataStoreConfig.dbMaxReadBatch());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG dbMaxWriteBatch",
                dataStoreConfig.dbMaxWriteBatch());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG dbMinReadBatch",
                dataStoreConfig.dbMinReadBatch());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG dbReadFlushQueueIntervalMS",
                dataStoreConfig.dbReadFlushQueueIntervalMS());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG levelDBCacheSize",
                dataStoreConfig.levelDBCacheSize());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG levelDBMaxOpenFiles",
                dataStoreConfig.levelDBMaxOpenFiles());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG levelDBWriteBufferSize",
                dataStoreConfig.levelDBWriteBufferSize());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG sqlBatchWrite",
                dataStoreConfig.sqlBatchWrite());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG threadErrorResumeTimeMS",
                dataStoreConfig.threadErrorResumeTimeMS());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG pollTimeoutMS",
                dataStoreConfig.pollTimeoutMS());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG blacklist" + dataStoreConfig.mySQLKeyBlackList(),
                dataStoreConfig.mySQLKeyBlackList().size());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG DB URL " + dataStoreConfig.dbUrl(),
                dataStoreConfig.dbUrl().length());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG levelDBFileName " + dataStoreConfig.levelDBFileName(),
                dataStoreConfig.levelDBFileName().length());

        transferQueue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG outputDirectory " + dataStoreConfig.outputDirectory(),
                dataStoreConfig.outputDirectory().length());

        transferQueue.put(statCount);
    }

    @ServiceMethod
    @Override
    public void set(SetRequest request) {
        _set(request.source(), request);
    }

    @ServiceMethod
    @Override
    public void get(GetRequest getRequest) {
        mapDataStore.get(getRequest);
    }

    @ServiceMethod
    @Override
    public void search(SearchRequest searchRequest) {
        if (levelDBDataStore != null) {
            levelDBDataStore.search(searchRequest);
        }
    }

    @ServiceMethod
    public String get(String key) {
        return mapDataStore.get(key);
    }

    @ServiceMethod
    @Override
    public void batchRead(ReadBatchRequest request) {
        mapDataStore.batchRead(request);
    }

    @ServiceMethod
    @Override
    public void remove(RemoveRequest removeRequest) {
        logStore.remove(removeRequest);
        if (replicationDataStore != null) {
            replicationDataStore.remove(removeRequest);
        }
        mapDataStore.remove(removeRequest);
        if (mySQLDataStore != null && !mySQLReadOnly) {
            mySQLDataStore.remove(removeRequest);
        }
        if (levelDBDataStore != null) {
            levelDBDataStore.remove(removeRequest);
        }
    }

    @ServiceMethod
    @Override
    public void addAll(BatchSetRequest batchSetRequest) {
        if (DataStoreSource.REPLICATION != batchSetRequest.source()) {
            logStore.addAll(batchSetRequest);
            if (replicationDataStore != null) {
                replicationDataStore.addAll(batchSetRequest);
            }
        }
        mapDataStore.addAll(batchSetRequest);
        if (mySQLDataStore != null && !mySQLReadOnly) {
            mySQLDataStore.addAll(batchSetRequest);
        }
        if (levelDBDataStore != null) {
            levelDBDataStore.addAll(batchSetRequest);
    }
    }

    @Override
    public void start() {
        for (DataStore dataStore : dataStoreList) {
            dataStore.start();
        }
        Sys.sleep(10);
    }

    @Override
    public void stop() {
        if (dataStoreList != null) {
            for (DataStore dataStore : dataStoreList) {
                dataStore.stop();
            }
        }
    }

    @ServiceMethod
    public void flush() {
        logStore.flush();
        if (replicationDataStore != null) {
            replicationDataStore.flush();
        }
    }

    @ServiceMethod
    public long countLocalDB() {
        return levelDBDataStore == null ? -1 : levelDBDataStore.count();
    }

    @ServiceMethod
    public long countKeyPrefix(String prefix) {
        return levelDBDataStore == null ? -1 : levelDBDataStore.countKeyPrefix(prefix);
    }

    @ServiceMethod
    public long deleteLocalDBByKeyPrefix(String prefix) {
        return levelDBDataStore == null ? -1 : levelDBDataStore.deleteKeysByPrefix(prefix);
    }

    @ServiceMethod
    public List<Pair<String, String>> search(String prefix) {
        return levelDBDataStore.search(prefix);
    }

    @ServiceMethod
    public List<String> searchForKeys(String prefix) {
        return levelDBDataStore == null ? new ArrayList<String>() : levelDBDataStore.searchForKeys(prefix);
    }

    @ServiceMethod
    public long deleteByKeyPrefix(String prefix) {
        List<String> keys = searchForKeys(prefix);
        for (String key : keys) {
            mapDataStore.remove(key);
        }
        if (mySQLDataStore != null) {
            for (String key : keys) {
                mySQLDataStore.remove(new RemoveRequest(666, "admin", key));
            }
        }

        return keys.size();
    }

    @ServiceMethod
    public long countMemory() {
        return mapDataStore.count();
    }

    @ServiceMethod
    public boolean exists(String key) {
        return mapDataStore.exists(key);
    }

    @ServiceMethod
    public DataStoreConfig config() {
        return dataStoreConfig;
    }

    @ServiceMethod
    public void set(String key, String value) {
        mapDataStore.set(key, value);
    }

    private void queueInvalidSource(BaseDataStoreRequest request) {
        transferQueue.put(invalidSourceResult(request));
    }

    private ErrorResult invalidSourceResult(BaseDataStoreRequest request) {
        return new ErrorResult(request.messageId(), request.clientId(), request.source(), "Invalid Source");
    }
}


/*
 * Copyright 2013-2014 Richard M. Hightower
 * 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.
 *
 * __________                              _____          __   .__
 * \______   \ ____   ____   ____   /\    /     \ _____  |  | _|__| ____    ____
 *  |    |  _//  _ \ /  _ \ /    \  \/   /  \ /  \\__  \ |  |/ /  |/    \  / ___\
 *  |    |   (  <_> |  <_> )   |  \ /\  /    Y    \/ __ \|    <|  |   |  \/ /_/  >
 *  |______  /\____/ \____/|___|  / \/  \____|__  (____  /__|_ \__|___|  /\___  /
 *         \/                   \/              \/     \/     \/       \//_____/
 *      ____.                     ___________   _____    ______________.___.
 *     |    |____ ___  _______    \_   _____/  /  _  \  /   _____/\__  |   |
 *     |    \__  \\  \/ /\__  \    |    __)_  /  /_\  \ \_____  \  /   |   |
 * /\__|    |/ __ \\   /  / __ \_  |        \/    |    \/        \ \____   |
 * \________(____  /\_/  (____  / /_______  /\____|__  /_______  / / ______|
 *               \/           \/          \/         \/        \/  \/
 */

package org.boon.core.reflection;

import org.boon.*;
import org.boon.core.Value;
import org.boon.core.reflection.fields.FieldAccessMode;
import org.boon.core.reflection.fields.FieldsAccessor;

import java.util.*;

import static org.boon.Exceptions.die;
import static org.boon.Exceptions.handle;
import static org.boon.core.Conversions.coerce;

/**
 * Created by rick on 12/26/13.
 * @author Richard Hightower
 * <p>
 * This class creates Java objects from java.util.Lists and java.util.Maps.
 * It is used by the JSON parser lib.
 * There are map like objects that are index overlays of the parsed JSON.
 * This set of utilties makes Java a bit more dynamic.
 * This is the core of the serialization for JSON and works in conjunction with org.boon.core.TypeType.
 * </p>
 */
public class MapObjectConversion {

    private static final Mapper mapper = new MapperSimple();

    private static final Mapper mapperWithType = new MapperComplex();

    private static final Mapper prettyMapper = new MapperComplex(
            false, //outputType
            FieldAccessMode.PROPERTY_THEN_FIELD, //fieldAccessType
            true, //useAnnotations
            false, //caseInsensitiveFields
            null, //ignoreSet
            null, //view
            true, true);

    /** Convert an item from a list into a class using the classes constructor.
     *
     * REFACTOR: Can't this just be from collection?
     * REFACTOR
     *
     * @param argList list if arguments
     * @param clazz  the type of the object we are creating
     * @param <T> generics
     * @return the new object that we just created.
     */
    public static <T> T fromList( List<?> argList, Class<T> clazz ) {
        return mapper.fromList(argList, clazz);
    }

    /** Convert an item from a list into a class using the classes constructor.
     *
     * REFACTOR: Can't this just be from collection?
     * REFACTOR
     *
     * @param respectIgnore  honor @JsonIgnore, transients, etc. of the field
     * @param view honor views for fields
     * @param fieldsAccessor how we are going to access the fields (by field, by property, combination)
     * @param argList list if arguments
     * @param clazz  the type of the object we are creating
     * @param ignoreSet a set of properties to ignore
     * @param <T> generics
     * @return the new object that we just created.
     */
    public static <T> T fromList( boolean respectIgnore, String view, FieldsAccessor fieldsAccessor,
                                  List<?> argList, Class<T> clazz, Set<String> ignoreSet ) {

        return new MapperComplex(fieldsAccessor, ignoreSet, view, respectIgnore).fromList(argList, clazz);

    }

    /** Convert an item from a list into a class using the classes constructor.
     *
     * REFACTOR: Can't this just be from collection?
     * REFACTOR
     *
     * @param fieldsAccessor how we are going to access the fields (by field, by property, combination)
     * @param argList list if arguments
     * @param clazz  the type of the object we are creating
     * @param <T> generics
     * @return the new object that we just created.
     */
    public static <T> T fromList( FieldsAccessor fieldsAccessor, List<?> argList, Class<T> clazz ) {
        return new MapperComplex(fieldsAccessor, null, null, false).fromList(argList, clazz);
    }

    /** Convert an object to a list.
     *
     * @param object the object we want to convert to a list
     * @return new list from an object
     */
    public static List<?> toList( Object object) {

        return mapper.toList(object);
    }

    /**
     * From map.
     * @param map map to create the object from.
     * @param clazz the new instance type
     * @param <T> generic type capture
     * @return new object
     */
    @SuppressWarnings( "unchecked" )
    public static <T> T fromMap( Map<String, Object> map, Class<T> clazz ) {
        return mapper.fromMap(map, clazz);
    }

    /**
     * fromMap converts a map into a java object.
     * @param map map to create the object from.
     * @param clazz  the new instance type
     * @param excludeProperties the properties to exclude
     * @param <T> generic type capture
     * @return the new object
     */
    @SuppressWarnings( "unchecked" )
    public static <T> T fromMap( Map<String, Object> map, Class<T> clazz, String... excludeProperties ) {
        Set<String> ignoreProps = excludeProperties.length > 0 ? Sets.set(excludeProperties) :  null;
        return new MapperComplex(FieldAccessMode.FIELD_THEN_PROPERTY.create( false ), ignoreProps, null, true).fromMap(map, clazz);

    }

    /**
     * fromMap converts a map into a Java object.
     * This version will see if there is a class parameter in the map, and dies if there is not.
     * @param map map to create the object from.
     * @return new object
     */
    public static Object fromMap( Map<String, Object> map ) {
        return mapper.fromMap(map);
    }

    /**
     * fromMap converts a map into a java object
     * @param respectIgnore honor @JsonIgnore, transients, etc. of the field
     * @param view the view of the object which can ignore certain fields given certain views
     * @param fieldsAccessor how we are going to access the fields (by field, by property, combination)
     * @param map map to create the object from.
     * @param cls class type of new object
     * @param ignoreSet a set of properties to ignore
     * @param <T> map to create teh object from.
     * @return new object of type cls <T>
     */
    @SuppressWarnings("unchecked")
    public static <T> T fromMap( boolean respectIgnore, String view, FieldsAccessor fieldsAccessor, Map<String, Object> map, Class<T> cls, Set<String> ignoreSet ) {

        Mapper mapper = new MapperComplex(ignoreSet, view, respectIgnore);

        return mapper.fromMap(map, cls);
    }

    /**
     * Creates an object from a value map.
     *
     * This does some special handling to take advantage of us using the value map so it avoids creating
     * a bunch of array objects and collections. Things you have to worry about when writing a
     * high-speed JSON serializer.
     * @param respectIgnore  honor @JsonIgnore, transients, etc. of the field
     * @param view the view of the object which can ignore certain fields given certain views
     * @param fieldsAccessor how we are going to access the fields (by field, by property, combination)
     * @param cls the new type
     * @param ignoreSet a set of properties to ignore
     * @return new object from value map
     */
    @SuppressWarnings("unchecked")
    public static <T> T fromValueMap( boolean respectIgnore, String view, final FieldsAccessor fieldsAccessor,
                                      final Map<String, Value> valueMap,
                                      final Class<T> cls, Set<String> ignoreSet ) {

        Mapper mapper = new MapperComplex(fieldsAccessor, ignoreSet, view, respectIgnore);
        return mapper.fromValueMap(valueMap, cls);

     }

    /**
     * Basic toMap to create an object into a map.
     * @param object the object we want to convert to a list
     * @param ignore do we honor ignore properties
     * @return new map
     */
    public static Map<String, Object> toMap( final Object object, final String... ignore ) {
        return toMap( object, Sets.set( ignore ) );
    }

    /**
     * This could be refactored to use core.TypeType class and it would run faster.
     * Converts an object into a map
     * @param object the object that we want to convert
     * @param ignore the map
     * @return map map representation of the object
     */
    public static Map<String, Object> toMap( final Object object, Set<String> ignore ) {

        return new MapperComplex(ignore).toMap(object);

    }

    /**
     * This could be refactored to use core.TypeType class and it would run faster.
     *
     * Converts an object into a map
     * @param object the object that we want to convert
     * @return map map representation of the object
     */
    public static Map<String, Object> toMap( final Object object ) {

        return mapper.toMap(object);
    }

    /**
     * This could be refactored to use core.TypeType class and it would run faster.
     *
     * Converts an object into a map
     * @param object the object that we want to convert
     * @return map map representation of the object
     */
    public static Map<String, Object> toMapWithType( final Object object ) {

        return mapperWithType.toMap(object);
    }

    /**
     * This converts a list of maps to objects.
     * I always forget that this exists. I need to remember.
     *
     * @param respectIgnore   honor @JsonIgnore, transients, etc. of the field
     * @param view the view of the object which can ignore certain fields given certain views
     * @param fieldsAccessor how we are going to access the fields (by field, by property, combination)
     * @param componentType The component type of the created list
     * @param list the input list
     * @param ignoreProperties properties to ignore
     * @param <T> generics
     * @return a new list
     */
    public  static <T> List<T> convertListOfMapsToObjects(   boolean respectIgnore, String view,
                                                            FieldsAccessor fieldsAccessor,
                                                            Class<T> componentType, List<Map> list, Set<String> ignoreProperties) {
        return new MapperComplex(fieldsAccessor, ignoreProperties,view, respectIgnore).convertListOfMapsToObjects(list, componentType);
    }

    /**
     *
     * @param componentType The component type of the created list
     * @param list the input list
     * @param <T>
     * @return
     */
    public static  <T> List<T> convertListOfMapsToObjects(Class<T> componentType, List<Map> list) {
        return mapper.convertListOfMapsToObjects(list, componentType);
    }

    /**
     * Creates a list of maps from a list of class instances.
     * @param collection  the collection we are coercing into a field value
     * @return the return value.
     */
    public static List<Map<String, Object>> toListOfMaps( Collection<?> collection ) {

        return mapper.toListOfMaps(collection);
    }

    public static Map toPrettyMap(Object object) {
        return prettyMapper.toMap(object);
    }
}


/*
 * Copyright 2013-2014 Richard M. Hightower
 * 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.
 *
 * __________                              _____          __   .__
 * \______   \ ____   ____   ____   /\    /     \ _____  |  | _|__| ____    ____
 *  |    |  _//  _ \ /  _ \ /    \  \/   /  \ /  \\__  \ |  |/ /  |/    \  / ___\
 *  |    |   (  <_> |  <_> )   |  \ /\  /    Y    \/ __ \|    <|  |   |  \/ /_/  >
 *  |______  /\____/ \____/|___|  / \/  \____|__  (____  /__|_ \__|___|  /\___  /
 *         \/                   \/              \/     \/     \/       \//_____/
 *      ____.                     ___________   _____    ______________.___.
 *     |    |____ ___  _______    \_   _____/  /  _  \  /   _____/\__  |   |
 *     |    \__  \\  \/ /\__  \    |    __)_  /  /_\  \ \_____  \  /   |   |
 * /\__|    |/ __ \\   /  / __ \_  |        \/    |    \/        \ \____   |
 * \________(____  /\_/  (____  / /_______  /\____|__  /_______  / / ______|
 *               \/           \/          \/         \/        \/  \/
 */

package org.boon.core.reflection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static org.boon.Str.uncapitalize;

/**
 * This is a helper class that helps us extract annotation data
 * from the Annotations.
 * <p/>
 * This was pulled over from crank. I have not given in a thorough review yet.
 *
 * @author Rick Hightower
 */
public class AnnotationData {

    /**
     * The name of the classname of the annotation.
     */
    private String annotationClassName;

    /**
     * The simple name of the annotation.
     */
    private String annotationSimpleName;

    /**
     * The package of the annotation.
     */
    private String annotationPackageName;

    private Set<String> allowedAnnotations;

    private String name;

    private Map<String, Object> values;

    public AnnotationData( Annotation annotation ) {
        this( annotation, new HashSet<String>() );
    }

    public AnnotationData( Annotation annotation, Set<String> allowedAnnotations ) {

        this.annotationSimpleName = annotation.annotationType().getSimpleName ();
        this.annotationClassName = annotation.annotationType().getName ();
        this.annotationPackageName = annotationClassName.substring ( 0, annotationClassName.length ()
                - annotationSimpleName.length () - 1 );
        this.allowedAnnotations = allowedAnnotations;
        this.name = uncapitalize( annotationSimpleName );
        this.values = doGetValues(annotation);
    }

    /**
     * Determines if this is an annotation we care about.
     * Checks to see if the package name is in the set.
     */
    public boolean isAllowed() {
        if (allowedAnnotations ==null || allowedAnnotations.size ()==0) return true;
        return allowedAnnotations.contains( annotationPackageName );
    }

    /**
     * Get the name of the annotation by lowercasing the first letter
     * of the simple name, e.g., short name Required becomes required.
     *
     * @return
     */
    public String getName() {
        return name;
    }

    /**
     * Get the values from the annotation.
     * We use reflection to turn the annotation into a simple HashMap
     * of values.
     *
     * @return
     */
    Map<String, Object> doGetValues(Annotation annotation) {
        /* Holds the value map. */
        Map<String, Object> values = new HashMap<String, Object>();
        /* Get the declared staticMethodMap from the actual annotation. */
        Method[] methods = annotation.annotationType().getDeclaredMethods();

        final Object[] noargs = ( Object[] ) null;

        /* Iterate through declared staticMethodMap and extract values
         * by invoking decalared staticMethodMap if they are no arg staticMethodMap.
         */
        for ( Method method : methods ) {
            /* If it is a no arg method assume it is an annoation value. */
            if ( method.getParameterTypes().length == 0 ) {
                try {
                    /* Get the value. */
                    Object value = method.invoke( annotation, noargs );
                    if (value instanceof Enum) {
                        Enum enumVal = (Enum)value;
                        value = enumVal.name ();
                    }
                    values.put( method.getName(), value );
                } catch ( Exception ex ) {
                    throw new RuntimeException( ex );
                }
            }
        }
        return values;
    }

    public Map<String, Object> getValues() {
        return values;
    }

    public String toString() {
        return name;
    }

    public String getFullClassName () {
        return annotationClassName;
    }

    public String getSimpleClassName () {
        return annotationSimpleName;
    }

}


/*
 * Copyright 2013-2014 Richard M. Hightower
 * 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.
 *
 * __________                              _____          __   .__
 * \______   \ ____   ____   ____   /\    /     \ _____  |  | _|__| ____    ____
 *  |    |  _//  _ \ /  _ \ /    \  \/   /  \ /  \\__  \ |  |/ /  |/    \  / ___\
 *  |    |   (  <_> |  <_> )   |  \ /\  /    Y    \/ __ \|    <|  |   |  \/ /_/  >
 *  |______  /\____/ \____/|___|  / \/  \____|__  (____  /__|_ \__|___|  /\___  /
 *         \/                   \/              \/     \/     \/       \//_____/
 *      ____.                     ___________   _____    ______________.___.
 *     |    |____ ___  _______    \_   _____/  /  _  \  /   _____/\__  |   |
 *     |    \__  \\  \/ /\__  \    |    __)_  /  /_\  \ \_____  \  /   |   |
 * /\__|    |/ __ \\   /  / __ \_  |        \/    |    \/        \ \____   |
 * \________(____  /\_/  (____  / /_______  /\____|__  /_______  / / ______|
 *               \/           \/          \/         \/        \/  \/
 */

package org.boon.validation;

import org.boon.Exceptions;
import org.boon.Maps;
import org.boon.core.Typ;
import org.boon.core.reflection.BeanUtils;
import org.boon.core.reflection.Reflection;
import org.boon.validation.readers.AnnotationValidatorMetaDataReader;
import org.boon.validation.validators.CompositeValidator;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.*;

public class RecursiveDescentPropertyValidator {
    protected ValidatorMetaDataReader validatorMetaDataReader = new AnnotationValidatorMetaDataReader();

    public class MessageHolder {
        public final String propertyPath;
        public final ValidatorMessageHolder holder;

        MessageHolder( String propertyPath, ValidatorMessageHolder holder ) {
            this.propertyPath = propertyPath;
            this.holder = holder;
        }
    }

    /**
     * Create the validator by looking it up in the ObjectRegistry and then
     * populating it with values from the meta-data list.
     *
     * @param validationMetaDataList Holds metadataInformation about validation.
     * @return composite validator with all of the validators for this property present.
     */
    protected CompositeValidator createValidator(
            List<ValidatorMetaData> validationMetaDataList ) {

        /*
         * A field (property) can be associated with many validators so we use a
         * CompositeValidator to hold all of the validators associated with this
         * validator.
         */
        CompositeValidator compositeValidator = new CompositeValidator(); // hold
        // all
        // of
        // the
        // validators
        // associated
        // with
        // the
        // field.

        /*
         * Lookup the list of validators for the current field and initialize
         * them with validation meta-data properties.
         */
        List<FieldValidator> validatorsList =
                lookupTheListOfValidatorsAndInitializeThemWithMetaDataProperties( validationMetaDataList );

        compositeValidator.setValidatorList( validatorsList );

        return compositeValidator;
    }

    private List<PropertyDescriptor> getFieldsToValidate( Object object ) {
        List<PropertyDescriptor> properties;
        BeanInfo beanInfo;
        try {
            beanInfo = Introspector.getBeanInfo( object.getClass() );
        } catch ( IntrospectionException e ) {

            throw new RuntimeException( e );
        }
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        properties = new ArrayList<>( propertyDescriptors.length );
        for ( PropertyDescriptor pd : propertyDescriptors ) {
            if ( !pd.getName().equals( "class" ) ) {
                properties.add( pd );
            }
        }
        return properties;
    }

    protected List<ValidatorMetaData> readMetaData( Class<?> clazz,
                                                    String propertyName ) {
        return validatorMetaDataReader.readMetaData( clazz,
                propertyName );
    }

    private void validateProperty( final Object object, final Object objectProperty, final String property,
                                   List<MessageHolder> vMessageHolders ) {

        List<ValidatorMetaData> metaDataList = readMetaData( object.getClass(),
                property );
        CompositeValidator cv = createValidator( metaDataList );
        ValidatorMessageHolder holder = cv.validate( objectProperty, property );
        vMessageHolders.add( new MessageHolder( ValidationContext.getBindingPath(), holder ) );
    }

    protected boolean shouldFieldBeValidated() {
        return true;
    }

    public List<MessageHolder> validateObject( final Object object ) {

        List<MessageHolder> list = Collections.EMPTY_LIST;
        try {
            ValidationContext.create();
            list = validateObjectWithMessages( object, null );
        } finally {
            ValidationContext.destroy();
        }
        return list;
    }

    public List<MessageHolder> validateObject( final Object object, Map<String, Object> registry ) {

        List<MessageHolder> list = Collections.EMPTY_LIST;
        try {
            ValidationContext.create();
            ValidationContext.get().setObjectRegistry( registry );
            list = validateObjectWithMessages( object, null );
        } finally {
            ValidationContext.destroy();
        }
        return list;
    }

    public List<MessageHolder> validateObjectWithMessages( final Object object, List<MessageHolder> validationMessages ) {
        List<PropertyDescriptor> fieldsToValidate = getFieldsToValidate( object );
        Map<String, Object> objectPropertiesAsMap = Maps.toMap( object );
        if ( validationMessages == null ) {
            validationMessages = new ArrayList<>();
        }

        for ( PropertyDescriptor field : fieldsToValidate ) {

            /* Keep track of the field name and parentObject so the field validators can access them. */
            ValidationContext.get().pushProperty( field.getName() );
            ValidationContext.get().setParentObject( object );
            if ( shouldFieldBeValidated() ) {
                Object propertyObject = objectPropertiesAsMap.get( field.getName() );
                validateProperty( object, propertyObject, field.getName(), validationMessages );

                /* Don't validate if it is not a basic type. */
                if ( propertyObject != null && !Typ.isBasicType( propertyObject ) ) {
                    validateObjectWithMessages( propertyObject, validationMessages );
                }
            }
            ValidationContext.get().pop();
        }

        return validationMessages;

    }

    /**
     * Lookup the list of validators for the current field and initialize them
     * with validation meta-data properties.
     *
     * @param validationMetaDataList list of validation meta-data
     * @return list of field validators.
     */
    private List<FieldValidator>
    lookupTheListOfValidatorsAndInitializeThemWithMetaDataProperties(
            List<ValidatorMetaData> validationMetaDataList ) {

        List<FieldValidator> validatorsList = new ArrayList<>();

        /*
         * Look up the crank validators and then apply the properties from the
         * validationMetaData to them.
         */
        for ( ValidatorMetaData validationMetaData : validationMetaDataList ) {
            /* Look up the FieldValidator. */
            FieldValidator validator = lookupValidatorInRegistry(
                    validationMetaData
                            .getName() );
            /*
             * Apply the properties from the validationMetaData to the
             * validator.
             */
            applyValidationMetaDataPropertiesToValidator( validationMetaData,
                    validator );
            validatorsList.add( validator );
        }
        return validatorsList;
    }

    /**
     * This method looks up the validator in the registry.
     *
     * @param validationMetaDataName The name of the validator that we are looking up.
     * @return field validator
     */
    private FieldValidator lookupValidatorInRegistry(
            String validationMetaDataName ) {

        Map<String, Object> applicationContext = ValidationContext.get().getObjectRegistry();

        Exceptions.requireNonNull( applicationContext );

        return ( FieldValidator ) applicationContext
                .get( "/org/boon/validator/" + validationMetaDataName );
    }

    /**
     * This method applies the properties from the validationMetaData to the
     * validator uses Spring's BeanWrapperImpl.
     *
     * @param metaData  validation meta data
     * @param validator field validator
     */
    private void applyValidationMetaDataPropertiesToValidator(
            ValidatorMetaData metaData, FieldValidator validator ) {
        Map<String, Object> properties = metaData.getProperties();
        ifPropertyBlankRemove( properties, "detailMessage" );
        ifPropertyBlankRemove( properties, "summaryMessage" );

        BeanUtils.copyProperties( validator,
                properties );
    }

    /**
     * Removes a property if it is null or an empty string.
     * This allows the property to have a null or emtpy string in the
     * meta-data but we don't copy it to the validator if the property
     * is not set.
     *
     * @param properties properties
     * @param property   property
     */
    private void ifPropertyBlankRemove( Map<String, Object> properties, String property ) {

        Object object = properties.get( property );
        if ( object == null ) {
            properties.remove( property );
        } else if ( object instanceof String ) {
            String string = ( String ) object;
            if ( "".equals( string.trim() ) ) {
                properties.remove( property );
            }
        }
    }

    public void setValidatorMetaDataReader(
            ValidatorMetaDataReader validatorMetaDataReader ) {
        this.validatorMetaDataReader = validatorMetaDataReader;
    }

}


/*
 * Copyright 2013-2014 Richard M. Hightower
 * 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.
 *
 * __________                              _____          __   .__
 * \______   \ ____   ____   ____   /\    /     \ _____  |  | _|__| ____    ____
 *  |    |  _//  _ \ /  _ \ /    \  \/   /  \ /  \\__  \ |  |/ /  |/    \  / ___\
 *  |    |   (  <_> |  <_> )   |  \ /\  /    Y    \/ __ \|    <|  |   |  \/ /_/  >
 *  |______  /\____/ \____/|___|  / \/  \____|__  (____  /__|_ \__|___|  /\___  /
 *         \/                   \/              \/     \/     \/       \//_____/
 *      ____.                     ___________   _____    ______________.___.
 *     |    |____ ___  _______    \_   _____/  /  _  \  /   _____/\__  |   |
 *     |    \__  \\  \/ /\__  \    |    __)_  /  /_\  \ \_____  \  /   |   |
 * /\__|    |/ __ \\   /  / __ \_  |        \/    |    \/        \ \____   |
 * \________(____  /\_/  (____  / /_______  /\____|__  /_______  / / ______|
 *               \/           \/          \/         \/        \/  \/
 */

package org.boon;

import java.net.URI;
import java.net.URL;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.util.*;

import static org.boon.Boon.sputs;
import static org.boon.Exceptions.requireNonNull;
import static org.boon.Lists.*;

public class Classpaths {

    public static List<URL> classpathResources( ClassLoader loader, String resource ) {
        try {

            Enumeration<URL> resources = loader.getResources( resource );
            List<URL> list = list( resources );

            if ( isEmpty( list ) && resource.startsWith( "/" ) ) {
                resource = resource.substring( 1 );
                return classpathResources( loader, resource );
            }

            return list;

        } catch ( Exception ex ) {

            return Exceptions.handle( List.class, sputs( "Unable to load listFromClassLoader for", resource ),
                    ex );
        }

    }

    public static List<URL> classpathResources( Class<?> clazz, String resource ) {

        List<URL> list = classpathResources( Thread.currentThread().getContextClassLoader(), resource );

        if ( isEmpty( list ) ) {
            list = classpathResources( clazz.getClassLoader(), resource );
        }

        if ( isEmpty( list ) && resource.startsWith( "/" ) ) {
            resource = resource.substring( 1 );
            return classpathResources( clazz, resource );
        }

        return list;
    }

    public static List<String> resources( Class<?> clazz, String resource ) {

        List<String> list = listFromClassLoader(Thread.currentThread().getContextClassLoader(), resource);

        if ( isEmpty( list ) ) {
            list = listFromClassLoader(clazz.getClassLoader(), resource);
        }

        if ( isEmpty( list ) && resource.startsWith( "/" ) ) {
            resource = resource.substring( 1 );
            return resources( clazz, resource );
        }

        return list;
    }

    public static List<Path> paths( Class<?> clazz, String resource ) {

        List<Path> list = pathsFromClassLoader(Thread.currentThread().getContextClassLoader(), resource);

        if ( isEmpty( list ) ) {
            list = pathsFromClassLoader(clazz.getClassLoader(), resource);
        }

        if ( isEmpty( list ) && resource.startsWith( "/" ) ) {
            resource = resource.substring( 1 );
            return paths( clazz, resource );
        }

        return list;
    }

    /**
     * Load the listFromClassLoader
     * @param loader
     * @param resource
     * @return
     */
    public static List<String> listFromClassLoader(ClassLoader loader, String resource) {
        final List<URL> resourceURLs = Classpaths.classpathResources( loader, resource );
        final List<String> resourcePaths = Lists.list( String.class );
        final Map<URI, FileSystem> pathToZipFileSystems = new HashMap<>(); //So you don't have to keep loading the same jar/zip file.
        for ( URL resourceURL : resourceURLs ) {

            if ( resourceURL.getProtocol().equals( "jar" ) ) {
                resourcesFromJar( resourcePaths, resourceURL, pathToZipFileSystems );

            } else {
                resourcesFromFileSystem( resourcePaths, resourceURL );
            }
        }
        return resourcePaths;
    }

    /**
     * Load the listFromClassLoader
     * @param loader
     * @param resource
     * @return
     */
    public static List<Path> pathsFromClassLoader(ClassLoader loader, String resource) {
        final List<URL> resourceURLs = Classpaths.classpathResources( loader, resource );
        final List<Path> resourcePaths = Lists.list( Path.class );
        final Map<URI, FileSystem> pathToZipFileSystems = new HashMap<>(); //So you don't have to keep loading the same jar/zip file.
        for ( URL resourceURL : resourceURLs ) {

            if ( resourceURL.getProtocol().equals( "jar" ) ) {
                pathsFromJar( resourcePaths, resourceURL, pathToZipFileSystems );

            } else {
                pathsFromFileSystem( resourcePaths, resourceURL );
            }
        }
        return resourcePaths;
    }

    private static void resourcesFromFileSystem( List<String> resourcePaths, URL u ) {
        URI fileURI = IO.createURI( u.toString() );

        add( resourcePaths, IO.uriToPath( fileURI ).toString() );
    }

    private static void pathsFromFileSystem( List<Path> resourcePaths, URL u ) {
        URI fileURI = IO.createURI( u.toString() );

        add( resourcePaths, IO.uriToPath( fileURI ) );
    }

    private static void resourcesFromJar( List<String> resourcePaths, URL resourceURL, Map<URI, FileSystem> pathToZipFileSystems ) {

        String str = resourceURL.toString();

        final String[] strings = StringScanner.split( str, '!' );

        URI fileJarURI = URI.create( strings[ 0 ] );
        String resourcePath = strings[ 1 ];

        if ( !pathToZipFileSystems.containsKey( fileJarURI ) ) {
            pathToZipFileSystems.put( fileJarURI, IO.zipFileSystem(fileJarURI) );
        }

        FileSystem fileSystem = pathToZipFileSystems.get( fileJarURI );

        Path path = fileSystem.getPath(resourcePath);

        if (path != null) {
            add( resourcePaths, str);
        }
    }

    private static void pathsFromJar( List<Path> resourcePaths, URL resourceURL, Map<URI, FileSystem> pathToZipFileSystems ) {

        String str = resourceURL.toString();

        final String[] strings = StringScanner.split( str, '!' );

        URI fileJarURI = URI.create( strings[ 0 ] );
        String resourcePath = strings[ 1 ];

        if ( !pathToZipFileSystems.containsKey( fileJarURI ) ) {
            pathToZipFileSystems.put( fileJarURI, IO.zipFileSystem(fileJarURI) );
        }

        FileSystem fileSystem = pathToZipFileSystems.get( fileJarURI );

        Path path = fileSystem.getPath(resourcePath);

        if (path != null) {
            add( resourcePaths, path);
        }
    }

    private static void resourcePathsFromJar( List<Path> resourcePaths, URL resourceURL, Map<URI, FileSystem> pathToZipFileSystems ) {

        String str = resourceURL.toString();

        final String[] strings = StringScanner.split( str, '!' );

        URI fileJarURI = URI.create( strings[ 0 ] );
        String resourcePath = strings[ 1 ];

        if ( !pathToZipFileSystems.containsKey( fileJarURI ) ) {
            pathToZipFileSystems.put( fileJarURI, IO.zipFileSystem(fileJarURI) );
        }

        FileSystem fileSystem = pathToZipFileSystems.get( fileJarURI );

        Path path = fileSystem.getPath(resourcePath);

        if (path != null) {
            add( resourcePaths, path);
        }
    }

}


package org.boon.slumberdb.stores.log;

import org.boon.Logger;
import org.boon.core.Dates;
import org.boon.core.Sys;
import org.boon.slumberdb.service.config.DataStoreConfig;
import org.boon.slumberdb.service.protocol.requests.*;
import org.boon.slumberdb.stores.DataStore;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

import static org.boon.Boon.configurableLogger;
import static org.boon.Boon.toJson;

/**
 * Created by Richard on 6/27/14.
 */
public class AsyncFileWriterDataStore implements DataStore, TimeAware {

    /**
     * How big our buffer size is, this is the max size of each write.
     */
    public final static int BUFFER_OUT_SIZE_MAX
            = Sys.sysProp("NFL.USER_DATA_TRACKER.BUFFER_OUT_SIZE_MAX", 10_000_000);
    public final static int BUFFER_MIN
            = Sys.sysProp("NFL.USER_DATA_TRACKER.BUFFER_OUT_SIZE_MIN", 2_000_000);
    /**
     * outputDataQueue manager for batch writer.
     */
    private final CollectorManager collector;
    /**
     * The current UTC time within 20 mili-seconds accuracy.
     */
    final private AtomicLong approxTime = new AtomicLong(Dates.utcNow());
    protected LinkedTransferQueue<DataStoreRequest> queue = new LinkedTransferQueue<>();
    protected List<ScheduledExecutorService> scheduledExecutorServices = new ArrayList<>();
    protected List<Future<?>> futures = new ArrayList<>();
    long lastForceFlush = 0;
    private Logger logger = configurableLogger(this.getClass());
    private AtomicLong time = new AtomicLong(Dates.utcNow());
    private DataStoreConfig config;
    /**
     * Index / sequence of the line we just wrote to the JSON log.
     */
    private long index = 0;

    public AsyncFileWriterDataStore() {
        this.collector = new CollectorManager();
    }

    public void start() {
        collector.start(this);

        startWorker("AsyncFileWriterDataStore ", new Runnable() {
            @Override
            public void run() {

                try {
                    processSets();

                } catch (Exception ex) {
                    logger.error(ex, "AsyncFileWriterDataStore failed");
                }
            }
        });

    }

    private void processSets() {

        /** Current output buffer. */
        ByteBuffer buffer = collector.allocateBuffer(BUFFER_OUT_SIZE_MAX + BUFFER_MIN);

        DataStoreRequest op = null;

        DataStoreRequest data = null;

        BatchSetRequest dataMap = null;

        while (true) {

            try {
                op = queue.poll(10, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                Thread.interrupted();
            }

            if (op instanceof RemoveRequest) {
                data = op;
                dataMap = null;
            } else if (op instanceof SetRequest) {
                data = op;
                dataMap = null;
            } else if (op instanceof BatchSetRequest) {
                dataMap = (BatchSetRequest) op;
                data = null;
            }

            while (op != null) {

                if (data != null) {
                    putDataInBuffer(buffer, data.messageId(), data.clientId(), data.key(), data.payload());
                } else if (dataMap != null) {

                    long messageId = dataMap.messageId();
                    String clientId = dataMap.clientId();
                    final Map<String, String> payload = dataMap.payloadAsMap();
                    for (Map.Entry<String, String> entry : payload.entrySet()) {

                        putDataInBuffer(buffer, messageId, clientId, entry.getKey(), entry.getValue());
                    }

                }

                /* If the buffer is bigger than max or if the writer is waiting then send
                buffer on output channel.  */
                if (buffer.position() >= BUFFER_OUT_SIZE_MAX || collector.isWriterWaiting()) {

                    if (buffer.position() > BUFFER_MIN) {
                        collector.offer(buffer);
                        buffer = collector.allocateBuffer(BUFFER_OUT_SIZE_MAX + BUFFER_MIN);
                    }
                }

                op = queue.poll();

                if (op == null) {

                    if (buffer.position() > BUFFER_MIN && collector.isWriterWaiting()) {
                        collector.offer(buffer);
                        buffer = collector.allocateBuffer(BUFFER_OUT_SIZE_MAX + BUFFER_MIN);
                    } else {
                        long now = time.get();

                        long duration = now - lastForceFlush;

                        if (duration > 30_000) {
                            collector.offer(buffer);
                            buffer = collector.allocateBuffer(BUFFER_OUT_SIZE_MAX + BUFFER_MIN);
                        }
                    }
                } else {

                    if (op instanceof RemoveRequest) {
                        data = op;
                        dataMap = null;
                    } else if (op instanceof SetRequest) {
                        data = op;
                        dataMap = null;
                    } else if (op instanceof BatchSetRequest) {
                        dataMap = (BatchSetRequest) op;
                        data = null;
                    }

                }

            }

        }

    }

    private void putDataInBuffer(ByteBuffer buffer, long messageId, String clientId, String key, String value) {
        LogEntry logEntry = new LogEntry(time.get(), index++, messageId, clientId, key, value);
        buffer.put(toJson(logEntry).getBytes());
        buffer.put((byte)'\n');
    }

    @Override
    public void stop() {

        flush();
        Sys.sleep(2_000);
        collector.stop();

        for (Future future : futures) {

            try {
                future.cancel(true);
            } catch (Exception ex) {

            }
        }

        for (ScheduledExecutorService service : scheduledExecutorServices) {

            try {
                service.shutdown();
            } catch (Exception ex) {

            }
        }
    }

    public void flush() {

    }

    @Override
    public void clearStats() {

    }

    @Override
    public void sendStats(long now) {

    }

    @Override
    public void set(SetRequest setRequest) {

        queue.put(setRequest);
    }

    @Override
    public void remove(RemoveRequest removeRequest) {
        queue.put(removeRequest);
    }

    @Override
    public void addAll(BatchSetRequest batchSetRequest) {
        queue.put(batchSetRequest);

    }

    @Override
    public void get(GetRequest getRequest) {

    }

    @Override
    public void search(SearchRequest searchRequest) {

    }

    @Override
    public void batchRead(ReadBatchRequest request) {

    }

    /**
     * Calculate utc time. This gets called every 20 mili-seconds or so.
     */
    @Override
    public void tick(long time) {
        this.time.set(time);
         /*Foreign thread    every 20 or so mili-seconds so we don't spend too
         much time figuring out utc time. */
        approxTime.set(Dates.utcNow());
    }

    private void startWorker(final String name, final Runnable runnable) {

        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1,
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable runnable) {
                        Thread thread = new Thread(runnable);
                        thread.setName(this.getClass().getSimpleName() + " " + name);
                        return thread;
                    }
                }
        );

        scheduledExecutorServices.add(scheduledExecutorService);

        Future<?> future;

        future = scheduledExecutorService.scheduleAtFixedRate(
                runnable, 0, 100, TimeUnit.MILLISECONDS);
        futures.add(future);

    }

    public void init(DataStoreConfig config) {
        this.config = config;
        collector.init(config);
    }

}


/*
 * Copyright 2013-2014 Richard M. Hightower
 * 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.
 *
 * __________                              _____          __   .__
 * \______   \ ____   ____   ____   /\    /     \ _____  |  | _|__| ____    ____
 *  |    |  _//  _ \ /  _ \ /    \  \/   /  \ /  \\__  \ |  |/ /  |/    \  / ___\
 *  |    |   (  <_> |  <_> )   |  \ /\  /    Y    \/ __ \|    <|  |   |  \/ /_/  >
 *  |______  /\____/ \____/|___|  / \/  \____|__  (____  /__|_ \__|___|  /\___  /
 *         \/                   \/              \/     \/     \/       \//_____/
 *      ____.                     ___________   _____    ______________.___.
 *     |    |____ ___  _______    \_   _____/  /  _  \  /   _____/\__  |   |
 *     |    \__  \\  \/ /\__  \    |    __)_  /  /_\  \ \_____  \  /   |   |
 * /\__|    |/ __ \\   /  / __ \_  |        \/    |    \/        \ \____   |
 * \________(____  /\_/  (____  / /_______  /\____|__  /_______  / / ______|
 *               \/           \/          \/         \/        \/  \/
 */

package org.boon.cache;

import org.boon.core.reflection.ClassMeta;
import org.boon.core.reflection.MethodAccess;
import org.boon.core.reflection.Reflection;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import static org.boon.primitive.Int.roundUpToPowerOf2;

/**
 * Uses striping to allow more than one thread to operate on the cache at a time, but
 * due to the nature of the independent tracking of each strip the exact LRUness liveness
 * is only an approximation. The tradeoff is speed for LRU approximation.
 *
 * Basically the very least recently used is not always harvested but one of the stripes
 * will harvest one of the least recently used.
 *
 * So if you cache size was 10,000 and you had 8 CPUs, then a reaping would only
 * get one of the least recently used, but maybe not the most least but within the least +-8.
 *
 * @param <K>
 * @param <V>
 */
public class SimpleConcurrentCache<K, V> implements Cache<K, V> {

    /** Cache regions.*/
    final SimpleCache<K, V>[] cacheRegions;

    private static final boolean useFastHash;

    private transient final int hashSeed = randomHashSeed( this );

    /**
     * Cache class
     * @param <K> key
     * @param <V> value
     */
    private static class SimpleThreadSafeCache<K, V> extends SimpleCache<K, V> {
        private final ReadWriteLock readWriteLock;

        SimpleThreadSafeCache( final int limit, CacheType type, boolean fair ) {

            super( limit, type );
            readWriteLock = new ReentrantReadWriteLock( fair );
        }

        @Override
        public void put( K key, V value ) {
            readWriteLock.writeLock().lock();
            try {

                super.put( key, value );
            } finally {
                readWriteLock.writeLock().unlock();
            }
        }

        @Override
        public V get( K key ) {
            readWriteLock.writeLock().lock();
            V value;

            try {

                value = super.get( key );
            } finally {
                readWriteLock.writeLock().unlock();
            }
            return value;
        }

        @Override
        public void remove( K key ) {

            readWriteLock.writeLock().lock();

            try {

                super.remove( key );
            } finally {
                readWriteLock.writeLock().unlock();
            }

        }

        public V getSilent( K key ) {
            readWriteLock.writeLock().lock();

            V value;

            try {
                value = super.getSilent( key );
            } finally {
                readWriteLock.writeLock().unlock();
            }

            return value;

        }

        public int size() {
            readWriteLock.readLock().lock();
            int size = -1;
            try {
                size = super.size();
            } finally {
                readWriteLock.readLock().unlock();
            }
            return size;
        }

        public String toString() {
            readWriteLock.readLock().lock();
            String str;
            try {
                str = super.toString();
            } finally {
                readWriteLock.readLock().unlock();
            }
            return str;
        }

    }

    /**
     * New
     * @param limit limit of the cache size
     */
    public SimpleConcurrentCache( final int limit ) {
        this( limit, false, CacheType.LRU );
    }

    /**
     * New
     * @param limit limit of the cache size
     * @param type type of cache
     */
    public SimpleConcurrentCache( final int limit, CacheType type ) {
        this( limit, false, type );
    }

    /**
     * Limit of hte cache size
     * @param limit limit of the cache size
     * @param fair should we be fair?
     * @param type type of cache
     */
    public SimpleConcurrentCache( final int limit, boolean fair, CacheType type ) {
        int cores = Runtime.getRuntime().availableProcessors();
        int stripeSize = cores < 2 ? 8 : cores * 4;
        stripeSize = roundUpToPowerOf2( stripeSize );
        cacheRegions = new SimpleCache[ stripeSize ];
        for ( int index = 0; index < cacheRegions.length; index++ ) {
            cacheRegions[ index ] = new SimpleThreadSafeCache<>( limit / cacheRegions.length, type, fair );
        }
    }

    /**
     *
     * The more stripes the less accurate the LRU but the faster the access.
     * Life is all about engineering trade-offs.
     *
     * 100,000 entries ok with 100 stripes, but 10 entries not so cool with 10 stripes.
     *
     * Try to keep the accuracy in the 1% to 5% range.
     *
     * @param concurrency how many stripes
     * @param limit limit of the cache size
     * @param fair should we be fair?
     * @param type type of cache
     */
    public SimpleConcurrentCache( final int concurrency, final int limit, boolean fair, CacheType type ) {

        final int stripeSize = roundUpToPowerOf2( concurrency );
        cacheRegions = new SimpleCache[ stripeSize ];
        for ( int index = 0; index < cacheRegions.length; index++ ) {
            cacheRegions[ index ] = new SimpleThreadSafeCache<>( limit / cacheRegions.length, type, fair );
        }
    }

    /**
     * The more stripes the less accurate the LRU but the faster the access.
     * Life is all about engineering trade-offs.
     *
     * @param concurrency how many stripes
     * @param limit limit of the cache size
     * @param fair should we be fair?
     */
    public SimpleConcurrentCache( final int concurrency, final int limit, boolean fair ) {

        final int stripeSize = roundUpToPowerOf2( concurrency );
        cacheRegions = new SimpleCache[ stripeSize ];
        for ( int index = 0; index < cacheRegions.length; index++ ) {
            cacheRegions[ index ] = new SimpleThreadSafeCache<>( limit / cacheRegions.length, CacheType.LRU, fair );
        }
    }

    /** Get the map for this region. */
    private SimpleCache<K, V> map( K key ) {
        return cacheRegions[ stripeIndex( key ) ];
    }

    /**
     * Put the key in.
     * @param key the key
     * @param value the value
     */
    @Override
    public void put( K key, V value ) {

        map( key ).put( key, value );
    }

    /**
     * Take the key out.
     * @param key the key
     * @return value
     */
    @Override
    public V get( K key ) {
        return map( key ).get( key );
    }

    /**
     * for testing only.
     * @param key key to get the value with
     * @return the value
     */
    @Override
    public V getSilent( K key ) {
        return map( key ).getSilent( key );

    }

    /**
     * Remove the key
     * @param key the key
     */
    @Override
    public void remove( K key ) {
        map( key ).remove( key );
    }

    /** Get the size of the cache.
     *  This is not 100% accurate if cache is being concurrenly accessed.
     */
    @Override
    public int size() {
        int size = 0;
        for ( SimpleCache<K, V> cache : cacheRegions ) {
            size += cache.size();
        }
        return size;
    }

    /**
     * toString
     * @return string
     */
    public String toString() {

        StringBuilder builder = new StringBuilder();
        for ( SimpleCache<K, V> cache : cacheRegions ) {
            builder.append( cache.toString() ).append( '\n' );
        }

        return builder.toString();
    }

    static final MethodAccess randomHashSeedMethod;

    static {

        boolean yes;
        MethodAccess randomHashSeed = null;
        try {
            Class cls = Class.forName( "sun.misc.Hashing" );

            ClassMeta classMeta = ClassMeta.classMeta(cls);

            yes = classMeta.respondsTo("randomHashSeed", Object.class)
                    && classMeta.classMethods().contains("randomHashSeed");

            randomHashSeed = classMeta.method("randomHashSeed");

        } catch ( Exception ex ) {
            yes = false;
        }

        useFastHash = yes;
        randomHashSeedMethod = randomHashSeed;
    }

    /** Create a hash seed.
     *
     * @param instance for me?
     * @return hash seed
     */
    private static int randomHashSeed( SimpleConcurrentCache instance ) {

        if ( useFastHash ) {
            //return sun.misc.Hashing.randomHashSeed( instance );
            return (int) randomHashSeedMethod.invoke(instance);
        }

        return 0;
    }

    /**
     * Calculate the hash.
     * @param k key
     * @return
     */
    private final int hash( Object k ) {
        int h = hashSeed;

        h ^= k.hashCode();

        h ^= ( h >>> 20 ) ^ ( h >>> 12 );
        return h ^ ( h >>> 7 ) ^ ( h >>> 4 );
    }

    /**
     * Returns index for hash code h.
     */
    static int indexFor( int h, int length ) {
        return h & ( length - 1 );
    }

    /**
     * Striping
     * @param key key to stripe
     * @return stripe
     */
    private int stripeIndex( K key ) {
        return indexFor( hash( key ), cacheRegions.length );
    }

}


package org.boon.slumberdb.leveldb;

import org.boon.Exceptions;
import org.boon.Logger;
import org.boon.slumberdb.entries.Entry;
import org.boon.slumberdb.KeyValueIterable;
import org.boon.slumberdb.KeyValueStore;
import org.fusesource.leveldbjni.JniDBFactory;
import org.iq80.leveldb.*;
import org.iq80.leveldb.impl.Iq80DBFactory;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

import static org.boon.Boon.configurableLogger;

/**
 * Stores key and values in LevelDB
 */
public class LevelDBKeyValueStore implements KeyValueStore<byte[], byte[]> {

    /**
     * The "filename" of the level db file.
     * This is really a directory.
     */
    private final String fileName;

    /**
     * Flag to denote if we are using JNI or not.
     */
    private final boolean usingJNI;

    /**
     * Setup the options for the database.
     * Also used for flush operation which closes the DB
     * and then reopens it.
     */
    private final Options options;
    /**
     * Actual database implementation.
     */
    DB database;
    WriteOptions flush = new WriteOptions();
    {
        flush.sync(true);
    }
    WriteOptions writeOptions = new WriteOptions();
    AtomicLong putAllWriteCount = new AtomicLong();
    /**
     * Logger.
     */
    private Logger logger = configurableLogger(LevelDBKeyValueStore.class);

    /**
     * Creates a level db database with the default options.
     */
    public LevelDBKeyValueStore(String fileName) {
        this(fileName, null, false);
    }

    /**
     * Creates a level db database with the options passed
     * Also allows setting up logging or not.
     *
     * @param fileName fileName
     * @param options  options
     * @param log      turn on logging
     */
    public LevelDBKeyValueStore(String fileName, Options options, boolean log) {
        this.fileName = fileName;
        File file = new File(fileName);

        if (options == null) {
            logger.info("Using default options");
            options = defaultOptions();
        }

        this.options = options;

        if (log) {
            options.logger(new org.iq80.leveldb.Logger() {
                @Override
                public void log(String message) {
                    logger.info("FROM DATABASE LOG", message);
                }
            });
        }

        usingJNI = openDB(file, options);
    }

    /**
     * Configures default options.
     *
     * @return
     */
    private Options defaultOptions() {

        Options options = new Options();
        options.createIfMissing(true);
        options.blockSize(32_768); //32K
        options.cacheSize(67_108_864);//64MB
        return options;
    }

    /**
     * Opens the database
     *
     * @param file    filename to open
     * @param options options
     * @return
     */
    private boolean openDB(File file, Options options) {

        try {
            database = JniDBFactory.factory.open(file, options);
            logger.info("Using JNI Level DB");
            return true;
        } catch (IOException ex1) {
            try {
                database = Iq80DBFactory.factory.open(file, options);
                logger.info("Using Java Level DB");
                return false;
            } catch (IOException ex2) {
                return Exceptions.handle(Boolean.class, ex2);
            }
        }

    }

    /**
     * Puts an item in the key value store.
     *
     * @param key   key
     * @param value value
     */
    @Override
    public void put(byte[] key, byte[] value) {
        database.put(key, value);
    }

    /**
     * Puts values into the key value store in batch mode
     *
     * @param values values
     */
    @Override
    public void putAll(Map<byte[], byte[]> values) {

        WriteBatch batch = database.createWriteBatch();

        try {

            for (Map.Entry<byte[], byte[]> entry : values.entrySet()) {
                batch.put(entry.getKey(), entry.getValue());
            }

            if (putAllWriteCount.addAndGet(values.size()) > 10_000) {
                putAllWriteCount.set(0);
                database.write(batch, flush);
            } else {
                database.write(batch, writeOptions);
            }

        } finally {
            closeBatch(batch);
        }
    }

    public void flush() {

        WriteBatch batch = database.createWriteBatch();

        try {

            database.write(batch, flush);

        } finally {
            closeBatch(batch);
        }
    }

    private void closeBatch(WriteBatch batch) {
        try {
            batch.close();
        } catch (IOException e) {
            Exceptions.handle(e);
        }
    }

    /**
     * Remove all of the keys passed.
     *
     * @param keys keys
     */
    @Override
    public void removeAll(Iterable<byte[]> keys) {

        WriteBatch batch = database.createWriteBatch();

        try {

            for (byte[] key : keys) {
                batch.delete(key);
            }

            database.write(batch);

        } finally {
            closeBatch(batch);
        }

    }

    /**
     * Remove items from list
     *
     * @param key
     */
    @Override
    public void remove(byte[] key) {
        database.delete(key);
    }

    /**
     * Search to a certain location.
     *
     * @param startKey startKey
     * @return
     */
    @Override
    public KeyValueIterable<byte[], byte[]> search(byte[] startKey) {

        final DBIterator iterator = database.iterator();
        iterator.seek(startKey);

        return new KeyValueIterable<byte[], byte[]>() {
            @Override
            public void close() {
                closeIterator(iterator);
            }

            @Override
            public Iterator<Entry<byte[], byte[]>> iterator() {
                return new Iterator<Entry<byte[], byte[]>>() {
                    @Override
                    public boolean hasNext() {
                        return iterator.hasNext();
                    }

                    @Override
                    public Entry<byte[], byte[]> next() {

                        Map.Entry<byte[], byte[]> next = iterator.next();
                        return new Entry<>(next.getKey(), next.getValue());
                    }

                    @Override
                    public void remove() {
                        iterator.remove();
                    }
                };
            }
        };

    }

    private void closeIterator(DBIterator iterator) {
        try {
            iterator.close();
        } catch (IOException e) {
            Exceptions.handle(e);
        }
    }

    /**
     * Load all of the key/values from the store.
     *
     * @return
     */
    @Override
    public KeyValueIterable<byte[], byte[]> loadAll() {

        final DBIterator iterator = database.iterator();
        iterator.seekToFirst();

        return new KeyValueIterable<byte[], byte[]>() {
            @Override
            public void close() {
                closeIterator(iterator);
            }

            @Override
            public Iterator<Entry<byte[], byte[]>> iterator() {
                return new Iterator<Entry<byte[], byte[]>>() {
                    @Override
                    public boolean hasNext() {
                        return iterator.hasNext();
                    }

                    @Override
                    public Entry<byte[], byte[]> next() {

                        Map.Entry<byte[], byte[]> next = iterator.next();
                        return new Entry<>(next.getKey(), next.getValue());
                    }

                    @Override
                    public void remove() {
                        iterator.remove();
                    }
                };
            }
        };
    }

    @Override
    public Collection<byte[]> loadAllKeys() {

        List<byte[]> keys = new ArrayList<>();
        DBIterator iterator = null;

        try {
            iterator = database.iterator();

            iterator.seekToFirst();

            while (iterator.hasNext()) {
                final Map.Entry<byte[], byte[]> next = iterator.next();
                keys.add(next.getKey());
            }
        } finally {
            try {
                if (iterator != null) {
                    iterator.close();
                }
            } catch (IOException e) {
                Exceptions.handle(e);
            }
        }
        return keys;
    }

    /**
     * Get the key from the store
     *
     * @param key key
     * @return value from store at location key
     */
    @Override
    public byte[] load(byte[] key) {
        return database.get(key);
    }

    /**
     * Keys are expected to be sorted
     *
     * @param keys
     * @return
     */
    @Override
    public Map<byte[], byte[]> loadAllByKeys(Collection<byte[]> keys) {

        if (keys == null || keys.size() == 0) {
            return Collections.EMPTY_MAP;
        }

        Map<byte[], byte[]> results = new LinkedHashMap<>(keys.size());

        DBIterator iterator = null;

        try {
            iterator = database.iterator();

            iterator.seek(keys.iterator().next());

            while (iterator.hasNext()) {
                final Map.Entry<byte[], byte[]> next = iterator.next();
                results.put(next.getKey(), next.getValue());
            }
        } finally {
            try {
                if (iterator != null) {
                    iterator.close();
                }
            } catch (IOException e) {
                Exceptions.handle(e);
            }
        }
        return results;

    }

    /**
     * Close the database connection.
     */
    @Override
    public void close() {
        try {
            flush();
            database.close();
        } catch (Exception e) {
            Exceptions.handle(e);
        }
    }

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

    @Override
    public boolean isClosed() {
        return false;
    }

}