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

import org.nustaq.offheap.bytez.Bytez;

import java.io.Serializable;

/**
 * Created with IntelliJ IDEA.
 * User: ruedi
 * Date: 01.11.13
 * Time: 14:17
 * To change this template use File | Settings | File Templates.
 */
// FIXME: move to long indizies
public class FSTStructChange implements Serializable {

    int changeOffsets[];
    int changeLength[];
    String changedFields[];
    int curIndex;
    transient public String _parentField;
    transient public FSTStructChange _parent;

    byte snapshot[]; // created by snapshotChanges, contains new byte values

    public FSTStructChange(FSTStructChange par, String parField) {
        _parentField = parField;
        _parent = par;
    }

    public FSTStructChange() {
        changeLength = new int[2];
        changeOffsets = new int[2];
        changedFields = new String[2];
    }

    public void addChange(int offset, int len, String field) {
        addChange((long)offset,(long)len,field);
    }

    public void addChange(long offset, int len, String field) {
        addChange((long)offset,(long)len,field);
    }

    public void addChange(long offset, long len, String field) {
        if ( _parent != null ) {
            _parent.addChange(offset,len,_parentField);
            return;
        }
        if ( curIndex > 0 && changeOffsets[curIndex-1]+changeLength[curIndex-1] == offset ) {
            changeLength[curIndex-1]+=len;
            return;
        }
        if ( curIndex >= changeOffsets.length ) {
            int newOff[] = new int[changeOffsets.length*2];
            System.arraycopy(changeOffsets,0,newOff,0,changeOffsets.length);
            int newLen[] = new int[changeOffsets.length*2];
            System.arraycopy(changeLength,0,newLen,0,changeLength.length);
            String newCF[] = new String[changeOffsets.length*2];
            System.arraycopy(changedFields,0,newCF,0,changedFields.length);

            changeOffsets = newOff;
            changeLength = newLen;
            changedFields = newCF;
        }
        changeOffsets[curIndex] = (int) (offset);
        changeLength[curIndex] = (int) len;
        changedFields[curIndex] = field;
        curIndex++;
    }

    public void rebase(int toSubtract) {
        for (int i = 0; i < curIndex; i++) {
            changeOffsets[i]-=toSubtract;
        }
    }

    /**
     * collects all changes and rebases.
     * @param originBase
     * @param origin
     */
    public void snapshotChanges(int originBase, Bytez origin) {
        int sumLen = 0;
        for (int i = 0; i < curIndex; i++) {
            sumLen += changeLength[i];
        }
        snapshot = new byte[sumLen];
        int targetIdx = 0;
        for (int i = 0; i < curIndex; i++) {
            int changeOffset = changeOffsets[i];
            int len = changeLength[i];
            for ( int ii = 0; ii < len; ii++) {
                snapshot[targetIdx++] = origin.get(changeOffset+ii);
            }
        }
        rebase(originBase);
    }

    public void applySnapshot(FSTStruct target) {
        Bytez arr = target.getBase();
        int baseIdx = (int) target.getOffset();
        int snapIdx = 0;
        for (int i = 0; i < curIndex; i++) {
            int changeOffset = changeOffsets[i];
            int len = changeLength[i];
            for ( int ii = 0; ii < len; ii++) {
                arr.put(baseIdx+changeOffset+ii,snapshot[snapIdx++]);
            }
        }
    }

    public String[] getChangedFields() {
        return changedFields;
    }

    public byte[] getSnapshot() {
        return snapshot;
    }
}


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

import org.nustaq.serialization.util.FSTIdentity2IdMap;
import org.nustaq.serialization.util.FSTInt2ObjectMap;
import org.nustaq.serialization.util.FSTUtil;

/**
 * Created with IntelliJ IDEA.
 * User: ruedi
 * Date: 11.11.12
 * Time: 15:34
 * To change this template use File | Settings | File Templates.
 */
public final class FSTObjectRegistry {

    public static final int OBJ_DIVISOR = 16;
    public static int POS_MAP_SIZE = 1000; // reduce this for testing

    boolean disabled = false;
    FSTIdentity2IdMap objects = new FSTIdentity2IdMap(11); // object => id
    FSTInt2ObjectMap idToObject;

    Object reuseMap[] = new Object[POS_MAP_SIZE];
    private int highestPos = -1;

    public FSTObjectRegistry(FSTConfiguration conf) {

        disabled = !conf.isShareReferences();
        idToObject = conf.intToObjectMapFactory.createMap(11);
    }

    public void clearForRead(FSTConfiguration conf) {
        disabled = !conf.isShareReferences();
        if ( !disabled ) {
            idToObject.clear();
            if ( highestPos > -1 )
                FSTUtil.clear( reuseMap, highestPos + 1 );
        }
        highestPos = -1;
    }

    public void clearForWrite(FSTConfiguration conf) {
        disabled = !conf.isShareReferences();
        if ( ! disabled ) {
            if ( objects.size() > 0 && objects.keysLength() > 6 * objects.size() ) {
                objects = new FSTIdentity2IdMap(objects.size());
            } else {
                objects.clear();
            }
        }
    }

    public Object getReadRegisteredObject(int handle) {
        if (disabled) {
            return null;
        }
        int pos = handle / OBJ_DIVISOR;
        if ( pos < reuseMap.length ) {
            if ( reuseMap[pos] == null ) {
                return null;
            } else {
                Object candidate = idToObject.get(handle);
                if ( candidate == null )
                    return reuseMap[pos];
                return candidate;
            }
        }
        return idToObject.get(handle);
    }

    public void replace(Object old, Object replaced, int streamPos) {
        int pos = streamPos / OBJ_DIVISOR;
        final Object[] reuseMap = this.reuseMap;
        if ( pos < reuseMap.length ) {
            if ( this.reuseMap[pos] == old ) {
                this.reuseMap[pos] = replaced;
            } else {
                if ( this.reuseMap[pos] == null || reuseMap[pos] == old )
                {
                    this.reuseMap[pos] = replaced;
                } else {
                    idToObject.put(streamPos,replaced);
                }
            }
        } else {
            idToObject.put(streamPos, replaced);
        }
    }

    public void registerObjectForRead(Object o, int streamPosition) {
        if (disabled /*|| streamPosition <= lastRegisteredReadPos*/) {
            return;
        }
//        System.out.println("POK REGISTER AT READ:"+streamPosition+" : "+o);
        int pos = streamPosition / OBJ_DIVISOR;
        Object[] reuseMap = this.reuseMap;
        if ( pos < reuseMap.length ) {
            highestPos = pos > highestPos ? pos : highestPos;
            if ( this.reuseMap[pos] == null ) {
                this.reuseMap[pos] = o;
            } else {
                idToObject.put(streamPosition,o);
            }
        } else {
            idToObject.put(streamPosition,o);
        }
    }

    /**
     * add an object to the register, return handle if already present. Called during write only
     *
     * @param o
     * @param streamPosition
     * @return 0 if added, handle if already present
     */
    public int registerObjectForWrite(Object o, int streamPosition, FSTClazzInfo clzInfo, int reUseType[]) {
        if (disabled) {
            return Integer.MIN_VALUE;
        }
//        System.out.println("REGISTER AT WRITE:"+streamPosition+" "+o.getClass().getSimpleName());
//        final Class clazz = o.getClass();
        if ( clzInfo == null ) { // array oder enum oder primitive
            // unused ?
//            clzInfo = reg.getCLInfo(clazz);
        } else if ( clzInfo.isFlat() ) {
            return Integer.MIN_VALUE;
        }
        int handle = objects.putOrGet(o,streamPosition);
        if ( handle >= 0 ) {
//            if ( idToObject.get(handle) == null ) { // (*) (can get improved)
//                idToObject.add(handle, o);
//            }
            reUseType[0] = 0;
            return handle;
        }
        return Integer.MIN_VALUE;
    }

    public int getObjectSize() {
        return objects.size();
    }

}


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

import org.nustaq.offheap.bytez.ByteSource;
import org.nustaq.offheap.bytez.bytesource.BytezByteSource;

import java.util.Iterator;

/**
 * An offheap map adding mechanics to encode and decode keys and values.
 *
 * Again remember keys have to be of fixed size and should be as short as possible.
 * Performance and memory consumtpion is best if significant digits are at the
 * end of a key (e.g. "year_montH_day" instead "day_month_year".
 *
 * This class can be used for manual implementation of encoding/decoding.
 *
 * See StringOffHeapMap for an example on how to do efficient wrapping/encoding of keys
 */
public abstract class FSTCodedOffheapMap<K,V> extends FSTBinaryOffheapMap {

    public FSTCodedOffheapMap(int keyLen, long sizeMemBytes, int numberOfElems) {
        super(keyLen, sizeMemBytes, numberOfElems);
    }

    public FSTCodedOffheapMap(String mappedFile, int keyLen, long sizeMemBytes, int numberOfElems) throws Exception {
        super(mappedFile, keyLen, sizeMemBytes, numberOfElems);
    }

    //    protected abstract K decodeKey(ByteSource key);

    public abstract ByteSource encodeKey(K key);

    public abstract ByteSource encodeValue(V value);

    public abstract V decodeValue(BytezByteSource val);

    public V get( K key ) {
        if ( key == null )
            return null;
        ByteSource bkey = encodeKey(key);
        BytezByteSource val = getBinary(bkey);
        if ( val == null )
            return null;
        try {
            return decodeValue(val);
        }catch (Exception ex) {
            System.out.println("ERROR in "+(val.getOff()-getHeaderLen())+" "+printBinaryKey(bkey)+" "+index.get(bkey));
            throw new RuntimeException(ex);
        }
    }

    public void put( K key, V value ) {
        ByteSource bkey = encodeKey(key);
        ByteSource decoded = encodeValue(value);
        putBinary(bkey, decoded);
    }

    public void remove( K key ) {
        ByteSource bkey = encodeKey(key);
        removeBinary(bkey);
    }

    /**
     * Warning: Concurrent Modification (e.g. add remove elements during iteration) is NOT SUPPORTED
     * and NOT CHECKED. Collect keys to change inside an iteration and perform changes on the map after iteration is
     * finished.
     * @return an iterator on the values contained in this map.
     */
    public Iterator<V> values() {
        final Iterator<ByteSource> iter = binaryValues();
        return new Iterator() {
            @Override
            public boolean hasNext() {
                return iter.hasNext();
            }

            @Override
            public V next() {
                BytezByteSource next = (BytezByteSource) iter.next();
                return decodeValue(next);
            }

            @Override
            public void remove() {
                throw new RuntimeException("unimplemented");
            }
        };
    }

}


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

import org.nustaq.offheap.bytez.onheap.HeapBytez;
import org.nustaq.serialization.*;
import org.nustaq.serialization.coders.FSTBytezDecoder;
import org.nustaq.serialization.coders.FSTBytezEncoder;
import org.nustaq.serialization.util.FSTUtil;

