/*
 * 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 org.boon.core.reflection.BeanUtils;
import org.boon.core.reflection.MapObjectConversion;
import org.boon.primitive.CharBuf;

import java.util.*;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArraySet;

public class Sets {

    /* Creation */
    public static <V> Set<V> set( Collection<V> collection ) {
        if (collection instanceof Set) {
            return (Set <V>) collection;
        }
        if (collection==null) {
            return Collections.EMPTY_SET;
        }
        return new LinkedHashSet<>( collection );
    }

    public static <V> Enumeration<V> enumeration( final Set<V> set ) {
        final Iterator<V> iter = set.iterator();
        return new Enumeration<V>() {
            @Override
            public boolean hasMoreElements() {
                return iter.hasNext();
            }

            @Override
            public V nextElement() {
                return iter.next();
            }
        };

    }

    public static <V> Set<V> set( Class<V> clazz ) {
        return new LinkedHashSet<>();
    }

    public static <V> Set<V> set( Iterable<V> iterable ) {
        Set<V> set = new LinkedHashSet<>();
        for ( V o : iterable ) {
            set.add( o );
        }
        return set;
    }

    public static <V> Set<V> set( Enumeration<V> enumeration ) {
        Set<V> set = new LinkedHashSet<>();
        while ( enumeration.hasMoreElements() ) {
            set.add( enumeration.nextElement() );
        }
        return set;
    }

    public static <V> Set<V> set( Iterator<V> iterator ) {
        Set<V> set = new LinkedHashSet<>();
        while ( iterator.hasNext() ) {
            set.add( iterator.next() );
        }
        return set;
    }

    @SafeVarargs
    public static <V> Set<V> set( final V... array ) {

        Set<V> set = new LinkedHashSet<>();

        for ( V v : array ) {
            set.add( v );
        }
        return set;
    }

    @SafeVarargs
    public static <V> Set<V> set( int size, final V... array  ) {

        int index=0;
        Set<V> set = new LinkedHashSet<>();

        for ( V v : array ) {
            set.add( v );
            index++;
            if (index == size) {
                break;
            }
        }
        return set;
    }

    public static <V> NavigableSet<V> sortedSet( Iterator<V> iterator ) {
        NavigableSet<V> set = new TreeSet<>();
        while ( iterator.hasNext() ) {
            set.add( iterator.next() );
        }
        return set;
    }

    public static <V> NavigableSet<V> sortedSet( Class<V> clazz ) {
        return new TreeSet<>();
    }

    public static <V> NavigableSet<V> sortedSet( Iterable<V> iterable ) {
        NavigableSet<V> set = new TreeSet<>();
        for ( V o : iterable ) {
            set.add( o );
        }
        return set;
    }

    public static <V> NavigableSet<V> sortedSet( Enumeration<V> enumeration ) {
        NavigableSet<V> set = new TreeSet<>();
        while ( enumeration.hasMoreElements() ) {
            set.add( enumeration.nextElement() );
        }
        return set;
    }

    @SafeVarargs
    public static <V> NavigableSet<V> sortedSet( final V... array ) {
        NavigableSet<V> set = new TreeSet<>();

        for ( V v : array ) {
            set.add( v );
        }
        return set;
    }

    public static <V> NavigableSet<V> sortedSet( Collection<V> collection ) {
        return new TreeSet<>( collection );
    }

    public static <V> NavigableSet<V> safeSortedSet( Iterator<V> iterator ) {
        NavigableSet<V> set = new ConcurrentSkipListSet<>();
        while ( iterator.hasNext() ) {
            set.add( iterator.next() );
        }
        return set;
    }

    public static <V> NavigableSet<V> safeSortedSet( Class<V> clazz ) {
        return new ConcurrentSkipListSet<>();
    }

    public static <V> NavigableSet<V> safeSortedSet( Iterable<V> iterable ) {
        NavigableSet<V> set = new ConcurrentSkipListSet<>();
        for ( V o : iterable ) {
            set.add( o );
        }
        return set;
    }

    public static <V> NavigableSet<V> safeSortedSet( Enumeration<V> enumeration ) {
        NavigableSet<V> set = new ConcurrentSkipListSet<>();
        while ( enumeration.hasMoreElements() ) {
            set.add( enumeration.nextElement() );
        }
        return set;
    }

    @SafeVarargs
    public static <V> NavigableSet<V> safeSortedSet( final V... array ) {

        NavigableSet<V> set = new ConcurrentSkipListSet<>();

        for ( V v : array ) {
            set.add( v );
        }
        return set;

    }

    public static <V> NavigableSet<V> safeSortedSet( Collection<V> collection ) {
        return new ConcurrentSkipListSet<>( collection );
    }

    public static <V> Set<V> safeSet( Class<V> clazz ) {
        return new CopyOnWriteArraySet<>();
    }

    public static <V> Set<V> safeSet( Iterable<V> iterable ) {
        Set<V> set = new CopyOnWriteArraySet<>();
        for ( V o : iterable ) {
            set.add( o );
        }
        return set;
    }

    public static <V> Set<V> safeSet( Enumeration<V> enumeration ) {
        Set<V> set = new CopyOnWriteArraySet<>();
        while ( enumeration.hasMoreElements() ) {
            set.add( enumeration.nextElement() );
        }
        return set;
    }

    public static <V> Set<V> safeSet( Iterator<V> iterator ) {
        Set<V> set = new CopyOnWriteArraySet<>();
        while ( iterator.hasNext() ) {
            set.add( iterator.next() );
        }
        return set;
    }

    @SafeVarargs
    public static <V> Set<V> safeSet( final V... array ) {

        List<V> list = Lists.list( array );
        Set<V> set = new CopyOnWriteArraySet<>( list );

        return set;
    }

    public static <V> Set<V> safeSet( Collection<V> collection ) {
        return new CopyOnWriteArraySet<>( collection );
    }

    @Universal
    public static int len( Set<?> set ) {
        return set.size();
    }

    @Universal
    public static <V> boolean in( V value, Set<?> set ) {
        return set.contains( value );
    }

    @Universal
    public static <V> void add( Set<V> set, V value ) {
        set.add( value );
    }

    @Universal
    public static <T> T idx( NavigableSet<T> set, final T index ) {

        return set.higher( index );
    }

    @Universal
    public static <T> T idx( Set<T> set, final T index ) {

        if ( set instanceof NavigableSet ) {
            return idx( ( NavigableSet<T> ) set, index );
        } else {
            throw new IllegalArgumentException( "Set must be a NavigableSet for idx operation to work" );
        }
    }

    public static <T> T after( NavigableSet<T> set, final T index ) {

        return set.higher( index );
    }

    public static <T> T before( NavigableSet<T> set, final T index ) {

        return set.lower( index );
    }

    @Universal
    public static <V> SortedSet<V> slc( NavigableSet<V> set, V startIndex, V endIndex ) {
        return set.subSet( startIndex, endIndex );
    }

    @Universal
    public static <V> SortedSet<V> slcEnd( NavigableSet<V> set, V fromIndex ) {
        return set.tailSet( fromIndex );
    }

    @Universal
    public static <V> SortedSet<V> slc( NavigableSet<V> set, V toIndex ) {
        return set.headSet( toIndex );
    }

    @Universal
    public static <V> Set<V> copy( HashSet<V> collection ) {
        return new LinkedHashSet<>( collection );
    }

    @Universal
    public static <V> NavigableSet<V> copy( TreeSet<V> collection ) {
        return new TreeSet<>( collection );
    }

    @Universal
    public static <V> Set<V> copy( CopyOnWriteArraySet<V> collection ) {
        return new CopyOnWriteArraySet<>( collection );
    }

    @Universal
    public static <V> NavigableSet<V> copy( ConcurrentSkipListSet<V> collection ) {
        return new ConcurrentSkipListSet<>( collection );
    }

    @Universal
    public static <V> NavigableSet<V> copy( NavigableSet<V> collection ) {
        if ( collection instanceof ConcurrentSkipListSet ) {
            return copy( ( ConcurrentSkipListSet<V> ) collection );
        } else {
            return copy( ( TreeSet<V> ) collection );
        }
    }

    @Universal
    public static <V> Set<V> copy( Set<V> collection ) {
        if ( collection instanceof NavigableSet ) {

            return copy( ( NavigableSet<V> ) collection );

        } else if ( collection instanceof CopyOnWriteArraySet ) {

            return copy( ( CopyOnWriteArraySet<V> ) collection );

        } else {

            return copy( ( LinkedHashSet<V> ) collection );
        }
    }

    public static <V> Set<V> deepCopy( Collection<V> collection ) {
        Set<V> newSet = new LinkedHashSet<>(collection.size());

        for (V v : collection) {
            newSet.add(BeanUtils.copy(v));
        }
        return newSet;
    }

    public static <V> Set<V> deepCopyToSet( Collection<V> src,  Set<V> dst) {

        for (V v : src) {
            dst.add( BeanUtils.copy( v ));
        }
        return  dst;
    }

    public static <V,T> List<T> deepCopy( Collection<V> src, Class<T> dest  ) {
        List<T> list = new ArrayList<>(src.size());

        for (V v : src) {
            list.add( BeanUtils.createFromSrc( v, dest ));
        }
        return list;
    }

    @Universal
    public static <V> Set<V> deepCopy( Set<V> set ) {
        if ( set instanceof LinkedHashSet ) {
            return deepCopyToSet(set, new LinkedHashSet<V>());
        } else if ( set instanceof CopyOnWriteArraySet ) {
            return deepCopyToSet(set, new CopyOnWriteArraySet<V>());
        } else if ( set instanceof HashSet ) {
            return deepCopyToSet(set, new HashSet<V>());
        } else {
            return deepCopy( (Collection)set);
        }
    }

    //end universal

    public static List<Map<String, Object>> toListOfMaps( Set<?> set ) {
        return MapObjectConversion.toListOfMaps( set );

    }

    public static <T> Set<T> setFromProperty( Class<T> propertyType, String propertyPath, Collection<?> list ) {
        Set<T> newSet = new LinkedHashSet<>( list.size() );

        for ( Object item : list ) {
            T newItem = ( T ) BeanUtils.idx( item, propertyPath );
            newSet.add(newItem);
        }

        return newSet;

    }

    public static <T> Set<T> setFromProperty( Class<T> propertyType, String propertyPath, Iterable<?> list ) {
        Set<T> newSet = new LinkedHashSet<>(  );

        for ( Object item : list ) {
            T newItem = ( T ) BeanUtils.idx( item, propertyPath );
            newSet.add(newItem);
        }

        return newSet;

    }

    public static String toPrettyJson(Set set) {
        CharBuf buf = CharBuf.createCharBuf();
        return buf.prettyPrintCollection(set, false, 0).toString();
    }

}


