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

import org.nustaq.serialization.*;
import org.nustaq.serialization.util.FSTOutputStream;
import org.nustaq.serialization.util.FSTUtil;

import java.io.IOException;
import java.io.OutputStream;

/**
 * Default Coder used for serialization. Serializes into a binary stream
 */
public class FSTStreamEncoder implements FSTEncoder {

    private FSTConfiguration conf;

    private FSTClazzNameRegistry clnames;
    private FSTOutputStream buffout;
    private byte[] ascStringCache;

    public FSTStreamEncoder(FSTConfiguration conf) {
        this.conf = conf;
        clnames = (FSTClazzNameRegistry) conf.getCachedObject(FSTClazzNameRegistry.class);
        if ( clnames == null ) {
            clnames = new FSTClazzNameRegistry(conf.getClassRegistry());
        } else {
            clnames.clear();
        }
    }

    @Override
    public void setConf(FSTConfiguration conf) {
        this.conf = conf;
        clnames = (FSTClazzNameRegistry) conf.getCachedObject(FSTClazzNameRegistry.class);
        if ( clnames == null ) {
            clnames = new FSTClazzNameRegistry(conf.getClassRegistry());
        } else {
            clnames.clear();
        }
    }

    void writeFBooleanArr(boolean[] arr, int off, int len) throws IOException {
        buffout.ensureFree(len);
        for (int i = off; i < off+len; i++) {
            buffout.buf[buffout.pos++] = (byte) (arr[i] ? 1 : 0);
        }
    }

    public void writeFFloatArr(float[] arr, int off, int len) throws IOException {
        int byteLen = arr.length * 4;
        buffout.ensureFree(byteLen);
        byte buf[] = buffout.buf;
        int count = buffout.pos;
        int max = off + len;
        for (int i = off; i < max; i++) {
            long anInt = Float.floatToIntBits(arr[i]);
            buf[count] = (byte) (anInt >>> 0);
            buf[count+1] = (byte) (anInt >>> 8);
            buf[count+2] = (byte) (anInt >>> 16);
            buf[count+3] = (byte) (anInt >>> 24);
            count+=4;
        }
        buffout.pos+= byteLen;
    }

    public void writeFDoubleArr(double[] arr, int off, int len) throws IOException {
        final int byteLen = arr.length * 8;
        buffout.ensureFree(byteLen);
        final byte buf[] = buffout.buf;
        int count = buffout.pos;
        final int max = off + len;
        for (int i = off; i < max; i++) {
            long aLong = Double.doubleToLongBits(arr[i]);
            buf[count] = (byte) (aLong >>> 0);
            buf[count+1] = (byte) (aLong >>> 8);
            buf[count+2] = (byte) (aLong >>> 16);
            buf[count+3] = (byte) (aLong >>> 24);
            buf[count+4] = (byte) (aLong >>> 32);
            buf[count+5] = (byte) (aLong >>> 40);
            buf[count+6] = (byte) (aLong >>> 48);
            buf[count+7] = (byte) (aLong >>> 56);
            count+=8;
        }
        buffout.pos+= byteLen;
    }

//    Using ByteBuffers is faster but only with 1.8_u40. seems terrible with prior versions.
//    also requires some hacking in order to reuse a bytebuffer

//    static Field bbHB, bbCap;
//    static {
//        Field[] fields = ByteBuffer.class.getDeclaredFields();
//        for (int i = 0; i < fields.length; i++) {
//            Field fi = fields[i];
//            if ( fi.getName() == "hb" ) {
//                bbHB = fi;
//                bbHB.setAccessible(true);
//            }
//        }
//        fields = Buffer.class.getDeclaredFields();
//        for (int i = 0; i < fields.length; i++) {
//            Field fi = fields[i];
//            if ( fi.getName() == "capacity" ) {
//                bbCap = fi;
//                bbCap.setAccessible(true);
//            }
//        }
//    }
//
//    ThreadLocal<ByteBuffer> buf = new ThreadLocal<ByteBuffer>() {
//        @Override
//        protected ByteBuffer initialValue() {
//            return ByteBuffer.wrap(new byte[0]);
//        }
//    };
//
//    public void writeFDoubleArr(double[] arr, int off, int len) throws IOException {
//        int byteLen = arr.length * 8;
//        buffout.ensureFree(byteLen);
//        int max = off + len;
//        ByteBuffer wrap = buf.get();
//        try {
//            bbHB.set(wrap, buffout.buf);
//            bbCap.set(wrap,buffout.buf.length);
//            wrap.limit(buffout.pos+byteLen);
//        } catch (IllegalAccessException e) {
//            e.printStackTrace();
//            wrap = ByteBuffer.wrap(buffout.buf, buffout.pos, byteLen).order(ByteOrder.LITTLE_ENDIAN);
//        }

//        int count = buffout.pos;
//        for (int i = off; i < max; i++) {
//            long aLong = Double.doubleToLongBits(arr[i]);
//            wrap.putLong(count,aLong);
//            count += 8;
//        }
//        buffout.pos+= byteLen;
//    }

    public void writeFShortArr(short[] arr, int off, int len) throws IOException {
        buffout.ensureFree(len*3);
        for (int i = off; i < off+len; i++) {
            short c = arr[i];
            if (c < 255 && c >= 0) {
                buffout.buf[buffout.pos++] = (byte) c;
            } else {
                buffout.buf[buffout.pos] = (byte) 255;
                buffout.buf[buffout.pos+1] = (byte) (c >>> 0);
                buffout.buf[buffout.pos+2] = (byte) (c >>> 8);
                buffout.pos += 3;
            }
        }
    }

    public void writeFCharArr(char[] arr, int off, int len) throws IOException {
        buffout.ensureFree(len*3);
        for (int i = off; i < off+len; i++) {
            char c = arr[i];
            if (c < 255 && c >= 0) {
                buffout.buf[buffout.pos++] = (byte) c;
            } else {
                byte[] buf = buffout.buf;
                int count = buffout.pos;
                buf[count] = (byte) 255;
                buf[count+1] = (byte) (c >>> 0);
                buf[count+2] = (byte) (c >>> 8);
                buffout.pos += 3;
            }
        }
    }

    // uncompressed version
    public void writeFIntArr(int[] arr, int off, int len) throws IOException {
        int byteLen = arr.length * 4;
        buffout.ensureFree(byteLen);
        byte buf[] = buffout.buf;
        int count = buffout.pos;
        int max = off + len;
        for (int i = off; i < max; i++) {
            long anInt = arr[i];
            buf[count] = (byte) (anInt >>> 0);
            buf[count+1] = (byte) (anInt >>> 8);
            buf[count+2] = (byte) (anInt >>> 16);
            buf[count+3] = (byte) (anInt >>> 24);
            count+=4;
        }
        buffout.pos+= byteLen;
    }