import java.io.IOException;

/**
 * Created by ruedi on 13.11.14.
 *
 * A coder writing fast binary encoding using unsafe to an underlying byte array ZERO COPY.
 *
 * Future version may choose to operate on DirectByteBuffer in case unsafe vanishes
 *
 * Note there is some copied code from OffHeaüCoder to avoid losing performance caused
 * by polymorphic method dispatch.
 *
 * ***********************************************************************
 * USE ONLY IF DEFAULTCODER (no unsafe) HAS BEEN PROVEN TO SLOW YOU DOWN.
 * ***********************************************************************
 *
 */
public class OnHeapCoder implements FSTCoder {

    protected FSTConfiguration conf;
    protected HeapBytez writeTarget;
    protected HeapBytez readTarget;
    protected FSTObjectOutput out;
    protected FSTObjectInput in;

    public OnHeapCoder() {
        this(true);
    }

    public OnHeapCoder(boolean sharedRefs) {
        conf = FSTConfiguration.createUnsafeBinaryConfiguration();
        conf.setShareReferences(sharedRefs);
        writeTarget = new HeapBytez(new byte[0]);
        readTarget = new HeapBytez(new byte[0]);
        conf.setStreamCoderFactory(new FSTConfiguration.StreamCoderFactory() {
            @Override
            public FSTEncoder createStreamEncoder() {
                FSTBytezEncoder fstBytezEncoder = new FSTBytezEncoder(conf, writeTarget);
                fstBytezEncoder.setAutoResize(false);
                return fstBytezEncoder;
            }
            @Override
            public FSTDecoder createStreamDecoder() {
                return new FSTBytezDecoder(conf,readTarget);
            }

            ThreadLocal input = new ThreadLocal();
            ThreadLocal output = new ThreadLocal();
            @Override
            public ThreadLocal getInput() {
                return input;
            }

            @Override
            public ThreadLocal getOutput() {
                return output;
            }

        });
        if ( sharedRefs ) {
            out = conf.getObjectOutput();
            in = conf.getObjectInput();
        } else {
            out = new FSTObjectOutputNoShared(conf);
            in = new FSTObjectInputNoShared(conf);
        }
    }

    /**
     * throw
     * @param preregister
     */
    public OnHeapCoder( Class ... preregister ) {
        this();
        conf.registerClass(preregister);
    }

    public OnHeapCoder( boolean sharedRefs, Class ... preregister ) {
        this(sharedRefs);
        conf.registerClass(preregister);
    }

    /**
     * throws FSTBufferTooSmallExcpetion in case object does not fit into given range
     * Zero Copy method
     *
     * @param o
     * @param availableSize
     * @throws java.io.IOException
     * @return number of bytes written to the memory region
     */
    @Override
    public int toByteArray( Object o, byte arr[], int startIndex, int availableSize ) {
        out.resetForReUse();
        writeTarget.setBase(arr, startIndex, availableSize);
        try {
            out.writeObject(o);
        } catch (IOException e) {
            FSTUtil.<RuntimeException>rethrow(e);
        }
        int written = out.getWritten();
        return written;
    }

    protected byte[] buff = new byte[4096];
    @Override
    public byte[] toByteArray(Object o) {
        try {
            out.resetForReUse();
            writeTarget.setBase(buff, 0, buff.length);
            try {
                out.writeObject(o);
            } catch (IOException e) {
                FSTUtil.<RuntimeException>rethrow(e);
            }
            return out.getCopyOfWrittenBuffer();
        } catch (FSTBufferTooSmallException ex) {
            buff = new byte[buff.length*2];
            return toByteArray(o);
        }
    }

    @Override
    public FSTConfiguration getConf() {
        return conf;
    }

    /**
     * throws FSTBufferTooSmallExcpetion in case object does not fit into given range
     *
     * @param arr
     * @param startIndex
     * @param availableSize
     * @return
     * @throws Exception
     */
    @Override
    public Object toObject( byte arr[], int startIndex, int availableSize) {
        try {
            in.resetForReuse(null);
            readTarget.setBase(arr,startIndex,availableSize);
            Object o = in.readObject();
            return o;
        } catch (Exception e) {
            FSTUtil.<RuntimeException>rethrow(e);
        }
        return null;
    }

    /**
     * decode object into byte array (at position null)
     *
     * @param arr
     * @return
     * @throws Exception
     */
    @Override
    public Object toObject(byte[] arr) {
        return toObject(arr,0,arr.length);
    }

}


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

package org.nustaq.kson;

import org.nustaq.logging.FSTLogger;

import java.lang.reflect.Array;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * simple implementation of type mapper.
 *  * Maps Classes to short string names and vice versa.
 *  * allows to add user-defined type conversions (e.g. Date, Collections)
 *
 *  This default implementation supports Date<=>String and Collections<=>Array coercion.
 */
public class KsonTypeMapper {

    private static final FSTLogger logger = FSTLogger.getLogger(KsonTypeMapper.class);
    public static final Object NULL_LITERAL = "NULL";
    protected boolean useSimplClzName = true;
    protected HashMap<String,Class> typeMap = new HashMap<String, Class>(31);
    protected HashMap<Class, String> reverseTypeMap = new HashMap<Class, String>(31);

    protected DateFormat dateTimeInstance = DateFormat.getDateTimeInstance();

    public KsonTypeMapper() {
        map("map", HashMap.class).map("list", HashMap.class).map("set",HashSet.class);
    }

    final Class NONE = Object.class;
    public Class getType(String type) {
        Class res = typeMap.get(type);
        if ( res == null ) {
            try {
                res = Class.forName(type);
                if ( res == null ) {
                    typeMap.put(type,NONE);
                } else
                    typeMap.put(type,res);
            } catch (ClassNotFoundException e) {
                typeMap.put(type,NONE);
                return null;
            }
        }
        if ( res == NONE )
            res = null;
        return res;
    }

    public KsonTypeMapper map(String name, Class c) {
        typeMap.put(name, c);
        reverseTypeMap.put(c,name);
        return this;
    }

    public KsonTypeMapper map(Object ... stringAndClasses) {
        for (int i = 0; i < stringAndClasses.length; i+=2) {
            map( stringAndClasses[i], stringAndClasses[i+1]);
        }
        return this;
    }

    public KsonTypeMapper map(Class ... c) {
        for (int i = 0; i < c.length; i++) {
            Class aClass = c[i];
            map(aClass.getSimpleName(),aClass);
        }
        return this;
    }

    public boolean isUseSimplClzName() {
        return useSimplClzName;
    }

    public void setUseSimplClzName(boolean useSimplClzName) {
        this.useSimplClzName = useSimplClzName;
    }

    /**
     * map given Object to a target type.
     * (needs support in coerceWriting also)
     * Note one could add a pluggable Serializer/Coercer pattern here if required. Skipped for now for simplicity.
     *
     * @param type - of target field
     * @param readObject - object read from string
     * @return
     */
    public Object coerceReading(Class type, Object readObject) {
        if (type==null)
            return readObject;
        // make hashmaps from arrays. warning: for optimal performance, use direct arrays[] only in your serialized classes
        if ( Map.class.isAssignableFrom(type) && readObject.getClass().isArray() ) {
            try {
                Map c = (Map) type.newInstance();
                int len = Array.getLength(readObject);
                for ( int i = 0; i < len; i+=2 ) {
                    c.put(Array.get(readObject, i), Array.get(readObject, i + 1));
                }
                return c;
            } catch (Exception e) {
                logger.log(FSTLogger.Level.INFO, "Exception thrown by newInstance", e);
            }
        } else // make collections from arrays. warning: for optimal performance, use direct arrays[] only in your serialized classes
            if ( Collection.class.isAssignableFrom(type) && readObject.getClass().isArray() ) {
            try {
                if ( type.isInterface() ) {
                    if ( List.class.isAssignableFrom(type) ) {
                        type = ArrayList.class;
                    } else if (Map.class.isAssignableFrom(type) ) {
                        type = HashMap.class;
                    }
                }
                Collection c = (Collection) type.newInstance();
                int len = Array.getLength(readObject);
                for ( int i = 0; i < len; i++ ) {
                    c.add(Array.get(readObject,i));
                }
                return c;
            } catch (Exception e) {
                logger.log(FSTLogger.Level.ERROR, "Exception thrown by newInstance", e);
            }
        } else if ( Date.class.isAssignableFrom(type) && readObject instanceof String) {
            try {
                return dateTimeInstance.parse((String) readObject);
            } catch (ParseException pe) {
                logger.log(FSTLogger.Level.ERROR, "Failed to parse date", pe);
            }
        } else if ( (type == char.class || Character.class.isAssignableFrom(type)) && readObject instanceof String ) {
            return ((String) readObject).charAt(0);
        }
        return readObject;
    }

    public DateFormat getDateTimeInstance() {
        return dateTimeInstance;
    }

    public void setDateTimeInstance(DateFormat dateTimeInstance) {
        this.dateTimeInstance = dateTimeInstance;
    }

    public Object mapLiteral(String type) {
        if (type.equals("null")) {
            return NULL_LITERAL;
        }
        if (type.equals("true") || type.equals("yes") || type.equals("y")) {
            return Boolean.TRUE;
        }
        if (type.equals("false") || type.equals("no") || type.equals("n")) {
            return Boolean.FALSE;
        }
        return null;
    }

    public String getStringForType(Class<? extends Object> aClass) {
        String res = reverseTypeMap.get(aClass);
        if (res==null)
            res = useSimplClzName ? aClass.getSimpleName() : aClass.getName();
        return res;
    }
}


package org.nustaq.offheap;

import org.nustaq.logging.FSTLogger;
import org.nustaq.offheap.bytez.ByteSink;
import org.nustaq.offheap.bytez.ByteSource;
import org.nustaq.offheap.bytez.Bytez;
import org.nustaq.offheap.bytez.onheap.HeapBytez;

/**
 * an automatically growing byte queue organized as a ringbuffer.
 *
 * poll* methods don't throw excpetions but return incomplete or no result instead if
 * the queue's content is not sufficient.
 * read* methods throw excpetions in case the q contains not enough bytes (need to use available() to
 * ensure data is present).
 *
 * add methods add to the queue. If the adding outperforms reading/polling, the queue is grown automatically.
 *
 */
public class BinaryQueue {

    private static final FSTLogger logger = FSTLogger.getLogger(BinaryQueue.class);
    Bytez storage;

    long addIndex = 0;
    long pollIndex = 0;

    public BinaryQueue() {
        this(1024);
    }

    public BinaryQueue(int qsize) {
        storage = new HeapBytez(qsize);
    }

    /**
     * add bytes to the queue. Again by using (reusable) Wrapper classes any kind of memory
     * (offheap, byte arrays, nio bytebuffer, memory mapped) can be added.
     *
     * @param source
     */
    public void add(ByteSource source) {
        add(source,0, source.length());
    }