package org.boon.slumberdb.stores.log;

import org.boon.slumberdb.service.config.DataStoreConfig;
import org.boon.Exceptions;
import org.boon.IO;
import org.boon.core.Sys;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import static org.boon.Boon.puts;
import static org.boon.primitive.Lng.str;

/**
 * Created by Richard on 6/27/14.
 */
public class BatchFileWriter implements TimeAware {

    public final static String FORMAT_PATTERN;
    public final static String SERVER_NAME;

    /**
     * Default log size 20,000,000, if beyond 20M create a new log *
     */
    public final static int FILE_SIZE_BYTES;

    /**
     * Default log batch time 10 minutes, if beyond ten, then create a new log.  *
     */
    public final static int FILE_TIMEOUT_MINUTES;
    public final static int FILE_TIMEOUT_MILISECONDS;

    /**
     * Default log size 20,000,000, if beyond 20M create a new log *
     */
    public final static int FLUSH_EVERY_N_BYTES;

    static {
        LogFilesConfig lfc = LogFilesConfig.load();
        FORMAT_PATTERN = Sys.sysProp("NFL.USER_DATA_TRACKER.FILE_NAME_FORMAT_PATTERN", lfc.logFilesNameFormatPattern);
        SERVER_NAME = Sys.sysProp("NFL.USER_DATA_TRACKER.SERVER_NAME", lfc.logFilesServerName);
        FILE_SIZE_BYTES = Integer.parseInt(System.getProperty("NFL.USER_DATA_TRACKER.FILE_SIZE_BYTES", lfc.logFileSizeBytes.toString()));
        FILE_TIMEOUT_MINUTES = Integer.parseInt(System.getProperty("NFL.USER_DATA_TRACKER.FILE_TIMEOUT_MINUTES", lfc.logFileTimeoutMinutes.toString()));
        FILE_TIMEOUT_MILISECONDS = FILE_TIMEOUT_MINUTES * 60 * 1_000;
        FLUSH_EVERY_N_BYTES = Integer.parseInt(System.getProperty("NFL.USER_DATA_TRACKER.FLUSH_EVERY_N_BYTES", lfc.logFileFlushEveryBytes.toString()));
    }

    private volatile long buffersSent = 0;
    private volatile long bytesTransferred = 0;
    private volatile long bytesSinceLastFlush = 0;
    private volatile long totalBytesTransferred = 0;
    private volatile long numFiles = 0;
    /**
     * Tracks the current time, used for log create timeouts and log flush.
     */
    private AtomicLong time = new AtomicLong();
    private AtomicBoolean fileTimeOut = new AtomicBoolean();
    private AtomicLong fileStartTime = new AtomicLong();
    private AtomicBoolean error = new AtomicBoolean(false);
    private Path outputDir;

    private volatile String fileName;
    private SeekableByteChannel outputStream;
    private ByteBuffer outputBuffer;
    private boolean dirty;

    private DataStoreConfig config;

    /**
     * Recieves a tick from our clock.
     */
    public void tick(long time) {
        this.time.set(time);
        long startTime = fileStartTime.get();
        long duration = time - startTime;

        if (duration > FILE_TIMEOUT_MILISECONDS) {
            fileTimeOut.set(true);
        }
    }

    /**
     * Reads the number of files created for status check.
     */
    public long numFiles() {
        return numFiles;
    }

    /**
     * Reads total bytes transferred for status check.
     */
    public long totalBytesTransferred() {
        return totalBytesTransferred;
    }