    // compressed version
    public void _writeFIntArr(int v[], int off, int len) throws IOException {
        final int free = 5 * len;
        buffout.ensureFree(free);
        final byte[] buf = buffout.buf;
        int count = buffout.pos;
        for (int i = off; i < off+len; i++) {
            final int anInt = v[i];
            if ( anInt > -127 && anInt <=127 ) {
                buffout.buf[count++] = (byte)anInt;
            } else
            if ( anInt >= Short.MIN_VALUE && anInt <= Short.MAX_VALUE ) {
                buf[count++] = -128;
                buf[count++] = (byte) ((anInt >>>  0) & 0xFF);
                buf[count++] = (byte) ((anInt >>> 8) & 0xFF);
            } else {
                buf[count++] = -127;
                buf[count++] = (byte) ((anInt >>>  0) & 0xFF);
                buf[count++] = (byte) ((anInt >>>  8) & 0xFF);
                buf[count++] = (byte) ((anInt >>> 16) & 0xFF);
                buf[count++] = (byte) ((anInt >>> 24) & 0xFF);
            }
        }
        buffout.pos = count;
    }

    void writeFLongArr(long[] arr, int off, int len) throws IOException {
        int byteLen = arr.length * 8;
        buffout.ensureFree(byteLen);
        byte buf[] = buffout.buf;
        int count = buffout.pos;
        for (int i = off; i < off+len; i++) {
            long anInt = arr[i];
            buf[count] = (byte) (anInt >>> 0);
            buf[count+1] = (byte) (anInt >>> 8);
            buf[count+2] = (byte) (anInt >>> 16);
            buf[count+3] = (byte) (anInt >>> 24);
            buf[count+4] = (byte) (anInt >>> 32);
            buf[count+5] = (byte) (anInt >>> 40);
            buf[count+6] = (byte) (anInt >>> 48);
            buf[count+7] = (byte) (anInt >>> 56);
            count += 8;
        }
        buffout.pos+= byteLen;
    }

    /**
     * write prim array no len no tag
     *
     *
     * @param array
     * @throws IOException
     */
    public void writePrimitiveArray(Object array, int off, int len) throws IOException {
        Class<?> componentType = array.getClass().getComponentType();
        if ( componentType == byte.class ) {
            writeRawBytes((byte[]) array, off, len);
        } else
        if ( componentType == char.class ) {
            writeFCharArr((char[]) array, off, len);
        } else
        if ( componentType == short.class ) {
            writeFShortArr((short[]) array, off, len);
        } else
        if ( componentType == int.class ) {
            writeFIntArr((int[]) array, off, len);
        } else
        if ( componentType == double.class ) {
            writeFDoubleArr((double[]) array, off, len);
        } else
        if ( componentType == float.class ) {
            writeFFloatArr((float[]) array, off, len);
        } else
        if ( componentType == long.class ) {
            writeFLongArr((long[]) array, off, len);
        } else
        if ( componentType == boolean.class ) {
            writeFBooleanArr((boolean[]) array, off, len);
        } else {
            throw new RuntimeException("expected primitive array");
        }
    }

    /**
     * does not write length, just plain bytes
     *
     * @param array
     * @param length
     * @throws java.io.IOException
     */
    public void writeRawBytes(byte[] array, int start, int length) throws IOException {
        buffout.ensureFree(length);
        System.arraycopy(array, start, buffout.buf, buffout.pos, length);
        buffout.pos += length;
    }

    @Override
    public void writeStringUTF(String str) throws IOException {
        final int strlen = str.length();

        writeFInt(strlen);
        buffout.ensureFree(strlen*3);
        final byte[] bytearr = buffout.buf;
        int count = buffout.pos;
        for (int i=0; i<strlen; i++) {
            final char c = str.charAt(i);
            bytearr[count++] = (byte)c;
            if ( c >= 255) {
                bytearr[count-1] = (byte)255;
                bytearr[count++] = (byte) ((c >>> 0) & 0xFF);
                bytearr[count++] = (byte) ((c >>> 8) & 0xFF);
            }
        }
        buffout.pos = count;
    }

    /**
     * length < 127 !!!!!
     *
     * @param name
     * @throws java.io.IOException
     */
    void writeStringAsc(String name) throws IOException {
        int len = name.length();
        if ( len >= 127 ) {
            throw new RuntimeException("Ascii String too long");
        }
        writeFByte((byte) len);
        buffout.ensureFree(len);
        if (ascStringCache == null || ascStringCache.length < len)
            ascStringCache = new byte[len];
        name.getBytes(0, len, ascStringCache, 0);
        writeRawBytes(ascStringCache, 0, len);
    }

    @Override
    public void writeFShort(short c) throws IOException {
        if (c < 255 && c >= 0) {
            writeFByte(c);
        } else {
            writeFByte(255);
            writePlainShort(c);
        }
    }

    public boolean writeAttributeName(FSTClazzInfo.FSTFieldInfo subInfo, Object value) {
        return false;
    }

    public boolean writeTag(byte tag, Object info, long somValue, Object toWrite, FSTObjectOutput oout) throws IOException {
        writeFByte(tag);
        return false;
    }

    @Override
    public void writeFChar(char c) throws IOException {
        // -128 = short byte, -127 == 4 byte
        if (c < 255 && c >= 0) {
            buffout.ensureFree(1);
            buffout.buf[buffout.pos++] = (byte) c;
        } else {
            buffout.ensureFree(3);
            byte[] buf = buffout.buf;
            int count = buffout.pos;
            buf[count++] = (byte) 255;
            buf[count++] = (byte) (c >>> 0);
            buf[count++] = (byte) (c >>> 8);
            buffout.pos += 3;
        }
    }

    @Override
    public final void writeFByte(int v) throws IOException {
        buffout.ensureFree(1);
        buffout.buf[buffout.pos++] = (byte) v;
    }

    @Override
    public void writeFInt(int anInt) throws IOException {
        // -128 = short byte, -127 == 4 byte
        if (anInt > -127 && anInt <= 127) {
            if (buffout.buf.length <= buffout.pos + 1) {
                buffout.ensureFree(1);
            }
            buffout.buf[buffout.pos++] = (byte) anInt;
        } else if (anInt >= Short.MIN_VALUE && anInt <= Short.MAX_VALUE) {
            ensureFree(3);
            byte[] buf = buffout.buf;
            int count = buffout.pos;
            buf[count++] = (byte) -128;
            buf[count++] = (byte) (anInt >>> 0);
            buf[count++] = (byte) (anInt >>> 8);
            buffout.pos = count;
        } else {
            buffout.ensureFree(5);
            byte[] buf = buffout.buf;
            int count = buffout.pos;
            buf[count++] = (byte) -127;
            buf[count++] = (byte) ((anInt >>> 0) & 0xFF);
            buf[count++] = (byte) ((anInt >>>  8) & 0xFF);
            buf[count++] = (byte) ((anInt >>> 16) & 0xFF);
            buf[count++] = (byte) ((anInt >>> 24) & 0xFF);
            buffout.pos = count;
        }
    }

