/*
   Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 */

package com.mysql.clusterj.tie;

import java.lang.reflect.Method;

import java.math.BigDecimal;
import java.math.BigInteger;

import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;

import com.mysql.clusterj.ClusterJFatalInternalException;
import com.mysql.clusterj.ClusterJUserException;
import com.mysql.clusterj.ColumnMetadata;

import com.mysql.clusterj.core.CacheManager;

import com.mysql.clusterj.core.metadata.DomainTypeHandlerImpl;
import com.mysql.clusterj.core.metadata.InvocationHandlerImpl;

import com.mysql.clusterj.core.spi.DomainFieldHandler;
import com.mysql.clusterj.core.spi.DomainTypeHandler;
import com.mysql.clusterj.core.spi.SmartValueHandler;

import com.mysql.clusterj.core.store.ClusterTransaction;
import com.mysql.clusterj.core.store.Db;
import com.mysql.clusterj.core.store.Operation;
import com.mysql.clusterj.core.store.ResultData;

import com.mysql.clusterj.core.util.I18NHelper;
import com.mysql.clusterj.core.util.Logger;
import com.mysql.clusterj.core.util.LoggerFactoryService;

/** NdbRecordSmartValueHandlerImpl is the implementation class that
 * provides a value handler for a proxy or dynamic object that stores
 * values in an ndb record buffer instead of an object array. 
 * Subsequent database operations (insert, update, delete) use the
 * already-stored data instead of constructing a new data buffer. This class
 * replaces the InvocationHandler and uses the type-specific data transforms
 * done by DomainFieldHandler.
 * There are two separate numbering patterns used by this class: ValueHandler
 * methods access data using field numbers indexed according to the domain model.
 * Field numbers are translated to column numbers when calling NdbRecordOperationImpl.
 * Transient fields are implemented here, since NdbRecord knows nothing about
 * transient fields in the domain model.
 * The operation wrapper (NdbRecordOperationImpl) is initialized when the domain object
 * is created. But the operation to be performed (insert, delete, update, write) is only known
 * when the domain object is involved in a user operation. So the initialization of 
 * the corresponding NdbOperation is deferred.
 */
public class NdbRecordSmartValueHandlerImpl implements SmartValueHandler {

    /** My message translator */
    static final I18NHelper local = I18NHelper.getInstance(InvocationHandlerImpl.class);

    /** My logger */
    static final Logger logger = LoggerFactoryService.getFactory().getInstance(InvocationHandlerImpl.class);

    /** Finalize this object. This method is called by the garbage collector
     * when the proxy that delegates to this object is no longer reachable.
     */
    protected void finalize() throws Throwable {
        if (logger.isDetailEnabled()) logger.detail("NdbRecordSmartValueHandler.finalize");
        try {
            release();
        } finally {
            super.finalize();
        }
    }

    /** Release any resources associated with this object.
     * This method is called by the owner of this object.
     */
    public void release() {
        if (logger.isDetailEnabled()) logger.detail("NdbRecordSmartValueHandler.release");
        if (wasReleased()) {
            return;
        }
        // NdbRecordOperationImpl holds references to key buffer and value buffer ByteBuffers
        operation.release();
        operation = null;
        domainTypeHandler = null;
        domainFieldHandlers = null;
        fieldNumberToColumnNumberMap = null;
        transientValues = null;
        proxy = null;
    }

    /** Was this value handler released? */
    public boolean wasReleased() {
        return operation == null;
    }

    /** Assert that this value handler was not released */
    void assertNotReleased() {
        if (wasReleased()) {
            throw new ClusterJUserException(local.message("ERR_Cannot_Access_Object_After_Release"));
        }
    }

    protected NdbRecordOperationImpl operation;

    protected DomainTypeHandlerImpl<?> domainTypeHandler;

    protected DomainFieldHandler[] domainFieldHandlers;

    private int[] fieldNumberToColumnNumberMap;

    private Boolean found = null;

    /** The number of transient fields (that do not have a column and so cannot be stored in the NdbRecord) */
    private int numberOfTransientFields;

    /** Values of transient fields; only initialized if there are transient fields */
    private Object[] transientValues = null;

    private boolean[] transientModified; 

    private Object proxy;

    public NdbRecordSmartValueHandlerImpl(DomainTypeHandlerImpl<?> domainTypeHandler) {
        this.domainTypeHandler = domainTypeHandler;
        this.domainFieldHandlers = domainTypeHandler.getFieldHandlers();
        fieldNumberToColumnNumberMap = domainTypeHandler.getFieldNumberToColumnNumberMap();
        numberOfTransientFields = domainTypeHandler.getNumberOfTransientFields();
        transientModified = new boolean[numberOfTransientFields];
        if (numberOfTransientFields != 0) {
            transientValues = this.domainTypeHandler.newTransientValues();
        }
    }

