/*
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * Initial developer(s): Sebastien Chassande
 * Contributor(s):
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.lib.ejb21.jorm;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
import javax.ejb.EJBException;

import org.ow2.jonas.lib.ejb21.JEntityFactory;
import org.ow2.jonas.lib.ejb21.TraceEjb;

import org.objectweb.jorm.api.PBinding;
import org.objectweb.jorm.api.PClassMapping;
import org.objectweb.jorm.api.PException;
import org.objectweb.jorm.api.PExceptionProtocol;
import org.objectweb.jorm.api.PGenClassAccessor;
import org.objectweb.jorm.api.PIndexedElem;
import org.objectweb.jorm.naming.api.PBinder;
import org.objectweb.jorm.naming.api.PName;
import org.objectweb.jorm.naming.api.PNameManager;
import org.objectweb.jorm.naming.api.PExceptionNaming;
import org.objectweb.util.monolog.api.BasicLevel;


/**
 * This class is a basic implementation of the PGenClassAccessor interface
 * usefull for the multivalued relation.
 *
 * @author S.Chassande-Barrioz
 */
public abstract class GenClassImpl implements PGenClassAccessor {

    /**
     * All elements of the gen class (unmodified, modified, created, deleted)
     * This ArrayList contains GenClassElem objects
     */
    protected ArrayList pIndexedElems = null;

    /**
     * This array represents the distance between two deleted elements in the
     * pIndexedElems arraylist. O means there is no element in the middle. The
     * first element is in fact the quantity of existing element before the
     * first deleted element.
     * The size of this array is also the quantity of deleted elements. Then if
     * the array is empty there is no deleted elements.
     *
     * for example: if this fields is equals to [3, 2] then the elements whith
     * the index 3 and 6 are marked as deleted: [a, b, c, D, d, e, D, ....]
     *
     * This array is useless to convert an index valid inside the user/virtual
     * collection to an index valid inside the 'pIndexedElems' ArrayList.
     */
    protected int[] deletedLength = null;

    /**
     * This is the size of the relation. This field is equal to the
     * pIndexedElems size minus the quantity of deleted element. The value is
     * always right and is updated during each delete or create action.
     */
    protected int size;

    /**
     * true when GenClass has been modified and must be written
     */
    protected boolean isModified = false;

    /**
     * true when we want use select .. for ..update
     */
    protected boolean selectForUpdate = false;

    /**
     * This field references the PClassMapping which manages the persistency of
     * this GenClass. (xxxGCM.java)
     * This is actually a GenClassMapping, returned by the bean Factory (PClassMapping)
     */
    protected PClassMapping gcm = null;

    protected org.ow2.jonas.lib.ejb21.JEntityContext ectx = null;

    /**
     * This fields is the PBinding associated to this instance.
     */
    protected PBinding pb = null;

    /**
     * All the actions are send to this listener.
     * There is 1 Listener for each GenClass, i.e. for each
     * multi-valued CMR field. (See JEntityCmp2.vm)
     * Used mainly for coherence.
     */
    protected GenClassListener listener = null;

    /**
     * A GenClassImpl object is created for each Multi-Valued CMR field
     * See JEntityCmp2.vm
     * It starts with an empty list.
     */
    public GenClassImpl() {
        pIndexedElems = new ArrayList(0);
        size = 0;
        deletedLength = new int[0];
        isModified = false;
    }

    /**
     * Set the PClassMapping for this GenClass. It's needed to get
     * Connections for read and write operations.
     * Used by JEntityCmp2.vm
     * @param gcm the PClassMapping
     */
    public void setPClassMapping(PClassMapping gcm) {
        this.gcm = gcm;
    }

    /**
     * Set the EntityContext needed for setDirty calls
     */
    public void setEntityContext(org.ow2.jonas.lib.ejb21.JEntityContext ectx) {
        this.ectx = ectx;
        selectForUpdate = ectx.getEntityFactory().getSelectForUpdate();
    }

    /**
     * reset the GenClass to its initial state.
     */
    public void reset() {
        pIndexedElems.clear();
        size = 0;
        deletedLength = new int[0];
        isModified = false;
    }

