/
codebase/src/net/sourceforge/pain/admin/console/command/
codebase/src/net/sourceforge/pain/data/role/
codebase/src/net/sourceforge/pain/network/console/telnet/
codebase/src/net/sourceforge/pain/network/guitool/
codebase/src/net/sourceforge/pain/plugin/
codebase/src/net/sourceforge/pain/util/
db/src/net/sourceforge/pain/util/
gui/
gui/lib/
gui/src/net/sourceforge/pain/tools/guitool/dbbrowse/
gui/src/net/sourceforge/pain/tools/guitool/dialog/
gui/src/net/sourceforge/pain/tools/guitool/menu/
gui/src/net/sourceforge/pain/tools/guitool/resources/
gui/src/net/sourceforge/pain/tools/guitool/resources/images/
gui/src/net/sourceforge/pain/tools/guitool/resources/images/explorer/
mudlibs/tinylib/
mudlibs/tinylib/area/
mudlibs/tinylib/etc/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/data/affect/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/data/prototype/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/data/trigger/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/affect/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/event/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/event/deploy/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/event/guitool/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/event/guitool/event/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/fn/util/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/trigger/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/trigger/impl/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/plugin/command/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/plugin/reset/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/plugin/shutdown/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/plugin/social/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/util/
tests/
tests/src/
tests/src/net/sourceforge/pain/db/data/
package net.sourceforge.pain.db;

import java.lang.reflect.*;
import java.util.*;

/**
 * User: fmike  Date: 18.03.2003  Time: 14:00:19
 */
abstract class DbClassImpl implements DbClass {

    private static final int DEFAULT_VALUES_CAPACITY = 128;
    private static final float VALUES_CAPACITY_EXT_K = 1.2F;

    private final PainDB db;
    final byte[] fieldTypes;
    final String[] fieldNames;
    private final String className;

    DbObject firstInExtent;
    private DbObject lastInExtent;
    private int nObjects = 0;
    int modCount = 0; // for extent iterator

    private final boolean hasCollections; // initialize collections mainly after allobjects instantiated
    private boolean dbClosed;

    final ClassData data;
    DbClassTransContext transContext = null;

    private final WeakHashMap weakIteratorsByObject = new WeakHashMap(10);

    DbClassImpl(final PainDB db, final byte[] fieldTypes, final String[] fieldNames, final String className) {
        this.db = db;
        this.fieldTypes = fieldTypes;
        this.fieldNames = fieldNames;
        this.className = className;
        hasCollections = hasCollections();
        dbClosed = false;
        data = new ClassData();
    }

    private boolean hasCollections() {
        final int len = fieldTypes.length;
        for (int i = 0; i < len; i++) {
            final int type = fieldTypes[i];
            if (type > 19) {
                return true;
            }
        }
        return false;
    }

    public final PainDB getDB() {
        checkDbState();
        return db;
    }

    final PainDB _getDB() {
        return db;
    }

    public final int getNumberOfFields() {
        return fieldNames.length;
    }

    final byte[] getFieldTypes() {
        return fieldTypes;
    }

    final String[] getFieldNames() {
        return fieldNames;
    }

    public final String getClassName() {
        return className;
    }

    public final String getFieldName(final int n) {
        return getFieldNames()[n];
    }

    public final byte getFieldType(final int n) {
        return getFieldTypes()[n];
    }

    public final Iterator extentIterator(boolean weak) {
        return new DbExtentIterator(this, weak);
    }

    public Iterator extentIterator() {
        return extentIterator(false);
    }

    public final int getNumberOfObjects() {
        return nObjects;
    }


    abstract Constructor getReadConstructor();

    final void addToExtent(final DbObject obj) {
        if (lastInExtent != null) {
            lastInExtent.next = obj;
            obj.prev = lastInExtent;
            lastInExtent = obj;
        } else {
            firstInExtent = lastInExtent = obj;
        }
        nObjects++;
        modCount++;
    }