    public NdbRecordSmartValueHandlerImpl(DomainTypeHandlerImpl<?> domainTypeHandler, Db db) {
        this(domainTypeHandler);
        this.operation = ((DbImpl)db).newNdbRecordOperationImpl(domainTypeHandler.getStoreTable());
    }

    public NdbRecordSmartValueHandlerImpl(DomainTypeHandlerImpl<?> domainTypeHandler, Db db, ResultData resultData) {
        this(domainTypeHandler);
        this.operation = ((NdbRecordResultDataImpl)resultData).transformOperation();
    }

    public Operation insert(ClusterTransaction clusterTransaction) {
        if (logger.isDetailEnabled()) logger.detail("smart insert for type: " + domainTypeHandler.getName()
                + "\n" + operation.dumpValues());
        ClusterTransactionImpl clusterTransactionImpl = (ClusterTransactionImpl)clusterTransaction;
        operation.insert(clusterTransactionImpl);
        return operation;
    }

    public Operation delete(ClusterTransaction clusterTransaction) {
        if (logger.isDetailEnabled()) logger.detail("smart delete for type: " + domainTypeHandler.getName()
                + "\n" + operation.dumpValues());
        operation.delete((ClusterTransactionImpl)clusterTransaction);
        return operation;
    }

    public Operation update(ClusterTransaction clusterTransaction) {
        if (logger.isDetailEnabled()) logger.detail("smart update for type: " + domainTypeHandler.getName()
                + " record: " + operation.dumpValues());
        operation.update((ClusterTransactionImpl)clusterTransaction);
        return operation;
    }

    public Operation write(ClusterTransaction clusterTransaction) {
        if (logger.isDetailEnabled()) logger.detail("smart write for type: " + domainTypeHandler.getName()
                + " record: " + operation.dumpValues());
        operation.write((ClusterTransactionImpl)clusterTransaction);
        return operation;
    }

    public Operation load(ClusterTransaction clusterTransaction) {
        if (logger.isDetailEnabled()) logger.detail("smart load for type: " + domainTypeHandler.getName()
                + " record: " + operation.dumpValues());
        // only load mapped columns that are persistent
        for (int i = 0; i < domainFieldHandlers.length; ++i) {
            DomainFieldHandler domainFieldHandler = domainFieldHandlers[i];
            int columnId = fieldNumberToColumnNumberMap[i];
            if (domainFieldHandler.isPersistent()) {
                if (domainFieldHandler.isLob()) {
                    operation.getBlobHandle(columnId);
                } else {
                    operation.columnSet(columnId);
                }
            }
        }
        operation.load((ClusterTransactionImpl)clusterTransaction);
        final NdbRecordSmartValueHandlerImpl valueHandler = this;
        // defer execution of the key operation until the next find, flush, or query
        Runnable postExecuteOperation = new Runnable() {
            public void run() {
                if (operation.getErrorCode() == 0) {
                    // found row in database
                    valueHandler.found(Boolean.TRUE);
                    operation.loadBlobValues();
                    domainTypeHandler.objectResetModified(valueHandler);
                } else {
                    // mark instance as not found
                    valueHandler.found(Boolean.FALSE);
                }
            }
        };
        clusterTransaction.postExecuteCallback(postExecuteOperation);
        return operation;
    }