    /**
     * Set the PBinding that will be used for this GenClass
     * Used by JEntityCmp2.vm
     * @param pb The PBinding for this GenClass
     */
    public void setPBinding(PBinding pb) {
        this.pb = pb;
    }

    /**
     * Get the PBinding used for this GenClass
     * Used by JEntityCmp2.vm
     * @return The PBinding for this GenClass
     */
    public PBinding gcGetPBinding() {
        return pb;
    }

    public void setListener(GenClassListener gcl) {
        listener = gcl;
    }

    public GenClassListener getListener() {
        return listener;
    }

    // IMPLEMENTATION OF THE Persitence lyfe cycle //
    //---------------------------------------------//

    public boolean gcIsModified() {
        return isModified;
    }

    /**
     * It loads the data of the gen class.
     * @param pn is the PName of the genclass
     * @param connection is a connection to access to the support. If it is null
     * a connection is asked to the mapper and closed after its use.
     */
    public void read(PName pn, Object connection, Object tx) throws PException {
        if (pn == null || pn.isNull()) {
            TraceEjb.genclass.log(BasicLevel.DEBUG, "null pn");
            reset();
            return;
        }
        if (pb == null) {
            throw new PExceptionProtocol("Impossible to read a persitent object withoout PBinding");
        }
        if (TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
            TraceEjb.genclass.log(BasicLevel.DEBUG, "PName=" + pn);
        }

        // Allocate a connection if needed
        Object conn = (connection == null
                       ? gcm.getPMapper().getConnection()
                       : connection);
        if (TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
            TraceEjb.genclass.log(BasicLevel.DEBUG, "Load the genclass");
        }
        pb.bind(pn);
        pb.read(conn, this, tx, selectForUpdate);
        //pb.unbind();
        isModified = false;

        // close the connection opened localy
        if (connection == null) {
            gcm.getPMapper().closeConnection(conn);
        }
    }

    /**
     * It writes the data of the gen class if it was modified (see the field
     * isModified).
     * @param pn is the PName of the genclass
     * @param connection is a connection to access to the support. If it is null
     * a connection is asked to the mapper and closed after its use.
     */
    public void write(PName pn, Object connection) throws PException {
        //TraceEjb.genclass.log(BasicLevel.DEBUG, "begin"); printState();
        if (pn == null || pn.isNull()) {
            throw new PExceptionProtocol("Impossible to write a persitent object with a null PName: " + pn);
        }
        if (TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
            TraceEjb.genclass.log(BasicLevel.DEBUG, "");
        }
        if (isModified) {
            // Allocate a connection if needed
            Object conn = (connection == null
                           ? gcm.getPMapper().getConnection()
                           : connection);

            if (pb == null) {
                throw new PExceptionProtocol("Impossible to write a persitent object without PBinding");
            }
            if (TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
                TraceEjb.genclass.log(BasicLevel.DEBUG, "Store the genclass " + pn);
            }
            pb.bind(pn);
            pb.write(conn, this);
            //pb.unbind();

            // The data are written on the support, then the memory instance is
            // up to date. The element marked as removed must really be removed
            // from the list.
            isModified = false;
            int pos = 0;
            for (int i = 0; i < deletedLength.length; i++) {
                pos += deletedLength[i];
                pIndexedElems.remove(pos);
            }
            deletedLength = new int[0];
            for (Iterator it = pIndexedElems.iterator(); it.hasNext();) {
                GenClassElement gce = (GenClassElement) it.next();
                gce.status = PIndexedElem.ELEM_UNMODIFIED;
                gce.hasBeenCreated = false;
            }

            // close the connection opened localy
            if (connection == null) {
                gcm.getPMapper().closeConnection(conn);
            }
        }
        //TraceEjb.genclass.log(BasicLevel.DEBUG, "end"); printState();
    }

    // IMPLEMENTATION OF THE PAccessor INTERFACE //
    //-------------------------------------------//

    /**
     * In most of cases this class is extented to personalize to a collection
     * type. Then an instance of this class is often the real collection.
     */
    public Object getMemoryInstance() {
        return this;
    }


    // IMPLEMENTATION OF THE PGenClassAccessor INTERFACE //
    //---------------------------------------------------//