    private void removeFromExtent(final DbObject obj) {
        if (obj == firstInExtent) {
            firstInExtent = obj.next;
        }
        if (obj == lastInExtent) {
            lastInExtent = obj.prev;
        }
        if (obj.prev != null) {
            obj.prev.next = obj.next;
        }
        if (obj.next != null) {
            obj.next.prev = obj.prev;
        }
        if (!weakIteratorsByObject.isEmpty()) {
            DbExtentIterator iterator = (DbExtentIterator) weakIteratorsByObject.get(obj);
            if (iterator != null) {
                iterator.onCurrentDelete();
            }
        }
        obj.prev = null;
        obj.next = null;
        nObjects--;
        modCount++;
    }


    /**
     * do not touches backupData
     */
    final void onMarkDeleted(final DbObject obj) {
        removeFromExtent(obj);
        if (hasCollections) {
            // collections has references to other objects, this references registered as inverse,
            // we should release it
            if (transContext != null && transContext.backupData != null && obj.transContext.backupDataIndex != -1) { //if not new class and not new obj
                /** if some collections was not backuped we should backup it now */
                transContext.backupData.ensureCollectionsBackup(data, obj.dataIndex, obj.transContext.backupDataIndex);
            }
            freeCollections(obj);// free all references to other objects from collections fields of deleted object (remove Inverse references from objs in collection)
        }
    }

    private void freeCollections(final DbObject obj) {
        final int dataIndex = obj.dataIndex;
        final int len = fieldTypes.length;
        for (int i = 0; i < len; i++) {
            switch (fieldTypes[i]) {
                //++deallocateDataIndex method will null all this references during owner detach
                case DbType.LINKED_LIST:
                case DbType.ARRAY_LIST:
                case DbType.INT_KEY_MAP:
                case DbType.STRING_KEY_MAP:
                case DbType.REFERENCE_SET:
//				case DbType.STRING_SET: has no refs to other objects
                    final DbCollection c = ((DbCollection) ((Object[]) data.fieldsValues[i])[dataIndex]);
                    if (c != null) {
                        c._clear();
                    }
                    break;
            }
        }
    }

    /**
     * startup method
     */
    final void onDbLoaded() {
        if (hasCollections) {
            int[] image;
            for (DbObject runner = firstInExtent; runner != null; runner = runner.next) {
                final int dataIndex = runner.dataIndex;
                final int len = fieldTypes.length;
                for (int i = 0; i < len; i++) {
                    switch (fieldTypes[i]) {
                        case DbType.LINKED_LIST:
                            // changing slot value from list image to DbLinkedList instance
                            image = (int[]) ((Object[]) data.fieldsValues[i])[dataIndex];
                            ((Object[]) data.fieldsValues[i])[dataIndex] = (image == null || image.length == 0) ? null : new DbLinkedList(runner, image, i);//lazy instantiation if null(empty)
                            break;
                        case DbType.ARRAY_LIST:
                            // changing slot value from list image to DbArrayList instance
                            image = (int[]) ((Object[]) data.fieldsValues[i])[dataIndex];
                            ((Object[]) data.fieldsValues[i])[dataIndex] = (image == null || image.length == 0) ? null : new DbArrayList(runner, image, i);//lazy instantiation if null(empty)
                            break;
                        case DbType.INT_KEY_MAP:
                            // changing slot value from map image to DbIntKeyMap instance
                            image = (int[]) ((Object[]) data.fieldsValues[i])[dataIndex];
                            ((Object[]) data.fieldsValues[i])[dataIndex] = (image == null || image.length == 0) ? null : new DbIntKeyMap(runner, image, i);//lazy instantiation if null(empty)
                            break;
                        case DbType.STRING_KEY_MAP:
                            // changing slot value from map image to DbIntKeyMap instance
                            Object[] skmImage = (Object[]) ((Object[]) data.fieldsValues[i])[dataIndex];
                            ((Object[]) data.fieldsValues[i])[dataIndex] = (skmImage == null || skmImage.length == 0) ? null : new DbStringKeyMap(runner, (String[]) skmImage[0], (int[]) skmImage[1], i);//lazy instantiation if null(empty)
                            break;
                        case DbType.REFERENCE_SET:
                            // changing slot value from set image to DbReferenceSet instance
                            image = (int[]) ((Object[]) data.fieldsValues[i])[dataIndex];
                            ((Object[]) data.fieldsValues[i])[dataIndex] = (image == null || image.length == 0) ? null : new DbReferenceSet(runner, image, i);//lazy instantiation if null(empty)
                            break;
                        case DbType.STRING_SET:
                        case DbType.STRING_MAP:
                            // lazy instantiation
                            break;

                    }
                }
            }
        }
    }