    @Override
    public void writeFLong(long anInt) throws IOException {
// -128 = short byte, -127 == 4 byte
        if (anInt > -126 && anInt <= 127) {
            writeFByte((int) anInt);
        } else if (anInt >= Short.MIN_VALUE && anInt <= Short.MAX_VALUE) {
            ensureFree(3);
            byte[] buf = buffout.buf;
            int count = buffout.pos;
            buf[count++] = (byte) -128;
            buf[count++] = (byte) (anInt >>> 0);
            buf[count++] = (byte) (anInt >>> 8);
            buffout.pos = count;
        } else if (anInt >= Integer.MIN_VALUE && anInt <= Integer.MAX_VALUE) {
            buffout.ensureFree(5);
            byte[] buf = buffout.buf;
            int count = buffout.pos;
            buf[count++] = (byte) -127;
            buf[count++] = (byte) ((anInt >>> 0) & 0xFF);
            buf[count++] = (byte) ((anInt >>>  8) & 0xFF);
            buf[count++] = (byte) ((anInt >>> 16) & 0xFF);
            buf[count++] = (byte) ((anInt >>> 24) & 0xFF);
            buffout.pos = count;
        } else {
            buffout.ensureFree(9);
            byte[] buf = buffout.buf;
            int count = buffout.pos;
            buf[count++] = (byte) -126;
            buf[count++] = (byte) (anInt >>> 0);
            buf[count++] = (byte) (anInt >>> 8);
            buf[count++] = (byte) (anInt >>> 16);
            buf[count++] = (byte) (anInt >>> 24);
            buf[count++] = (byte) (anInt >>> 32);
            buf[count++] = (byte) (anInt >>> 40);
            buf[count++] = (byte) (anInt >>> 48);
            buf[count++] = (byte) (anInt >>> 56);
            buffout.pos = count;
        }
    }

    /**
     * Writes a 4 byte float.
     */
    @Override
    public void writeFFloat(float value) throws IOException {
        writePlainInt(Float.floatToIntBits(value));
    }

    @Override
    public void writeFDouble(double value) throws IOException {
        writePlainLong(Double.doubleToLongBits(value));
    }

    @Override
    public int getWritten() {
        return buffout.pos-buffout.getOff();
    }

    /**
     * close and flush to underlying stream if present. The stream is also closed
     * @throws IOException
     */
    @Override
    public void close() throws IOException {
        buffout.close();
        conf.returnObject(clnames);
    }

    @Override
    public void reset(byte[] out) {
        if (out==null) {
            buffout.reset();
        } else {
            buffout.reset(out);
        }
        clnames.clear();
    }

    @Override
    public void skip(int i) {
        buffout.pos+=i;
    }

    /**
     * used to write uncompressed int (guaranteed length = 4) at a (eventually recent) position
     * @param position
     * @param v
     */
    @Override
    public void writeInt32At(int position, int v) {
        buffout.buf[position] = (byte)  (v >>> 0);
        buffout.buf[position+1] = (byte) (v >>> 8);
        buffout.buf[position+2] = (byte) (v >>> 16);
        buffout.buf[position+3] = (byte) (v >>> 24);
    }

    /**
     * if output stream is null, just encode into a byte array
     * @param outstream
     */
    @Override
    public void setOutstream(OutputStream outstream) {
        if ( buffout == null )
        {
            // try reuse
            buffout = (FSTOutputStream) conf.getCachedObject(FSTOutputStream.class);
            if ( buffout == null ) // if fail, alloc
                buffout = new FSTOutputStream(1000, outstream);
            else
                buffout.reset(); // reset resued fstoutput
        }
        if ( outstream == null )
            buffout.setOutstream(buffout);
        else
            buffout.setOutstream(outstream);
    }

    public OutputStream getOutstream() {
        return buffout;
    }

    /**
     * writes current buffer to underlying output and resets buffer.
     * @throws IOException
     */
    @Override
    public void flush() throws IOException {
        buffout.flush();
    }

    @Override
    public void ensureFree(int bytes) throws IOException {
        buffout.ensureFree(bytes);
    }

    @Override
    public byte[] getBuffer() {
        return buffout.buf;
    }

    public void registerClass(Class possible) {
        clnames.registerClass(possible,conf);
    }

    @Override
    public final void writeClass(Class cl) {
        try {
            clnames.encodeClass(this,cl);
        } catch ( IOException e) {
            FSTUtil.<RuntimeException>rethrow(e);
        }
    }

    @Override
    public final void writeClass(FSTClazzInfo clInf) {
        try {
            clnames.encodeClass(this, clInf);
        } catch ( IOException e) {
            FSTUtil.<RuntimeException>rethrow(e);
        }
    }

    private void writePlainLong(long v) throws IOException {
        buffout.ensureFree(8);
        byte[] buf = buffout.buf;
        int count = buffout.pos;
        buf[count++] = (byte) (v >>> 0);
        buf[count++] = (byte) (v >>> 8);
        buf[count++] = (byte) (v >>> 16);
        buf[count++] = (byte) (v >>> 24);
        buf[count++] = (byte) (v >>> 32);
        buf[count++] = (byte) (v >>> 40);
        buf[count++] = (byte) (v >>> 48);
        buf[count++] = (byte) (v >>> 56);
        buffout.pos += 8;
    }

    private void writePlainShort(int v) throws IOException {
        buffout.ensureFree(2);
        byte[] buf = buffout.buf;
        int count = buffout.pos;
        buf[count++] = (byte) (v >>> 0);
        buf[count++] = (byte) (v >>> 8);
        buffout.pos += 2;
    }

    private void writePlainChar(int v) throws IOException {
        buffout.ensureFree(2);
        byte[] buf = buffout.buf;
        int count = buffout.pos;
        buf[count++] = (byte) (v >>> 0);
        buf[count++] = (byte) (v >>> 8);
        buffout.pos += 2;
    }

    private void writePlainInt(int v) throws IOException {
        buffout.ensureFree(4);
        byte[] buf = buffout.buf;
        int count = buffout.pos;
        buf[count++] = (byte) ((v >>> 0) & 0xFF);
        buf[count++] = (byte) ((v >>>  8) & 0xFF);
        buf[count++] = (byte) ((v >>> 16) & 0xFF);
        buf[count++] = (byte) ((v >>> 24) & 0xFF);
        buffout.pos = count;
    }

    public void externalEnd(FSTClazzInfo clz) {
    }

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

    public boolean isPrimitiveArray(Object array, Class<?> componentType) {
        return componentType.isPrimitive();
    }

    public boolean isTagMultiDimSubArrays() {
        return false;
    }

    @Override
    public void writeVersionTag(int version) throws IOException {
        writeFByte(version);
    }

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

    @Override
    public void writeArrayEnd() {

    }

    @Override
    public void writeFieldsEnd(FSTClazzInfo serializationInfo) {
    }

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

}


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

import org.nustaq.offheap.bytez.Bytez;
import org.nustaq.offheap.bytez.BytezAllocator;
import org.nustaq.offheap.bytez.onheap.HeapBytezAllocator;
import org.nustaq.offheap.structs.*;
import org.nustaq.offheap.structs.structtypes.StructArray;
import org.nustaq.offheap.structs.structtypes.StructByteString;
import org.nustaq.offheap.structs.structtypes.StructString;
import org.nustaq.serialization.FSTClazzInfo;
import org.nustaq.serialization.FSTConfiguration;
import org.nustaq.serialization.util.DefaultFSTInt2ObjectMap;
import org.nustaq.serialization.util.FSTInt2ObjectMap;
import org.nustaq.serialization.util.FSTUtil;
import javassist.*;
import javassist.Modifier;
import javassist.bytecode.*;
import javassist.expr.ExprEditor;
import javassist.expr.FieldAccess;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;

