/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.openjpa.kernel;

import java.io.IOException;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.BitSet;
import java.util.Collection;
import java.util.Map;

import org.apache.openjpa.conf.Compatibility;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.enhance.StateManager;
import org.apache.openjpa.lib.util.Localizer;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.ValueMetaData;
import org.apache.openjpa.util.Exceptions;
import org.apache.openjpa.util.Proxy;
import org.apache.openjpa.util.UnsupportedException;
import org.apache.openjpa.util.ImplHelper;

/**
 * Internal state manager for detached instances. Does not fully
 * implement {@link OpenJPAStateManager} contract to allow for serialization.
 *
 * @author Steve Kim
 */
public class DetachedStateManager
    extends AttachStrategy
    implements OpenJPAStateManager, Serializable {

    private static final long serialVersionUID = 4112223665584731100L;

    private static final Localizer _loc = Localizer.forPackage
        (DetachedStateManager.class);

    private final PersistenceCapable _pc;
    private final boolean _embedded;
    private final boolean _access;
    private final BitSet _loaded;
    private final BitSet _dirty;
    private final Object _oid;
    private final Object _version;
    private final ReentrantLock _lock;
    private final boolean _useDSFForUnproxy;   // old releases will default to FALSE, which is the old behavior

    /**
     * Constructor.
     *
     * @param pc the managed instance
     * @param sm the instance's state manager
     * @param load the set of detached field indexes
     * @param access whether to allow access to unloaded fields
     * @param multithreaded whether the instance will be used concurrently
     * by multiple threads
     */
    public DetachedStateManager(PersistenceCapable pc, OpenJPAStateManager sm,
        BitSet load, boolean access, boolean multithreaded) {
        _pc = pc;
        _embedded = sm.isEmbedded();
        _loaded = load;
        _access = access;
        if (!sm.isFlushed())
            _dirty = (BitSet) sm.getDirty().clone();
        else
            _dirty = new BitSet(_loaded.length());
        _oid = sm.fetchObjectId();
        _version = sm.getVersion();
        if (multithreaded)
            _lock = new ReentrantLock();
        else
            _lock = null;
        if (sm.getContext() != null && sm.getContext().getConfiguration() != null) {
            Compatibility compat = sm.getContext().getConfiguration().getCompatibilityInstance();
            if (compat != null && !compat.getIgnoreDetachedStateFieldForProxySerialization())
                _useDSFForUnproxy = true;      // new 2.0 behavior
            else
                _useDSFForUnproxy = false;
        } else
            _useDSFForUnproxy = false;
    }

    /////////////////////////////////
    // AttachStrategy implementation
    /////////////////////////////////

    public Object attach(AttachManager manager, Object toAttach,
        ClassMetaData meta, PersistenceCapable into, OpenJPAStateManager owner,
        ValueMetaData ownerMeta, boolean explicit) {
        BrokerImpl broker = manager.getBroker();
        StateManagerImpl sm;
        if (_embedded) {
            if (_dirty.length () > 0)
                owner.dirty(ownerMeta.getFieldMetaData().getIndex());
            sm = (StateManagerImpl) broker.embed(_pc, _oid, owner, ownerMeta);
            ImplHelper.toPersistenceCapable(toAttach, broker.getConfiguration())
                .pcReplaceStateManager(this);
        } else {
            PCState state = (_dirty.length() > 0) ? PCState.PDIRTY
                : PCState.PCLEAN;
            sm = (StateManagerImpl) broker.copy(this, state);
        }
        PersistenceCapable pc = sm.getPersistenceCapable();
        manager.setAttachedCopy(toAttach, pc);

        manager.fireBeforeAttach(toAttach, meta);

        // pre-load for efficiency: current field values for restore, dependent
        // for delete
        FieldMetaData[] fields = sm.getMetaData().getFields();
        int restore = broker.getRestoreState();

        boolean postLoadOnMerge = broker.getPostLoadOnMerge();
        if (_dirty.length() > 0 || postLoadOnMerge) {
            BitSet load = new BitSet(fields.length);
            if (postLoadOnMerge && broker.getLifecycleEventManager().hasLoadListeners(pc, meta)) {
                // load all fields
                // this will automatically lead to invoking the PostLoad lifecycle event
                // when the last field got set
                // @see StateManagerImpl#postLoad(String, FetchConfiguration)
                load.set(0, fields.length);
            }
            else {
                for (int i = 0; i < fields.length; i++) {
                    if (!_dirty.get(i))
                        continue;

                    switch (fields[i].getDeclaredTypeCode()) {
                        case JavaTypes.ARRAY:
                        case JavaTypes.COLLECTION:
                            if (restore == RestoreState.RESTORE_ALL
                                || fields[i].getElement().getCascadeDelete()
                                == ValueMetaData.CASCADE_AUTO)
                                load.set(i);
                            break;
                        case JavaTypes.MAP:
                            if (restore == RestoreState.RESTORE_ALL
                                || fields[i].getElement().getCascadeDelete()
                                == ValueMetaData.CASCADE_AUTO
                                || fields[i].getKey().getCascadeDelete()
                                == ValueMetaData.CASCADE_AUTO)
                                load.set(i);
                            break;
                        default:
                            if (restore != RestoreState.RESTORE_NONE
                                || fields[i].getCascadeDelete()
                                == ValueMetaData.CASCADE_AUTO)
                                load.set(i);
                    }
                }
            }

            if (!postLoadOnMerge) {
                // prevent PostLoad callbacks even for the load operation
                sm.setPostLoadCallback(false);
            }
            FetchConfiguration fc = broker.getFetchConfiguration();
            sm.loadFields(load, fc, fc.getWriteLockLevel(), null);
        }
        Object origVersion = sm.getVersion();
        sm.setVersion(_version);

        BitSet loaded = sm.getLoaded();
        int set = StateManager.SET_ATTACH;
        sm.setPostLoadCallback(false);
        for (int i = 0; i < fields.length; i++) {
            if (!_loaded.get(i))
                continue;
            // don't reload already loaded non-mutable objects
            if (!_dirty.get(i) && loaded.get(i) && ignoreLoaded(fields[i]))
                continue;

            provideField(i);
            switch (fields[i].getDeclaredTypeCode()) {
                case JavaTypes.BOOLEAN:
                    if (_dirty.get(i))
                        sm.settingBooleanField(pc, i,
                            (loaded.get(i)) && sm.fetchBooleanField(i),
                            longval == 1, set);
                    else
                        sm.storeBooleanField(i, longval == 1);
                    break;
                case JavaTypes.BYTE:
                    if (_dirty.get(i))
                        sm.settingByteField(pc, i, (!loaded.get(i)) ? (byte) 0
                            : sm.fetchByteField(i), (byte) longval, set);
                    else
                        sm.storeByteField(i, (byte) longval);
                    break;
                case JavaTypes.CHAR:
                    if (_dirty.get(i))
                        sm.settingCharField(pc, i, (!loaded.get(i)) ? (char) 0
                            : sm.fetchCharField(i), (char) longval, set);
                    else
                        sm.storeCharField(i, (char) longval);
                    break;
                case JavaTypes.INT:
                    if (_dirty.get(i))
                        sm.settingIntField(pc, i, (!loaded.get(i)) ? 0
                            : sm.fetchIntField(i), (int) longval, set);
                    else
                        sm.storeIntField(i, (int) longval);
                    break;
                case JavaTypes.LONG:
                    if (_dirty.get(i))
                        sm.settingLongField(pc, i, (!loaded.get(i)) ? 0L
                            : sm.fetchLongField(i), longval, set);
                    else
                        sm.storeLongField(i, longval);
                    break;
                case JavaTypes.SHORT:
                    if (_dirty.get(i))
                        sm.settingShortField(pc, i, 
                            (!loaded.get(i)) ? (short) 0 : sm.fetchShortField(i), (short) longval, set);
                    else
                        sm.storeShortField(i, (short) longval);
                    break;
                case JavaTypes.FLOAT:
                    if (_dirty.get(i))
                        sm.settingFloatField(pc, i, (!loaded.get(i)) ? 0F
                            : sm.fetchFloatField(i), (float) dblval, set);
                    else
                        sm.storeFloatField(i, (float) dblval);
                    break;
                case JavaTypes.DOUBLE:
                    if (_dirty.get(i))
                        sm.settingDoubleField(pc, i, (!loaded.get(i)) ? 0D
                            : sm.fetchDoubleField(i), dblval, set);
                    else
                        sm.storeDoubleField(i, dblval);
                    break;
                case JavaTypes.STRING:
                    if (_dirty.get(i))
                        sm.settingStringField(pc, i, (!loaded.get(i)) ? null
                            : sm.fetchStringField(i), (String) objval, set);
                    else
                        sm.storeStringField(i, (String) objval);
                    objval = null;
                    break;
                case JavaTypes.PC:
                case JavaTypes.PC_UNTYPED:
                    if (fields[i].getCascadeAttach() == ValueMetaData
                        .CASCADE_NONE) {
                        // Use the attached copy of the object, if available
                        PersistenceCapable cpy = manager.getAttachedCopy(objval);
                        if (cpy != null) {
                            objval = cpy;
                        } else {
                        	objval = getReference(manager, objval, sm, fields[i]);
                        }
                    } 
                    else {
                        PersistenceCapable toPC = null;
                        if (objval != null && fields[i].isEmbeddedPC())
                            toPC = ImplHelper.toPersistenceCapable(objval,
                                broker.getConfiguration());
                        objval = manager.attach(objval, toPC, sm, fields[i],
                            false);
                    }
                    if (_dirty.get(i))
                        sm.settingObjectField(pc, i, (!loaded.get(i)) ? null
                            : sm.fetchObjectField(i), objval, set);
                    else
                        sm.storeObjectField(i, objval);
                    objval = null;
                    break;
                case JavaTypes.COLLECTION:
                    Collection coll = (Collection) objval;
                    objval = null;
                    if (coll != null)
                        coll = attachCollection(manager, coll, sm, fields[i]);
                    if (_dirty.get(i))
                        sm.settingObjectField(pc, i, (!loaded.get(i)) ? null
                            : sm.fetchObjectField(i), coll, set);
                    else
                        sm.storeObjectField(i, coll);
                    break;
                case JavaTypes.MAP:
                    Map map = (Map) objval;
                    objval = null;
                    if (map != null)
                        map = attachMap(manager, map, sm, fields[i]);
                    if (_dirty.get(i))
                        sm.settingObjectField(pc, i, (!loaded.get(i)) ? null
                            : sm.fetchObjectField(i), map, set);
                    else
                        sm.storeObjectField(i, map);
                    break;
                default:
                    if (_dirty.get(i))
                        sm.settingObjectField(pc, i, (!loaded.get(i)) ? null
                            : sm.fetchObjectField(i), objval, set);
                    else
                        sm.storeObjectField(i, objval);
                    objval = null;
            }
        }
        sm.setPostLoadCallback(true);
        pc.pcReplaceStateManager(sm);

        // if we were clean at least make sure a version check is done to
        // prevent using old state
        if (!sm.isVersionCheckRequired() && broker.isActive()
            && _version != origVersion && (origVersion == null 
            || broker.getStoreManager().compareVersion(sm, _version, 
            origVersion) != StoreManager.VERSION_SAME)) {
            broker.transactional(sm.getManagedInstance(), false, 
                manager.getBehavior());
        }

        return sm.getManagedInstance();
    }

    protected Object getDetachedObjectId(AttachManager manager,
        Object toAttach) {
        return _oid;
    }

    void provideField(int field) {
        _pc.pcProvideField(field);
    }

    protected void provideField(Object toAttach, StateManagerImpl sm,
        int field) {
        provideField(field);
    }

    /**
     * Ignore if the field is not dirty but loaded
     */
    protected static boolean ignoreLoaded(FieldMetaData fmd) {
        switch (fmd.getTypeCode()) {
            case JavaTypes.BOOLEAN:
            case JavaTypes.BOOLEAN_OBJ:
            case JavaTypes.BYTE:
            case JavaTypes.BYTE_OBJ:
            case JavaTypes.INT:
            case JavaTypes.INT_OBJ:
            case JavaTypes.LONG:
            case JavaTypes.LONG_OBJ:
            case JavaTypes.SHORT:
            case JavaTypes.SHORT_OBJ:
            case JavaTypes.DOUBLE:
            case JavaTypes.DOUBLE_OBJ:
            case JavaTypes.FLOAT:
            case JavaTypes.FLOAT_OBJ:
            case JavaTypes.CHAR:
            case JavaTypes.CHAR_OBJ:
            case JavaTypes.STRING:
                return true;
        }
        return false;
    }

    ///////////////////////////////
    // StateManager implementation
    ///////////////////////////////

    public Object getGenericContext() {
        return null;
    }

    public Object getPCPrimaryKey(Object oid, int field) {
        throw new UnsupportedOperationException();
    }

    public StateManager replaceStateManager(StateManager sm) {
        return sm;
    }

    public Object getVersion() {
        return _version;
    }

    public void setVersion(Object version) {
        throw new UnsupportedException();
    }

    public boolean isDirty() {
        return _dirty.length() != 0;
    }

    public boolean isTransactional() {
        return false;
    }

    public boolean isPersistent() {
        return false;
    }

    public boolean isNew() {
        return false;
    }

    public boolean isDeleted() {
        return false;
    }

    public boolean isDetached() {
        return true;
    }

    public boolean isVersionUpdateRequired() {
        return false;
    }

    public boolean isVersionCheckRequired() {
        return false;
    }

    public void dirty(String field) {
        // should we store ClassMetaData?
        throw new UnsupportedException();
    }

    public Object fetchObjectId() {
        return _oid;
    }

    public void accessingField(int idx) {
        if (!_access && !_loaded.get(idx))
            // do not access the pc fields by implictly invoking _pc.toString()
            // may cause infinite loop if again tries to access unloaded field 
            throw new IllegalStateException(_loc.get("unloaded-detached",
               Exceptions.toString(_pc)).getMessage());
    }

    public boolean serializing() {
        return false;
    }

    public boolean writeDetached(ObjectOutput out)
        throws IOException {
        out.writeObject(_pc.pcGetDetachedState());
        out.writeObject(this);
        return false;
    }

    public void proxyDetachedDeserialized(int idx) {
        lock();
        try {
            _pc.pcProvideField(idx);
            if (objval instanceof Proxy)
                ((Proxy) objval).setOwner(this, idx);
            objval = null;
        } finally {
            unlock();
        }
    }

    public void settingBooleanField(PersistenceCapable pc, int idx,
        boolean cur, boolean next, int set) {
        accessingField(idx);
        if (cur == next || !_loaded.get(idx))
            return;
        lock();
        try {
            _dirty.set(idx);
            longval = next ? 1 : 0;
            pc.pcReplaceField(idx);
        } finally {
            unlock();
        }
    }

    public void settingCharField(PersistenceCapable pc, int idx, char cur,
        char next, int set) {
        accessingField(idx);
        if (cur == next || !_loaded.get(idx))
            return;
        lock();
        try {
            _dirty.set(idx);
            longval = next;
            pc.pcReplaceField(idx);
        } finally {
            unlock();
        }
    }

    public void settingByteField(PersistenceCapable pc, int idx, byte cur,
        byte next, int set) {
        accessingField(idx);
        if (cur == next || !_loaded.get(idx))
            return;
        lock();
        try {
            _dirty.set(idx);
            longval = next;
            pc.pcReplaceField(idx);
        } finally {
            unlock();
        }
    }

    public void settingShortField(PersistenceCapable pc, int idx, short cur,
        short next, int set) {
        accessingField(idx);
        if (cur == next || !_loaded.get(idx))
            return;
        lock();
        try {
            _dirty.set(idx);
            longval = next;
            pc.pcReplaceField(idx);
        } finally {
            unlock();
        }
    }

    public void settingIntField(PersistenceCapable pc, int idx, int cur,
        int next, int set) {
        accessingField(idx);
        if (cur == next || !_loaded.get(idx))
            return;
        lock();
        try {
            _dirty.set(idx);
            longval = next;
            pc.pcReplaceField(idx);
        } finally {
            unlock();
        }
    }

    public void settingLongField(PersistenceCapable pc, int idx, long cur,
        long next, int set) {
        accessingField(idx);
        if (cur == next || !_loaded.get(idx))
            return;
        lock();
        try {
            _dirty.set(idx);
            longval = next;
            pc.pcReplaceField(idx);
        } finally {
            unlock();
        }
    }

    public void settingFloatField(PersistenceCapable pc, int idx, float cur,
        float next, int set) {
        accessingField(idx);
        if (cur == next || !_loaded.get(idx))
            return;
        lock();
        try {
            _dirty.set(idx);
            dblval = next;
            pc.pcReplaceField(idx);
        } finally {
            unlock();
        }
    }

    public void settingDoubleField(PersistenceCapable pc, int idx, double cur,
        double next, int set) {
        accessingField(idx);
        if (cur == next || !_loaded.get(idx))
            return;
        lock();
        try {
            _dirty.set(idx);
            dblval = next;
            pc.pcReplaceField(idx);
        } finally {
            unlock();
        }
    }

    public void settingStringField(PersistenceCapable pc, int idx, String cur,
        String next, int set) {
        accessingField(idx);
        if (cur == next || (cur != null && cur.equals(next))
                || !_loaded.get(idx))
            return;
        lock();
        try {
            _dirty.set(idx);
            objval = next;
            pc.pcReplaceField(idx);
        } finally {
            unlock();
            objval = null;
        }
    }

    public void settingObjectField(PersistenceCapable pc, int idx, Object cur,
        Object next, int set) {
        accessingField(idx);
        if (cur == next || !_loaded.get(idx))
            return;
        lock();
        try {
            _dirty.set(idx);
            objval = next;
            pc.pcReplaceField(idx);
        } finally {
            unlock();
            objval = null;
        }
    }

    public void providedBooleanField(PersistenceCapable pc, int idx,
        boolean cur) {
        longval = cur ? 1 : 0;
    }

    public void providedCharField(PersistenceCapable pc, int idx, char cur) {
        longval = cur;
    }

    public void providedByteField(PersistenceCapable pc, int idx, byte cur) {
        longval = cur;
    }

    public void providedShortField(PersistenceCapable pc, int idx, short cur) {
        longval = cur;
    }

    public void providedIntField(PersistenceCapable pc, int idx, int cur) {
        longval = cur;
    }

    public void providedLongField(PersistenceCapable pc, int idx, long cur) {
        longval = cur;
    }

    public void providedFloatField(PersistenceCapable pc, int idx, float cur) {
        dblval = cur;
    }

    public void providedDoubleField(PersistenceCapable pc, int idx,
        double cur) {
        dblval = cur;
    }

    public void providedStringField(PersistenceCapable pc, int idx,
        String cur) {
        objval = cur;
    }

    public void providedObjectField(PersistenceCapable pc, int idx,
        Object cur) {
        objval = cur;
    }

    public boolean replaceBooleanField(PersistenceCapable pc, int idx) {
        return longval == 1;
    }

    public char replaceCharField(PersistenceCapable pc, int idx) {
        return (char) longval;
    }

    public byte replaceByteField(PersistenceCapable pc, int idx) {
        return (byte) longval;
    }

    public short replaceShortField(PersistenceCapable pc, int idx) {
        return (short) longval;
    }

    public int replaceIntField(PersistenceCapable pc, int idx) {
        return (int) longval;
    }

    public long replaceLongField(PersistenceCapable pc, int idx) {
        return longval;
    }

    public float replaceFloatField(PersistenceCapable pc, int idx) {
        return (float) dblval;
    }

    public double replaceDoubleField(PersistenceCapable pc, int idx) {
        return dblval;
    }

    public String replaceStringField(PersistenceCapable pc, int idx) {
        String str = (String) objval;
        objval = null;
        return str;
    }

    public Object replaceObjectField(PersistenceCapable pc, int idx) {
        Object ret = objval;
        objval = null;
        return ret;
    }

    //////////////////////////////////////
    // OpenJPAStateManager implementation
    //////////////////////////////////////

    public void initialize(Class forType, PCState state) {
        throw new UnsupportedOperationException();
    }

    public void load(FetchConfiguration fetch) {
        throw new UnsupportedOperationException();
    }

    public Object getManagedInstance() {
        return _pc;
    }

    public PersistenceCapable getPersistenceCapable() {
        return _pc;
    }

    public ClassMetaData getMetaData() {
        throw new UnsupportedOperationException();
    }

    public OpenJPAStateManager getOwner() {
        throw new UnsupportedOperationException();
    }

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

    public boolean isEmbedded() {
        return _embedded;
    }

    public boolean isFlushed() {
        throw new UnsupportedOperationException();
    }

    public boolean isFlushedDirty() {
        throw new UnsupportedOperationException();
    }

    public boolean isProvisional() {
        throw new UnsupportedOperationException();
    }

    public BitSet getLoaded() {
        return _loaded;
    }

    public BitSet getDirty() {
        return _dirty;
    }

    /**
     * Should DetachedStateField be used by Proxies to determine when to remove
     * $proxy wrappers during serialization.
     * @since 2.0.0
     */
    public boolean getUseDSFForUnproxy() {
        return _useDSFForUnproxy;
    }

    public BitSet getFlushed() {
        throw new UnsupportedOperationException();
    }

    public BitSet getUnloaded(FetchConfiguration fetch) {
        throw new UnsupportedOperationException();
    }

    public Object newProxy(int field) {
        throw new UnsupportedOperationException();
    }

    public Object newFieldProxy(int field) {
        throw new UnsupportedOperationException();
    }

    public boolean isDefaultValue(int field) {
        throw new UnsupportedOperationException();
    }

    public StoreContext getContext() {
        return null;
    }

    public PCState getPCState() {
        throw new UnsupportedOperationException();
    }

    public Object getObjectId() {
        return _oid;
    }

    public void setObjectId(Object oid) {
        throw new UnsupportedOperationException();
    }

    public boolean assignObjectId(boolean flush) {
        return true;
    }

    public Object getId() {
        return getObjectId();
    }

    public Object getLock() {
        throw new UnsupportedOperationException();
    }

    public void setLock(Object lock) {
        throw new UnsupportedOperationException();
    }

    public void setNextVersion(Object version) {
        throw new UnsupportedOperationException();
    }

    public Object getImplData() {
        throw new UnsupportedOperationException();
    }

    public Object setImplData(Object data, boolean cacheable) {
        throw new UnsupportedOperationException();
    }

    public boolean isImplDataCacheable() {
        return false;
    }

    public Object getImplData(int field) {
        throw new UnsupportedOperationException();
    }

    public Object setImplData(int field, Object data) {
        throw new UnsupportedOperationException();
    }

    public boolean isImplDataCacheable(int field) {
        throw new UnsupportedOperationException();
    }

    public Object getIntermediate(int field) {
        throw new UnsupportedOperationException();
    }

    public void setIntermediate(int field, Object data) {
        throw new UnsupportedOperationException();
    }

    public void removed(int field, Object removed, boolean key) {
        dirty(field);
    }

    public boolean beforeRefresh(boolean all) {
        throw new UnsupportedOperationException();
    }

    public void dirty(int field) {
        lock();
        try {
            _dirty.set(field);
        } finally {
            unlock();
        }
    }

    public void storeBoolean(int field, boolean extVal) {
        throw new UnsupportedOperationException();
    }

    public void storeByte(int field, byte extVal) {
        throw new UnsupportedOperationException();
    }

    public void storeChar(int field, char extVal) {
        throw new UnsupportedOperationException();
    }

    public void storeInt(int field, int extVal) {
        throw new UnsupportedOperationException();
    }

    public void storeShort(int field, short extVal) {
        throw new UnsupportedOperationException();
    }

    public void storeLong(int field, long extVal) {
        throw new UnsupportedOperationException();
    }

    public void storeFloat(int field, float extVal) {
        throw new UnsupportedOperationException();
    }

    public void storeDouble(int field, double extVal) {
        throw new UnsupportedOperationException();
    }

    public void storeString(int field, String extVal) {
        throw new UnsupportedOperationException();
    }

    public void storeObject(int field, Object extVal) {
        throw new UnsupportedOperationException();
    }

    public void store(int field, Object extVal) {
        throw new UnsupportedOperationException();
    }

    public void storeField(int field, Object value) {
        throw new UnsupportedOperationException();
    }

    public boolean fetchBoolean(int field) {
        throw new UnsupportedOperationException();
    }

    public byte fetchByte(int field) {
        throw new UnsupportedOperationException();
    }

    public char fetchChar(int field) {
        throw new UnsupportedOperationException();
    }

    public short fetchShort(int field) {
        throw new UnsupportedOperationException();
    }

    public int fetchInt(int field) {
        throw new UnsupportedOperationException();
    }

    public long fetchLong(int field) {
        throw new UnsupportedOperationException();
    }

    public float fetchFloat(int field) {
        throw new UnsupportedOperationException();
    }

    public double fetchDouble(int field) {
        throw new UnsupportedOperationException();
    }

    public String fetchString(int field) {
        throw new UnsupportedOperationException();
    }

    public Object fetchObject(int field) {
        throw new UnsupportedOperationException();
    }

    public Object fetch(int field) {
        throw new UnsupportedOperationException();
    }

    public Object fetchField(int field, boolean transitions) {
        throw new UnsupportedOperationException();
    }

    public Object fetchInitialField(int field) {
        throw new UnsupportedOperationException();
    }

    public void setRemote(int field, Object value) {
        throw new UnsupportedOperationException();
    }

    public void lock() {
        if (_lock != null)
            _lock.lock();
    }

    public void unlock() {
        if (_lock != null)
            _lock.unlock();
    }
    
    @Override
    public boolean isDelayed(int field) {
        return false;
    }
    
    @Override
    public void setDelayed(int field, boolean delay) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void loadDelayedField(int field) {
        throw new UnsupportedOperationException();
    }
}