    /**
     * add bytes to the queue. Again by using (reusable) Wrapper classes any kind of memory
     * (offheap, byte arrays, nio bytebuffer, memory mapped) can be added.
     *
     * @param source
     * @param sourceoff
     * @param sourcelen
     */
    public void add(ByteSource source, long sourceoff, long sourcelen) {
        if ( sourcelen >= remaining() ) {
            grow(sourcelen+1); //issue85
            add(source,sourceoff,sourcelen);
            return;
        }
        for ( int i=0; i < sourcelen; i++ ) {
            storage.put(addIndex++,source.get(i+sourceoff));
            if ( addIndex >= storage.length() )
                addIndex -= storage.length();
        }
    }

    public void addInt(int written)
    {
        add((byte) ((written >>> 0) & 0xFF));
        add((byte) ((written >>> 8) & 0xFF));
        add((byte) ((written >>> 16) & 0xFF));
        add((byte) ((written >>> 24) & 0xFF));
    }

    public void add(byte b) {
        long remaining = remaining();
        if ( 1 >= remaining) {
            grow(1+1); // issue85
            add(b);
            return;
        }
        storage.put(addIndex++, b);
        if ( addIndex >= storage.length() )
            addIndex -= storage.length();
    }

    protected void grow(long sourcelen) {
        HeapBytez newStorage = new HeapBytez((int) Math.max(capacity()*2,capacity()+sourcelen+available()));
        long len = poll(newStorage, 0, available());
        pollIndex = 0;
        addIndex = len;
        storage = newStorage;
    }

    /**
     * @return number of bytes free for an add operation
     */
    public long remaining() {
        return capacity() - available();
    }

    /**
     * read up to destlen bytes (if available).
     * Note you can use HeapBytez (implements ByteSink) in order to read to a regular byte array.
     * HeapBytez wrapper can be reused to avoid unnecessary allocation.
     * Also possible is ByteBufferBasicBytes to read into a ByteBuffer and MallocBytes or MMFBytes to
     * read into Unsafe alloc'ed off heap memory or persistent mem mapped memory regions.
     *
     * @param destination
     * @param destoff
     * @param destlen
     * @return
     */
    public long poll(ByteSink destination, long destoff, long destlen) {
        long count = 0;
        try {
            while (pollIndex != addIndex && count < destlen) {
                destination.put(destoff + count++, storage.get(pollIndex++));
                if (pollIndex >= storage.length()) {
                    pollIndex = 0;
                }
            }
        } catch (Exception e) {
            logger.log(FSTLogger.Level.ERROR, "Failed to poll", e);
        }
        return count;
    }

    /**
     * convenience method to read len byte array. Throws an excpetion if not enough data is present
     *
     * @param len
     * @return
     */
    public byte[] readByteArray(int len) {
        if ( available() < len ) {
            throw new RuntimeException("not enough data available, check available() > len before calling");
        }
        byte b[] = new byte[len];
        int count = 0;
        while ( pollIndex != addIndex && count < len ) {
            b[count++] = storage.get(pollIndex++);
            if ( pollIndex >= storage.length() ) {
                pollIndex = 0;
            }
        }
        return b;
    }

    /**
     * read an int. throws an exception if not enough data is present
     * @return
     */
    public int readInt() {
        if ( available() < 4 ) {
            throw new RuntimeException("not enough data available, check available() > 4 before calling");
        }
        int ch1 = poll();
        int ch2 = poll();
        int ch3 = poll();
        int ch4 = poll();
        return (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
    }

    /**
     * @return -1 or the next byte unsigned value
     */
    public int poll() {
        int result = -1;
        if ( pollIndex != addIndex ) {
            result = (storage.get(pollIndex++)+256)&0xff;
            if ( pollIndex >= storage.length() ) {
                pollIndex = 0;
            }
        }
        return result;
    }

    /**
     * 'unread' len bytes
     * @param len
     */
    public void back( int len ) {
        if ( pollIndex >= len )
            pollIndex -= len;
        else
            pollIndex = pollIndex + capacity() - len;

    }

    /**
     * @return number of readable bytes
     */
    public long available() {
        return addIndex>=pollIndex ? addIndex-pollIndex : addIndex+capacity()-pollIndex;
    }

    /**
     * @return size of underlying ringbuffer
     */
    public long capacity() {
        return storage.length();
    }

    @Override
    public String toString() {
        return "BinaryQueue{" +
                "storage=" + storage +
                ", addIndex=" + addIndex +
                ", pollIndex=" + pollIndex +
                '}';
    }
}


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

import org.nustaq.offheap.structs.unsafeimpl.FSTStructFactory;
import org.nustaq.serialization.util.FSTIdentity2IdMap;
import org.nustaq.serialization.util.FSTObject2IntMap;
import org.nustaq.serialization.util.FSTUtil;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Created with IntelliJ IDEA.
 * User: ruedi
 * Date: 10.11.12
 * Time: 00:34
 *
 * maps classnames => id and vice versa.
 *
 * FSTConfiguration holds a parent containing default mappings (e.g. created by
 * registerClass ). A stream instance then creates a temporary instance to record/id encode
 * classes dynamically during serialization. This way a class name is only written once per
 * object graph.
 *
 * This class is thread safe,
 *
 */
public class FSTClazzNameRegistry {

    public static final int LOWEST_CLZ_ID = 3;
    public static final int FIRST_USER_CLZ_ID = 1000;

    FSTIdentity2IdMap clzToId;
    FSTClazzInfo idToClz[];
    FSTClazzNameRegistry parent;
    int classIdCount = LOWEST_CLZ_ID;

    public FSTClazzNameRegistry(FSTClazzNameRegistry par) {
        parent = par;
        if ( parent != null ) {
            classIdCount = Math.max(FIRST_USER_CLZ_ID,parent.classIdCount+1);
            clzToId = new FSTIdentity2IdMap(13);
            idToClz = new FSTClazzInfo[31];
        } else {
            clzToId = new FSTIdentity2IdMap(FSTObject2IntMap.adjustSize(400));
            idToClz = new FSTClazzInfo[200];
        }
    }

    public void clear() {
        if ( clzToId.size() > 0 ) {
            clzToId.clear();
            //idToClz.clear();
        }
        classIdCount = LOWEST_CLZ_ID;
        if ( parent != null ) {
            classIdCount = Math.max(FIRST_USER_CLZ_ID,parent.classIdCount+1);
        }
    }

    // for read => always increase handle (wg. replaceObject)
    public void registerClass(Class c,FSTConfiguration conf) {
        if ( getIdFromClazz(c) != Integer.MIN_VALUE ) {
            return;
        }
        registerClassNoLookup(c,null,conf);
    }

    private void registerClassNoLookup(Class c,FSTClazzInfo cli, FSTConfiguration conf) {
        addClassMapping(c, classIdCount++,cli,conf);
    }

    public void registerClass( Class c, int code, FSTConfiguration conf) {
        if ( getIdFromClazz(c) != Integer.MIN_VALUE ) {
            return;
        }
        addClassMapping(c, code, null,conf);
    }

    protected void addClassMapping( Class c, int id, FSTClazzInfo clInfo, FSTConfiguration conf ) {
        clzToId.put(c, id);
        if (clInfo==null)
            clInfo = conf.getCLInfoRegistry().getCLInfo(c, conf);
        if (idToClz.length<=id)
        {
            final FSTClazzInfo[] tmp = new FSTClazzInfo[id + 100];
            System.arraycopy(idToClz,0,tmp,0,idToClz.length);
            idToClz = tmp;
        }
        idToClz[id] = clInfo;
        if ( parent == null ) {
            clInfo.setClzId(id);
        }
    }

    public int getIdFromClazz(Class c) {
        int res = Integer.MIN_VALUE;
        if ( parent != null ) {
            res = parent.getIdFromClazz(c);
        }
        if ( res == Integer.MIN_VALUE ) {
            res = clzToId.get(c);
        }
        return res;
    }

    public void encodeClass(FSTEncoder out, FSTClazzInfo ci) throws IOException {
        int clzId = ci.getClzId();
        if ( clzId >= 0 ) {
            out.writeFShort((short) clzId); // > 2 !!
        } else {
            if ( ci.isAsciiNameShortString) {
                final Class aClass = ci.getClazz();
                int clid = getIdFromClazz(aClass);
                if ( clid != Integer.MIN_VALUE ) {
                    out.writeFShort((short) clid); // > 2 !!
                } else {
                    // ugly hack, also making assumptions about
                    // on how the encoder works internally
                    final byte[] bufferedName = ci.getBufferedName();
                    out.writeFShort((short) 1); // no direct cl id ascii enc
                    out.writeFInt((char) bufferedName.length);
                    out.writeRawBytes(bufferedName,0,bufferedName.length);
                    registerClassNoLookup(aClass,ci,ci.conf);
                }
            } else {
                encodeClass(out,ci.getClazz());
            }
        }
    }

    public void encodeClass(FSTEncoder out, Class c) throws IOException {
        int clid = getIdFromClazz(c);
        if ( clid != Integer.MIN_VALUE ) {
            out.writeFShort((short) clid); // > 2 !!
        } else {
            encodeClassName(out, c, out.getConf() );
        }
    }

    private void encodeClassName(FSTEncoder out, Class c, FSTConfiguration conf) throws IOException {
        out.writeFShort((short) 0); // no direct cl id
        out.writeStringUTF(c.getName());
        registerClassNoLookup(c,null,conf);
    }

    public FSTClazzInfo decodeClass(FSTDecoder in, FSTConfiguration conf) throws IOException, ClassNotFoundException {
        short c = in.readFShort();
        if ( c < LOWEST_CLZ_ID ) {
            // full class name
            String clName;
            if ( c==0) {
                clName = in.readStringUTF();
            }
            else {
                clName = in.readStringAsc();
            }
            Class cl = classForName(clName,conf);
            final FSTClazzInfo clInfo = conf.getCLInfoRegistry().getCLInfo(cl, conf);
            registerClassNoLookup(cl,clInfo,conf);
            return clInfo;
        } else {
            FSTClazzInfo aClass = getClazzFromId(c);
            if ( aClass == null ) {
                throw new RuntimeException("unable to find class for code "+c);
            }
            return aClass;
        }
    }