    /**
     * flush to disk.
     */
    public boolean syncToDisk() {

        /** if we have a stream and we are dirty then flush. */
        if (outputStream != null && dirty) {

            try {
                //outputStream.flush ();

                if (outputStream instanceof FileChannel) {
                    FileChannel channel = (FileChannel) outputStream;
                    channel.force(true);
                }
                dirty = false;
                return true;
            } catch (Exception ex) {
                cleanupOutputStream();
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * Attempts to close down log stream.
     */
    private void cleanupOutputStream() {

        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {

                e.printStackTrace(System.err);
            } finally {
                outputStream = null;
            }
        }

    }

    /**
     * Used for status message to report the bytes sent to files.
     * So bytesTransferred is copied from a register setting to main memory (sort of).
     */
    public long bytesTransferred() {
        return bytesTransferred;
    }

    public long averageBufferSize() {
        return buffersSent != 0 ? totalBytesTransferred / buffersSent : 0;
    }

    /**
     * Writes a buffer of data to the log system.
     */
    public void nextBufferToWrite(final ByteBuffer bufferOut) throws InterruptedException {
        dirty = true;
        final int size = bufferOut.limit();

        write(bufferOut);

        /* only increment bytes transferred after a successful write. */
        if (!error.get()) {
            totalBytesTransferred += size;
            bytesTransferred += size;
            bytesSinceLastFlush += size;
            buffersSent++;
        }

        if (this.bytesTransferred >= FILE_SIZE_BYTES || fileTimeOut.get()) {

            try {
                outputStream.close();
            } catch (IOException e) {
                cleanupOutputStream();
                e.printStackTrace(System.err);
            } finally {
                outputStream = null;
            }
        }

    }

    /**
     * Write the actual data to disk.
     */
    private void write(final ByteBuffer bufferOut) throws InterruptedException {

        initOutputStream();
        try {
            if (outputStream != null) {

                outputStream.write(bufferOut);

            } else {
                error.set(true);
            }

            if (bytesSinceLastFlush > FLUSH_EVERY_N_BYTES) {
                syncToDisk();
                bytesSinceLastFlush = 0;
            }
        } catch (ClosedByInterruptException cbie) {
            throw new InterruptedException("File closed by interruption");
        } catch (Exception e) {
            cleanupOutputStream();
            error.set(true);
            e.printStackTrace(System.err);

            diagnose();

            Exceptions.handle(e);
        }

    }

    public void diagnose() {

        Objects.requireNonNull(fileName(), "the filename should not be null, " +
                "you have misconfigured this service, fatal error");

        final Path path =
                IO.path(fileName());

        puts("in diagnose");

        puts("Filename           :", path.toAbsolutePath());
        puts("File exists?       :", Files.exists(path));
        puts("File writeable?    :", Files.isWritable(path));

        puts("Output dir                :", outputDir.toAbsolutePath());
        puts("Output dir  exists?       :", Files.exists(outputDir));
        puts("Output dir  writeable?    :", Files.isWritable(outputDir));

        if (!Files.isWritable(outputDir) || !Files.exists(outputDir)) {
            error.set(true);
        }

        try {
            FileStore fileStore = Files.getFileStore(path.getParent());
            puts("Total space           :", str(fileStore.getTotalSpace()));
            puts("Use-able space        :", str(fileStore.getUsableSpace()));
            puts("Free Space            :", str(fileStore.getUnallocatedSpace()));
            puts("type                  :", fileStore.type());
            puts("name                  :", fileStore.name());
            puts("read-only             :", fileStore.isReadOnly());

        } catch (IOException e) {

            e.printStackTrace();
        }
    }

    public String outputDir() {
        return outputDir.toString();
    }

    /**
     * Initialize the output stream.
     */
    private void initOutputStream() {

        long time = this.time.get();

        if (error.get() || this.totalBytesTransferred == 0) {
            cleanupOutputStream();
            error.set(false);
            time = System.nanoTime() / 1_000_000;
        }

        if (outputStream != null) {
            return;
        }

        fileName = LogFilesConfig.getLogFileName(FORMAT_PATTERN, outputDirPath(), numFiles, time, SERVER_NAME);
        try {
            fileTimeOut.set(false);
            outputStream = streamCreator();

            fileStartTime.set(time);

            bytesTransferred = 0;
            bytesSinceLastFlush = 0;

        } catch (Exception ex) {
            cleanupOutputStream();
            error.set(true);
            Exceptions.handle(ex);
        } finally {
            numFiles++;
        }
    }

    protected SeekableByteChannel streamCreator() throws Exception {

        SeekableByteChannel byteChannel = Files.newByteChannel(IO.path(fileName), StandardOpenOption.CREATE, StandardOpenOption.WRITE);

        if (outputBuffer == null) {
            outputBuffer = ByteBuffer.allocateDirect(FILE_SIZE_BYTES + 1_000_000);
        }

        return byteChannel;
    }

    public String fileName() {
        return fileName;
    }

    protected Path outputDirPath() {
        return IO.path(this.outputDir);
    }

    public void setError() {
        this.error.set(true);
    }

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

        this.outputDir = IO.path(config.outputDirectory());

    }

}


package org.boon.slumberdb.base;

import org.boon.core.Function;
import org.boon.slumberdb.entries.Entry;
import org.boon.slumberdb.KeyValueIterable;
import org.boon.slumberdb.KeyValueStore;
import org.boon.slumberdb.serialization.NoOpSerializer;

import java.util.*;

/**
 * Created by Richard on 4/9/14.
 */
public class BaseStringStringKeyValueStore<K, V> {
    /**
     * Store that does the actual writing to DB (likely).
     */
    protected final KeyValueStore<String, String> store;

    protected Function<String, K> keyObjectConverter = new NoOpSerializer<>();
    protected Function<K, String> keySerializer = new NoOpSerializer<>();

    protected Function<String, V> valueObjectConverter;
    protected Function<V, String> valueSerializer;

    public BaseStringStringKeyValueStore(final KeyValueStore<String, String> store) {
        this.store = store;
    }

    /**
     * Remove a key from the store.
     *
     * @param key
     */
    public void remove(K key) {

        store.remove(toKeyString(key));
    }