/**
 * manages + generates struct instrumented classes
 */
public class FSTStructFactory {

    public static int SIZE_ALIGN = 2;
    static FSTStructFactory instance;

    public static FSTStructFactory getInstance() {
        if (instance==null) {
            instance = new FSTStructFactory(); // fixme: should be final
        }
        return instance;
    }

    public static final int MAX_CLASSES = 1000;
    static FSTConfiguration conf = FSTConfiguration.createStructConfiguration();

    ClassPool defaultPool;
    Loader proxyLoader;
    ClassLoader parentLoader;

    {
        defaultPool = new ClassPool(null) {
            @Override
            public CtClass get(String classname) throws NotFoundException {
                if ( rawByteClassDefs.containsKey(classname)) {
                    try {
                        return makeClass(new ByteArrayInputStream(rawByteClassDefs.get(classname)));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                return super.get(classname);
            }
        };
        defaultPool.appendSystemPath();

        proxyLoader = new Loader(FSTStructFactory.class.getClassLoader(), defaultPool)
        {
            protected Class loadClassByDelegation(String name)
                    throws ClassNotFoundException
            {
                try { return delegateToParent(name); } catch (Exception ex) {
                    return null;
                }
            }
        };
    }

    ConcurrentHashMap<Class, Class> proxyClzMap = new ConcurrentHashMap<Class, Class>();
    FSTStructGeneration structGen = new FSTByteArrayUnsafeStructGeneration();
    ConcurrentHashMap<String,byte[]> rawByteClassDefs = new ConcurrentHashMap<String, byte[]>();
    boolean autoRegister = true;

    BytezAllocator allocator = new HeapBytezAllocator();

    public FSTStructFactory() {
        registerClz(FSTStruct.class);
        registerClz(StructString.class);
        registerClz(StructArray.class);
        registerClz(StructByteString.class);
    }

    public void registerRawClass( String name, byte bytes[] ) {
        rawByteClassDefs.put(name,bytes);
    }

    public <T> Class<T> createStructClz( Class<T> clazz ) throws Exception {
        //FIXME: ensure FSTStruct is superclass, check protected, no private methods+fields
        if ( Modifier.isFinal(clazz.getModifiers()) || Modifier.isAbstract(clazz.getModifiers()) ) {
            throw new RuntimeException("Cannot add final classes to structs "+clazz.getName());
        }
        if ( clazz.getName().endsWith("_Struct") ) {
            throw new RuntimeException("cannot create Struct on Struct class. Class "+clazz+" is already instrumented" );
        }
        String proxyName = clazz.getName()+"_Struct";
        Class present = null;
        try {
            present = proxyLoader.loadClass(proxyName);
        } catch (ClassNotFoundException ex) {
            //
        }
        if ( present != null )
            return present;
        ClassPool pool = defaultPool;
        CtClass newClz = pool.makeClass(proxyName);
        CtClass orig = null;
        if ( rawByteClassDefs.get(clazz.getName()) != null ) {
            orig = pool.makeClass( new ByteArrayInputStream(rawByteClassDefs.get(clazz.getName())));
        } else {
            orig = pool.getOrNull(clazz.getName());
            if ( orig == null ) {
                pool.insertClassPath(new ClassClassPath(clazz));
                orig = pool.get(clazz.getName());
                if (orig == null)
                {
                    throw new RuntimeException("unable to locate class byte code for "+clazz.getName());
                }
            }
        }
        newClz.setSuperclass(orig);

        final FSTClazzInfo clInfo = conf.getClassInfo(clazz);

        CtMethod[] methods = orig.getMethods();
        for (int i = 0; i < methods.length; i++) {
            CtMethod method = methods[i];
            final Class curClz = Class.forName( method.getDeclaringClass().getName() );
            boolean allowed = ((method.getModifiers() & AccessFlag.ABSTRACT) == 0 ) &&
                    (method.getModifiers() & AccessFlag.NATIVE) == 0 &&
                    (method.getModifiers() & AccessFlag.FINAL) == 0 &&
                    (   !method.getDeclaringClass().getName().equals(FSTStruct.class.getName())
                       ||method.getName().equals("getFieldValues")) &&
                    ! method.getDeclaringClass().getName().equals(Object.class.getName());
            allowed &= method.getAnnotation(NoAssist.class) == null;
            allowed &= (method.getModifiers() & AccessFlag.STATIC) == 0;
            if ( allowed && (method.getModifiers() & AccessFlag.FINAL) != 0 && ! method.getDeclaringClass().getName().equals("java.lang.Object") ) {
                throw new RuntimeException("final methods are not allowed for struct classes:"+method.getName());
            }
            if ( allowed && (method.getModifiers() & AccessFlag.PRIVATE) != 0 && ! method.getDeclaringClass().getName().equals("java.lang.Object")) {
                throw new RuntimeException("private methods are not allowed for struct classes:"+method.getName());
            }
            if ( allowed ) {
                ClassMap mp = new ClassMap();
                mp.fix(clazz.getName());
                mp.fix(clazz.getSuperclass().getName()); // ?? only depth 2 ??
                method = new CtMethod(method,newClz,mp);
                String methName = method.getName();
                // array access:
                //      void [name](int, type)
                //      [type] [name](int)
                FSTClazzInfo.FSTFieldInfo arrayFi = checkForSpecialArrayMethod(clInfo, method, "", null, null);
                // array length:
                //      int [name]Len()
                FSTClazzInfo.FSTFieldInfo lenfi = checkForSpecialArrayMethod(clInfo, method, "Len", CtClass.intType, new CtClass[0]);
                // get byte index of array data:
                //      int [name]Index()
                FSTClazzInfo.FSTFieldInfo indexfi = checkForSpecialArrayMethod(clInfo, method, "Index", CtClass.intType, new CtClass[0]);
                // get size of array element:
                //      int [name]ElementSize()
                FSTClazzInfo.FSTFieldInfo elemlen = checkForSpecialArrayMethod(clInfo, method, "ElementSize", CtClass.intType, new CtClass[0]);
                // CREATE non volatile pointer to array[0] element:
                //      type [name]Pointer() OR type [name]Pointer(pointerToSetup) (for reuse)
                FSTClazzInfo.FSTFieldInfo pointerfi = checkForSpecialArrayMethod(clInfo, method, "Pointer", null, null);
                // get byte index to structure or array header element:
                //      type [name]StructIndex()
                FSTClazzInfo.FSTFieldInfo structIndex = checkForSpecialMethod(clInfo, method, "StructIndex", CtClass.intType, new CtClass[0], false);
                // set with CAS
                //      boolean [name]CAS(expectedValue,value)
                FSTClazzInfo.FSTFieldInfo casAcc = checkForSpecialMethod(clInfo, method, "CAS", CtClass.booleanType, null, false);

                if ( casAcc != null ) {
                    structGen.defineStructSetCAS(casAcc, clInfo, method);
                    newClz.addMethod(method);
                } else
                if ( pointerfi != null ) {
                    structGen.defineArrayPointer(pointerfi, clInfo, method);
                    newClz.addMethod(method);
                } else
                if ( structIndex != null ) {
                    structGen.defineFieldStructIndex(structIndex, clInfo, method);
                    newClz.addMethod(method);
                } else
                if ( indexfi != null ) {
                    structGen.defineArrayIndex(indexfi, clInfo, method);
                    newClz.addMethod(method);
                } else
                if ( elemlen != null ) {
                    structGen.defineArrayElementSize(elemlen, clInfo, method);
                    newClz.addMethod(method);
                } else
                if (  arrayFi != null ) {
                    structGen.defineArrayAccessor(arrayFi, clInfo, method);
                    newClz.addMethod(method);
                } else if ( methName.endsWith("Len") && lenfi != null )
                {
                    structGen.defineArrayLength(lenfi, clInfo, method);
                    newClz.addMethod(method);
                } else {
                    if ( methName.equals("getFieldValues") &&
                            ( (clInfo.getClazz().getSuperclass().getName().equals("de.nustaq.reallive.impl.RLStructRow")) // oops
                            || (curClz != FSTStruct.class) )
                       ) {
                        FSTClazzInfo.FSTFieldInfo[] fieldInfo = clInfo.getFieldInfo();
                        StringBuilder body = new StringBuilder("{  return new Object[] { ");
                        for (int j = 0; j < fieldInfo.length; j++) {
                            FSTClazzInfo.FSTFieldInfo fstFieldInfo = fieldInfo[j];
                            int modifiers = fstFieldInfo.getField().getModifiers();
                            if ( (java.lang.reflect.Modifier.isProtected(modifiers) ||
                                  java.lang.reflect.Modifier.isPublic(modifiers)) &&
                                  !java.lang.reflect.Modifier.isStatic(modifiers)
                                )
                            {
                                body.append( "\""+fstFieldInfo.getName()+"\", " );
                                Class type = fstFieldInfo.getType();
                                if ( FSTStruct.class.isAssignableFrom(type) ) {
                                    body.append(fstFieldInfo.getName()).append(".getFieldValues()");
                                } else {
                                    if ( type.isPrimitive() ) {
                                        if ( long.class == type ) {
                                            body.append("new Long("+fstFieldInfo.getName()+")");
                                        } else if ( float.class == type ||double.class == type ) {
                                            body.append("new Double("+fstFieldInfo.getName()+")");
                                        } else {
                                            body.append("new Integer("+fstFieldInfo.getName()+")");
                                        }
                                    } else {
                                        body.append(fstFieldInfo.getName());
                                    }
                                }
                                if ( j != fieldInfo.length-1 )
                                    body.append(",");
                            }
                        }
                        body.append("}; }");
                        method.setBody(body.toString());
                    }
                    newClz.addMethod(method);
                    method.instrument( new ExprEditor() {
                        @Override
                        public void edit(FieldAccess f) throws CannotCompileException {
                            try {
                                if ( ! f.isStatic() ) {
                                    CtClass type = null;
                                    type = f.getField().getType();
                                    FSTClazzInfo.FSTFieldInfo fieldInfo = clInfo.getFieldInfo(f.getFieldName(), null);
                                    if ( fieldInfo == null ) {
                                        return;
                                    }
                                    if ( f.isReader() ) {
                                        structGen.defineStructReadAccess(f, type, fieldInfo);
                                    } else if ( f.isWriter() ) {
                                        structGen.defineStructWriteAccess(f, type, fieldInfo);
                                    }
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
        }

        return (Class<T>) loadProxyClass(clazz, pool, newClz);
    }

    FSTClazzInfo.FSTFieldInfo checkForSpecialArrayMethod( FSTClazzInfo clzInfo, CtMethod method, String postFix, Object returnType, CtClass requiredArgs[] ) {
        return checkForSpecialMethod(clzInfo, method, postFix, returnType, requiredArgs, true);
    }

    FSTClazzInfo.FSTFieldInfo checkForSpecialMethod(FSTClazzInfo clzInfo, CtMethod method, String postFix, Object returnType, CtClass requiredArgs[], boolean array) {
        int len = postFix.length();
        String methName = method.getName();
        if ( ! methName.endsWith(postFix) ) {
            return null;
        }
        FSTClazzInfo.FSTFieldInfo res = clzInfo.getFieldInfo(methName.substring(0, methName.length() - len), null);
        if ( res == null ) {
            return null;
        }
        if ( array && res.isArray() && res.getArrayType().isArray() ) {
            throw new RuntimeException("nested arrays not supported "+res.getDesc());
        }
        if ( array && !res.isArray() ) {
            //throw new RuntimeException("expect array type for field "+res.getDesc()+" special method:"+method);
            // just ignore
            return null;
        }
        if ( res.isArray() || ! array ) {
            if ( returnType instanceof Class ) {
                try {
                    if ( ! method.getReturnType().getName().equals(((Class) returnType).getName()) ) {
                        throw new RuntimeException("expected method "+method+" to return "+returnType );
                    }
                } catch (NotFoundException e) {
                    e.printStackTrace();
                }
            } else if ( returnType instanceof CtClass ) {
                try {
                    if ( ! method.getReturnType().equals(returnType) ) {
                        throw new RuntimeException("expected method "+method+" to return "+returnType );
                    }
                } catch (NotFoundException e) {
                    e.printStackTrace();
                }
            }
            return res;
        }
        return null;
    }

    private <T> Class loadProxyClass(Class<T> clazz, ClassPool pool, final CtClass cc) throws ClassNotFoundException {
        Class ccClz;
        Loader cl = new Loader(clazz.getClassLoader(), pool) {
            protected Class loadClassByDelegation(String name)
                    throws ClassNotFoundException
            {
                if ( name.equals(cc.getName()) )
                    return null;
                return delegateToParent(name);
            }
        };
        ccClz = cl.loadClass(cc.getName());
        return ccClz;
    }

    public Class getProxyClass(Class clz) throws Exception {
//        synchronized (this)
        {
            Class res = proxyClzMap.get(clz);
            if ( res == null ) {
                res = createStructClz(clz);
                proxyClzMap.put(clz,res);
            }
            return res;
        }
    }

    public <T extends FSTStruct> T createWrapper(Class<T> onHeap, Bytez bytes, long index) throws Exception {
        Class proxy = getProxyClass(onHeap);
        T res = (T) FSTUtil.getUnsafe().allocateInstance(proxy);
        res.baseOn(bytes, index, this);
        return res;
    }

    public <T extends FSTStruct> T createEmptyStructPointer(Class<T> onHeap) {
        try {
            return createWrapper(onHeap,null,0);
        } catch (Exception e) {
            FSTUtil.<RuntimeException>rethrow(e);
        }
        return null;
    }

    /**
     * allocates a StructAccessor ("pointer") matching the struct data expected in the byte
     * array at given position. The resulting pointer object is not "volatile" (not a cached instance)
     * @param b
     * @param index
     * @return
     */
    public FSTStruct createStructWrapper(Bytez b, long index) {
        int clzId = b.getInt(index + 4);
        return createStructPointer(b, index, clzId);
    }

    /**
     * allocates a StructAccessor ("pointer") matching the struct data expected in the byte
     * array at given position with given classId. The resulting pointer object is not "volatile" (not a cached instance).
     * The class id should match the Struct stored in the byte array. (classId must be the correct struct or a superclass of it)
     * @param b
     * @param index
     * @return
     */
    public FSTStruct createStructPointer(Bytez b, long index, int clzId) {
//        synchronized (this) // FIXME FIXME FIXME: contention point
        // desynced expecting class registering happens on startup
        {
            Class clazz = mIntToClz.get(clzId);
            if (clazz==null)
                throw new RuntimeException("unregistered class "+clzId);
            try {
                return (FSTStruct) createWrapper(clazz, b, index);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    public FSTStruct createTypedArrayBasePointer(Bytez base, long objectBaseOffset /*offset of object containing array*/, int arrayStructIndex /*position of array header in struct*/) {
        int arrayElementZeroindex = base.getInt(objectBaseOffset + arrayStructIndex);
        int elemSiz = base.getInt(objectBaseOffset+arrayStructIndex+8);
        int len = base.getInt(objectBaseOffset+arrayStructIndex+4);
        int clId = base.getInt(objectBaseOffset+arrayStructIndex+12);
        FSTStruct structPointer = null;
        if ( clId <= 0 ) { // untyped array
            structPointer = new FSTStruct();
            structPointer.baseOn(base,objectBaseOffset+arrayElementZeroindex,this);
        } else {
            structPointer = createStructPointer(base, (int) (objectBaseOffset+arrayElementZeroindex), clId);
        }
        structPointer.___elementSize = elemSiz;
        return structPointer;
    }

    public void fillTypedArrayBasePointer(FSTStruct result, Bytez base, long objectBaseOffset /*offset of object containing array*/, int arrayStructIndex /*position of array header in struct*/) {
        int arrayElementZeroindex = base.getInt(objectBaseOffset+arrayStructIndex);
        int elemSiz = base.getInt(objectBaseOffset+arrayStructIndex+8);
//        int len = unsafe.getInt(base,objectBaseOffset+arrayStructIndex+4);
//        int clId = unsafe.getInt(base,objectBaseOffset+arrayStructIndex+12);
        result.baseOn(base, objectBaseOffset + arrayElementZeroindex, this);
        result.___elementSize = elemSiz;
    }

    public void fillPrimitiveArrayBasePointer(FSTStruct result, Bytez base, long objectBaseOffset /*offset of object containing array*/, int arrayStructIndex /*position of array header in struct*/) {
        int arrayElementZeroindex = base.getInt(objectBaseOffset+arrayStructIndex);
        result.baseOn(base,objectBaseOffset+arrayElementZeroindex,this);
    }

    public FSTStruct createPrimitiveArrayBasePointer(Bytez base, long objectBaseOffset /*offset of object containing array*/, int arrayStructIndex /*position of array header in struct*/) {
        int arrayElementZeroindex = base.getInt(objectBaseOffset+arrayStructIndex);
//        int len = unsafe.getInt(base,objectBaseOffset+arrayStructIndex+4);
        FSTStruct structPointer = new FSTStruct();
        structPointer.baseOn(base,objectBaseOffset+arrayElementZeroindex,this);
        return structPointer;
    }

    public <T extends FSTStruct> StructArray<T> toStructArray(int size, T onHeap) {
        StructArray<T> arr = new StructArray<T>(size,onHeap);
        return toStruct(arr);
    }

    public <T extends FSTStruct> T toStruct(T onHeap) {
        return toStruct(onHeap,allocator);
    }

    public <T extends FSTStruct> T toStruct(T onHeap, BytezAllocator alloc) {
        if ( onHeap.isOffHeap() ) {
            return onHeap;
        }
        try {
            Bytez b = toByteArray(onHeap, alloc);
            return (T)createWrapper(onHeap.getClass(),b,0);
        } catch (Exception e) {
            if ( e instanceof RuntimeException )
                throw (RuntimeException)e;
            else
                throw new RuntimeException(e);
        }
    }

    ThreadLocal<Object[]> cachedWrapperMap = new ThreadLocal<Object[]>() {
        @Override
        protected Object[] initialValue() {
            return new Object[MAX_CLASSES];
        }
    };

    public void detach(FSTStruct structPointer) {
        int id = structPointer.getClzId();
        Object o = cachedWrapperMap.get()[id];
        if ( o == structPointer )
            cachedWrapperMap.get()[id] = null;
    }

    public FSTStruct getStructPointerByOffset(Bytez b, long offset) {
        if ( b.length() < offset+8 )
            throw new RuntimeException("array to short "+b.length()+" offset "+offset);
        int clzId = b.getInt(offset+4);
        int ptr = b.getInt(offset);
        if (clzId <= 0) {
            return null;
        }
        Object[] wrapperMap = cachedWrapperMap.get();
        Object res = wrapperMap[clzId];
        if ( res != null ) {
            ((FSTStruct)res).baseOn(b, offset, this);
            return (FSTStruct) res;
        }
        res = createStructPointer(b, (int) (offset), clzId);
        wrapperMap[clzId] = res;
        return (FSTStruct) res;
    }

    /**
     * returns a struct wrapper for given structured object from the thread local wrapper cache.
     * @param b
     * @param index
     * @return
     */
    public FSTStruct getStructPointer(Bytez b, long index) {
        return getStructPointerByOffset(b,index);
    }

    public static int align(int val, int align) {
        while( val%align != 0 )
            val++;
        return val;
    }

    public int calcStructSize(FSTStruct onHeapStruct) {
        try {
            if ( onHeapStruct == null ) {
                return 0;
            }
            if (onHeapStruct.isOffHeap())
                return onHeapStruct.getByteSize();
            int siz = 8;
            FSTClazzInfo clInfo = conf.getClassInfo(onHeapStruct.getClass());
            FSTClazzInfo.FSTFieldInfo fis[] = clInfo.getFieldInfo();
            for (int i = 0; i < fis.length; i++) {
                FSTClazzInfo.FSTFieldInfo fi = fis[i];
                if ( fi.getField().getDeclaringClass() == FSTStruct.class )
                    continue;
                int modifiers = fi.getField().getModifiers();
                if ( ! Modifier.isProtected(modifiers) && ! Modifier.isPublic(modifiers) )
                    throw new RuntimeException("all fields of a structable class must be public or protected. Field:"+fi.getName()+" in class "+fi.getField().getDeclaringClass().getName() );
                // FIXME: check for null refs, check for FSTStruct subclasses
                if ( fi.getType().isArray() ) {
                    if ( fi.getType().getComponentType().isArray() ) {
                        throw new RuntimeException("nested arrays not supported");
                    }
                    // if array is @aligned, add align-1 to size (overestimation), because I don't know the exact position of the array data here
                    // embedded object data is currently not aligned, only the header position respects the @align
                    if ( fi.isIntegral() ) { // prim array
                        Object objectValue = fi.getObjectValue(onHeapStruct);
                        if ( objectValue == null ) {
                            throw new RuntimeException("arrays in struct templates must not be null !");
                        }
                        siz += Array.getLength(objectValue) * fi.getComponentStructSize() + fi.getStructSize() + fi.getAlignPad()+(fi.getAlign()>0?fi.getAlign()-1:0);
                    } else { // object array
                        Object objectValue[] = (Object[]) fi.getObjectValue(onHeapStruct);
                        if (objectValue==null) {
                            siz+=fi.getStructSize()+fi.getAlignPad()+(fi.getAlign()>0?fi.getAlign()-1:0);
                        } else {
                            int elemSiz = computeElemSize(onHeapStruct,objectValue, fi);
                            siz += Array.getLength(objectValue) * elemSiz + fi.getStructSize() + fi.getAlignPad()+(fi.getAlign()>0?fi.getAlign()-1:0);
                        }
                    }
                } else if ( fi.isIntegral() ) { // && ! array
                    siz += fi.getStructSize();
                } else { // objectref
                    FSTStruct obj = (FSTStruct) fi.getObjectValue(onHeapStruct);
                    siz += fi.getStructSize()+calcStructSize(obj)+fi.getAlignPad();
                }
            }
            if ( onHeapStruct instanceof FSTEmbeddedBinary) {
                siz+=((FSTEmbeddedBinary) onHeapStruct).getEmbeddedSizeAdditon(this);
            }
            return siz;
        } catch ( Exception e ) {
            throw new RuntimeException(e);
        }
    }

    protected int computeElemSize(Object container, Object[] objectValue, FSTClazzInfo.FSTFieldInfo fi) {
        if ( container instanceof FSTArrayElementSizeCalculator) {
            int res = ((FSTArrayElementSizeCalculator) container).getElementSize(fi.getField(),this);
            if ( res >= 0 )
                return res;
        }
        Templated annotation = fi.getField().getAnnotation(Templated.class);
        if ( annotation != null ) {
            Object template = objectValue[0];
            return align(calcStructSize((FSTStruct) template),SIZE_ALIGN);
        }
        int elemSiz = 0;
        for (int j = 0; j < objectValue.length; j++) {
            Object o = objectValue[j];
            if ( o != null )
                elemSiz=Math.max( elemSiz, calcStructSize((FSTStruct) o) );
        }
        return align(elemSiz,SIZE_ALIGN);
    }

    FSTInt2ObjectMap<Class> mIntToClz = conf.getIntToObjectMapFactory().createMap(97); // id to onheap class
    HashMap<Class,Integer> mClzToInt = new HashMap<Class,Integer>(); // reverse

    int idCount = 1;
    public void registerClz(Class ... classes) {
        for (int i = 0; i < classes.length; i++) {
            Class c = classes[i];
            if ( mClzToInt.containsKey(c) ) {
                continue;
            }
            int id = idCount++;
            mIntToClz.put(id,c);
            mClzToInt.put(c,id);
            try {
                getProxyClass(c);
            } catch (Exception e) {
                FSTUtil.<RuntimeException>rethrow(e);
            }
        }
    }

    // register from top to bottom to avoid interference with application (fastcast)
    public void registerSystemClz(byte startVal, Class ... classes) {
        for (int i = 0; i < classes.length; i++) {
            Class c = classes[i];
            if ( mClzToInt.containsKey(c) ) {
                continue;
            }
            int id = startVal--;
            mIntToClz.put(id,c);
            mClzToInt.put(c,id);
            try {
                getProxyClass(c);
            } catch (Exception e) {
                FSTUtil.<RuntimeException>rethrow(e);
            }
        }
    }

    public void registerClzId(Class c, int id) {
        mIntToClz.put(id,c);
        mClzToInt.put(c,id);
    }

    public int getClzId(Class c) {
        Integer integer = mClzToInt.get(c);
        if (autoRegister && integer == null && c != null ) {
            if ( c.getName().endsWith("_Struct") )
                return getClzId(c.getSuperclass());
            registerClz(c);
            return getClzId(c);
        }
        return integer == null ? 0: integer;
    }

    public Class getClazz(int clzId) {
        return mIntToClz.get(clzId);
    }

    public Bytez toByteArray(FSTStruct onHeapStruct) {
        return toByteArray(onHeapStruct,allocator);
    }

    public Bytez toByteArray(FSTStruct onHeapStruct, BytezAllocator allocator) {
        try {
            int sz = align(calcStructSize(onHeapStruct),SIZE_ALIGN);
            Bytez b = allocator.alloc(sz);
            toByteArray(onHeapStruct,b,0);
            return b;
        } catch (Exception e) {
            if ( e instanceof RuntimeException )
                throw (RuntimeException)e;
            else
                throw new RuntimeException(e);
        }
    }

    static class ForwardEntry {

        ForwardEntry(int pointerPos, Object forwardObject, FSTClazzInfo.FSTFieldInfo fsfi) {
            this.pointerPos = pointerPos;
            this.forwardObject = forwardObject;
            fi = fsfi;
        }

        FSTClazzInfo.FSTFieldInfo fi;
        int pointerPos;
        Object forwardObject;
        FSTStruct template;
    }

    public int toByteArray(FSTStruct onHeapStruct, Bytez bytes, int index) throws Exception {
        ArrayList<ForwardEntry> positions = new ArrayList<ForwardEntry>();
        if ( onHeapStruct == null ) {
            return index;
        }
        if (onHeapStruct.isOffHeap()) {
//            unsafe.copyMemory(onHeapStruct.___bytes,onHeapStruct.___offset,bytes,FSTStruct.bufoff+index,onHeapStruct.getByteSize());
            onHeapStruct.___bytes.copyTo(bytes, index, onHeapStruct.___offset, onHeapStruct.getByteSize());
            return onHeapStruct.getByteSize();
        }
        int initialIndex =index;
        Class<? extends FSTStruct> aClass = onHeapStruct.getClass();
        int clzId = getClzId(aClass);
        bytes.putInt(index+4,clzId);
        index+=8;
        FSTClazzInfo clInfo = conf.getClassInfo(aClass);
        FSTClazzInfo.FSTFieldInfo fis[] = clInfo.getFieldInfo();
        for (int i = 0; i < fis.length; i++) {
            FSTClazzInfo.FSTFieldInfo fi = fis[i];
            if ( fi.getField().getDeclaringClass() == FSTStruct.class )
                continue;
            index+=fi.getAlignPad();
            if ( fi.getType().isArray() ) {
                if ( fi.getType().getComponentType().isArray() ) {
                    throw new RuntimeException("nested arrays not supported");
                }
                if ( fi.isIntegral() ) { // prim array
                    Object objectValue = fi.getObjectValue(onHeapStruct);
                    positions.add(new ForwardEntry(index,objectValue,fi));
                    index += fi.getStructSize();
                } else { // object array
                    Object objArr[] = (Object[]) fi.getObjectValue(onHeapStruct);
                    if ( objArr == null ) {
                        bytes.putInt(index, -1);
                        index+=fi.getStructSize();
                    } else {
                        Templated takeFirst = fi.getField().getAnnotation(Templated.class);
                        ForwardEntry fe = new ForwardEntry(index, objArr, fi);
                        if ( takeFirst != null ) {
                            fe.template = (FSTStruct) objArr[0];
                        }
                        positions.add(fe);
                        index += fi.getStructSize();
                        int elemSiz = computeElemSize(onHeapStruct,objArr,fi);
                        bytes.putInt(index-8,elemSiz);
                    }
                }
            } else if ( fi.isIntegral() ) { // && ! array
                Class type = fi.getType();
                int structIndex = fi.getStructOffset();
                if ( index != structIndex+initialIndex ) {
                    throw new RuntimeException("internal error. please file an issue");
                }
                if ( type == boolean.class ) {
                    bytes.putBool(index, fi.getBooleanValue(onHeapStruct));
                } else
                if ( type == byte.class ) {
                    bytes.put(index, (byte) fi.getByteValue(onHeapStruct));
                } else
                if ( type == char.class ) {
                    bytes.putChar(index, (char) fi.getCharValue(onHeapStruct));
                } else
                if ( type == short.class ) {
                    bytes.putShort(index, (short) fi.getShortValue(onHeapStruct));
                } else
                if ( type == int.class ) {
                    bytes.putInt(index, fi.getIntValue(onHeapStruct));
                } else
                if ( type == long.class ) {
                    bytes.putLong( index, fi.getLongValue(onHeapStruct) );
                } else
                if ( type == float.class ) {
                    bytes.putFloat(index, fi.getFloatValue(onHeapStruct));
                } else
                if ( type == double.class ) {
                    bytes.putDouble(index, fi.getDoubleValue(onHeapStruct));
                } else {
                    throw new RuntimeException("this is an error");
                }
                index += fi.getStructSize();
            } else { // objectref
                Object obj = fi.getObjectValue(onHeapStruct);
                int structIndex = fi.getStructOffset();
                if ( index != structIndex+initialIndex ) {
                    throw new RuntimeException("internal error. please file an issue");
                }
                if ( obj == null ) {
                    bytes.putInt(index, -1);
                    bytes.putInt(index+4, -1);
                    index+=fi.getStructSize();
                } else {
                    Object objectValue = fi.getObjectValue(onHeapStruct);
                    positions.add(new ForwardEntry(index,objectValue,fi));
                    index += fi.getStructSize();
                }
            }
        }
        for ( int i=0; i < positions.size(); i++) {
            ForwardEntry en = positions.get(i);
            Object o = en.forwardObject;
            if ( o == null ) {
                throw new RuntimeException("this is a bug");
            }
            Class c = o.getClass();
            if (c.isArray()) {
                if ( en.fi.getAlign() > 0 ) {
                    while( (index%en.fi.getAlign()) != 0 ) {
                        index++;
                    }
                }
                long siz = 0;
                if ( c == byte[].class ) {
                    siz = Array.getLength(o);
                    bytes.set(index,(byte[])o,0, (int) siz);
                } else if ( c == boolean[].class ) {
                    siz = Array.getLength(o);
                    bytes.setBoolean(index, (boolean[]) o, 0, (int) siz);
                } else if ( c == char[].class ) {
                    siz = Array.getLength(o);
                    bytes.setChar(index, (char[]) o, 0, (int) siz);
                    siz *= 2;
                } else if ( c == short[].class ) {
                    siz = Array.getLength(o); // * FSTUtil.chscal;
                    bytes.setShort(index, (short[]) o, 0, (int) siz);
                    siz *= 2;
                } else if ( c == int[].class ) {
                    siz = Array.getLength(o); // * FSTUtil.intscal;
                    bytes.setInt(index, (int[]) o, 0, (int) siz);
                    siz *= 4;
                } else if ( c == long[].class ) {
                    siz = Array.getLength(o); // * FSTUtil.longscal;
                    bytes.setLong(index, (long[]) o, 0, (int) siz);
                    siz *= 8;
                } else if ( c == float[].class ) {
                    siz = Array.getLength(o); // * FSTUtil.floatscal;
                    bytes.setFloat(index, (float[]) o, 0, (int) siz);
                    siz *= 4;
                } else if ( c == double[].class ) {
                    siz = Array.getLength(o); // * FSTUtil.doublescal;
                    bytes.setDouble(index, (double[]) o, 0, (int) siz);
                    siz *= 8;
                } else {
                    Object[] objArr = (Object[]) o;
                    int elemSiz = bytes.getInt(en.pointerPos+8);
                    siz = Array.getLength(o) * elemSiz;
                    int tmpIndex = index;
                    Bytez templatearr = null;
                    boolean hasClzId = false;
                    if ( onHeapStruct instanceof FSTArrayElementSizeCalculator ) {
                        Class elemClz = ((FSTArrayElementSizeCalculator)onHeapStruct).getElementType(en.fi.getField(),this);
                        if ( elemClz != null ) {
                            int clid = getClzId(elemClz);
                            bytes.putInt(en.pointerPos + 12, clid);
                            hasClzId = true;
                        }
                    }
                    if (en.template != null) {
                        templatearr = toByteArray(en.template); // fixme: unnecessary alloc
                        if ( ! hasClzId ) {
                            bytes.putInt(en.pointerPos + 12, getClzId(en.template.getClass()));
                            hasClzId = true;
                        }
                    }
                    for (int j = 0; j < objArr.length; j++) {
                        Object objectValue = objArr[j];
                        if ( templatearr != null ) {
//                            unsafe.copyMemory(templatearr,FSTStruct.bufoff,bytes,FSTStruct.bufoff+tmpIndex,templatearr.length);
                            templatearr.copyTo(bytes,tmpIndex,0,templatearr.length());
                            tmpIndex+=elemSiz;
                        } else {
                            if ( objectValue == null ) {
                                bytes.putInt(tmpIndex + 4, -1);
                                tmpIndex += elemSiz;
                            } else {
                                toByteArray((FSTStruct) objectValue, bytes, tmpIndex);
                                bytes.putInt(tmpIndex, elemSiz); // need to patch size in case of smaller objects in obj array
                                tmpIndex += elemSiz;
                                if ( !hasClzId ) {
                                    bytes.putInt(en.pointerPos + 12, getClzId(en.fi.getArrayType()));
                                    hasClzId = true;
                                }
                            }
                        }
                    }
                }
                bytes.putInt(en.pointerPos, index - initialIndex); // offset to real data
                bytes.putInt(en.pointerPos + 4, Array.getLength(o)); // array len
                index+=siz;
            } else { // object ref or objarray elem
                int newoffset = toByteArray((FSTStruct) o, bytes, index);
                bytes.putInt(en.pointerPos, index - initialIndex);
                index = newoffset;
            }
        }
        if ( onHeapStruct instanceof FSTEmbeddedBinary ) {
            FSTEmbeddedBinary embeddedBinary = (FSTEmbeddedBinary) onHeapStruct;
            index = embeddedBinary.insertEmbedded(this, bytes, index);
        }
        bytes.putInt(initialIndex, index - initialIndex); // set object size
        return index;
    }

    public int getShallowStructSize(Class clz) {
        return conf.getClassInfo(clz).getStructSize();
    }

    Class classForName( String name ) throws ClassNotFoundException {
        try {
            return Class.forName(name);
        } catch ( ClassNotFoundException ex ) {
            if ( parentLoader != null ) {
                return parentLoader.loadClass(name);
            }
            throw ex;
        }
    }

    public ClassLoader getParentLoader() {
        return parentLoader;
    }

    public void setParentLoader(ClassLoader parentLoader) {
        this.parentLoader = parentLoader;
    }
}