    HashMap<String,Class> classCache = new HashMap<String, Class>(200);
    AtomicBoolean classCacheLock = new AtomicBoolean(false);
    public Class classForName(String clName, FSTConfiguration conf) throws ClassNotFoundException {
        if ( parent != null ) {
            return parent.classForName(clName,conf);
        }
        try {
            while (!classCacheLock.compareAndSet(false, true)) ;
            Class res = classCache.get(clName);
            if (res == null) {
                try {
                    res = Class.forName(clName, false, conf.getClassLoader());
                } catch (Throwable th) {
                    if (clName.endsWith("_Struct")) // hack to define struct proxys on the fly if sent from another process
                    {
                        try {
                            clName = clName.substring(0, clName.length() - "_Struct".length());
                            Class onHeapStructClz = classCache.get(clName);
                            if (onHeapStructClz == null)
                                onHeapStructClz = Class.forName(clName, false, conf.getClassLoader() );
                            res = FSTStructFactory.getInstance().getProxyClass(onHeapStructClz);
                        } catch (Throwable th1) {
                            FSTUtil.<RuntimeException>rethrow(th1);
                        }
                    } else if ( clName.endsWith("_ActorProxy") ) {
                        // same as above for actors. As there is a custom serializer defined for actors, just instantiate
                        // actor clazz
                        String clName0 = clName;
                        clName = clName.substring(0, clName.length() - "_ActorProxy".length());
                        Class actorClz = classCache.get(clName);
                        if (actorClz == null) {
                            try {
                                actorClz = Class.forName(clName, false, conf.getClassLoader());
                            } catch (ClassNotFoundException clf) {
                                if ( conf.getLastResortResolver() != null ) {
                                    Class aClass = conf.getLastResortResolver().getClass(clName0);
                                    if ( aClass != null )
                                        return aClass;
                                }
                                FSTUtil.<RuntimeException>rethrow(clf);
                            }
                        }
                        return actorClz;
                    } else {
                        if ( conf.getLastResortResolver() != null ) {
                            Class aClass = conf.getLastResortResolver().getClass(clName);
                            if ( aClass != null )
                                return aClass;
                        }
                        throw new RuntimeException("class not found CLASSNAME:" + clName + " loader:"+conf.getClassLoader(), th);
                    }
                }
                if (res != null) {
                    classCache.put(clName, res);
                }
            }
            return res;
        } finally {
            classCacheLock.set(false);
        }
    }

    public void registerClazzFromOtherLoader( Class cl ) {
        while( ! classCacheLock.compareAndSet(false,true) );
        classCache.put(cl.getName(),cl);
        classCacheLock.set(false);
    }

    public FSTClazzInfo getClazzFromId(int c) {
        FSTClazzInfo res = null;
        if ( parent != null ) {
            res = parent.getClazzFromId(c);
        }
        if ( res == null ) {
            if ( c < 0 || c >= idToClz.length )
                return null;
            return idToClz[c];
        } else {
            return res;
        }
    }

}


/**********************************************************\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: http://www.hprose.com/                 |
|                   http://www.hprose.org/                 |
|                                                          |
\**********************************************************/
/**********************************************************\
 *                                                        *
 * HproseTcpServer.java                                   *
 *                                                        *
 * hprose tcp server class for Java.                      *
 *                                                        *
 * LastModified: Apr 20, 2018                             *
 * Author: Ma Bingyao <andot@hprose.com>                  *
 *                                                        *
\**********************************************************/
package hprose.server;

import hprose.common.HproseContext;
import hprose.common.HproseMethods;
import hprose.io.ByteBufferStream;
import hprose.net.Acceptor;
import hprose.net.Connection;
import hprose.net.ConnectionHandler;
import hprose.net.TimeoutType;
import hprose.util.concurrent.Action;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;

public class HproseTcpServer extends HproseService {

    private final static ThreadLocal<TcpContext> currentContext = new ThreadLocal<TcpContext>();
    private volatile ExecutorService threadPool = null;
    private volatile int readTimeout = 30000;
    private volatile int writeTimeout = 30000;
    private boolean threadPoolEnabled = false;
    private int reactorThreads = 2;
    private Acceptor acceptor = null;
    private String host = null;
    private int port = 0;

    private final class ServerHandler implements Runnable {
        private final Connection conn;
        private final ByteBuffer data;
        private final Integer id;

        public ServerHandler(Connection conn, ByteBuffer data, Integer id) {
            this.conn = conn;
            this.data = data;
            this.id = id;
        }

        @SuppressWarnings("unchecked")
        public final void run() {
            TcpContext context = new TcpContext(HproseTcpServer.this, conn.socketChannel());
            currentContext.set(context);
            HproseTcpServer.this.handle(data, context).then(new Action<ByteBuffer>() {
                public void call(ByteBuffer value) throws Throwable {
                    conn.send(value, id);
                }
            }).catchError(new Action<Throwable>() {
                public void call(Throwable e) throws Throwable {
                    conn.close();
                }
            }).whenComplete(new Runnable() {
                public void run() {
                    currentContext.remove();
                    ByteBufferStream.free(data);
                }
            });
        }
    }

    private final class ServerConnectionHandler implements ConnectionHandler {

        public void onConnect(Connection conn) {}

        public void onConnected(Connection conn) {
                fireAcceptEvent(conn.socketChannel());
        }

        public final void onReceived(Connection conn, ByteBuffer data, Integer id) {
            ServerHandler handler = new ServerHandler(conn, data, id);
            if (threadPool != null) {
                try {
                    threadPool.execute(handler);
                }
                catch (RejectedExecutionException e) {
                    conn.close();
                }
            }
            else {
                handler.run();
            }
        }

        public final void onSended(Connection conn, ByteBuffer data, Integer id) {
            ByteBufferStream.free(data);
        }

        public final void onClose(Connection conn) {
            fireCloseEvent(conn.socketChannel());
        }

        public void onError(Connection conn, Exception e) {
            if (conn == null) {
                fireErrorEvent(e, null);
            }
        }

        public void onTimeout(Connection conn, TimeoutType type) {}

        public int getReadTimeout() {
            return readTimeout;
        }

        public int getWriteTimeout() {
            return writeTimeout;
        }

        public int getConnectTimeout() {
            throw new UnsupportedOperationException();
        }
    }

    public HproseTcpServer(String uri) throws URISyntaxException {
        URI u = new URI(uri);
        host = u.getHost();
        port = u.getPort();
    }

    public HproseTcpServer(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String value) {
        host = value;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int value) {
        port = value;
    }

    public int getReactorThreads() {
        return reactorThreads;
    }

    public void setReactorThreads(int reactorThreads) {
        this.reactorThreads = reactorThreads;
    }

    public boolean isStarted() {
        return (acceptor != null);
    }

    public void start() throws IOException {
        if (!isStarted()) {
            acceptor = new Acceptor(host, port, new ServerConnectionHandler(), reactorThreads);
            acceptor.start();
        }
    }

    public void stop() {
        if (isStarted()) {
            acceptor.close();
            if (threadPool != null && !threadPool.isShutdown()) {
                try {
                    threadPool.shutdown();
                }
                catch (SecurityException e) {
                    fireErrorEvent(e, null);
                }
            }
            acceptor = null;
        }
    }

    @Override
    public HproseMethods getGlobalMethods() {
        if (globalMethods == null) {
            globalMethods = new HproseTcpMethods();
        }
        return globalMethods;
    }

    @Override
    public void setGlobalMethods(HproseMethods methods) {
        if (methods instanceof HproseTcpMethods) {
            this.globalMethods = methods;
        }
        else {
            throw new ClassCastException("methods must be a HproseTcpMethods instance");
        }
    }

    @Override
    protected Object[] fixArguments(Type[] argumentTypes, Object[] arguments, ServiceContext context) {
        int count = arguments.length;
        TcpContext tcpContext = (TcpContext)context;
        if (argumentTypes.length != count) {
            Object[] args = new Object[argumentTypes.length];
            System.arraycopy(arguments, 0, args, 0, count);
            Class<?> argType = (Class<?>) argumentTypes[count];
            if (argType.equals(HproseContext.class) || argType.equals(ServiceContext.class)) {
                args[count] = context;
            }
            else if (argType.equals(TcpContext.class)) {
                args[count] = tcpContext;
            }
            else if (argType.equals(SocketChannel.class)) {
                args[count] = tcpContext.getSocketChannel();
            }
            else if (argType.equals(Socket.class)) {
                args[count] = tcpContext.getSocket();
            }
            return args;
        }
        return arguments;
    }

    public static TcpContext getCurrentContext() {
        return currentContext.get();
    }

    /**
     * Is thread pool enabled.
     * This thread pool is not for the service threads, it is for the user service method.
     * The default value is false.
     * @return is thread pool enabled
     */
    public boolean isThreadPoolEnabled() {
        return threadPoolEnabled;
    }

    /**
     * Set thread pool enabled.
     * This thread pool is not for the service threads, it is for the user service method.
     * If your service method takes a long time, or will be blocked, please set this property to be true.
     * @param value is thread pool enabled
     */
    public void setThreadPoolEnabled(boolean value) {
        if (value && (threadPool == null)) {
            threadPool = Executors.newCachedThreadPool();
        }
        threadPoolEnabled = value;
    }

    /**
     * get the thread pool.
     * This thread pool is not for the service threads, it is for the user service method.
     * The default value is null.
     * @return the thread pool
     */
    public ExecutorService getThreadPool() {
        return threadPool;
    }

    /**
     * set the thread pool.
     * This thread pool is not for the service threads, it is for the user service method.
     * Set it to null will disable thread pool.
     * @param value is the thread pool
     */
    public void setThreadPool(ExecutorService value) {
        threadPool = value;
        threadPoolEnabled = (value != null);
    }

    protected void fireAcceptEvent(SocketChannel channel) {
        if (event != null && HproseTcpServiceEvent.class.isInstance(event)) {
            ((HproseTcpServiceEvent)event).onAccept(new TcpContext(this, channel));
        }
    }

    protected void fireCloseEvent(SocketChannel channel) {
        if (event != null && HproseTcpServiceEvent.class.isInstance(event)) {
            ((HproseTcpServiceEvent)event).onClose(new TcpContext(this, channel));
        }
    }

    public int getReadTimeout() {
        return readTimeout;
    }

    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    public int getWriteTimeout() {
        return writeTimeout;
    }

    public void setWriteTimeout(int writeTimeout) {
        this.writeTimeout = writeTimeout;
    }

}


package org.cassandraunit.utils;

import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.QueryOptions;
import com.datastax.driver.core.Session;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.commitlog.CommitLog;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.service.CassandraDaemon;
import org.apache.commons.lang3.StringUtils;
import org.apache.thrift.transport.TTransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.reader.UnicodeReader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

/**
 * @author Jeremy Sevellec
 */
public class EmbeddedCassandraServerHelper {

    private static Logger log = LoggerFactory.getLogger(EmbeddedCassandraServerHelper.class);