    final void setDbClosed() {
        dbClosed = true;
    }

    final void checkDbState() {
        if (dbClosed) {
            throw new RuntimeException("database was closed!");
        }
    }

    public final ClassData createClassData() {
        return new ClassData();
    }

    /**
     * here we are sure that obj and class has the same trans context
     * 
     * @param obj 
     * @return 
     */
    final int backupObject(final DbObject obj) {
        assert(obj.dbClass == this);
        final ClassData backup = transContext.backupData;
        final int toIndex = backup.allocateDataIndex();
        return data.backupTo(obj.dataIndex, backup, toIndex);
    }

    public final int moveBackupData(final DbObject obj, final int backupDataIndex, final ClassData backupFrom, final ClassData backupTo) {
        assert(obj.dbClass == this);
        final int toIndex = backupTo.allocateDataIndex();
        backupFrom.moveBackupData(backupDataIndex, backupTo, toIndex);
        return toIndex;
    }


    /**
     * copy all values from current classtranscontext.backupData to data, used on rollback only
     * 
     * @param obj 
     */
    public final void restoreObject(final DbObject obj) {
        assert(obj.dbClass == this);
        if (obj.transContext.state == DbConstants.STATE_OBJ_DELETED) {
            addToExtent(obj);
        }
        data.restoreFromBackup(obj.dataIndex, transContext.backupData, obj.transContext.backupDataIndex);
    }

    public boolean isDetached() {
        return dbClosed;
    }

    void weakIteratorReassign(DbExtentIterator iterator, DbObject currentObj) {
        if (currentObj == null) {
            weakIteratorsByObject.values().remove(iterator);
        } else {
            weakIteratorsByObject.put(currentObj, iterator);
        }
    }

    final class ClassData {

        final Object[] fieldsValues;
        private final DbIntBuffer freeDataIds = new DbIntBuffer();
        private int valuesCapacity;

        public ClassData() {
            fieldsValues = new Object[getNumberOfFields()];
            valuesCapacity = 0;
            extendValuesCapacity(DEFAULT_VALUES_CAPACITY);
        }