    /**
     * It adds the elements in the list. This method is used by the PBinding to
     * load the data.
     * The elements is added at the end of the 'pIndexedElems' then
     * 'deletedLength' is not impacted.
     */
    public void paAdd(PIndexedElem elem, Object conn) throws PException {
        pIndexedElems.add(elem);
        size++;
        //TraceEjb.genclass.log(BasicLevel.DEBUG, "printState"); printState();
    }

    /**
     * The default implementation of the PIndexedElem is GenClassElement.
     * This method may be redefined for different GenClass implementations.
     */
    public PIndexedElem createPIndexedElem() {
        return new GenClassElement(this);
    }

    /**
     * This implementation is able to isolate the modification by element of the
     * gen class.
     */
    public boolean paDeltaSupported() {
        return true;
    }

    /**
     * This method is used by the PBinding to allocated data structure during a
     * write operation. The returned size must then contains also the deleted
     * elements.
     */
    public int paGetNbElem() {
        return pIndexedElems.size();
    }

    /**
     * This method is used by the PBinding to fetch all PIndexedElem.
     */
    public Iterator paIterator() {
        return pIndexedElems.iterator();
    }

    /**
     * This method is call in first during a read operation in order to indicate
     * the size of the gen class. if the value is equals to -1 then that means
     * the data support is unable to known the size of the relation in advance.
     * Then the previous size is kept. Otherwise the list is initialized to the
     * specified size.
     */
    public void paSetNbElem(int nbelem) {
        if (TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
            TraceEjb.genclass.log(BasicLevel.DEBUG, "nbElem: " + nbelem);
        }
        if (nbelem == -1) {
            pIndexedElems.clear();
        } else {
            pIndexedElems = new ArrayList(nbelem);
        }
        deletedLength = new int[0];
        size = 0;
    }


    // METHODS USABLE FOR THE SUB CLASS //
    //----------------------------------//

    /**
     * This method calculates the real index of an element.
     * @param idx the index valid in the colllection
     * @return an index valid into the 'pIndexedElems' ArrayList.
     */
    protected int gcGetRealIndex(int idx) {
        int a = idx;
        int i = 0;
        while (i < deletedLength.length && (a -= deletedLength[i]) >= 0) {
            i++;
        }
        return idx + i;
    }

    /**
     * This method add a new element in the collection. A PIndexedElem is built
     * via the 'createPIndexedElem' method. Then this instance is initialized
     * with the element and the staus specified by the parameter 'status'.
     * Before added the element, a checking is done. If the element was
     * previously removed in the same transaction then the status of the
     * removed element is changed to ELEM_UNMODIFIED. This optimization avoid
     * two useless I/O.
     *
     * @param element the object to add
     * @param callListener indicates if the gen class listener must be call
     * about this action
     */
    protected void gcAdd(PObject element, boolean callListener) {
        //TraceEjb.genclass.log(BasicLevel.DEBUG, "printState"); printState();
        int pos = 0;
        GenClassElement gce = null;
        int i = 0;

        // call setDirty before, because if there is a conflict, we must
        // not update the collection.
        ectx.setDirty(true);

        while (i < deletedLength.length) {
            pos += deletedLength[i];
            gce = (GenClassElement) pIndexedElems.get(pos);
            if (gce.value == element) {
                break;
            }
            pos++;
            i++;
        }
        if (i < deletedLength.length) {
            if (gce.hasBeenCreated) {
                throw new EJBException("Internal error state: A deleted element has not been created");
            }
            if (TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
            TraceEjb.genclass.log(BasicLevel.DEBUG,
                                  "The element added was previously removed. i: " + i
                                  + "gce.pname: " + gce.pname);
            }
            // The element was previously removed and added now.
            // - the status must be change to unmodified
            gce.status = PIndexedElem.ELEM_UNMODIFIED;
            if (deletedLength.length == 1) {
                deletedLength = new int[0];
            } else {
                int[] old = deletedLength;
                deletedLength = new int[deletedLength.length - 1];
                System.arraycopy(old, 0, deletedLength, 0, i);
                if (i < deletedLength.length) {
                    System.arraycopy(old, i + 1,
                                     deletedLength, i,
                                     old.length - 1 - i);
                    deletedLength[i] += old[i] + 1;
                }
            }
        } else {
            if (TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
                try {
                    TraceEjb.genclass.log(BasicLevel.DEBUG, "Add the element '"
                            + element.getPName().encodeString());
                } catch (PExceptionNaming pExceptionNaming) {
                }
            }
            gce = (GenClassElement) createPIndexedElem();
            gce.value = element;
            gce.status = PIndexedElem.ELEM_CREATED;
            gce.hasBeenCreated = true;
            // Check if the element is legal, before added it (fix bug #300521)
            listener.isLegalElement(gce);
            pIndexedElems.add(gce);
        }
        isModified = true;
        size++;
        //TraceEjb.genclass.log(BasicLevel.DEBUG, "printState"); printState();
        if (callListener) {
            if (gce == null) {
                TraceEjb.genclass.log(BasicLevel.ERROR, "null gce");
                return;
            }
            listener.gcAdd(gce);
        }
    }