    public static final long DEFAULT_STARTUP_TIMEOUT = 20000;
    public static final String DEFAULT_TMP_DIR = "target/embeddedCassandra";
    /** Default configuration file. Starts embedded cassandra under the well known ports */
    public static final String DEFAULT_CASSANDRA_YML_FILE = "cu-cassandra.yaml";
    /** Configuration file which starts the embedded cassandra on a random free port */
    public static final String CASSANDRA_RNDPORT_YML_FILE = "cu-cassandra-rndport.yaml";
    public static final String DEFAULT_LOG4J_CONFIG_FILE = "/log4j-embedded-cassandra.properties";
    private static final String INTERNAL_CASSANDRA_KEYSPACE = "system";
    private static final String INTERNAL_CASSANDRA_AUTH_KEYSPACE = "system_auth";
    private static final String INTERNAL_CASSANDRA_DISTRIBUTED_KEYSPACE = "system_distributed";
    private static final String INTERNAL_CASSANDRA_SCHEMA_KEYSPACE = "system_schema";
    private static final String INTERNAL_CASSANDRA_TRACES_KEYSPACE = "system_traces";

    private static final Set<String> systemKeyspaces = new HashSet<>(Arrays.asList(INTERNAL_CASSANDRA_KEYSPACE,
            INTERNAL_CASSANDRA_AUTH_KEYSPACE, INTERNAL_CASSANDRA_DISTRIBUTED_KEYSPACE,
            INTERNAL_CASSANDRA_SCHEMA_KEYSPACE, INTERNAL_CASSANDRA_TRACES_KEYSPACE));

    public static Predicate<String> nonSystemKeyspaces() {
        return keyspace -> !systemKeyspaces.contains(keyspace);
    }

    private static CassandraDaemon cassandraDaemon = null;
    private static String launchedYamlFile;
    private static com.datastax.driver.core.Cluster cluster;
    private static Session session;

    public static void startEmbeddedCassandra() throws TTransportException, IOException, InterruptedException, ConfigurationException {
        startEmbeddedCassandra(DEFAULT_STARTUP_TIMEOUT);
    }

    public static void startEmbeddedCassandra(long timeout) throws TTransportException, ConfigurationException, IOException {
        startEmbeddedCassandra(DEFAULT_CASSANDRA_YML_FILE, timeout);
    }

    public static void startEmbeddedCassandra(String yamlFile) throws TTransportException, IOException, ConfigurationException {
        startEmbeddedCassandra(yamlFile, DEFAULT_STARTUP_TIMEOUT);
    }

    public static void startEmbeddedCassandra(String yamlFile, long timeout) throws TTransportException, IOException, ConfigurationException {
        startEmbeddedCassandra(yamlFile, DEFAULT_TMP_DIR, timeout);
    }

    public static void startEmbeddedCassandra(String yamlFile, String tmpDir) throws TTransportException, IOException, ConfigurationException {
        startEmbeddedCassandra(yamlFile, tmpDir, DEFAULT_STARTUP_TIMEOUT);
    }

    public static void startEmbeddedCassandra(String yamlFile, String tmpDir, long timeout) throws TTransportException, IOException, ConfigurationException {
        if (cassandraDaemon != null) {
            /* nothing to do Cassandra is already started */
            return;
        }

        if (!StringUtils.startsWith(yamlFile, "/")) {
            yamlFile = "/" + yamlFile;
        }

        rmdir(tmpDir);
        File file = copy(yamlFile, tmpDir).toFile();
        readAndAdaptYaml(file);
        startEmbeddedCassandra(file, tmpDir, timeout);
    }

    public static void startEmbeddedCassandra(File file, long timeout) throws TTransportException, IOException, ConfigurationException {
        startEmbeddedCassandra(file, DEFAULT_TMP_DIR, timeout);
    }
        /**
         * Set embedded cassandra up and spawn it in a new thread.
         *
         * @throws TTransportException
         * @throws IOException
         * @throws ConfigurationException
         */
    public static void startEmbeddedCassandra(File file, String tmpDir, long timeout) throws IOException, ConfigurationException {
        if (cassandraDaemon != null) {
            /* nothing to do Cassandra is already started */
            return;
        }

        checkConfigNameForRestart(file.getAbsolutePath());

        log.debug("Starting cassandra...");
        log.debug("Initialization needed");

        System.setProperty("cassandra.config", "file:" + file.getAbsolutePath());
        System.setProperty("cassandra-foreground", "true");
        System.setProperty("cassandra.native.epoll.enabled", "false"); // JNA doesnt cope with relocated netty
        System.setProperty("cassandra.unsafesystem", "true"); // disable fsync for a massive speedup on old platters

        // If there is no log4j config set already, set the default config
        if (System.getProperty("log4j.configuration") == null) {
            copy(DEFAULT_LOG4J_CONFIG_FILE, tmpDir);
            System.setProperty("log4j.configuration", "file:" + tmpDir + DEFAULT_LOG4J_CONFIG_FILE);
        }

        DatabaseDescriptor.daemonInitialization();

        cleanupAndLeaveDirs();
        final CountDownLatch startupLatch = new CountDownLatch(1);
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(() -> {
            cassandraDaemon = new CassandraDaemon();
            cassandraDaemon.activate();
            startupLatch.countDown();
        });
        try {
            if (!startupLatch.await(timeout, MILLISECONDS)) {
                log.error("Cassandra daemon did not start after " + timeout + " ms. Consider increasing the timeout");
                throw new AssertionError("Cassandra daemon did not start within timeout");
            }
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                if (session != null) session.close();
                if (cluster != null) cluster.close();
            }));
        } catch (InterruptedException e) {
            log.error("Interrupted waiting for Cassandra daemon to start:", e);
            throw new AssertionError(e);
        } finally {
            executor.shutdown();
        }
    }

    private static void checkConfigNameForRestart(String yamlFile) {
        boolean wasPreviouslyLaunched = launchedYamlFile != null;
        if (wasPreviouslyLaunched && !launchedYamlFile.equals(yamlFile)) {
            throw new UnsupportedOperationException("We can't launch two Cassandra configurations in the same JVM instance");
        }
        launchedYamlFile = yamlFile;
    }

    /**
     * Now deprecated, previous version was not fully operating.
     * This is now an empty method, will be pruned in future versions.
     */
    @Deprecated
    public static void stopEmbeddedCassandra() {
        log.warn("EmbeddedCassandraServerHelper.stopEmbeddedCassandra() is now deprecated, " +
                "previous version was not fully operating");
        cassandraDaemon.deactivate();
    }

    /**
     * drop all keyspaces (expect system)
     */
    public static void cleanEmbeddedCassandra() {
        if (session != null) {
            dropKeyspaces();
        }
    }

    /**
     * truncate data in keyspace, except specified tables
     */
    public static void cleanDataEmbeddedCassandra(String keyspace, String... excludedTables) {
            if (session != null) {
                cleanDataWithNativeDriver(keyspace, excludedTables);
            }
    }

    public static com.datastax.driver.core.Cluster getCluster() {
        initCluster();
        return cluster;
    }

    public static Session getSession() {
        initSession();
        return session;
    }

    private static synchronized void initCluster() {
        if (cluster == null) {
            QueryOptions queryOptions = new QueryOptions();
            queryOptions.setRefreshSchemaIntervalMillis(0);
            queryOptions.setRefreshNodeIntervalMillis(0);
            queryOptions.setRefreshNodeListIntervalMillis(0);
            cluster = com.datastax.driver.core.Cluster.builder()
                    .addContactPoints(EmbeddedCassandraServerHelper.getHost())
                    .withPort(EmbeddedCassandraServerHelper.getNativeTransportPort())
                    .withoutJMXReporting()
                    .withQueryOptions(queryOptions)
                    .build();
        }
    }

    private static synchronized void initSession() {
        if (session == null) {
            initCluster();
            session = cluster.connect();
        }
    }

    /**
     * Get the embedded cassandra cluster name
     *
     * @return the cluster name
     */
    public static String getClusterName() {
        return DatabaseDescriptor.getClusterName();
    }

    /**
     * Get embedded cassandra host.
     *
     * @return the cassandra host
     */
    public static String getHost() {
        return DatabaseDescriptor.getRpcAddress().getHostName();
    }

    /**
     * Get embedded cassandra RPC port.
     *
     * @return the cassandra RPC port
     */
    public static int getRpcPort() {
        return DatabaseDescriptor.getRpcPort();
    }

    /**
     * Get embedded cassandra native transport port.
     *
     * @return the cassandra native transport port.
     */
    public static int getNativeTransportPort() {
        return DatabaseDescriptor.getNativeTransportPort();
    }

    private static void cleanDataWithNativeDriver(String keyspace, String... excludedTables) {
        HashSet<String> excludedTableList = new HashSet<>(Arrays.asList(excludedTables));
        cluster.getMetadata().getKeyspace(keyspace).getTables().stream()
                .map(table -> table.getName())
                .filter(tableName -> !excludedTableList.contains(tableName))
                .map(tableName -> keyspace + "." + tableName)
                .forEach(CqlOperations.truncateTable(session));
    }

    private static void dropKeyspaces() {
            dropKeyspacesWithNativeDriver();
    }

    private static void dropKeyspacesWithNativeDriver() {
        cluster.getMetadata().getKeyspaces().stream()
                .map(KeyspaceMetadata::getName)
                .filter(nonSystemKeyspaces())
                .forEach(CqlOperations.dropKeyspace(session));
    }

    private static void rmdir(String dir) {
        File dirFile = new File(dir);
        if (dirFile.exists()) {
            FileUtils.deleteRecursive(dirFile);
        }
    }

    /**
     * Copies a resource from within the jar to a directory.
     *
     * @param resource
     * @param directory
     * @throws IOException
     */
    private static Path copy(String resource, String directory) throws IOException {
        mkdir(directory);
        String fileName = resource.substring(resource.lastIndexOf("/") + 1);
        InputStream from = EmbeddedCassandraServerHelper.class.getResourceAsStream(resource);
        Path copyName = Paths.get(directory, fileName);
        Files.copy(from, copyName);
        return copyName;
    }

    /**
     * Creates a directory
     *
     * @param dir
     * @throws IOException
     */
    private static void mkdir(String dir) {
        FileUtils.createDirectory(dir);
    }

    private static void cleanupAndLeaveDirs() throws IOException {
        mkdirs();
        cleanup();
        mkdirs();
        CommitLog commitLog = CommitLog.instance;
        commitLog.resetUnsafe(true); // cleanup screws w/ CommitLog, this brings it back to safe state
    }

    private static void cleanup() {
        // clean up commitlog and data directory which are stored as data directory/table/data files
        List<String> directories = new ArrayList<>(Arrays.asList(DatabaseDescriptor.getAllDataFileLocations()));
        directories.add(DatabaseDescriptor.getCommitLogLocation());
        for (String dirName : directories) {
            File dir = new File(dirName);
            if (!dir.exists())
                throw new RuntimeException("No such directory: " + dir.getAbsolutePath());
            FileUtils.deleteRecursive(dir);
        }
    }

    public static void mkdirs() {
        DatabaseDescriptor.createAllDirectories();
    }

    private static void readAndAdaptYaml(File cassandraConfig) throws IOException {
        String yaml = readYamlFileToString(cassandraConfig);

        // read the ports and replace them if zero. dump back the changed string, preserving comments (thus no snakeyaml)
        Pattern portPattern = Pattern.compile("^([a-z_]+)_port:\\s*([0-9]+)\\s*$", Pattern.MULTILINE);
        Matcher portMatcher = portPattern.matcher(yaml);
        StringBuffer sb = new StringBuffer();
        boolean replaced = false;
        while (portMatcher.find()) {
            String portName = portMatcher.group(1);
            int portValue = Integer.parseInt(portMatcher.group(2));
            String replacement;
            if (portValue == 0) {
                portValue = findUnusedLocalPort();
                replacement = portName + "_port: " + portValue;
                replaced = true;
            } else {
                replacement = portMatcher.group(0);
            }
            portMatcher.appendReplacement(sb, replacement);
        }
        portMatcher.appendTail(sb);

        if (replaced) {
            writeStringToYamlFile(cassandraConfig, sb.toString());
        }
    }

    private static String readYamlFileToString(File yamlFile) throws IOException {
        // using UnicodeReader to read the correct encoding according to BOM
        try (UnicodeReader reader = new UnicodeReader(new FileInputStream(yamlFile))) {
            StringBuilder sb = new StringBuilder();
            char[] cbuf = new char[1024];

            int readden = reader.read(cbuf);
            while(readden >= 0) {
                sb.append(cbuf, 0, readden);
                readden = reader.read(cbuf);
            }
            return sb.toString();
        }
    }

    private static void writeStringToYamlFile(File yamlFile, String yaml) throws IOException {
        // write utf-8 without BOM
        try (Writer writer = new OutputStreamWriter(new FileOutputStream(yamlFile), "utf-8")) {
            writer.write(yaml);
        }
    }

    private static int findUnusedLocalPort() throws IOException {
        try (ServerSocket serverSocket = new ServerSocket(0)) {
            return serverSocket.getLocalPort();
        }
    }
}