    public BigDecimal getBigDecimal(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getDecimal(columnId);
        }
        return (BigDecimal)transientValues[-1 - columnId];
    }

    public BigInteger getBigInteger(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getBigInteger(columnId);
        }
        return (BigInteger)transientValues[-1 - columnId];
    }

    public boolean getBoolean(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getBoolean(columnId);
        }
        Boolean value = (Boolean)transientValues[-1 - columnId];
        return value == null?false:value;
    }

    public boolean[] getBooleans(int fieldNumber) {
        throw new ClusterJFatalInternalException(local.message("ERR_Method_Not_Implemented",
                "NdbRecordSmartValueHandler.getBooleans(int)"));
    }

    public byte getByte(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getByte(columnId);
        }
        Byte value = (Byte)transientValues[-1 - columnId];
        return value == null?0:value;
    }

    public byte[] getBytes(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getBytes(columnId);
        }
        return (byte[])transientValues[-1 - columnId];
    }

    public double getDouble(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getDouble(columnId);
        }
        Double value = (Double)transientValues[-1 - columnId];
        return value == null?0.0D:value;
    }

    public float getFloat(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getFloat(columnId);
        }
        Float value = (Float)transientValues[-1 - columnId];
        return value == null?0.0F:value;
    }

    public int getInt(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            int result = operation.getInt(columnId);
            return result;
        }
        Integer value = (Integer)transientValues[-1 - columnId];
        return (value == null)?0:value;
    }

    public Date getJavaSqlDate(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            Long millis = operation.getObjectLong(columnId);
            return millis == null? null:new java.sql.Date(millis);
        }
        return (java.sql.Date)transientValues[-1 - columnId];
    }

    public Time getJavaSqlTime(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            Long millis = operation.getObjectLong(columnId);
            return millis == null? null:new java.sql.Time(millis);
        }
        return (java.sql.Time)transientValues[-1 - columnId];
    }

    public Timestamp getJavaSqlTimestamp(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            Long millis = operation.getObjectLong(columnId);
            return millis == null? null:new java.sql.Timestamp(millis);
        }
        return (java.sql.Timestamp)transientValues[-1 - columnId];
    }

    public java.util.Date getJavaUtilDate(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            Long millis = operation.getObjectLong(columnId);
            return millis == null? null:new java.util.Date(millis);
        }
        return (java.util.Date)transientValues[-1 - columnId];
    }

    public byte[] getLobBytes(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        NdbRecordBlobImpl blob = operation.getBlobHandle(columnId);
        return blob.getBytesData();
    }

    public String getLobString(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        NdbRecordBlobImpl blob = operation.getBlobHandle(columnId);
        String result = blob.getStringData();
        return result;
    }

    public long getLong(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getLong(columnId);
        }
         Long value = (Long)transientValues[-1 - columnId];
         return value == null?0L:value;
    }

    public Boolean getObjectBoolean(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getObjectBoolean(columnId);
        }
        return (Boolean)transientValues[-1 - columnId];
    }

    public Byte getObjectByte(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getObjectByte(columnId);
        }
        return (Byte)transientValues[-1 - columnId];
    }

    public Double getObjectDouble(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getObjectDouble(columnId);
        }
        return (Double)transientValues[-1 - columnId];
    }

    public Float getObjectFloat(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getObjectFloat(columnId);
        }
        return (Float)transientValues[-1 - columnId];
    }

    public Integer getObjectInt(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getObjectInteger(columnId);
        }
        return (Integer)transientValues[-1 - columnId];
    }

    public Long getObjectLong(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getObjectLong(columnId);
        }
        return (Long)transientValues[-1 - columnId];
    }

    public Short getObjectShort(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getObjectShort(columnId);
        }
        return (Short)transientValues[-1 - columnId];
    }

    public short getShort(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getShort(columnId);
        }
        Short value = (Short)transientValues[-1 - columnId];
        return value == null?0:value;
    }

    public String getString(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.getString(columnId);
        }
        return (String)transientValues[-1 - columnId];
    }

    public boolean isModified(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.isModified(columnId);
        }
        return transientModified[-1 - columnId];
    }

    public boolean isNull(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            return operation.isNull(columnId);
        }
        return transientValues[-1 - columnId] == null;
    }

    public void markModified(int fieldNumber) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.markModified(columnId);
            return;
        }
        transientModified[-1 - columnId] = true;
    }

    public String pkToString(DomainTypeHandler<?> domainTypeHandler) {
        // TODO Auto-generated method stub
        return null;
    }

    public void resetModified() {
        operation.resetModified();
        transientModified = new boolean[numberOfTransientFields];
    }

    public void setBigDecimal(int fieldNumber, BigDecimal value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setDecimal(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setBigInteger(int fieldNumber, BigInteger bigIntegerExact) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setBigInteger(columnId, bigIntegerExact);
        } else {
            transientValues[-1 - columnId] = bigIntegerExact;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setBoolean(int fieldNumber, boolean b) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setBoolean(columnId, b);
        } else {
            transientValues[-1 - columnId] = b;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setBooleans(int fieldNumber, boolean[] b) {
        throw new ClusterJFatalInternalException(local.message("ERR_Method_Not_Implemented",
                "NdbRecordSmartValueHandler.setBooleans(int, boolean[])"));
    }

    public void setByte(int fieldNumber, byte value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setByte(columnId, value);
        } else {
            transientModified[-1 - columnId] = true;
            transientValues[-1 - columnId] = value;
        }
    }

    public void setBytes(int fieldNumber, byte[] value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setBytes(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setDouble(int fieldNumber, double value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setDouble(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setFloat(int fieldNumber, float value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setFloat(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setInt(int fieldNumber, int value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setInt(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setJavaSqlDate(int fieldNumber, Date value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (value == null) {
            operation.setNull(columnId);
        } else {
            setLong(fieldNumber, value.getTime());            
        }
    }

    public void setJavaSqlTime(int fieldNumber, Time value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (value == null) {
            operation.setNull(columnId);
        } else {
            setLong(fieldNumber, value.getTime());            
        }
    }

    public void setJavaSqlTimestamp(int fieldNumber, Timestamp value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (value == null) {
            operation.setNull(columnId);
        } else {
            setLong(fieldNumber, value.getTime());            
        }
    }

    public void setJavaUtilDate(int fieldNumber, java.util.Date value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (value == null) {
            operation.setNull(columnId);
        } else {
            setLong(fieldNumber, value.getTime());            
        }
    }

    public void setLobBytes(int fieldNumber, byte[] value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        NdbRecordBlobImpl blob = operation.getBlobHandle(columnId);
        operation.columnSet(columnId);
        blob.setData(value);
    }

    public void setLobString(int fieldNumber, String value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        NdbRecordBlobImpl blob = operation.getBlobHandle(columnId);
        operation.columnSet(columnId);
        blob.setData(value);
    }

    public void setLong(int fieldNumber, long value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setLong(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setObject(int fieldNumber, Object value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            throw new ClusterJFatalInternalException(local.message("ERR_Method_Not_Implemented",
            "NdbRecordSmartValueHandler.setObject(int, Object) for persistent values"));
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setObjectBoolean(int fieldNumber, Boolean value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setObjectBoolean(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setObjectByte(int fieldNumber, Byte value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setObjectByte(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setObjectDouble(int fieldNumber, Double value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setObjectDouble(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setObjectFloat(int fieldNumber, Float value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setObjectFloat(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setObjectInt(int fieldNumber, Integer value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setObjectInt(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setObjectLong(int fieldNumber, Long value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setObjectLong(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setObjectShort(int fieldNumber, Short value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setObjectShort(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setShort(int fieldNumber, short value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setObjectShort(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public void setString(int fieldNumber, String value) {
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId >= 0) {
            operation.setString(columnId, value);
        } else {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        }
    }

    public ColumnMetadata[] columnMetadata() {
        return domainTypeHandler.columnMetadata();
    }

    public Boolean found() {
        return found;
    }

    public void found(Boolean found) {
        this.found = found;
    }

    /** Return the value of a dynamic field stored in the NdbRecord buffer.
     * @param fieldNumber the field number
     * @return the value from data storage
     */
    public Object get(int fieldNumber) {
        assertNotReleased();
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId < 0) {
            return transientValues[-1 - columnId];
        }
        return domainFieldHandlers[fieldNumber].objectGetValue(this);
    }

    public void set(int fieldNumber, Object value) {
        assertNotReleased();
        int columnId = fieldNumberToColumnNumberMap[fieldNumber];
        if (columnId < 0) {
            transientValues[-1 - columnId] = value;
            transientModified[-1 - columnId] = true;
        } else {
            domainFieldHandlers[fieldNumber].objectSetValue(value, this);
        }
    }

    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        String methodName = method.getName();
        if (logger.isDetailEnabled()) logger.detail("invoke with Method: " + method.getName());
        
        String propertyHead = methodName.substring(3,4);
        String propertyTail = methodName.substring(4);
        String propertyName = propertyHead.toLowerCase() + propertyTail;
        int fieldNumber;
        Object result;
        assertNotReleased();
        if (methodName.startsWith("get")) {
            // get the field number from the name
            fieldNumber = domainTypeHandler.getFieldNumber(propertyName);
            result = get(fieldNumber);
            if (logger.isDetailEnabled()) logger.detail(methodName + ": Returning field number " + fieldNumber
                + " value: " + result);
            return result;
        } else if (methodName.startsWith("set")) {
            if (logger.isDetailEnabled()) logger.detail("Property name: " + propertyName
                    + " value: " + args[0]);
            // get the field number from the name
            fieldNumber = domainTypeHandler.getFieldNumber(propertyName);
            set(fieldNumber, args[0]);
        } else if ("toString".equals(methodName)) {
            return(domainTypeHandler.getDomainClass().getName()
                    + pkToString(domainTypeHandler));
        } else if ("hashCode".equals(methodName)) {
            return(this.hashCode());
        } else {
            throw new ClusterJUserException(
                    local.message("ERR_Method_Name", methodName));
        }
        return null;
    }

    public void setCacheManager(CacheManager cm) {
    }

    public void setProxy(Object proxy) {
        this.proxy = proxy;
    }

    public Object getProxy() {
        return this.proxy;
    }

}