    /**
     * It removes the first occurence of an element from the relation.
     * @param element an element which must be removed
     * @param callListener indicates if the gen class listener must be call
     * about this action
     * @return the remove element
     */
    protected Object gcRemove(Object element, boolean callListener) throws PException {
        //TraceEjb.genclass.log(BasicLevel.DEBUG, "printState"); printState();
        GenClassElement gce = null;
        boolean found = false;
        int b = 0; //The number of existing element since the last deleted element
        int i = 0; // Quantity of deleted elements before
        int a;
        for (a = 0; a < pIndexedElems.size() && !found; a++) {
            gce = (GenClassElement) pIndexedElems.get(a);
            if (gce.status == PIndexedElem.ELEM_DELETED) {
                i++;
                b = 0;
                continue; //Do not search the element among the deleted elements
            }
            b++;
            if (gce.pname != null && gce.value == null) {
                gce.value = gcDeref(gce.pname);
            }
            found = (element == null && gce.value == null)
                || (element != null && element.equals(gce.value));
        }
        if (!found) {
            if (TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
                TraceEjb.genclass.log(BasicLevel.DEBUG, "Nothing to remove");
            }
            return null;
        }

        // call setDirty before, because if there is a conflict, we must
        // not update the collection.
        ectx.setDirty(true);

        // call listener before removing field
        if (callListener) {
            listener.gcRemove(gce, false);
        }
        if (gce.hasBeenCreated) {
            // Remove the element permanantly
            if (TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
                TraceEjb.genclass.log(BasicLevel.DEBUG, "Remove permanantly the element");
            }
            pIndexedElems.remove(a - 1);
            if (i < deletedLength.length) {
                //decrease the number of existing element until the next deletec
                // element.
                deletedLength[i]--;
            }
        } else {
            //Mark the element as removed
            gce.status = PIndexedElem.ELEM_DELETED;

            // add the entry in the deletedLength array
            if (TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
            TraceEjb.genclass.log(BasicLevel.DEBUG,
                                  "Add the entry in the deletedLength array: i:" + i + " / b:" + b + ")");
            }
            int[] old = deletedLength;
            deletedLength = new int[old.length + 1];
            System.arraycopy(old, 0, deletedLength, 0, i);
            deletedLength[i] = b - 1;
            if (i < old.length) {
                deletedLength[i + 1] = old[i] - b;
                System.arraycopy(old, i + 1, deletedLength, i + 2, old.length - i - 1);
            }
        }
        isModified = true;
        size--;
        //TraceEjb.genclass.log(BasicLevel.DEBUG, "printState"); printState();
        return gce.value;
    }

    private static String toString(int[] t) {
        StringBuffer sb = new StringBuffer("[");
        for (int i = 0; i < t.length;) {
            sb.append(t[i]);
            i++;
            if (i < t.length) {
                sb.append(',');
            }
        }
        sb.append(']');
        return sb.toString();
    }

    public int gcGetSize() {
        return size;
    }

    protected boolean gcContains(PObject element, Object connection)
        throws PException {
        //Open a connection if the parameter is null
        Object conn = (connection == null
                       ? gcm.getPMapper().getConnection()
                       : connection);

        boolean result = false;
        for (Iterator it = gcIterator(conn); !result && it.hasNext();) {
            Object o = it.next();
            result =  (element == null && o == null)
                || (element != null && element.equals(o));
        }

        if (connection == null) {
            // close the localy opened connection
            gcm.getPMapper().closeConnection(conn);
        }
        if (TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
            try {
                TraceEjb.genclass.log(BasicLevel.DEBUG, "Looking for the element '"
                        + element.getPName().encodeString()
                        + ". return " + result);
            } catch (PExceptionNaming pExceptionNaming) {
            }
        }
        return result;
    }