package com.googlecode.objectify.repackaged.gentyref;

import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class CaptureTypeImpl implements CaptureType {
	private final WildcardType wildcard;
	private final TypeVariable<?> variable;
	private final Type[] lowerBounds;
	private Type[] upperBounds;

	/**
	 * Creates an uninitialized CaptureTypeImpl. Before using this type, {@link #init(VarMap)} must be called.
	 * @param wildcard The wildcard this is a capture of
	 * @param variable The type variable where the wildcard is a parameter for.
	 */
	public CaptureTypeImpl(WildcardType wildcard, TypeVariable<?> variable) {
		this.wildcard = wildcard;
		this.variable = variable;
		this.lowerBounds = wildcard.getLowerBounds();
	}

	/**
	 * Initialize this CaptureTypeImpl.
	 * This is needed for type variable bounds referring to each other: we need the capture of the argument.
	 */
	void init(VarMap varMap) {
		ArrayList<Type> upperBoundsList = new ArrayList<>();
		upperBoundsList.addAll(Arrays.asList(varMap.map(variable.getBounds())));

		List<Type> wildcardUpperBounds = Arrays.asList(wildcard.getUpperBounds());
		if (wildcardUpperBounds.size() > 0 && wildcardUpperBounds.get(0) == Object.class) {
			// skip the Object bound, we already have a first upper bound from 'variable'
			upperBoundsList.addAll(wildcardUpperBounds.subList(1, wildcardUpperBounds.size()));
		} else {
			upperBoundsList.addAll(wildcardUpperBounds);
		}
		upperBounds = new Type[upperBoundsList.size()];
		upperBoundsList.toArray(upperBounds);
	}

	/*
	 * @see com.googlecode.gentyref.CaptureType#getLowerBounds()
	 */
	public Type[] getLowerBounds() {
		return lowerBounds.clone();
	}

	/*
	 * @see com.googlecode.gentyref.CaptureType#getUpperBounds()
	 */
	public Type[] getUpperBounds() {
		assert upperBounds != null;
		return upperBounds.clone();
	}

	@Override
	public String toString() {
		return "capture of " + wildcard;
	}
}


package com.googlecode.objectify.impl;

import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 */
public class TypeUtils
{
	/** A map of the primitive types to their wrapper types */
	private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPER = new HashMap<>();
	static {
		PRIMITIVE_TO_WRAPPER.put(boolean.class, Boolean.class);
		PRIMITIVE_TO_WRAPPER.put(byte.class, Byte.class);
		PRIMITIVE_TO_WRAPPER.put(short.class, Short.class);
		PRIMITIVE_TO_WRAPPER.put(int.class, Integer.class);
		PRIMITIVE_TO_WRAPPER.put(long.class, Long.class);
		PRIMITIVE_TO_WRAPPER.put(float.class, Float.class);
		PRIMITIVE_TO_WRAPPER.put(double.class, Double.class);
		PRIMITIVE_TO_WRAPPER.put(char.class, Character.class);
	}

	private TypeUtils() {
	}

	/**
	 * Throw an IllegalStateException if the class does not have a no-arg constructor.
	 */
	public static <T> Constructor<T> getNoArgConstructor(Class<T> clazz) {
		try {
			Constructor<T> ctor = clazz.getDeclaredConstructor(new Class[0]);
			ctor.setAccessible(true);
			return ctor;
		}
		catch (NoSuchMethodException e) {
			// lame there is no way to tell if the class is a nonstatic inner class
			if (clazz.isMemberClass() || clazz.isAnonymousClass() || clazz.isLocalClass())
				throw new IllegalStateException(clazz.getName() + " must be static and must have a no-arg constructor", e);
			else
				throw new IllegalStateException(clazz.getName() + " must have a no-arg constructor", e);
		}
	}

	/**
	 * Gets a constructor that has the specified types of arguments.
	 * Throw an IllegalStateException if the class does not have such a constructor.
	 */
	public static MethodHandle getConstructor(Class<?> clazz, Class<?>... args) {
		try {
			// We use unreflect so that we can make the constructor accessible
			Constructor<?> ctor = clazz.getDeclaredConstructor(args);
			ctor.setAccessible(true);
			return MethodHandles.lookup().unreflectConstructor(ctor);
		}
		catch (NoSuchMethodException e) {
			throw new IllegalStateException(clazz.getName() + " has no constructor with args " + Arrays.toString(args), e);
		} catch (IllegalAccessException e) {
			throw new IllegalStateException("Problem getting constructor for " + clazz.getName() + " with args " + Arrays.toString(args), e);
		}
	}

	/** Wraps any non-runtime exceptions with a runtime exception */
	public static <T> T invoke(MethodHandle methodHandle, Object... params) {
		try {
			return (T)methodHandle.invokeWithArguments(params);
		} catch (RuntimeException e) {
			throw e;
		} catch (Throwable throwable) {
			throw new RuntimeException(throwable);
		}
	}

	/** Checked exceptions are LAME. */
	public static <T> T newInstance(Constructor<T> ctor, Object... params) {
		try {
			return ctor.newInstance(params);
		}
		catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); }
	}

	/** Checked exceptions are LAME. */
	public static Object field_get(Field field, Object obj) {
		try {
			return field.get(obj);
		}
		catch (IllegalArgumentException | IllegalAccessException e) { throw new RuntimeException(e); }
	}

	/**
	 * Just like Class.isAssignableFrom(), but does the right thing when considering autoboxing.
	 */
	public static boolean isAssignableFrom(Class<?> to, Class<?> from) {
		Class<?> notPrimitiveTo = to.isPrimitive() ? PRIMITIVE_TO_WRAPPER.get(to) : to;
		Class<?> notPrimitiveFrom = from.isPrimitive() ? PRIMITIVE_TO_WRAPPER.get(from) : from;

		return notPrimitiveTo.isAssignableFrom(notPrimitiveFrom);
	}

	/** Gets the annotation that has the specified type, or null if there isn't one */
	@SuppressWarnings("unchecked")
	public static <A extends Annotation> A getAnnotation(Annotation[] annotations, Class<A> annotationType) {
		for (Annotation anno: annotations)
			if (annotationType.isAssignableFrom(anno.getClass()))
				return (A)anno;

		return null;
	}

	/**
	 * Get the declared annotation, ignoring any inherited annotations
	 */
	public static <A extends Annotation> A getDeclaredAnnotation(Class<?> onClass, Class<A> annotationType) {
		return getAnnotation(onClass.getDeclaredAnnotations(), annotationType);
	}

	/**
	 * Is the declared annotation present, ignoring any inherited annotations
	 */
	public static <A extends Annotation> boolean isDeclaredAnnotationPresent(Class<?> onClass, Class<A> annotationType) {
		return getDeclaredAnnotation(onClass, annotationType) != null;
	}
}


package com.googlecode.objectify.util;

import com.googlecode.objectify.Result;
import lombok.RequiredArgsConstructor;

import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * A dynamic proxy that wraps a Result<?> value.  For example, if you had a Result<List<String>>, the
 * proxy would implement List<String> and call through to the inner object.
 */
@RequiredArgsConstructor
public class ResultProxy<T> implements InvocationHandler, Serializable
{
	/**
	 * Create a ResultProxy for the given interface.
	 */
	@SuppressWarnings("unchecked")
	public static <S> S create(Class<? super S> interf, Result<S> result) {
		return (S)Proxy.newProxyInstance(result.getClass().getClassLoader(), new Class[] { interf }, new ResultProxy<>(result));
	}

	private final Result<T> result;

	@Override
	public Object invoke(Object obj, Method meth, Object[] params) throws Throwable {
		return meth.invoke(result.now(), params);
	}

	private Object writeReplace() throws ObjectStreamException {
        return new NowProxy<>(result.now());
    }

}


package com.googlecode.objectify.impl;

import com.google.cloud.datastore.Transaction;
import com.google.cloud.datastore.Transaction.Response;
import com.google.protobuf.ByteString;
import com.googlecode.objectify.Result;
import lombok.Getter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/** */
public class AsyncTransactionImpl extends AsyncDatastoreReaderWriterImpl implements PrivateAsyncTransaction {

	private final Transaction transaction;

	/**
	 * Hook that is run immediately after commit
	 */
	private final Runnable afterCommit;

	/**
	 * Operations which modify the session must be enlisted in the transaction and completed
	 * before the transaction commits.  This is so that the session reaches a consistent state
	 * before it is propagated to the parent session.
	 */
	private List<Result<?>> enlisted = new ArrayList<>();

	/** Listeners that will be executed _after_ a commit completes successfully */
	private final List<Runnable> listeners = new ArrayList<>();

	/**
	 * An arbitrary bag of stuff that can be associated with the current transaction context. Not used
	 * by Objectify internally but useful sometimes for application programming.
	 */
	@Getter
	private final Map<Object, Object> bag = new HashMap<>();