    /**
     * Search for a key in the key / value store.
     *
     * @param startKey
     * @return
     */
    public KeyValueIterable<K, V> search(K startKey) {
        final KeyValueIterable<String, String> search = store.search(toKeyString(startKey));
        final Iterator<Entry<String, String>> iterator = search.iterator();
        return new KeyValueIterable<K, V>() {
            @Override
            public void close() {
                search.close();
            }

            @Override
            public Iterator<Entry<K, V>> iterator() {
                return new Iterator<Entry<K, V>>() {
                    @Override
                    public boolean hasNext() {
                        return iterator.hasNext();
                    }

                    @Override
                    public Entry<K, V> next() {
                        final Entry<String, String> next = iterator.next();

                        return new Entry<>(toKeyObject(next.key()),
                                toValueObject(next.value()));
                    }

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

    /**
     * Load all of the key / values from the store.
     *
     * @return
     */
    public KeyValueIterable<K, V> loadAll() {
        final KeyValueIterable<String, String> search = store.loadAll();
        final Iterator<Entry<String, String>> iterator = search.iterator();
        return new KeyValueIterable<K, V>() {
            @Override
            public void close() {
                search.close();
            }

            @Override
            public Iterator<Entry<K, V>> iterator() {
                return new Iterator<Entry<K, V>>() {
                    @Override
                    public boolean hasNext() {
                        return iterator.hasNext();
                    }

                    @Override
                    public Entry<K, V> next() {
                        final Entry<String, String> next = iterator.next();

                        return new Entry<>(toKeyObject(next.key()),
                                toValueObject(next.value()));
                    }

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

    public Collection<K> loadAllKeys() {
        final Collection<String> keys = store.loadAllKeys();

        final Set<K> set = new HashSet<>();

        for (String key : keys) {
            set.add(toKeyObject(key));
        }

        return set;
    }

    /**
     * Get a value from the key value store.
     *
     * @param key key
     * @return
     */
    public V load(K key) {
        final String value = store.load(toKeyString(key));
        if (value != null) {
            return toValueObject(value);
        } else {
            return null;
        }
    }

    public Map<K, V> loadAllByKeys(Collection<K> keys) {

        Set<K> keySet = new TreeSet<>(keys);

        List<String> byteKeys = new ArrayList<>();

        for (K key : keySet) {
            String bKey = toKeyString(key);
            byteKeys.add(bKey);
        }
        final Map<String, String> map = store.loadAllByKeys(byteKeys);
        final Map<K, V> results = new LinkedHashMap<>();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            results.put(toKeyObject(entry.getKey()), toValueObject(entry.getValue()));
        }

        return results;
    }

    /**
     * Close the store.
     */
    public void close() {
        store.close();

    }

    /**
     * Convert a binary array to a String.
     *
     * @param key key
     * @return
     */
    protected K toKeyObject(String key) {
        return keyObjectConverter.apply(key);
    }

    /**
     * Use serializer to read this byte array as an object.
     *
     * @param value value read
     * @return new object read from key/value store
     */
    protected V toValueObject(String value) {
        return valueObjectConverter.apply(value);
    }

    /**
     * Converts an object to a byte array.
     *
     * @param v object to convert
     * @return byte array representation of object.
     */
    protected String toValueString(V v) {
        return this.valueSerializer.apply(v);
    }

    /**
     * Convert a String key to bytes.
     *
     * @param key key to convert
     * @return value
     */
    protected String toKeyString(K key) {
        return this.keySerializer.apply(key);
    }

    /**
     * Put a value in the key/value store.
     *
     * @param key   key
     * @param value value
     */
    public void put(K key, V value) {
        store.put(toKeyString(key), toValueString(value));
    }

    /**
     * Put all of these values in the key value store.
     *
     * @param values values
     */
    public void putAll(Map<K, V> values) {
        Set<Map.Entry<K, V>> entries = values.entrySet();
        Map<String, String> map = new HashMap<>(values.size());

        for (Map.Entry<K, V> entry : entries) {
            map.put(toKeyString(entry.getKey()), toValueString(entry.getValue()));
        }

        store.putAll(map);
    }

    /**
     * Remove all of these values from the key value store.
     *
     * @param keys
     */
    public void removeAll(Iterable<K> keys) {
        List<String> list = new ArrayList<>();

        for (K key : keys) {
            list.add(toKeyString(key));
        }

        store.removeAll(list);
    }

    public boolean isOpen() {
        return store.isOpen();
    }

    public boolean isClosed() {
        return store.isClosed();
    }

}


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

import org.boon.core.Sys;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

/* This timer is used for caches and such
 * It is a default timer.
 * It should never be used direct.
 * There should always be a fall back.
 * By default this will reset the time every 100 times it is called to the latest
 * System.nanoTime
 * At most 1 thread will incur the cost of calling nanoTime about every 100 invocations.
 *
 */
public class TimeKeeperBasic implements TimeKeeper {

    private final AtomicInteger callEveryNowAndThen = new AtomicInteger();
    private final AtomicLong time = new AtomicLong();
    private final int TIME_KEEPER_FREQUENCY = Sys.sysProp( "org.boon.timekeeper.frequency", 100 );

    private final ReentrantLock lock = new ReentrantLock();

    private final AtomicLong lastDeltaTime = new AtomicLong();

    @Override
    public final long time() {

        long limit = callEveryNowAndThen.incrementAndGet();
        long time;
        boolean shouldGetTime = false;

        if ( limit > TIME_KEEPER_FREQUENCY ) {
            callEveryNowAndThen.set( 0 );
            shouldGetTime = true;

        }

        /* Somewhat Ensure two calls to time do not return the exact same value. */
        time = this.time.get() + limit;

        if ( !shouldGetTime && ( limit % 20 == 0 ) ) {
            checkForDrift( time );
        }

        return time;

    }

    /* Never let the drift get greater than 200 ms. */
    private long checkForDrift( long time ) {
        long delta = Math.abs( System.currentTimeMillis() - time );
        long lastDelta = lastDeltaTime.getAndSet( delta );
        if ( delta > lastDelta + 200 ) {
            return getTheTime( time );
        }
        return time;
    }

    private long getTheTime( long time ) {
        boolean locked = lock.tryLock(); //make sure two or more threads are not calling nanoTime.
        if ( locked ) {
            try {
                //I don't want more than one thread calling nanoTime
                time = System.nanoTime() / 1_000_000;
                this.time.set( time );
                return time;

            } finally {
                lock.unlock();
            }
        }
        return time;
    }

}


package org.boon.slumberdb.base;

import org.boon.cache.SimpleCache;
import org.boon.core.Function;
import org.boon.slumberdb.entries.Entry;
import org.boon.slumberdb.KeyValueIterable;
import org.boon.slumberdb.KeyValueStore;

import java.util.*;

/**
 * Created by Richard on 4/9/14.
 */
public abstract class BaseSimpleSerializationKeyValueStore<K, V> {
    /**
     * Store that does the actual writing to DB (likely).
     */
    protected final KeyValueStore<byte[], byte[]> store;

    protected Function<byte[], K> keyObjectConverter;
    protected Function<K, byte[]> keyToByteArrayConverter;

    protected Function<byte[], V> valueObjectConverter;
    protected Function<V, byte[]> valueToByteArrayConverter;
    protected SimpleCache<K, byte[]> keyCache = new SimpleCache<>(1_000);

    public BaseSimpleSerializationKeyValueStore(final KeyValueStore<byte[], byte[]> store) {
        this.store = store;
    }

    /**
     * Remove a key from the store.
     *
     * @param key
     */
    public void remove(K key) {

        store.remove(toKeyBytes(key));
    }

    /**
     * Search for a key in the key / value store.
     *
     * @param startKey
     * @return
     */
    public KeyValueIterable<K, V> search(K startKey) {
        final KeyValueIterable<byte[], byte[]> search = store.search(toKeyBytes(startKey));
        final Iterator<Entry<byte[], byte[]>> iterator = search.iterator();
        return new KeyValueIterable<K, V>() {
            @Override
            public void close() {
                search.close();
            }

            @Override
            public Iterator<Entry<K, V>> iterator() {
                return new Iterator<Entry<K, V>>() {
                    @Override
                    public boolean hasNext() {
                        return iterator.hasNext();
                    }

                    @Override
                    public Entry<K, V> next() {
                        final Entry<byte[], byte[]> next = iterator.next();

                        return new Entry<>(toKeyObject(next.key()),
                                toValueObject(next.value()));
                    }

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

    /**
     * Load all of the key / values from the store.
     *
     * @return
     */
    public KeyValueIterable<K, V> loadAll() {
        final KeyValueIterable<byte[], byte[]> search = store.loadAll();
        final Iterator<Entry<byte[], byte[]>> iterator = search.iterator();
        return new KeyValueIterable<K, V>() {
            @Override
            public void close() {
                search.close();
            }

            @Override
            public Iterator<Entry<K, V>> iterator() {
                return new Iterator<Entry<K, V>>() {
                    @Override
                    public boolean hasNext() {
                        return iterator.hasNext();
                    }

                    @Override
                    public Entry<K, V> next() {
                        final Entry<byte[], byte[]> next = iterator.next();

                        return new Entry<>(toKeyObject(next.key()),
                                toValueObject(next.value()));
                    }

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

    public Collection<K> loadAllKeys() {
        final Collection<byte[]> keys = store.loadAllKeys();

        final Set<K> set = new HashSet<>();

        for (byte[] key : keys) {
            set.add(toKeyObject(key));
        }

        return set;
    }

    /**
     * Get a value from the key value store.
     *
     * @param key key
     * @return
     */
    public V load(K key) {
        final byte[] bytes = store.load(toKeyBytes(key));
        if (bytes != null) {
            return toValueObject(bytes);
        } else {
            return null;
        }
    }

    public Map<K, V> loadAllByKeys(Collection<K> keys) {

        Set<K> keySet = new TreeSet<>(keys);

        List<byte[]> byteKeys = new ArrayList<>();

        for (K key : keySet) {
            byte[] bKey = toKeyBytes(key);
            byteKeys.add(bKey);
        }
        final Map<byte[], byte[]> map = store.loadAllByKeys(byteKeys);
        final Map<K, V> results = new LinkedHashMap<>();
        for (Map.Entry<byte[], byte[]> entry : map.entrySet()) {
            results.put(toKeyObject(entry.getKey()), toValueObject(entry.getValue()));
        }

        return results;
    }

    /**
     * Close the store.
     */
    public void close() {
        store.close();

    }

    /**
     * Convert a binary array to a String.
     *
     * @param key key
     * @return
     */
    protected K toKeyObject(byte[] key) {
        return keyObjectConverter.apply(key);
    }

    /**
     * Use serializer to read this byte array as an object.
     *
     * @param value value read
     * @return new object read from key/value store
     */
    protected V toValueObject(byte[] value) {
        return valueObjectConverter.apply(value);
    }

    /**
     * Converts an object to a byte array.
     *
     * @param v object to convert
     * @return byte array representation of object.
     */
    protected byte[] toValueBytes(V v) {
        return this.valueToByteArrayConverter.apply(v);
    }

    /**
     * Convert a String key to bytes.
     *
     * @param key key to convert
     * @return value
     */
    protected byte[] toKeyBytes(K key) {

        byte[] keyBytes = keyCache.get(key);
        if (keyBytes == null) {
            keyBytes = this.keyToByteArrayConverter.apply(key);
            keyCache.put(key, keyBytes);
        }
        return keyBytes;

    }

    /**
     * Put a value in the key/value store.
     *
     * @param key   key
     * @param value value
     */
    public void put(K key, V value) {
        store.put(toKeyBytes(key), toValueBytes(value));
    }

    /**
     * Put all of these values in the key value store.
     *
     * @param values values
     */
    public void putAll(Map<K, V> values) {
        Set<Map.Entry<K, V>> entries = values.entrySet();
        Map<byte[], byte[]> map = new HashMap<>(values.size());

        for (Map.Entry<K, V> entry : entries) {
            map.put(toKeyBytes(entry.getKey()), toValueBytes(entry.getValue()));
        }

        store.putAll(map);
    }

    /**
     * Remove all of these values from the key value store.
     *
     * @param keys
     */
    public void removeAll(Iterable<K> keys) {
        List<byte[]> list = new ArrayList<>();

        for (K key : keys) {
            list.add(toKeyBytes(key));
        }

        store.removeAll(list);
    }

    public boolean isOpen() {
        return store.isOpen();
    }

    public boolean isClosed() {
        return store.isClosed();
    }

}


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

import org.boon.validation.ValidatorMetaData;
import org.boon.validation.ValidatorMetaDataReader;

import java.io.IOException;
import java.util.*;

/**
 *

 * PropertiesFileValidatorMetaDataReader reads validation meta-data from
 * a properties files.
 *

 *

 *

 * This class reads a properties file as follows:

 *

 * If the class name is com.foo.Foo, then the resource name is
 * com.foo.Foo.properties.
 *

 *

 *

 * The properties file will contain validation meta-data as follows:

 *

 *

 *

 *  firstName=required; length min=10, max=100
 *  age=required; range min=10, max=100
 *

 *

 * The firstName corresponds to a property of the Foo class. The
 * firstName is associated with the validation rules required
 * and length. The length validation rule states the minimum and
 * maximum allowed number of characters with the min and max
 * parameters.
 *

 *

 *

 * Two different frameworks read this meta-data (curently). Our validation
 * framework, which is mostly geared towards server-side validation and our
 * client-side JavaScript framework, which is geared towards producing
 * client-side JavaScript.
 *

 *
 * @author Rick Hightower
 */
public class PropertiesFileValidatorMetaDataReader implements ValidatorMetaDataReader {

    /**
     * Holds a cache of Properties file contents to reduce IO.
     */
    private Map<String, Properties> metaDataPropsCache =
            new HashMap<String, Properties>();

    /**
     * Holds a cache of meta-data to reduce parsing with regex.
     */
    private Map<String, List<ValidatorMetaData>> metaDataCache =
            new HashMap<String, List<ValidatorMetaData>>();

    /**
     * Read the meta-data from a properties file.
     */
    public List<ValidatorMetaData> readMetaData( Class<?> clazz, String propertyName ) {

        /* Load the properties file. */
        Properties props = loadMetaDataPropsFile( clazz );
        /* Get the raw validation data for the given property. */
        String unparsedString = props.getProperty( propertyName );
        /* Parse the string into a list of ValidationMetaData. */
        return extractMetaDataFromString( clazz, propertyName, unparsedString );
    }

    /**
     * This method loads the MetaData properties file. The Properties are cached
     * in metaDataPropsCache and will not be reloaded twice.
     *
     * @param clazzWhoseValidationMetaDataWeAreReading
     *         The class whose property meta-data we are retrieving.
     * @return
     */
    private Properties loadMetaDataPropsFile(
            Class<?> clazzWhoseValidationMetaDataWeAreReading ) {
        String className = clazzWhoseValidationMetaDataWeAreReading.getName();

        /*
         * If the class is proxied there will be a $CGLIB on the end of it.
         * Remove this.
         */
        className = className.split( "[$]" )[ 0 ];

        /*
         * The resourceName is as follows: If the class name is com.foo.Foo Then
         * the resource name is com.foo.Foo.properties.
         */
        String[] sourceParts = className.split( "[.]" );
        String resourceName = ( sourceParts[ sourceParts.length - 1 ] ) + ".properties";

        /* Check to see if this properties file was already loaded. */
        Properties validationMetaDataProps = metaDataPropsCache.get( resourceName );

        /* If the properties file was not loaded, then load it. */
        if ( validationMetaDataProps == null ) {
            validationMetaDataProps = new Properties();
            try {
                /*
                 * Try to load the properties file that contains the validation
                 * meta-data.
                 */
                validationMetaDataProps.load( this.getClass()
                        .getClassLoader()
                        .getResourceAsStream( resourceName ) );
            } catch ( IOException ioex ) {
                /*
                 * This can happen and is not an error. It just means there is
                 * no validation for this guy. Maybe we should log this.
                 * Note self... addObject logging capability to this project!.
                 */
            }
            /*
             * Put the properties file into the cache so we don't have to read
             * it again.
             */
            metaDataPropsCache.put( resourceName, validationMetaDataProps );
        }
        assert validationMetaDataProps != null :
                "Properties for validation meta-data were loaded";
        return validationMetaDataProps;
    }

    /**
     * This method extracts meta-data from a string.
     *
     * @param clazz
     * @param propertyName
     * @param unparsedString
     * @return
     */
    private List<ValidatorMetaData> extractMetaDataFromString( Class<?> clazz,
                                                               String propertyName, String unparsedString ) {
        String propertyKey = clazz.getName() + "." + propertyName;

        /* See if we parsed this bad boy already. */
        List<ValidatorMetaData> validatorMetaDataList =
                metaDataCache.get( propertyKey );

        /* If we did not find the list, then we have some work to do.*/
        if ( validatorMetaDataList == null ) {
            /* Initialize a new list. */
            validatorMetaDataList = new ArrayList<ValidatorMetaData>();

            /* Remember we have a string that looks like this:
             * required; length min=10, max=100
             * So we need to split on semi-colon.
             */
            String[] validatorsParts = unparsedString.split( "[;]" );

            /* Now we have the two strings as follows:
             *  ["required",
             *  ["length min=10, max=100"]
             *
             */
            for ( String validatorString : validatorsParts ) {
                ValidatorMetaData validatorMetaData = new ValidatorMetaData();
                validatorMetaDataList.add( validatorMetaData );

                /* Now we split one of the string (we will use length)
                 * as follows:
                 * parts=["length", "min=10", "max=100"]
                 * */
                String[] parts = validatorString.trim().split( "[ ,]" );

                /* The first part is the name of the validation,
                 * e.g., "length".
                 *
                 */
                validatorMetaData.setName( parts[ 0 ] );

                /* If the string has more than one part, then there must
                 * be arguments as in: ["min=10", "max=100"]
                 *
                 * Parse the arguments and addObject them to the list as well.
                 */
                if ( parts.length > 1 ) {

                    /* This line converts:
                     *
                     * ["length", "min=10", "max=100"]
                     *
                     * into:
                     *
                     * ["min=10", "max=100"]
                     */
                    List<String> values =
                            Arrays.asList( parts ).subList( 1, parts.length );

                    /* For each value convert it into name value pairs. */
                    for ( String value : values ) {

                        if ( value.indexOf( "=" ) != -1 ) {
                            /* Split "min=10" into ["min", "10"] */
                            String[] valueParts = value.split( "[=]" );
                            /* Stick this value into validatorMetaData's
                             * list of properties.
                             */
                            validatorMetaData.getProperties().put(
                                    valueParts[ 0 ], valueParts[ 1 ] );
                        }
                    }
                }
            }
            metaDataCache.put( propertyKey, validatorMetaDataList );
        }
        return validatorMetaDataList;
    }

}


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

import org.boon.core.reflection.AnnotationData;
import org.boon.core.reflection.Annotations;
import org.boon.validation.ValidatorMetaData;
import org.boon.validation.ValidatorMetaDataReader;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 *

 * AnnotationValidatorMetaDataReader reads validation meta-data from
 * annotation.
 *

 *

 *

 * This class reads a annotation as follows: You pass in the base package of the
 * annotation it defaults to "org.boon.annotation.validation". It then takes
 * the name of the ValidatorMetaData and
 * capitalizes the first letter. Thus if you pass the package
 * "com.my.company.annotation", and
 * ValidatorMetaData.name = "required", then it will look for an
 * annotation called com.my.company.annotation.Required. The idea behind this is
 * that you can use annotation without polluting your model classes with Boon
 * annotation.
 *

 *

 *

 * The parent class that owns the annotation should have annotation as follows:
 *

 *

 *   @Required @Length (min=10, max=100)
 *   public String getFirstName(){...
 *
 *   @Required @Range (min=10, max=100)
 *   public void setAge() {...
 *

 *

 * The firstName corresponds to a property of the Foo class. The
 * firstName is associated with the validation rules required
 * and length. The length validation rule states the minimum and
 * maximum allowed number of characters with the min and max
 * parameters.
 *

 *

 * This was take from the crank project and the crank version was also written by
 * Rick Hightower.
 *

 *

 *

 * Two different frameworks read this meta-data (currently).
 *

 *
 * @author Rick Hightower
 */
public class AnnotationValidatorMetaDataReader implements ValidatorMetaDataReader, Serializable {

    /**
     * Holds a cache of meta-data to reduce parsing with regex and to avoid
     * reflection.
     * Since this could get hit by multiple threads.
     */
    private Map<String, List<ValidatorMetaData>> metaDataCache =
            new ConcurrentHashMap<>();

    /**
     * Holds a list of packages that contain annotation that we will process.
     * If the annotation package is not in this list, it will not be processed.
     */
    private Set<String> validationAnnotationPackages = new HashSet<>();

    {
            /* By default, we only process our own annotation. */
        validationAnnotationPackages.add( "org.boon.validation.annotation" );
    }

    /**
     * Read the meta-data from annotation. This copies the meta-data
     * from the annotation into a POJO. It first checks the meta-data cache,
     * if the meta data is not found in the cache it then reads it from the
     * class.
     *
     * @param clazz        The class that contains the annotation.
     * @param propertyName The name of the property that we are reading
     *                     the annotation meta-data from.
     */
    public List<ValidatorMetaData> readMetaData( Class<?> clazz, String propertyName ) {

        /* Generate a key to the cache based on the classname and the propertyName. */
        String propertyKey = clazz.getName() + "." + propertyName;

        /* Look up the validation meta data in the cache. */
        List<ValidatorMetaData> validatorMetaDataList = metaDataCache.get( propertyKey );

        /* If the meta-data was not found, then generate it. */
        if ( validatorMetaDataList == null ) { // if not found
            validatorMetaDataList = extractValidatorMetaData( clazz, propertyName, validatorMetaDataList );
            /* Put it in the cache to avoid the processing in the future.
             * Design notes: The processing does a lot of reflection, there
             * is no need to do this each time.
             */
            metaDataCache.put( propertyKey, validatorMetaDataList );
        }

        return validatorMetaDataList;

    }

    /**
     * Extract BaseValidator Meta Data.
     *
     * @param clazz                 class
     * @param propertyName          property name
     * @param validatorMetaDataList validatorMetaDataList
     * @return validator meta data
     */
    private List<ValidatorMetaData> extractValidatorMetaData( Class<?> clazz, String propertyName, List<ValidatorMetaData> validatorMetaDataList ) {
        /* If the meta-data was not found, then generate it. */
        if ( validatorMetaDataList == null ) { // if not found
            /* Read the annotation from the class based on the property name. */
            Collection<AnnotationData> annotations = Annotations.getAnnotationDataForFieldAndProperty( clazz, propertyName, this.validationAnnotationPackages );

            /* Extract the POJO based meta-data from the annotation. */
            validatorMetaDataList =
                    extractMetaDataFromAnnotations( annotations );

        }
        return validatorMetaDataList;
    }

    /**
     * Extract meta-data from the annotationData we collected thus far.
     *
     * @param annotations The annotationData (preprocessed annotation).
     * @return list of validation meta data.
     */
    private List<ValidatorMetaData> extractMetaDataFromAnnotations(
            Collection<AnnotationData> annotations ) {
        List<ValidatorMetaData> list = new ArrayList<ValidatorMetaData>();

        for ( AnnotationData annotationData : annotations ) {
            ValidatorMetaData validatorMetaData = convertAnnotationDataToValidatorMetaData( annotationData );
            list.add( validatorMetaData );
        }

        return list;
    }

    /**
     * Converts an AnnotationData into a ValidatorMetaData POJO.
     *
     * @param annotationData annotationData
     * @return validator meta data
     *

     *         TODO
     * @NeedsRefactoring("This method shows we are calling annotationData.getValues a lot. " +
     * "Therefore, we must cache the results of getValues as the annoationData is static " +
     * "per property per class. ")
     */
    private ValidatorMetaData convertAnnotationDataToValidatorMetaData(
            AnnotationData annotationData ) {

        ValidatorMetaData metaData = new ValidatorMetaData();
        metaData.setName( annotationData.getName() );

        metaData.setProperties( annotationData.getValues() );

        return metaData;
    }

    /**
     * We allow a set of validation annotation packages to be configured.
     *
     * @param validationAnnotationPackages validationAnnotationPackages
     */
    public void setValidationAnnotationPackages( Set<String> validationAnnotationPackages ) {
        this.validationAnnotationPackages = validationAnnotationPackages;
    }

}


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

import org.boon.validation.ValidatorMessage;
import org.boon.validation.ValidatorMessageHolder;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * <p>
 * <small>
 * Regex validator.
 * </small>
 * </p>
 *
 * @author Rick Hightower
 */
public class RegexValidator extends BaseValidator {

    private String match;
    private boolean negate;
    private Map<String, Pattern> compiledRegexCache = new HashMap<>();

    public boolean isNegate() {
        return this.negate;
    }

    public void setNegate( boolean negate ) {
        this.negate = negate;
    }

    /**
     * The match.
     *
     * @return the regular expression
     */
    protected String getMatch() {
        return this.match;
    }

    public void setMatch( String regex ) {
        this.match = regex;
    }

    public ValidatorMessageHolder validate( Object object, String fieldLabel ) {
        ValidatorMessage message = new ValidatorMessage();
        if ( object == null ) {
            return message;
        }
        String string = object.toString();
        Pattern pattern = compileRegex();
        boolean valid;
        if ( negate ) {
            valid = !pattern.matcher( string ).matches();
        } else {
            valid = pattern.matcher( string ).matches();
        }

        if ( !valid ) {
            populateMessage( message, fieldLabel );
            return message;
        }

        return message;
    }

    /**
     * Compiles a match.
     *
     * @return the resulting pattern object
     */
    private Pattern compileRegex() {

        Pattern pattern = compiledRegexCache.get( getMatch() );
        if ( pattern == null ) {
            pattern = Pattern.compile( getMatch() );
            compiledRegexCache.put( getMatch(), pattern );
        }
        return pattern;
    }

}


/*
 * 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 org.boon.core.reflection.FastStringUtils;
import org.boon.primitive.CharScanner;
import org.boon.primitive.Chr;

public class StringScanner {

    private static final char[] WHITE_SPACE = new char[] {'\n', '\t', ' ', '\r'};

    /**
     * Checks the input string to see if it is only digits
     * @param input digits
     * @return true or false
     */
    public static boolean isDigits( String input ) {
        return CharScanner.isDigits( FastStringUtils.toCharArray( input ) );
    }

    /**
     * Splits a string into many parts
     * @param string input string
     * @param split char to split by
     * @param limit the limit of the times you want it split up
     * @return the split string
     */
    public static String[]  split( final String string,
                                  final char split, final int limit ) {

        char[][] comps = CharScanner.split( FastStringUtils.toCharArray( string ), split, limit );

        return Str.fromCharArrayOfArrayToStringArray( comps );

    }

    /**
     * Splits a string
     * @param string string to split
     * @param split what you want to split it by
     * @return the split up string
     */
    public static String[] split( final String string,
                                  final char split ) {

        char[][] comps = CharScanner.split( FastStringUtils.toCharArray( string ), split );

        return Str.fromCharArrayOfArrayToStringArray( comps );

    }

    /**
     * Split string by a list of delimiters
     * @param string string to split
     * @param delimiters delimeters to split it by
     * @return the split up string
     */
    public static String[] splitByChars( final String string,
                                         final char... delimiters ) {

        char[][] comps = CharScanner.splitByChars( FastStringUtils.toCharArray( string ), delimiters );

        return Str.fromCharArrayOfArrayToStringArray( comps );

    }

    public static String[] splitByCharsFromToDelims( final String string, int from, int to,
                                         final char... delimiters ) {

        char[][] comps = CharScanner.splitByCharsFromToDelims( FastStringUtils.toCharArray( string ), from, to, delimiters );

        return Str.fromCharArrayOfArrayToStringArray( comps );

    }

    public static String[] splitByCharsFrom( final String string, int from,
                                                     final char... delimiters ) {

        char[][] comps = CharScanner.splitByCharsFromToDelims( FastStringUtils.toCharArray( string ), from, string.length(), delimiters );

        return Str.fromCharArrayOfArrayToStringArray( comps );

    }

    /**
     * Split string by white space
     * @param string string to split
     * @return the split up string
     */
    public static String[] splitByWhiteSpace( final String string
                                         ) {

        char[][] comps = CharScanner.splitByChars( FastStringUtils.toCharArray( string ), WHITE_SPACE );

        return Str.fromCharArrayOfArrayToStringArray( comps );

    }

    /**
     * Split string by a list of delimiters
     * @param string string to split
     * @param delimiters delimeters to split it by
     * @return the split up string
     */
    public static String[] splitByDelimiters( final String string,
                                              final String delimiters ) {

        char[][] comps = CharScanner.splitByChars( FastStringUtils.toCharArray( string ), delimiters.toCharArray() );

        return Str.fromCharArrayOfArrayToStringArray( comps );

    }

    /**
     * Split string by a list of delimiters but none are empty
     * @param string string to split
     * @param delimiters delimeters to split it by
     * @return the split up string
     */
    public static String[] splitByCharsNoneEmpty( final String string, final char... delimiters ) {

        char[][] comps = CharScanner.splitByCharsNoneEmpty( FastStringUtils.toCharArray( string ), delimiters );
        return Str.fromCharArrayOfArrayToStringArray( comps );
    }

    /**
     * remove chars from a string
     * @param string string to split
     * @param delimiters delimeters to remove
     * @return the split up string
     */
    public static String removeChars( final String string, final char... delimiters ) {
        char[][] comps = CharScanner.splitByCharsNoneEmpty( FastStringUtils.toCharArray( string ), delimiters );
        return new String(Chr.add ( comps ));
    }

    /**
     * Split string by a list of delimiters but none are empty within a range
     * @param string string to split
     * @param delimiters delimeters to split it by
     * @return the split up string
     */
    public static String[] splitByCharsNoneEmpty( final String string, int start, int end, final char... delimiters ) {
        Exceptions.requireNonNull( string );

        char[][] comps = CharScanner.splitByCharsNoneEmpty( FastStringUtils.toCharArray( string ), start, end, delimiters );
        return Str.fromCharArrayOfArrayToStringArray( comps );
    }

    /**
     * Parse float
     * @param buffer input buffer
     * @param from from
     * @param to to
     * @return value
     */
    public static float parseFloat( String buffer, int from, int to ) {
        return CharScanner.parseFloat( FastStringUtils.toCharArray(buffer), from , to );
    }

    /**
     * parse a float
     * @param buffer input string
     * @return value
     */
    public static float parseFloat( String buffer ) {
        return CharScanner.parseFloat( FastStringUtils.toCharArray(buffer) );
    }

    /**
     * parse a double
     * @param buffer input string
     * @return value
     */
    public static double parseDouble( String buffer, int from, int to ) {
        return CharScanner.parseDouble( FastStringUtils.toCharArray(buffer), from , to );
    }

    /**
     * parse a double
     * @param buffer input string
     * @return value
     */
    public static double parseDouble( String buffer ) {
        return CharScanner.parseDouble( FastStringUtils.toCharArray(buffer) );
    }

    /**
     * parse an int within a range
     * @param buffer input string
     * @return value
     */
    public static int parseInt( String buffer, int from, int to ) {
        return CharScanner.parseInt( FastStringUtils.toCharArray(buffer), from , to );
    }

    /**
     * parse an int within a range
     * @param buffer input string
     * @return value
     */
    public static int parseInt( String buffer ) {
        return CharScanner.parseInt( FastStringUtils.toCharArray(buffer) );
    }

    /**
     * parse an long within a range
     * @param buffer input string
     * @return value
     */
    public static long parseLong( String buffer, int from, int to ) {
        return CharScanner.parseLong( FastStringUtils.toCharArray(buffer), from , to );
    }

    /**
     * parse an long within a range
     * @param buffer input string
     * @return value
     */
    public static long parseLong( String buffer ) {
        return CharScanner.parseLong( FastStringUtils.toCharArray(buffer) );
    }

    public static short parseShort( String buffer, int from, int to ) {
        return (short) CharScanner.parseInt( FastStringUtils.toCharArray(buffer), from , to );
    }

    public static short parseShort( String buffer ) {
        return (short) CharScanner.parseInt( FastStringUtils.toCharArray(buffer) );
    }

    public static short parseByte( String buffer, int from, int to ) {
        return (byte) CharScanner.parseInt( FastStringUtils.toCharArray(buffer), from , to );
    }

    public static short parseByte( String buffer ) {
        return (byte) CharScanner.parseInt( FastStringUtils.toCharArray(buffer) );
    }

    public static int findWhiteSpace(String buffer) {
        return CharScanner.findWhiteSpace(FastStringUtils.toCharArray(buffer));
    }

    public static String substringAfter(String string, String after) {

        int index = StringScanner.findString(string, after);
        if (index==-1) {
            return "";
        } else {
            return Str.slc(string, index+after.length());
        }

    }

    private static int findString(String string, String after) {
        return CharScanner.findChars(FastStringUtils.toCharArray(after),
                FastStringUtils.toCharArray(string));
    }

    public static String substringBefore(String string, String before) {

        int index = StringScanner.findString(string, before);
        if (index==-1) {
            return "";
        } else {
            return Str.slcEnd(string, index);
        }
    }
}


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

import org.boon.StringScanner;
import org.boon.core.reflection.BeanUtils;
import org.boon.core.reflection.fields.FieldAccess;
import org.boon.primitive.Lng;

import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

import static org.boon.primitive.Lng.grow;

/**
 * Holds primitive values in a list like object for long.
 *

 *

 * Has sum, mean, median, standardDeviation, reduceBy,
 * variance.
 *

 *
 * @author Rick Hightower
 */
public class LongList extends AbstractList<Long> {

    /**
     * Values in this list.
     */
    private long[] values;
    /**
     * Index of last value added.
     */
    private int end;

    /**
     * Create a new list with this many items in it.
     */
    public LongList(int capacity) {
        this.values = new long[capacity];
    }

    /**
     * Create a new list with exactly 10 items in it.
     */
    public LongList() {
        this.values = new long[10];
    }

    /**
     * Create a new list with this many items in it.
     */
    public LongList(long values[]) {
        this.values = values;
        this.end = values.length;
    }

    /**
     * Creates a primitive list based on an input list and a property path
     *
     * @param inputList    input list
     * @param propertyPath property path
     * @return primitive list
     */
    public static LongList toLongList(Collection<?> inputList, String propertyPath) {
        if (inputList.size() == 0) {
            return new LongList(0);
        }

        LongList outputList = new LongList(inputList.size());

        if (propertyPath.contains(".") || propertyPath.contains("[")) {

            String[] properties = StringScanner.splitByDelimiters(propertyPath, ".[]");

            for (Object o : inputList) {
                outputList.add(BeanUtils.getPropertyLong(o, properties));
            }

        } else {

            Map<String, FieldAccess> fields = BeanUtils.getFieldsFromObject(inputList.iterator().next());
            FieldAccess fieldAccess = fields.get(propertyPath);
            for (Object o : inputList) {
                outputList.add(fieldAccess.getLong(o));
            }
        }

        return outputList;
    }

    /**
     * Get the value at index
     *
     * @param index index
     * @return value
     */
    @Override
    public Long get(int index) {
        return values[index];
    }

    /**
     * Get the value at index but don't use a wrapper
     *
     * @param index index
     * @return value
     */
    public final long getInt(int index) {
        return values[index];
    }

    /**
     * Add a new value to the list.
     *
     * @param integer new value
     * @return was able to add.
     */
    @Override
    public boolean add(Long integer) {
        if (end + 1 >= values.length) {
            values = grow(values);
        }
        values[end] = integer;
        end++;
        return true;
    }

    /**
     * Add a new value to the list but don't employ a wrapper.
     *
     * @param integer new value
     * @return was able to add.
     */
    public boolean addLong(long integer) {
        if (end + 1 >= values.length) {
            values = grow(values);
        }
        values[end] = integer;
        end++;
        return true;
    }

    /**
     * Add a new value to the list but don't employ a wrapper.
     *
     * @param integer new value
     * @return was able to add.
     */
    public LongList add(long integer) {
        if (end + 1 >= values.length) {
            values = grow(values);
        }
        values[end] = integer;
        end++;
        return this;
    }

    /**
     * Add a new array to the list.
     *
     * @return was able to add.
     */
    public boolean addArray(long... integers) {
        if (end + integers.length >= values.length) {
            values = grow(values, (values.length + integers.length) * 2);
        }

        System.arraycopy(integers, 0, values, end, integers.length);
        end += integers.length;
        return true;
    }

    /**
     * Set a value in the list.
     *
     * @param index   index
     * @param element new value
     * @return old value at this index
     */
    @Override
    public Long set(int index, Long element) {
        long oldValue = values[index];
        values[index] = element;
        return oldValue;
    }

    /**
     * Set in a new value no wrapper
     *
     * @param index   index
     * @param element new value
     * @return old value at this index
     */
    public long setLong(int index, int element) {
        long oldValue = values[index];
        values[index] = element;
        return oldValue;
    }

    /**
     * Return the current size.
     *
     * @return
     */
    @Override
    public int size() {
        return end;
    }

    /**
     * Sums the values with bounds checking.
     */
    public long sum() {

        return Lng.sum(values, end);
    }

    /**
     * Get a copy of the array up to the end element.
     *
     * @return
     */
    public long[] toValueArray() {

        return java.util.Arrays.copyOfRange(values, 0, end);
    }

    /**
     * This would be a good opportunity to reintroduce dynamic invoke
     *
     * @param function function
     * @return
     */
    public long reduceBy(Object function) {
        return Lng.reduceBy(values, end, function);
    }

    /**
     * This would be a good opportunity to reintroduce dynamic invoke
     *
     * @param function function
     * @return
     */
    public long reduceBy(Object function, String name) {
        return Lng.reduceBy(values, end, function, name);
    }

    /**
     * @param reduceBy reduceBy function
     * @return the reduction
     */
    public long reduceBy(Lng.ReduceBy reduceBy) {
        return Lng.reduceBy(values, end, reduceBy);
    }

    /**
     * Mean
     *
     * @return mean
     */
    public long mean() {
        return Lng.mean(values, end);
    }

    /**
     * standardDeviation
     *
     * @return standardDeviation
     */
    public long standardDeviation() {
        return Lng.standardDeviation(values, end);
    }

    /**
     * variance
     *
     * @return variance
     */
    public long variance() {
        return Lng.variance(values, end);
    }

    /**
     * max
     *
     * @return max
     */
    public long max() {
        return Lng.max(values, end);
    }

    /**
     * min
     *
     * @return min
     */
    public long min() {
        return Lng.min(values, end);
    }

    /**
     * median
     *
     * @return median
     */
    public long median() {
        return Lng.median(values, end);
    }

    /**
     * sort
     *
     * @return sort
     */
    public void sort() {
        Arrays.sort(values, 0, end);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        LongList longs = (LongList) o;

        if (end != longs.end) return false;
        if (!Lng.equals(0, end, values, longs.values)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = 131313;
        result = 31 * result + (values != null ? Lng.hashCode(0, end, values) : 0);
        result = 31 * result + end;
        return result;
    }

    public void clear() {
        this.values = new long[10];
        this.end = 0;
    }

}


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

import org.boon.StringScanner;
import org.boon.core.reflection.BeanUtils;
import org.boon.core.reflection.fields.FieldAccess;
import org.boon.primitive.Int;

import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

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

/**
 * Holds primitive values in a list like object for ints.
 *

 *

 * Has sum, mean, median, standardDeviation, reduceBy,
 * variance.
 *

 *
 * @author Rick Hightower
 */
public class IntList extends AbstractList<Integer> {

    /**
     * Values in this list.
     */
    private int[] values;
    /**
     * Index of last value added.
     */
    private int end;

    /**
     * Create a new list with this many items in it.
     */
    public IntList(int capacity) {
        this.values = new int[capacity];
    }

    /**
     * Create a new list with exactly 10 items in it.
     */
    public IntList() {
        this.values = new int[10];
    }

    /**
     * Create a new list with this many items in it.
     */
    public IntList(int values[]) {
        this.values = values;
        this.end = values.length;
    }

    /**
     * Creates a primitive list based on an input list and a property path
     *
     * @param inputList    input list
     * @param propertyPath property path
     * @return primitive list
     */
    public static IntList toIntList(Collection<?> inputList, String propertyPath) {
        if (inputList.size() == 0) {
            return new IntList(0);
        }

        IntList outputList = new IntList(inputList.size());

        if (propertyPath.contains(".") || propertyPath.contains("[")) {

            String[] properties = StringScanner.splitByDelimiters(propertyPath, ".[]");

            for (Object o : inputList) {
                outputList.add(BeanUtils.getPropertyInt(o, properties));
            }

        } else {

            Map<String, FieldAccess> fields = BeanUtils.getFieldsFromObject(inputList.iterator().next());
            FieldAccess fieldAccess = fields.get(propertyPath);
            for (Object o : inputList) {
                outputList.add(fieldAccess.getInt(o));
            }
        }

        return outputList;
    }

    public void clear() {
        this.values = new int[10];
        this.end = 0;
    }

    /**
     * Get the value at index
     *
     * @param index index
     * @return value
     */
    @Override
    public Integer get(int index) {
        return values[index];
    }

    /**
     * Get the value at index but don't use a wrapper
     *
     * @param index index
     * @return value
     */
    public final int getInt(int index) {
        return values[index];
    }

    /**
     * Add a new value to the list.
     *
     * @param integer new value
     * @return was able to add.
     */
    @Override
    public boolean add(Integer integer) {
        if (end + 1 >= values.length) {
            values = grow(values);
        }
        values[end] = integer;
        end++;
        return true;
    }

    /**
     * Add a new value to the list but don't employ a wrapper.
     *
     * @param integer new value
     * @return was able to add.
     */
    public boolean addInt(int integer) {
        if (end + 1 >= values.length) {
            values = grow(values);
        }
        values[end] = integer;
        end++;
        return true;
    }

    /**
     * Add a new value to the list but don't employ a wrapper.
     *
     * @param integer new value
     * @return was able to add.
     */
    public IntList add(int integer) {
        if (end + 1 >= values.length) {
            values = grow(values);
        }
        values[end] = integer;
        end++;
        return this;
    }

    /**
     * Add a new array to the list.
     *
     * @return was able to add.
     */
    public boolean addArray(int... integers) {
        if (end + integers.length >= values.length) {
            values = grow(values, (values.length + integers.length) * 2);
        }

        System.arraycopy(integers, 0, values, end, integers.length);
        end += integers.length;
        return true;
    }

    /**
     * Set a value in the list.
     *
     * @param index   index
     * @param element new value
     * @return old value at this index
     */
    @Override
    public Integer set(int index, Integer element) {
        int oldValue = values[index];
        values[index] = element;
        return oldValue;
    }

    /**
     * Set in a new value no wrapper
     *
     * @param index   index
     * @param element new value
     * @return old value at this index
     */
    public int setInt(int index, int element) {
        int oldValue = values[index];
        values[index] = element;
        return oldValue;
    }

    /**
     * Return the current size.
     *
     * @return
     */
    @Override
    public int size() {
        return end;
    }

    /**
     * Sums the values with bounds checking.
     */
    public int sum() {

        return Int.sum(values, end);
    }

    /**
     * Get a copy of the array up to the end element.
     *
     * @return
     */
    public int[] toValueArray() {

        return java.util.Arrays.copyOfRange(values, 0, end);
    }

    /**
     * This would be a good opportunity to reintroduce dynamic invoke
     *
     * @param function function
     * @return
     */
    public long reduceBy(Object function) {
        return Int.reduceBy(values, end, function);
    }

    /**
     * This would be a good opportunity to reintroduce dynamic invoke
     *
     * @param function function
     * @return
     */
    public long reduceBy(Object function, String name) {
        return Int.reduceBy(values, end, function, name);
    }

    /**
     * @param reduceBy reduceBy function
     * @return the reduction
     */
    public long reduceBy(Int.ReduceBy reduceBy) {
        return Int.reduceBy(values, end, reduceBy);
    }

    /**
     * Mean
     *
     * @return mean
     */
    public int mean() {
        return Int.mean(values, end);
    }

    /**
     * standardDeviation
     *
     * @return standardDeviation
     */
    public int standardDeviation() {
        return Int.standardDeviation(values, end);
    }

    /**
     * variance
     *
     * @return variance
     */
    public int variance() {
        return Int.variance(values, end);
    }

    /**
     * max
     *
     * @return max
     */
    public int max() {
        return Int.max(values, end);
    }

    /**
     * min
     *
     * @return min
     */
    public int min() {
        return Int.min(values, end);
    }

    /**
     * median
     *
     * @return median
     */
    public int median() {
        return Int.median(values, end);
    }

    /**
     * sort
     *
     * @return sort
     */
    public void sort() {
        Arrays.sort(values, 0, end);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        IntList integers = (IntList) o;

        if (end != integers.end) return false;
        if (!Int.equals(0, end, values, integers.values)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = 1;
        result = 31 * result + (values != null ? Int.hashCode(0, end, values) : 0);
        result = 31 * result + end;
        return result;
    }
}