    protected Iterator gcIterator() throws PException {
        return gcIterator(null);
    }

    /**
     * @param connection the connection to use during the PName resolving (if it has not
     * already dereferenced).
     * @return an Iterator over the virtual relations (only the existing elements).
     */
    protected Iterator gcIterator(Object connection) throws PException {
        return new ElementIterator(this, connection);
    }

    /**
     * It dereferences an element if needed
     * @param gce is the PIndexedElem which must be dereferenced
     * @param connection a connection to use to resolve the PName. If this parameter is
     * null a new connection is allocate via the mapper.
     * This connection is closed just after its use.
     * @return a reference to the bean (The local interface in fact).
     */
    protected PObject gcGetElement(GenClassElement gce, Object connection) throws PException {
        if (gce.pname != null && gce.value == null) {
            // Dereference the PName
            if (gce.pname == null || gce.pname.isNull()) {
                return null;
            }
            PName current = gce.pname;

            // Allocate a connection if needed
            Object conn = (connection == null
                           ? gcm.getPMapper().getConnection()
                           : connection);

            // Resolve the pname
            Object res = current.resolve(conn);
            while (res != null && res instanceof PName && !res.equals(current)) {
                current = (PName) res;
                res = current.resolve(conn);
            }

            // close the connection opened localy
            if (connection == null) {
                gcm.getPMapper().closeConnection(conn);
            }

            // Fetch a Local Object on the factory of the referenced bean
            gce.value = gcDeref(current);
        }
        return gce.value;
    }

    /**
     * Clear the GenClass.
     * @param delete true if cascade delete must be checked (ONE - MANY)
     */
    public void gcClear(boolean delete) {
        if (TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
            TraceEjb.genclass.log(BasicLevel.DEBUG, "Clear the gc");
        }
        //TraceEjb.genclass.log(BasicLevel.DEBUG, "begin"); printState();

        // Call listener now because ejbRemove can still access fields.
        for (Iterator it = pIndexedElems.iterator(); it.hasNext();) {
            GenClassElement gce = (GenClassElement) it.next();
            if (gce.status != PIndexedElem.ELEM_DELETED) {
                if (gce.pname != null && gce.value == null) {
                    gce.value = gcDeref(gce.pname);
                }
                if (TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
                    TraceEjb.genclass.log(BasicLevel.DEBUG, "listener.gcRemove");
                }
                listener.gcRemove(gce, delete);
            }
        }

        // Mark all elements as DELETED or remove the created element.
        for (Iterator it = pIndexedElems.iterator(); it.hasNext();) {
            GenClassElement gce = (GenClassElement) it.next();
            if (gce.status != PIndexedElem.ELEM_DELETED) {
                gce.status = PIndexedElem.ELEM_DELETED;
                if (gce.hasBeenCreated) {
                    // Remove the element permanantly
                    if (TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
                        TraceEjb.genclass.log(BasicLevel.DEBUG, "Clear permanantly an element");
                    }
                    it.remove();
                }
            }
        }
        // update the virtual size
        size = 0;
        isModified = true;
        ectx.setDirty(true);

        // As all elements are deleted, the 'deletedLength' contains only 0
        // numbers. The size of this array is the same as the size of the
        // pIndexedElems list.
        deletedLength = new int[pIndexedElems.size()];
        Arrays.fill(deletedLength, 0);
        //TraceEjb.genclass.log(BasicLevel.DEBUG, "end"); printState();

    }

    protected PObject gcDeref(PName pn) {
        JEntityFactory f = (JEntityFactory)
            ((PBinder) pn.getPNameManager()).getBinderClassMapping();
        if (f.getLocalHome() != null) {
            return ((PObjectHome) f.getLocalHome()).getPObject(pn);
        } else {
            return ((PObjectHome) f.getHome()).getPObject(pn);
        }
    }