	/** */
	public AsyncTransactionImpl(final Transaction raw, final Runnable afterCommit) {
		super(raw);
		this.transaction = raw;
		this.afterCommit = afterCommit;
	}

	/**
	 * Enlist any operations that modify the session.
	 */
	@Override
	public void enlist(final Result<?> result) {
		enlisted.add(result);
	}

	/**
	 * Add a listener to be called after the transaction commits.
	 */
	@Override
	public void listenForCommit(final Runnable listener) {
		listeners.add(listener);
	}

	@Override
	public ByteString getTransactionHandle() {
		return transaction.getTransactionId();
	}

	@Override
	public void runCommitListeners() {
		for (final Runnable listener : listeners) {
			listener.run();
		}
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.util.cmd.TransactionWrapper#commit()
	 */
	@Override
	public Response commit() {
		// Complete any enlisted operations so that the session becomes consistent. Note that some of the
		// enlisted load operations might result in further enlistment... so we have to do this in a loop
		// that protects against concurrent modification exceptions
		while (!enlisted.isEmpty()) {
			final List<Result<?>> last = enlisted;
			enlisted = new ArrayList<>();

			for (final Result<?> result: last)
				result.now();
		}

		final Response response = transaction.commit();
		afterCommit.run();
		return response;
	}

	@Override
	public boolean isActive() {
		return transaction.isActive();
	}

	@Override
	public void rollback() {
		transaction.rollback();
	}
}


package com.googlecode.objectify.impl;

import com.google.cloud.datastore.BaseEntity;
import com.google.cloud.datastore.EntityValue;
import com.google.cloud.datastore.FullEntity;
import com.google.cloud.datastore.FullEntity.Builder;
import com.google.cloud.datastore.Value;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.LoadException;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.SaveException;
import com.googlecode.objectify.annotation.Cache;
import com.googlecode.objectify.impl.translate.ClassTranslator;
import com.googlecode.objectify.impl.translate.LoadContext;
import com.googlecode.objectify.impl.translate.SaveContext;
import lombok.Getter;

/**
 * Holds basic information about POJO entities, and can translate back and forth to the
 * datastore representation.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
public class EntityMetadata<P>
{
	/** The base entity class type, ie the class with the @Entity annotation */
	@Getter
	private final Class<P> entityClass;

	/** The cached annotation, or null if entity should not be cached */
	private final Cache cached;

	/**
	 * The translator that will convert between native datastore representation and pojo for this type.
	 */
	@Getter
	private final ClassTranslator<P> translator;

	/**
	 * Specific metadata about the key for this type.
	 */
	@Getter
	private final KeyMetadata<P> keyMetadata;

	/**
	 * @param clazz must have @Entity in its hierarchy
	 */
	public EntityMetadata(final ObjectifyFactory fact, final Class<P> clazz) {
		assert clazz.isAnnotationPresent(com.googlecode.objectify.annotation.Entity.class);

		this.entityClass = clazz;
		this.cached = clazz.getAnnotation(Cache.class);
		this.translator = (ClassTranslator<P>)fact.getTranslators().getRoot(clazz);
		this.keyMetadata = translator.getKeyMetadata();
	}

	/**
	 * Get the expiry associated with this kind, defined by the @Cached annotation.
	 * For polymorphic types, this is always the instruction on the root @Entity - you
	 * cannot provide per-type caching.
	 *
	 * @return null means DO NOT CACHE, 0 means "no limit", otherwise # of seconds
	 */
	public Integer getCacheExpirySeconds() {
		return this.cached == null ? null : this.cached.expirationSeconds();
	}

	/**
	 * Converts an entity to an object of the appropriate type for this metadata structure.
	 * Does not check that the entity is appropriate; that should be done when choosing
	 * which EntityMetadata to call.
	 */
	public P load(final BaseEntity<?> ent, final LoadContext ctx) {
		try {
			// The context needs to know the root entity for any given point
			ctx.setCurrentRoot(Key.create((com.google.cloud.datastore.Key)ent.getKey()));

			final EntityValue entityValue = makeLoadEntityValue(ent);
			return translator.load(entityValue, ctx, Path.root());
		}
		catch (LoadException ex) { throw ex; }
		catch (Exception ex) {
			throw new LoadException(ent, ex.getMessage(), ex);
		}
	}

	/**
	 * The problem is ProjectionEntity; there's no way to create an EntityValue with a ProjectionEntity
	 * so we can't use the standard translation system for {@code Value<Entity>}. Instead of making the
	 * translation API really complicated, just convert it to a FullEntity.
	 */
	private EntityValue makeLoadEntityValue(final BaseEntity<?> ent) {
		if (ent instanceof FullEntity<?>) {
			return EntityValue.of((FullEntity<?>)ent);
		} else {
			// Sadly there's no more graceful way of doing this
			final Builder<?> builder = FullEntity.newBuilder(ent.getKey());
			for (final String name : ent.getNames()) {
				final Value<?> value = ent.getValue(name);
				builder.set(name, value);
			}

			return EntityValue.of(builder.build());
		}
	}

	/**
	 * Converts an object to a datastore Entity with the appropriate Key type.
	 */
	public FullEntity<?> save(final P pojo, final SaveContext ctx) {
		try {
			return translator.save(pojo, false, ctx, Path.root()).get();
		}
		catch (SaveException ex) { throw ex; }
		catch (Exception ex) {
			throw new SaveException(pojo, ex);
		}
	}
}


package com.googlecode.objectify.impl;

import com.google.cloud.datastore.Cursor;
import com.google.cloud.datastore.KeyQuery;
import com.google.cloud.datastore.QueryResults;
import com.google.cloud.datastore.StringValue;
import com.google.cloud.datastore.StructuredQuery.Filter;
import com.google.cloud.datastore.StructuredQuery.OrderBy;
import com.google.cloud.datastore.StructuredQuery.PropertyFilter;
import com.google.cloud.datastore.Value;
import com.google.common.base.MoreObjects;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.LoadResult;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.annotation.Subclass;
import com.googlecode.objectify.cmd.Query;
import com.googlecode.objectify.cmd.QueryResultIterable;
import com.googlecode.objectify.impl.translate.ClassTranslator;
import com.googlecode.objectify.util.IteratorFirstResult;
import com.googlecode.objectify.util.MakeListResult;
import com.googlecode.objectify.util.ResultProxy;
import lombok.SneakyThrows;

import java.util.Iterator;
import java.util.List;