        private void extendValuesCapacity(final int newValuesCapacity) {
            final int len = fieldsValues.length;
            for (int i = 0; i < len; i++) {
                final Object container;
                if (fieldTypes[i] == DbType.REFERENCE) { // reference presented by two values: int(indexId) and long(versionId)
                    final Object[] arr;
                    final Object[] oldArr = (Object[]) fieldsValues[i];
                    container = arr = new Object[2];
                    arr[0] = new int[newValuesCapacity];
                    arr[1] = new long[newValuesCapacity];
                    if (valuesCapacity != 0) {
                        System.arraycopy(oldArr[0], 0, arr[0], 0, valuesCapacity);
                        System.arraycopy(oldArr[1], 0, arr[1], 0, valuesCapacity);
                    }
                } else {
                    switch (fieldTypes[i]) {
                        case DbType.BOOLEAN:
                            container = new boolean[newValuesCapacity];
                            break;
                        case DbType.BYTE:
                            container = new byte[newValuesCapacity];
                            break;
                        case DbType.CHAR:
                            container = new char[newValuesCapacity];
                            break;
                        case DbType.DOUBLE:
                            container = new double[newValuesCapacity];
                            break;
                        case DbType.FLOAT:
                            container = new float[newValuesCapacity];
                            break;
                        case DbType.INT:
                            container = new int[newValuesCapacity];
                            break;
                        case DbType.LONG:
                            container = new long[newValuesCapacity];
                            break;
                        case DbType.SHORT:
                            container = new short[newValuesCapacity];
                            break;
                        case DbType.STRING:
                            container = new String[newValuesCapacity];
                            break;
                        case DbType.ARRAY_OF_BYTE:
                        case DbType.ARRAY_OF_CHAR:
                        case DbType.ARRAY_OF_INT:
                        case DbType.ARRAY_OF_STRING:
                        case DbType.LINKED_LIST:
                        case DbType.ARRAY_LIST:
                        case DbType.INT_KEY_MAP:
                        case DbType.STRING_KEY_MAP:
                        case DbType.REFERENCE_SET:
                        case DbType.STRING_SET:
                        case DbType.STRING_MAP:
                            container = new Object[newValuesCapacity];
                            break;
                        default:
                            throw new RuntimeException("not valid type:" + fieldTypes[i]);
                    }
                    if (valuesCapacity != 0) {
                        System.arraycopy(fieldsValues[i], 0, container, 0, valuesCapacity);
                    }
                }
                fieldsValues[i] = container;
            }
            freeDataIds.ensureCapacity(newValuesCapacity);
            for (int i = newValuesCapacity - 1; --i >= valuesCapacity;) {
                freeDataIds.add(i);
            }
            valuesCapacity = newValuesCapacity;
        }

        final synchronized int allocateDataIndex() {
            if (freeDataIds.getSize() == 0) {
                extendValuesCapacity((int) (valuesCapacity * VALUES_CAPACITY_EXT_K));
            }
            return freeDataIds.removeLast();
        }

        void deallocateDataIndex(final int index) {
            freeDataIds.add(index);
            //+ free some resources, we should  free primitives-> this slot will be reused-> should set default values for new obj
            final int len = fieldsValues.length;
            for (int i = 0; i < len; i++) {
                switch (fieldTypes[i]) {
                    case DbType.BOOLEAN:
                        ((boolean[]) fieldsValues[i])[index] = false;
                        break;
                    case DbType.BYTE:
                        ((byte[]) fieldsValues[i])[index] = 0;
                        break;
                    case DbType.CHAR:
                        ((char[]) fieldsValues[i])[index] = 0;
                        break;
                    case DbType.DOUBLE:
                        ((double[]) fieldsValues[i])[index] = 0;
                        break;
                    case DbType.FLOAT:
                        ((float[]) fieldsValues[i])[index] = 0;
                        break;
                    case DbType.INT:
                        ((int[]) fieldsValues[i])[index] = 0;
                        break;
                    case DbType.LONG:
                        ((long[]) fieldsValues[i])[index] = 0;
                        break;
                    case DbType.SHORT:
                        ((short[]) fieldsValues[i])[index] = 0;
                        break;
                    case DbType.STRING:
                        ((String[]) fieldsValues[i])[index] = null;
                        break;
                    case DbType.REFERENCE:
                        ((int[]) ((Object[]) fieldsValues[i])[0])[index] = -1;
                        ((long[]) ((Object[]) fieldsValues[i])[1])[index] = -1;
                        break;
                    case DbType.ARRAY_OF_BYTE:
                    case DbType.ARRAY_OF_CHAR:
                    case DbType.ARRAY_OF_INT:
                    case DbType.ARRAY_OF_STRING:
                    case DbType.LINKED_LIST:
                    case DbType.ARRAY_LIST:
                    case DbType.INT_KEY_MAP:
                    case DbType.REFERENCE_SET:
                    case DbType.STRING_KEY_MAP:
                    case DbType.STRING_SET:
                    case DbType.STRING_MAP:
                        ((Object[]) fieldsValues[i])[index] = null;
                        break;
                }
            }
        }