    /**
     * This method permits to find the PName of an object.
     */
    protected PName gcObject2ref(PObject value) throws PException {
        if (value != null) {
            Object conn = gcm.getPMapper().getConnection();
            PNameManager pnm = (PNameManager) gcm.getPNameCoder();
            PName pn = pnm.export(conn, value.getPName(), null);
            gcm.getPMapper().closeConnection(conn);
            return pn;
        } else {
            return gcm.getPNameCoder().getNull();
        }
    }

    public void printState() {
        if (!TraceEjb.genclass.isLoggable(BasicLevel.DEBUG)) {
            return;
        }
        TraceEjb.genclass.log(BasicLevel.DEBUG, "deletedLength: " + toString(deletedLength));
        TraceEjb.genclass.log(BasicLevel.DEBUG, "pIndexedElems.size():" + pIndexedElems.size());
        TraceEjb.genclass.log(BasicLevel.DEBUG, "isModified: " + isModified);
        TraceEjb.genclass.log(BasicLevel.DEBUG, "size:" + size);
        int  i = 0;
        for (Iterator it = pIndexedElems.iterator(); it.hasNext();) {
            GenClassElement gce = (GenClassElement) it.next();
            TraceEjb.genclass.log(BasicLevel.DEBUG, "GCE:" + i
                                  + " / status:" + gce.status
                                  + " / hasBeenCreated:" + gce.hasBeenCreated
                                  );
            TraceEjb.genclass.log(BasicLevel.DEBUG, "- pname:" + gce.pname);
            TraceEjb.genclass.log(BasicLevel.DEBUG, "- value:" + gce.value);
            i++;
        }
    }

    /**
     * this class is an implementation of the Iterator interface which return
     * only the existing objects. The return elements are dereferenced, then
     * PObject instances.
     */
    protected class ElementIterator implements Iterator {

        private int cursor = 0;
        private int next = 0;
        private GenClassImpl gc;
        private Object conn;

        public ElementIterator(GenClassImpl gc, Object connection) {
            this.gc = gc;
            conn = connection;
            reset();
        }

        public void reset() {
            cursor = -1;
            next = nextExist(cursor);
        }

        private int nextExist(int pos) {
            int tmp = pos + 1;
            while (tmp < pIndexedElems.size()) {
                GenClassElement gce = (GenClassElement) pIndexedElems.get(tmp);
                if (gce == null) {
                    TraceEjb.genclass.log(BasicLevel.ERROR, "null GenClassElement");
                    return -1;
                }
                if (gce.status != PIndexedElem.ELEM_DELETED) {
                    //TraceEjb.genclass.log(BasicLevel.DEBUG, "nextExist("+pos+")="+tmp);
                    return tmp;
                }
                tmp++;
            }
            //TraceEjb.genclass.log(BasicLevel.DEBUG, "nextExist("+pos+")=-1");
            return -1;
        }

        // IMPLEMENTATION OF THE Iterator INTERFACE //
        //------------------------------------------//

        /**
         * Returns <tt>true</tt> if the iteration has more elements. (In other
         * words, returns <tt>true</tt> if <tt>next</tt> would return an element
         * rather than throwing an exception.)
         *
         * @return <tt>true</tt> if the iterator has more elements.
         */
        public boolean hasNext() {
            return next != -1;
        }

        /**
         * Returns the next element in the interation.
         *
         * @return the next element in the iteration.
         * @throws NoSuchElementException iteration has no more elements.
         */
        public Object next() {
            if (next == -1) {
                throw new NoSuchElementException();
            }
            cursor = next;
            try {
                GenClassElement gce = (GenClassElement) pIndexedElems.get(cursor);
                gc.gcGetElement(gce, conn);
                //Caculate the next
                next = nextExist(cursor);
                return gce.value;
            } catch (PException e) {
                next = -1;
                TraceEjb.genclass.log(BasicLevel.ERROR, "Impossible to obtain value:", e);
                throw new NoSuchElementException("Impossible to obtain value:" + e);
            }
        }

        public void remove() {
            try {
                gcRemove(((GenClassElement) pIndexedElems.get(cursor)).value, true);
            } catch (PException e) {
                throw new UnsupportedOperationException(e.getMessage());
            }
        }
    }
}