/**
 * Implementation of Query.
 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
public class QueryImpl<T> extends SimpleQueryImpl<T> implements Query<T>, Cloneable
{
	/**
	 * Because we process @Load batches, we need to always work in chunks.  So we should always specify
	 * a chunk size to the query.  This is the default if user does not specify an explicit chunk size.
	 */
	static final int DEFAULT_CHUNK_SIZE = 30;

	/** Track this so we can prevent attempts to filter/sort by id or parent */
	private Class<T> classRestriction;

	/**  */
	private QueryDef actual;

	/** */
	private Integer chunk;

	/** Three states; null is "figure it out automatically" */
	private Boolean hybrid;

	/** */
	QueryImpl(final LoaderImpl loader) {
		super(loader);
		this.actual = new QueryDef();
	}

	/** */
	QueryImpl(final LoaderImpl loader, final String kind, final Class<T> clazz) {
		super(loader);

		this.actual = new QueryDef().kind(kind);

		// If this is a polymorphic subclass, add an extra filter
		if (clazz != null) {
			final Subclass sub = clazz.getAnnotation(Subclass.class);
			if (sub != null) {
				final String discriminator = sub.name().length() > 0 ? sub.name() : clazz.getSimpleName();
				this.addFilter(FilterOperator.EQUAL.of(ClassTranslator.DISCRIMINATOR_INDEX_PROPERTY, StringValue.of(discriminator)));
			}

			this.classRestriction = clazz;
		}
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.impl.cmd.QueryBase#createQuery()
	 */
	@Override
	QueryImpl<T> createQuery() {
		return this.clone();
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Query#filter(java.lang.String, java.lang.Object)
	 */
	@Override
	public QueryImpl<T> filter(final String condition, final Object value) {
		final QueryImpl<T> q = createQuery();
		q.addFilter(condition, value);
		return q;
	}

	/* */
	@Override
	public QueryImpl<T> filter(final Filter filter) {
		final QueryImpl<T> q = createQuery();
		q.addFilter(filter);
		return q;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Query#order(java.lang.String)
	 */
	@Override
	public QueryImpl<T> order(final String condition) {
		final QueryImpl<T> q = createQuery();
		q.addOrder(condition);
		return q;
	}

	/** Modifies the instance */
	void addFilter(final String condition, final Object value) {

		final String[] parts = condition.trim().split(" ");
		if (parts.length < 1 || parts.length > 2)
			throw new IllegalArgumentException("'" + condition + "' is not a legal filter condition");

		final String prop = parts[0].trim();
		final FilterOperator op = (parts.length == 2) ? this.translate(parts[1]) : FilterOperator.EQUAL;

		// If we have a class restriction, check to see if the property is the @Parent or @Id. We used to try to convert
		// filtering on the id field to a __key__ query, but that tended to confuse users about the real capabilities
		// of GAE and Objectify. So let's force users to use filterKey() instead.
		if (this.classRestriction != null) {
			final KeyMetadata<?> meta = loader.ofy.factory().keys().getMetadataSafe(this.classRestriction);

			if (prop.equals(meta.getParentFieldName())) {
				throw new IllegalArgumentException("@Parent fields cannot be filtered on. Perhaps you wish to use filterKey() or ancestor() instead?");
			}
			else if (prop.equals(meta.getIdFieldName())) {
				throw new IllegalArgumentException("@Id fields cannot be filtered on. Perhaps you wish to use filterKey() instead?");
			}
		}

		// Convert to something filterable, possibly extracting/converting keys
		final Value<?> translated = loader.getObjectifyImpl().makeFilterable(value);

		addFilter(op.of(prop, translated));
	}

	/**
	 * Add the filter as an AND to whatever is currently set as the actual filter.
	 */
	void addFilter(final Filter filter) {
		actual = actual.andFilter(filter);
	}

	/**
	 * Converts the textual operator (">", "<=", etc) into a FilterOperator.
	 * Forgiving about the syntax; != and <> are NOT_EQUAL, = and == are EQUAL.
	 */
	protected FilterOperator translate(String operator) {
		operator = operator.trim();

		if (operator.equals("=") || operator.equals("=="))
			return FilterOperator.EQUAL;
		else if (operator.equals(">"))
			return FilterOperator.GREATER_THAN;
		else if (operator.equals(">="))
			return FilterOperator.GREATER_THAN_OR_EQUAL;
		else if (operator.equals("<"))
			return FilterOperator.LESS_THAN;
		else if (operator.equals("<="))
			return FilterOperator.LESS_THAN_OR_EQUAL;
		else if (operator.equals("!=") || operator.equals("<>"))
			//return FilterOperator.NOT_EQUAL;
			throw new UnsupportedOperationException("The Cloud Datastore SDK does not currently support 'NOT EQUAL' filters");
		else if (operator.toLowerCase().equals("in"))
			//return FilterOperator.IN;
			throw new UnsupportedOperationException("The Cloud Datastore SDK does not currently support 'IN' filters");
		else
			throw new IllegalArgumentException("Unknown operator '" + operator + "'");
	}

	/** Modifies the instance */
	void addOrder(String condition) {
		condition = condition.trim();
		boolean descending = false;

		if (condition.startsWith("-")) {
			descending = true;
			condition = condition.substring(1).trim();
		}

		// Prevent ordering by @Id or @Parent fields, which are really part of the key
		if (this.classRestriction != null) {
			final KeyMetadata<?> meta = loader.ofy.factory().keys().getMetadataSafe(this.classRestriction);

			if (condition.equals(meta.getParentFieldName()))
				throw new IllegalArgumentException("You cannot order by @Parent field. Perhaps you wish to order by __key__ instead?");

			if (condition.equals(meta.getIdFieldName())) {
				throw new IllegalArgumentException("You cannot order by @Id field. Perhaps you wish to order by __key__ instead?");
			}
		}

		this.actual = actual.orderBy(descending ? OrderBy.desc(condition) : OrderBy.asc(condition));
	}

	/** Modifies the instance */
	void setAncestor(final Object keyOrEntity) {
		final com.google.cloud.datastore.Key key = loader.ofy.factory().keys().anythingToRawKey(keyOrEntity);
		this.actual = this.actual.andFilter(PropertyFilter.hasAncestor(key));
	}

	/** Modifies the instance */
	void setLimit(final int value) {
		this.actual = this.actual.limit(value);

		if (this.chunk == null)
			this.chunk = value;
	}

	/** Modifies the instance */
	void setOffset(final int value) {
		this.actual = this.actual.offset(value);
	}

	/** Modifies the instance */
	void setStartCursor(final Cursor value) {
		this.actual = this.actual.startCursor(value);
	}

	/** Modifies the instance */
	void setEndCursor(final Cursor value) {
		this.actual = this.actual.endCursor(value);
	}

	/** Modifies the instance */
	void setChunk(final int value) {
		this.chunk = value;
	}

	/** Modifies the instance */
	void setHybrid(final boolean force) {
		this.hybrid = force;
	}

	/** Just a sanity check */
	void checkKeysOnlyOk() {
		if (!this.actual.getProjection().isEmpty())
			throw new IllegalStateException("You cannot ask for both keys-only and projections in the same query. That makes no sense!");
	}

	/** Modifies the instance */
	void setDistinct(final boolean value) {
		this.actual = this.actual.distinctOnAll(value);
	}

	/** Modifies the instance */
	void addProjection(final String... fields) {
		if (this.hybrid != null && this.hybrid)
			throw new IllegalStateException("You cannot ask for both hybrid and projections in the same query. That makes no sense!");

		for (final String field: fields) {
			this.actual = this.actual.project(field);
		}
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return MoreObjects.toStringHelper(this).add("query", actual).toString();
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Query#first()
	 */
	@Override
	public LoadResult<T> first() {
		// By the way, this is the same thing that PreparedQuery.asSingleEntity() does internally
		final Iterator<T> it = this.limit(1).iterator();

		return new LoadResult<>(null, new IteratorFirstResult<>(it));
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.Query#count()
	 */
	@Override
	public int count() {
		return loader.createQueryEngine().queryCount(this.actual.newKeyQuery());
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.QueryExecute#iterable()
	 */
	@Override
	public QueryResultIterable<T> iterable() {
		return this::iterator;
	}

	/* (non-Javadoc)
	 * @see com.google.cloud.datastore.QueryResultIterable#iterator()
	 */
	@Override
	public QueryResults<T> iterator() {
		if (!actual.getProjection().isEmpty())
			return loader.createQueryEngine().queryProjection(this.actual.newProjectionQuery());
		else if (shouldHybridize())
			return loader.createQueryEngine().queryHybrid(this.actual.newKeyQuery(), chunk == null ? Integer.MAX_VALUE : chunk);
		else
			return loader.createQueryEngine().queryNormal(this.actual.newEntityQuery(), chunk == null ? Integer.MAX_VALUE : chunk);
	}

	/* (non-Javadoc)
	 * @see com.googlecode.objectify.cmd.Query#list()
	 */
	@Override
	public List<T> list() {
		return ResultProxy.create(List.class, new MakeListResult<>(this.chunk(Integer.MAX_VALUE).iterator()));
	}

	/**
	 * Get an iterator over the keys.  Not part of the public api, but used by QueryKeysImpl.  Assumes
	 * that setKeysOnly() has already been set.
	 */
	QueryResults<Key<T>> keysIterator() {
		final QueryEngine queryEngine = loader.createQueryEngine();
		final KeyQuery query = this.actual.newKeyQuery();
		return queryEngine.queryKeysOnly(query);
	}

	/**
	 * @return true if we should hybridize this query
	 */
	private boolean shouldHybridize() {
		if (hybrid != null)
			return hybrid;

		// If the class is cacheable
		if (classRestriction != null && loader.getObjectifyImpl().getOptions().isCache() && fact().getMetadata(classRestriction).getCacheExpirySeconds() != null)
			return true;

		return false;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#clone()
	 */
	@SuppressWarnings({"unchecked", "CloneDoesntDeclareCloneNotSupportedException"})
	@SneakyThrows
	public QueryImpl<T> clone() {
		return (QueryImpl<T>)super.clone();
	}

	/** Convenience method */
	private ObjectifyFactory fact() {
		return loader.getObjectify().factory();
	}
}


package com.googlecode.objectify.util;

import com.google.cloud.datastore.ProjectionEntityQuery;
import com.google.cloud.datastore.StructuredQuery;
import com.google.cloud.datastore.StructuredQuery.OrderBy;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import java.util.List;
import java.util.function.Supplier;

/**
 * Some static utility methods for interacting with basic datastore objects like keys and queries.
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Queries
{
	/**
	 * Copy all the behavior characteristics of the orignial query into the builder.
	 */
	public static <T, B extends StructuredQuery.Builder<T>> B clone(final StructuredQuery<?> orig, final Supplier<B> into) {
		final B builder = into.get();

		builder.setNamespace(orig.getNamespace());
		builder.setEndCursor(orig.getEndCursor());
		builder.setFilter(orig.getFilter());
		builder.setKind(orig.getKind());
		builder.setLimit(orig.getLimit());
		builder.setOffset(orig.getOffset());
		builder.setStartCursor(orig.getStartCursor());

		addOrderBy(builder, orig.getOrderBy());

		return builder;
	}

	/**
	 * The Builder api is programmer-hostile
	 */
	public static void addOrderBy(final StructuredQuery.Builder<?> builder, final List<OrderBy> orderBy) {
		if (!orderBy.isEmpty()) {
			builder.addOrderBy(orderBy.get(0), orderBy.subList(1, orderBy.size()).toArray(new OrderBy[orderBy.size() - 1]));
		}
	}

	/**
	 * The Builder api is programmer-hostile
	 */
	public static void addProjection(final ProjectionEntityQuery.Builder builder, final List<String> projection) {
		if (!projection.isEmpty()) {
			builder.addProjection(projection.get(0), projection.subList(1, projection.size()).toArray(new String[projection.size() - 1]));
		}
	}

	/**
	 * The Builder api is programmer-hostile
	 */
	public static void addDistinctOn(final ProjectionEntityQuery.Builder builder, final List<String> distinctOn) {
		if (!distinctOn.isEmpty()) {
			builder.addDistinctOn(distinctOn.get(0), distinctOn.subList(1, distinctOn.size()).toArray(new String[distinctOn.size() - 1]));
		}
	}
}


package com.googlecode.objectify.cache;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 *
Dynamic proxy which wraps a MemcacheService and adds retries when an exception occurs.
 * It logs and masks exceptions on complete failure.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@RequiredArgsConstructor
@Slf4j
public class MemcacheServiceRetryProxy implements InvocationHandler
{
	/** */
	private static final int DEFAULT_TRIES = 4;

	/**
	 * Create the proxy that does retries. Adds a strict error handler to the service.
	 */
	public static MemcacheService createProxy(final MemcacheService raw)
	{
		return createProxy(raw, DEFAULT_TRIES);
	}

	/**
	 * Create the proxy that does retries. Adds a strict error handler to the service.
	 */
	public static MemcacheService createProxy(final MemcacheService raw, final int retryCount) {
		return (MemcacheService)java.lang.reflect.Proxy.newProxyInstance(
			raw.getClass().getClassLoader(),
			raw.getClass().getInterfaces(),
			new MemcacheServiceRetryProxy(raw, retryCount));
	}

	/** */
	private final MemcacheService raw;

	/** */
	private final int tries;

	/* (non-Javadoc)
	 * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
	 */
	@Override
	public Object invoke(Object proxy, Method meth, Object[] args) throws Throwable {
		for (int i = 0; i<this.tries; i++) {
			try {
				return meth.invoke(this.raw, args);
			} catch (InvocationTargetException ex) {
				if (i == (this.tries - 1))
					log.error("Memcache operation failed, giving up", ex);
				else
					log.warn("Error performing memcache operation, retrying: " + meth, ex);
			}
		}

		// Will reach this point when we have exhausted our retries.
		return null;
	}

}


package com.googlecode.objectify.impl.translate;

import com.google.cloud.datastore.Value;
import com.googlecode.objectify.impl.Path;
import lombok.RequiredArgsConstructor;

/**
 *
In case someone has a field of type Object or uses a raw collection without specifying the generic type.
 * This is less efficient because we have to dynamically figure out the type, but at least it works.

 *
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
@RequiredArgsConstructor
public class ObjectTranslatorFactory implements TranslatorFactory<Object, Object>
{
	private final Translators translators;

	/* */
	@Override
	public Translator<Object, Object> create(final TypeKey<Object> tk, final CreateContext createCtx, final Path path) {
		if (tk.getTypeAsClass() != Object.class)
			return null;

		return new NullSafeTranslator<Object, Object>() {
			@Override
			protected Object loadSafe(final Value<Object> value, final LoadContext ctx, final Path path) throws SkipException {
				return value.get();
			}

			@Override
			protected Value<Object> saveSafe(final Object pojo, final boolean index, final SaveContext saveCtx, final Path path) throws SkipException {
				final TypeKey<Object> runtimeTypeKey = new TypeKey<>(pojo.getClass());

				// We really only need the createctx so that the translators can get a factory.
				final Translator<Object, Object> realTranslator = translators.get(runtimeTypeKey, createCtx, path);
				return realTranslator.save(pojo, index, saveCtx, path);
			}
		};
	}
}