        /**
         * called only for backups during flush or after rollbacks
         */
        void clear() {
            assert(this != data);
            // nothing to to really before we will use cache for classData. GC will do it's work
            final int len = fieldsValues.length;
            for (int i = 0; i < len; i++) {
                fieldsValues[i] = null;
            }
        }


        public void moveBackupData(final int fromIndex, final ClassData backupTo, final int toIndex) {
            assert(this != data && backupTo != data);//remove after tests
            final Object[] toValues = backupTo.fieldsValues;
            final Object[] fromValues = this.fieldsValues;
            final int len = fieldsValues.length;
            for (int i = 0; i < len; i++) {
                switch (fieldTypes[i]) {
                    case DbType.BOOLEAN:
                        ((boolean[]) toValues[i])[toIndex] = ((boolean[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.BYTE:
                        ((byte[]) toValues[i])[toIndex] = ((byte[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.CHAR:
                        ((char[]) toValues[i])[toIndex] = ((char[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.DOUBLE:
                        ((double[]) toValues[i])[toIndex] = ((double[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.FLOAT:
                        ((float[]) toValues[i])[toIndex] = ((float[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.INT:
                        ((int[]) toValues[i])[toIndex] = ((int[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.LONG:
                        ((long[]) toValues[i])[toIndex] = ((long[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.SHORT:
                        ((short[]) toValues[i])[toIndex] = ((short[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.STRING:
                        ((String[]) toValues[i])[toIndex] = ((String[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.REFERENCE:
                        ((int[]) ((Object[]) toValues[i])[0])[toIndex] = ((int[]) ((Object[]) fromValues[i])[0])[fromIndex];
                        ((long[]) ((Object[]) toValues[i])[1])[toIndex] = ((long[]) ((Object[]) fromValues[i])[1])[fromIndex];
                        break;
                    case DbType.ARRAY_OF_BYTE:
                    case DbType.ARRAY_OF_CHAR:
                    case DbType.ARRAY_OF_INT:
                    case DbType.ARRAY_OF_STRING:
                    case DbType.LINKED_LIST:
                    case DbType.ARRAY_LIST:
                    case DbType.INT_KEY_MAP:
                    case DbType.STRING_KEY_MAP:
                    case DbType.REFERENCE_SET:
                    case DbType.STRING_SET:
                    case DbType.STRING_MAP:
                        ((Object[]) toValues[i])[toIndex] = ((Object[]) fromValues[i])[fromIndex];
                        break;

                }
            }
        }

        public int backupTo(final int fromIndex, final ClassData backupData, final int toIndex) {
            assert(this == data); // this operation used only to save data from DbClassImpl.data transaction backupData
            final Object[] toValues = backupData.fieldsValues;
            final Object[] fromValues = this.fieldsValues;
            final int len = fieldsValues.length;
            for (int i = 0; i < len; i++) {
                switch (fieldTypes[i]) {
                    case DbType.BOOLEAN:
                        ((boolean[]) toValues[i])[toIndex] = ((boolean[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.BYTE:
                        ((byte[]) toValues[i])[toIndex] = ((byte[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.CHAR:
                        ((char[]) toValues[i])[toIndex] = ((char[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.DOUBLE:
                        ((double[]) toValues[i])[toIndex] = ((double[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.FLOAT:
                        ((float[]) toValues[i])[toIndex] = ((float[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.INT:
                        ((int[]) toValues[i])[toIndex] = ((int[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.LONG:
                        ((long[]) toValues[i])[toIndex] = ((long[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.SHORT:
                        ((short[]) toValues[i])[toIndex] = ((short[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.STRING:
                        ((String[]) toValues[i])[toIndex] = ((String[]) fromValues[i])[fromIndex];
                        break;
                    case DbType.REFERENCE:
                        ((int[]) ((Object[]) toValues[i])[0])[toIndex] = ((int[]) ((Object[]) fromValues[i])[0])[fromIndex];
                        ((long[]) ((Object[]) toValues[i])[1])[toIndex] = ((long[]) ((Object[]) fromValues[i])[1])[fromIndex];
                        break;
                        //collections use lazy backup (backupCollection method)
                        // arrays use lazy backup to
                }
            }
            return toIndex;
        }

        public void restoreFromBackup(final int toIndex, final ClassData backupData, final int backupIndex) {
            assert(this == data); // only data could be restored from backups during rollbacks
            final Object[] toValues = fieldsValues;
            final Object[] fromValues = backupData.fieldsValues;
            final int len = fieldsValues.length;
            for (int i = 0; i < len; i++) {
                switch (fieldTypes[i]) {
                    case DbType.BOOLEAN:
                        ((boolean[]) toValues[i])[toIndex] = ((boolean[]) fromValues[i])[backupIndex];
                        break;
                    case DbType.BYTE:
                        ((byte[]) toValues[i])[toIndex] = ((byte[]) fromValues[i])[backupIndex];
                        break;
                    case DbType.CHAR:
                        ((char[]) toValues[i])[toIndex] = ((char[]) fromValues[i])[backupIndex];
                        break;
                    case DbType.DOUBLE:
                        ((double[]) toValues[i])[toIndex] = ((double[]) fromValues[i])[backupIndex];
                        break;
                    case DbType.FLOAT:
                        ((float[]) toValues[i])[toIndex] = ((float[]) fromValues[i])[backupIndex];
                        break;
                    case DbType.INT:
                        ((int[]) toValues[i])[toIndex] = ((int[]) fromValues[i])[backupIndex];
                        break;
                    case DbType.LONG:
                        ((long[]) toValues[i])[toIndex] = ((long[]) fromValues[i])[backupIndex];
                        break;
                    case DbType.SHORT:
                        ((short[]) toValues[i])[toIndex] = ((short[]) fromValues[i])[backupIndex];
                        break;
                    case DbType.STRING:
                        ((String[]) toValues[i])[toIndex] = ((String[]) fromValues[i])[backupIndex];
                        break;
                    case DbType.REFERENCE:
                        ((int[]) ((Object[]) toValues[i])[0])[toIndex] = ((int[]) ((Object[]) fromValues[i])[0])[backupIndex];
                        ((long[]) ((Object[]) toValues[i])[1])[toIndex] = ((long[]) ((Object[]) fromValues[i])[1])[backupIndex];
                        break;
                    case DbType.ARRAY_OF_BYTE:
                    case DbType.ARRAY_OF_CHAR:
                    case DbType.ARRAY_OF_INT:
                    case DbType.ARRAY_OF_STRING:
                        final Object backupArr = ((Object[]) fromValues[i])[backupIndex];
                        if (backupArr != null) { // null means that array was not modified
                            if (backupArr == DbConstants.ZERO_INT_ARRAY) {    // zero_int_array is used to backup null values
                                ((Object[]) toValues[i])[toIndex] = null;
                            } else {
                                ((Object[]) toValues[i])[toIndex] = backupArr;
                            }
                        }
                        break;
                    case DbType.LINKED_LIST:
                    case DbType.ARRAY_LIST:
                    case DbType.INT_KEY_MAP:
                    case DbType.STRING_KEY_MAP:
                    case DbType.REFERENCE_SET:
                        final Object backup = ((Object[]) fromValues[i])[backupIndex];
                        if (backup != null) {//backup == null means there was no backup (no collection change)
                            final DbCollection c = (DbCollection) ((Object[]) toValues[i])[toIndex];
                            c.restoreFromBackup(backup);
                        }
                        break;
                    case DbType.STRING_SET:
                    case DbType.STRING_MAP:
                        final Object cbackup = ((Object[]) fromValues[i])[backupIndex];
                        if (cbackup != null) {//backup == null means there was no backup (no collection change) 
                            final Object dest = ((Object[]) toValues[i])[toIndex];
                            if (dest instanceof DbAbstractStringMap) {
                                ((DbAbstractStringMap) dest).restoreFromBackup(cbackup);
                            } else {
                                ((Object[]) toValues[i])[toIndex] = cbackup; //was not instantiated (backuped if obj deleted)
                            }
                        }
                        break;
                }
            }

        }

        /**
         * collections use lazy backup (backup on change)
         * 
         * @param collection 
         */
        void backupCollection(final DbCollection collection) {
            assert(this == data);

            final int toIndex = collection.owner.transContext.backupDataIndex; // backup index
            final int fid = collection.fid;
            if (toIndex == -1) {
                return; // new object
            }
            final Object[] toValues = transContext.backupData.fieldsValues;
            if (((Object[]) toValues[fid])[toIndex] != null) {
                // backup already done
                return;
            }
//			final Object[] fromValues = this.fieldsValues;
//			final int fromIndex = collection.owner.dataIndex;
//			final DbCollection c = (DbCollection) ((Object[]) fromValues[fid])[fromIndex];
            ((Object[]) toValues[fid])[toIndex] = collection.createBackupImage();
        }

        /**
         * arrays use lazy backup (backup on change)
         */
        void backupArray(final DbObject obj, final int fid) {
            assert(this == data);
            final int toIndex = obj.transContext.backupDataIndex;
            if (toIndex == -1) {// new object
                return;
            }
            final int fromIndex = obj.dataIndex;
            final Object[] toValues = transContext.backupData.fieldsValues;

            if (((Object[]) toValues[fid])[toIndex] != null) {
                return; // already backed up
            }
            final Object[] fromValues = this.fieldsValues;
            final Object data = ((Object[]) fromValues[fid])[fromIndex];
            final Object backupData = data == null ? DbConstants.ZERO_INT_ARRAY : data;
            ((Object[]) toValues[fid])[toIndex] = backupData;
        }


        public void ensureCollectionsBackup(ClassData fromData, int fromIndex, int toIndex) {
            final int len = fieldTypes.length;
            for (int i = 0; i < len; i++) {
                switch (fieldTypes[i]) {
                    //++deallocateDataIndex method will null all this references during owner detach
                    case DbType.LINKED_LIST:
                    case DbType.ARRAY_LIST:
                    case DbType.INT_KEY_MAP:
                    case DbType.STRING_KEY_MAP:
                    case DbType.REFERENCE_SET:
                        if (((Object[]) fieldsValues[i])[toIndex] == null) { // if was not backuped
                            final DbCollection fromCollection = ((DbCollection) ((Object[]) fromData.fieldsValues[i])[fromIndex]);
                            if (fromCollection != null) { // null => zero capacity and was not instantiated
                                ((Object[]) fieldsValues[i])[toIndex] = fromCollection.createBackupImage();
                            }
                        }
                        break;
                    case DbType.STRING_SET:
                    case DbType.STRING_MAP:
                        if (((Object[]) fieldsValues[i])[toIndex] == null) { // if was not backuped
                            final Object o = ((Object[]) fromData.fieldsValues[i])[fromIndex];
                            if (o != null && o instanceof DbAbstractStringMap) {
                                ((Object[]) fieldsValues[i])[toIndex] = ((DbStringSet) o).createBackupImage();
                            } else { //was not instantiated (String[] or null if zero capacity)
                                ((Object[]) fieldsValues[i])[toIndex] = o;
                            }
                        }
                        break;
                }
            }
        }
    };
}