com.quinsoft.zeidon.standardoe.ViewImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.quinsoft.zeidon.standardoe.ViewImpl.java

Source

/**
Zeidon JOE 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 3 of the License, or
(at your option) any later version.
    
Zeidon JOE 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 Zeidon JOE.  If not, see <http://www.gnu.org/licenses/>.
    
Copyright 2009-2015 QuinSoft
 */
package com.quinsoft.zeidon.standardoe;

import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import com.quinsoft.zeidon.ActivateFlags;
import com.quinsoft.zeidon.ActivateOptions;
import com.quinsoft.zeidon.Application;
import com.quinsoft.zeidon.Blob;
import com.quinsoft.zeidon.CommitOptions;
import com.quinsoft.zeidon.CreateEntityFlags;
import com.quinsoft.zeidon.CursorPosition;
import com.quinsoft.zeidon.DropViewCleanup;
import com.quinsoft.zeidon.DuplicateOiOptions;
import com.quinsoft.zeidon.EntityCursor;
import com.quinsoft.zeidon.EntityInstance;
import com.quinsoft.zeidon.EntityIterator;
import com.quinsoft.zeidon.Level;
import com.quinsoft.zeidon.SelectSet;
import com.quinsoft.zeidon.SerializeOi;
import com.quinsoft.zeidon.Task;
import com.quinsoft.zeidon.TaskQualification;
import com.quinsoft.zeidon.View;
import com.quinsoft.zeidon.WriteOiFlags;
import com.quinsoft.zeidon.ZeidonException;
import com.quinsoft.zeidon.objectdefinition.EntityDef;
import com.quinsoft.zeidon.objectdefinition.LodDef;

/**
 *
 * @author DGC
 *
 */
class ViewImpl extends AbstractTaskQualification implements InternalView, Comparable<ViewImpl> {
    private static final DuplicateOiOptions DEFAULT_COPY_OI_OPTIONS = new DuplicateOiOptions();

    private static final EnumSet<CreateEntityFlags> CREATE_OI_FLAGS = EnumSet.of(CreateEntityFlags.fNO_SPAWNING,
            CreateEntityFlags.fIGNORE_MAX_CARDINALITY, CreateEntityFlags.fIGNORE_PERMISSIONS);

    private final LodDef lodDef;
    private final long id;

    /**
     * Points to the owning task of this view.  This should only be non-null if the view
     * belongs to one task but the OI belongs to another task.  This can happen when an
     * OI is named at the system task but a temp view is created for another task.
     */
    private final TaskImpl task;

    private ViewCursor viewCursor;
    private ViewNameList subtaskViewNameList = null;

    private Map<Object, SelectSet> selectSets;
    private boolean enableLazyLoad = true;
    private Object defaultSelectSet = 0;

    /**
     * True if this view is read only.
     */
    private boolean isReadOnly = false;

    /**
     * True if this view was created by internal JOE processing.  This is intended
     * to be used by the browser to ignore views that weren't created by the user.
     */
    private boolean isInternal = false;

    private boolean allowHiddenEntities = false;

    private List<DropViewCleanup> cleanupWork;

    ViewImpl(TaskImpl task, LodDef lodDef) {
        super(task.getApplication());
        this.lodDef = lodDef;
        id = task.getObjectEngine().getNextObjectKey();
        viewCursor = new ViewCursor(task, this, lodDef);
        task.addNewView(this);

        // The task for this view is the same as the OI so we'll rely on using the
        // task from the ViewCursor.
        this.task = null;
    }

    ViewImpl(TaskImpl task, ViewImpl source) {
        super(source.getTask().getApplication());
        this.lodDef = source.getLodDef();
        id = task.getObjectEngine().getNextObjectKey();
        viewCursor = new ViewCursor(this, source.viewCursor);
        isReadOnly = source.isReadOnly;
        allowHiddenEntities = source.allowHiddenEntities;
        enableLazyLoad = source.enableLazyLoad;
        if (source.selectSets != null) {
            selectSets = new HashMap<Object, SelectSet>();
            for (Object key : source.selectSets.keySet())
                selectSets.put(key, new SelectSetImpl(this, (SelectSetImpl) source.selectSets.get(key)));
        }

        task.addNewView(this);

        if (task.equals(source.getTask()))
            this.task = null;
        else
            this.task = task;
    }

    ViewImpl(ObjectInstance oi) {
        super(oi.getLodDef().getApplication());
        task = oi.getTask();
        this.lodDef = oi.getLodDef();
        id = task.getObjectEngine().getNextObjectKey();
        viewCursor = new ViewCursor(this, oi);
        task.addNewView(this);
    }

    /* (non-Javadoc)
     * @see com.quinsoft.zeidon.TaskQualification#getTask()
     */
    @Override
    public TaskImpl getTask() {
        TaskImpl t = viewCursor.getTask();
        if (!t.isValid())
            throw new ZeidonException("A view is referencing an OI that belongs to a dropped task.");

        if (task != null)
            return task;

        return t;
    }

    @Override
    public Task getSystemTask() {
        return getTask().getSystemTask();
    }

    @Override
    public LodDef getLodDef() {
        return lodDef;
    }

    @Override
    public Application getApplication() {
        Application application = lodDef.getApplication();

        // If the application of the LodDef is the system application (i.e. ZeidonSystem)
        // then we'll return the default application for the the task instead of
        // ZeidonSystem.
        if (application.equals(getSystemTask().getApplication()))
            return super.getApplication();

        return application;
    }

    ObjectInstance getObjectInstance() {
        return viewCursor.getObjectInstance();
    }

    @Override
    public EntityCursorImpl cursor(String entityName) {
        return viewCursor.getEntityCursor(entityName);
    }

    @Override
    public EntityCursorImpl cursor(EntityDef entityDef) {
        return viewCursor.getEntityCursor(entityDef);
    }

    @Override
    public EntityCursorImpl root() {
        return viewCursor.getEntityCursor(getLodDef().getRoot());
    }

    @Override
    public void setName(String name) {
        getTask().setNameForView(name, this);
    }

    @Override
    public void dropNameForView(String name) {
        getTask().dropNameForView(name, this);
    }

    @Override
    public void drop() {
        getTask().dropView(this);
        if (cleanupWork != null) {
            for (DropViewCleanup work : cleanupWork)
                work.viewDropped(this);
        }
    }

    @Override
    public void logObjectInstance() {
        log().debug("Displaying OI for %s", lodDef);

        for (EntityInstanceImpl ei = viewCursor.getObjectInstance().getRootEntityInstance(); ei != null; ei = ei
                .getNextTwin()) {
            ei.logEntity(true, 0);
        }

        log().debug("--- Done displaying ---");
    }

    /**
     * @deprecated Use logObjectInstance instead.
     */
    @Deprecated
    @Override
    public void displayObjectInstance() {
        logObjectInstance();
    }

    @Override
    public Iterable<EntityInstance> getHierEntityList() {
        return getHierEntityList(true, null);
    }

    @Override
    public Iterable<EntityInstance> getHierEntityList(boolean includeRoot) {
        return getHierEntityList(includeRoot, null);
    }

    @Override
    public Iterable<EntityInstance> getHierEntityList(boolean includeRoot, String entityName) {
        EntityDef entityDef = null;
        if (!StringUtils.isBlank(entityName))
            entityDef = getLodDef().getEntityDef(entityName);

        ObjectInstance oi = viewCursor.getObjectInstance();
        final EntityIterator<EntityInstanceImpl> iter = new IteratorBuilder(getObjectInstance()).withOiScoping(oi)
                .forEntityDef(entityDef).setLazyLoad(true).build();

        // Skip the root?
        if (!includeRoot)
            iter.next();

        // We have to create an iterator so that we can set the cursor in next().
        return new Iterable<EntityInstance>() {
            @Override
            public Iterator<EntityInstance> iterator() {
                return new Iterator<EntityInstance>() {
                    @Override
                    public boolean hasNext() {
                        return iter.hasNext();
                    }

                    @Override
                    public EntityInstance next() {
                        EntityInstanceImpl ei = iter.next();
                        EntityDef entityDef = ei.getEntityDef();
                        EntityCursorImpl entityCursor = viewCursor.getEntityCursor(entityDef);
                        entityCursor.setCursor(ei);
                        return entityCursor.getEntityInstance();
                    }

                    @Override
                    public void remove() {
                        iter.remove();
                    }
                };
            }
        };
    }

    /* (non-Javadoc)
     * @see com.quinsoft.zeidon.View#writeOiToXml(java.lang.String, long)
     */
    @Override
    public void writeOiToXml(String filename, EnumSet<WriteOiFlags> control) {
        serializeOi().asXml().setFlags(control).toFile(filename);
    }

    @Override
    public void writeOiToFile(String filename, EnumSet<WriteOiFlags> control) {
        serializeOi().setFlags(control).toFile(filename);
    }

    @Override
    public Blob writeOiToBlob(long control) {
        OutputStreamWriter stream = null;
        try {
            ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
            stream = new OutputStreamWriter(byteArray);
            serializeOi().setFlags(control).toWriter(stream);
            return new Blob(byteArray.toByteArray());
        } finally {
            IOUtils.closeQuietly(stream);
        }
    }

    @Override
    public Collection<String> getNameList() {
        return getTask().getViewNameList(this);
    }

    @Override
    public int commit() {
        return getTask().commitMultipleOis(this);
    }

    @Override
    public int commit(CommitOptions options) {
        return getTask().commitMultipleOis(options, this);
    }

    @Override
    public boolean isReadOnly() {
        if (isReadOnly)
            return true;

        return getObjectInstance().isReadOnly();
    }

    @Override
    public void setReadOnly(boolean b) {
        isReadOnly = b;
    }

    @Override
    public ViewImpl newView() {
        return newView(getTask(), isReadOnly());
    }

    @Override
    public ViewImpl newView(TaskQualification owningTask) {
        return newView(owningTask, isReadOnly());
    }

    @Override
    public ViewImpl newView(boolean readOnly) {
        return newView(getTask(), readOnly);
    }

    @Override
    public ViewImpl newView(TaskQualification owningTask, boolean readOnly) {
        ViewImpl newView = new ViewImpl((TaskImpl) owningTask.getTask(), this);
        if (readOnly)
            newView.setReadOnly(true);

        return newView;
    }

    /**
     * Create a new internal view.
     */
    ViewImpl newInternalView() {
        ViewImpl newView = newView(getTask(), isReadOnly());
        newView.setInternal(true);
        return newView;
    }

    @Override
    public View getViewByName(String name) {
        return getTask().getViewByName(name);
    }

    @Override
    public View getViewByKey(long key) {
        return getTask().getViewByKey(key);
    }

    @Override
    public View getViewByNameForSubtask(String name) {
        return getSubtaskViewNameList().getViewByName(name);
    }

    @Override
    public void dropNameForSubtask(String name, View view) {
        getSubtaskViewNameList().dropNameForView(name, view);
    }

    @Override
    public Collection<String> getSubtaskNameList() {
        return getSubtaskViewNameList().getAllViewNames();
    }

    @Override
    public void setNameForSubtask(String name, View view) {
        getSubtaskViewNameList().setNameForView(name, view);
    }

    @Override
    public View getViewByName(String name, Level level) {
        switch (level) {
        case SYSTEM:
            return getTask().getSystemTask().getViewByName(name);

        case APPLICATION:
            return getApplication().getViewByName(name);

        case TASK:
            return getViewByName(name);

        case SUBTASK:
            return getViewByNameForSubtask(name);
        }

        throw new ZeidonException("Internal error: unsupported level");
    }

    @Override
    public void setName(String name, Level level) {
        switch (level) {
        case SYSTEM:
            getTask().getSystemTask().setNameForView(name, this);
            return;

        case APPLICATION:
            getApplication().setNameForView(name, this);
            return;

        case TASK:
            setName(name);
            return;

        case SUBTASK:
            setNameForSubtask(name, this);
            break;

        default:
            throw new ZeidonException("Unknown level %s", level);
        }

        throw new ZeidonException("Level %s not valid for setNameForView", level);
    }

    @Override
    public View activateOiFromOi(ActivateFlags flag) {
        return activateOiFromOi(EnumSet.of(flag));
    }

    @Override
    public View activateOiFromOi(Set<ActivateFlags> flags) {
        ViewImpl srcView = this;

        // TODO: Make sure there are no outstanding temporal entities.

        LodDef lodDef = srcView.getLodDef();
        ViewImpl view = getTask().activateEmptyObjectInstance(lodDef);
        ObjectInstance oi = view.getObjectInstance();

        EntityInstanceImpl firstSrcEntityInstance;
        EntityInstanceImpl lastSrcEntityInstance;
        if (flags.contains(ActivateFlags.fSINGLE)) {
            // We only want the root pointed to by the root cursor.
            firstSrcEntityInstance = srcView.cursor(lodDef.getEntityDef(0)).getEntityInstance();
            lastSrcEntityInstance = firstSrcEntityInstance.getNextTwin();
        } else {
            // We want all entities.
            firstSrcEntityInstance = srcView.getObjectInstance().getRootEntityInstance();
            lastSrcEntityInstance = null;
        }

        // Set book-keeping flags in src OI.
        long hierIdx = 0;
        for (EntityInstanceImpl srcEntityInstance = firstSrcEntityInstance; srcEntityInstance != lastSrcEntityInstance; srcEntityInstance = srcEntityInstance
                .getNextHier()) {
            srcEntityInstance.setHierIndex(hierIdx++);
            srcEntityInstance.setRecordOwner(false);
        }

        // Copy source entities to target.
        for (EntityInstanceImpl srcEntityInstance = firstSrcEntityInstance; srcEntityInstance != lastSrcEntityInstance; srcEntityInstance = srcEntityInstance
                .getNextHier()) {
            EntityDef entityDef = srcEntityInstance.getEntityDef();

            EntityCursorImpl cursor = view.cursor(entityDef);
            EntityInstanceImpl newInstance = cursor.createEntity(CursorPosition.NEXT, CREATE_OI_FLAGS);
            newInstance.setHierIndex(srcEntityInstance.getHierIndex());

            // Find the record owner of srcEntityInstance.  Start out by assuming that
            // srcEntityInstance is the record owner.
            EntityInstanceImpl recordOwner = srcEntityInstance;
            if (!srcEntityInstance.isRecordOwner()) {
                for (EntityInstanceImpl ei : srcEntityInstance.getAllLinkedInstances()) {
                    if (ei.getObjectInstance() != srcEntityInstance.getObjectInstance())
                        continue;

                    if (ei.isRecordOwner()) {
                        recordOwner = ei;
                        break;
                    }
                }
            }

            boolean copyPersist;
            if (recordOwner == srcEntityInstance) {
                // srcEntityInstance is the owner.  Set flag and copy attributes.
                srcEntityInstance.setRecordOwner(true);
                copyPersist = true;
            } else {
                // srcEntityInstance is not the owner.  Find the corresponding entity
                // instance in the new OI by hierIndex.
                EntityInstanceImpl linked = oi.findByHierIndex(recordOwner.getHierIndex());

                // Now link them.
                newInstance.linkInternalInstances(linked);

                // Copy only work attributes.
                copyPersist = false;
            }

            newInstance.copyAttributes(srcEntityInstance, copyPersist, true);
            newInstance.copyFlags(srcEntityInstance);
        }

        view.getObjectInstance().setUpdated(srcView.getObjectInstance().isUpdated());
        view.getObjectInstance().setUpdatedFile(srcView.getObjectInstance().isUpdatedFile());

        return view;
    }

    @Override
    public void resetSubobject() {
        viewCursor.resetSubobjectToParent();
    }

    @Override
    public void resetSubobjectTop() {
        viewCursor.resetSubobjectTop();
    }

    ViewCursor getViewCursor() {
        return viewCursor;
    }

    @Override
    public EntityCursor getCursor(String entityName) {
        return cursor(entityName);
    }

    @Override
    public EntityCursor getCursor(EntityDef entityDef) {
        return cursor(entityDef);
    }

    @Override
    public String toString() {
        return String.format("%d %s", id, getLodDef());
    }

    @Override
    public long getId() {
        return id;
    }

    @Override
    public long getOiId() {
        return getViewCursor().getObjectInstance().getId();
    }

    @Override
    public int compareTo(ViewImpl o) {
        return Long.valueOf(getId()).compareTo(o.getId());
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof ViewImpl)
            return compareTo(((InternalView) o).getViewImpl()) == 0;

        return false;
    }

    @Override
    public void reset() {
        viewCursor.reset();
    }

    /**
     * This is used to look for memory leaks of EntityInstanceImpl.
     *
     * @return
     */
    int countAllEntities() {
        int count = 0;
        HashSet<EntityInstanceImpl> allEntities = new HashSet<EntityInstanceImpl>();
        HashMap<String, Integer> entities = new HashMap<String, Integer>();
        for (EntityInstanceImpl ei = getObjectInstance().getRootEntityInstance(); ei != null; ei = ei
                .getNextHier()) {
            ei.countAllVersions(allEntities);
            for (EntityInstanceImpl linked : ei.getAllLinkedInstances(true)) {
                if (!allEntities.contains(linked))
                    linked.countAllVersions(allEntities);
            }
        }

        for (EntityInstanceImpl ei : allEntities) {
            String name = (ei == null) ? "null" : ei.getEntityDef().getName();
            Integer ec = entities.get(name);
            if (ec == null)
                ec = 1;
            else
                ec = ec + 1;
            entities.put(name, ec);
        }

        count = allEntities.size();
        log().info("Entities = " + entities.toString());
        log().info("Total entities = " + count);
        return count;
    }

    /* (non-Javadoc)
     * @see com.quinsoft.zeidon.View#copyCursors(com.quinsoft.zeidon.View)
     */
    @Override
    public void copyCursors(View src) {
        // TODO: should we attempt to copy cursor values using csr.getEntityInstance() if src
        // isn't a ViewImpl?

        ViewImpl source = ((InternalView) src).getViewImpl();
        if (getLodDef() != source.getLodDef())
            throw new ZeidonException(
                    "Attempting to copy cursors from a different LodDef. Target = %s, source = %s", this, source);

        viewCursor = new ViewCursor(this, source.viewCursor);
    }

    /* (non-Javadoc)
     * @see com.quinsoft.zeidon.View#createSelectSet()
     */
    @Override
    public SelectSetImpl createSelectSet() {
        return new SelectSetImpl(this);
    }

    /* (non-Javadoc)
     * @see com.quinsoft.zeidon.View#getSelectSet()
     */
    @Override
    public SelectSet getSelectSet() {
        return getSelectSet(defaultSelectSet);
    }

    @Override
    public Set<Object> getSelectSetNames() {
        if (selectSets == null)
            return Collections.emptySet();

        return selectSets.keySet();
    }

    /* (non-Javadoc)
     * @see com.quinsoft.zeidon.View#getSelectSet(int)
     */
    @Override
    public SelectSet getSelectSet(Object index) {
        if (selectSets == null)
            selectSets = new HashMap<Object, SelectSet>();

        SelectSet set = selectSets.get(index);
        if (set != null)
            return set;

        SelectSetImpl newSet = createSelectSet();
        newSet.setName(index);
        selectSets.put(index, newSet);
        return newSet;
    }

    @Override
    public Object setCurrentSelectSet(Object key) {
        Object oldKey = defaultSelectSet;
        defaultSelectSet = key;
        return oldKey;
    }

    @Override
    public void dropSelectSet(Object index) {
        if (selectSets != null && selectSets.containsKey(index))
            selectSets.remove(index);
    }

    /* (non-Javadoc)
     * @see com.quinsoft.zeidon.View#getEntityByHierPosition(long)
     */
    @Override
    public EntityInstance getEntityByHierPosition(long position) {
        for (EntityInstanceImpl ei = getObjectInstance().getRootEntityInstance(); ei != null; ei = ei
                .getNextHier()) {
            if (ei.isHidden())
                continue;

            if (position == 0)
                return ei;

            position--;
        }

        return null;
    }

    /* (non-Javadoc)
     * @see com.quinsoft.zeidon.View#equals(com.quinsoft.zeidon.View)
     */
    @Override
    public boolean equalsOi(View v) {
        ViewImpl view = ((InternalView) v).getViewImpl();

        ObjectInstanceComparer comparer = new ObjectInstanceComparer(this.getObjectInstance(),
                view.getObjectInstance());
        //        comparer.displayDiff( getTask() );
        return comparer.areEqual();
    }

    /* (non-Javadoc)
     * @see com.quinsoft.zeidon.View#relinkOis(com.quinsoft.zeidon.View, com.quinsoft.zeidon.View[])
     */
    @Override
    public int relinkOis(View... views) {
        OiRelinker linker = new OiRelinker(this);
        linker.add(this.getObjectInstance());
        if (views != null) {
            for (View v : views)
                linker.add((((InternalView) v).getViewImpl()).getObjectInstance());
        }

        return linker.relinkOis();
    }

    /* (non-Javadoc)
     * @see com.quinsoft.zeidon.standardoe.InternalView#getViewImpl()
     */
    @Override
    public ViewImpl getViewImpl() {
        return this;
    }

    private synchronized ViewNameList getSubtaskViewNameList() {
        if (subtaskViewNameList == null)
            subtaskViewNameList = new ViewNameList();

        return subtaskViewNameList;
    }

    /**
     * Reassigns the the OI referenced by this view to be part of 'task'.
     *
     * @param task
     */
    void reassignTask(Task task) {
        // Do we care if we're reassigning to a non-system task?  It probably will never
        // happen but why restrict it?
        assert task == task.getSystemTask() : "Attempting to reassign to a non-system task.";
        getObjectInstance().setTask((TaskImpl) task);
    }

    @Override
    public Collection<ZeidonException> validateOi() {
        Collection<ZeidonException> list = new ArrayList<ZeidonException>();

        // Run the validation on all the root entities.
        for (EntityInstanceImpl ei = getObjectInstance().getRootEntityInstance(); ei != null; ei = ei
                .getNextTwin()) {
            if (ei.isHidden()) // Only validate non-hidden entities.
                continue;

            ei.validateSubobject(this, list);
        }

        if (list.size() == 0)
            return null;

        return list;
    }

    @Override
    public void setLazyLoad(boolean lazyLoad) {
        enableLazyLoad = lazyLoad;
    }

    @Override
    public boolean isLazyLoad() {
        return enableLazyLoad;
    }

    @Override
    public boolean isUpdated() {
        return getObjectInstance().isUpdated();
    }

    @Override
    public boolean isUpdatedFile() {
        return getObjectInstance().isUpdatedFile();
    }

    private EnumSet<WriteOiFlags> convertFlags(WriteOiFlags flag) {
        return EnumSet.of(flag);
    }

    private EnumSet<WriteOiFlags> convertFlags(WriteOiFlags... flags) {
        return EnumSet.copyOf(Arrays.asList(flags));
    }

    @Override
    public void writeOi(Writer writer, EnumSet<WriteOiFlags> flags) {
        serializeOi().setFlags(flags).toWriter(writer);
    }

    @Override
    public void writeOi(Writer writer, WriteOiFlags flag) {
        writeOi(writer, convertFlags(flag));
    }

    @Override
    public void writeOi(Writer writer, WriteOiFlags... flags) {
        writeOi(writer, convertFlags(flags));
    }

    @Override
    public void writeOi(Writer writer) {
        writeOi(writer, WriteOiFlags.INCREMENTAL);
    }

    @Override
    public View duplicateOi() {
        return duplicateOi(DEFAULT_COPY_OI_OPTIONS);
    }

    @Override
    public View duplicateOi(DuplicateOiOptions options) {
        Task owningTask = options.owningTask;
        if (owningTask == null)
            owningTask = getTask();

        EntityDef rootEntityDef = getLodDef().getRoot();
        View copy = owningTask.activateEmptyObjectInstance(getLodDef());
        EntityCursor rootCursor = copy.cursor(rootEntityDef);
        for (EntityInstance srcInstance = getObjectInstance()
                .getRootEntityInstance(); srcInstance != null; srcInstance = srcInstance.getNextTwin()) {
            if (srcInstance.isHidden())
                continue;

            rootCursor.copySubobject(srcInstance, CursorPosition.NEXT);
        }

        return copy;
    }

    @Override
    public ActivateOptions getActivateOptions() {
        return getObjectInstance().getActivateOptions();
    }

    @Override
    public boolean isEmpty() {
        ObjectInstance oi = getObjectInstance();
        for (EntityInstanceImpl ei = oi.getRootEntityInstance(); ei != null; ei = ei.getNextTwin()) {
            if (!ei.isHidden())
                return false; // OI is not empty
        }

        return true; // If we get here then there are no non-hidden EIs.
    }

    @Override
    public SerializeOi serializeOi() {
        return new SerializeOi(this);
    }

    /**
     * Calls drop().  This is implemented to support Closeable interface.
     */
    @Override
    public void close() {
        drop();
    }

    @Override
    public int getEntityCount(boolean includeHidden) {
        return getObjectInstance().getEntityCount(includeHidden);
    }

    /**
     * This is the total count of root entities.  For OIs loaded with paging this
     * is the total number of roots, not just the ones loaded.
     */
    @Override
    public Integer getTotalRootCount() {
        return getObjectInstance().getTotalRootCount();
    }

    /**
     * Sets the total root count.  Intended to be used by the dbhandler.
     */
    @Override
    public void setTotalRootCount(int totalRootCount) {
        getObjectInstance().setTotalRootCount(totalRootCount);
    }

    /**
     * If true then allow cursors to refer to hidden entities without throwing
     * an exception.  Intended to be used  by DBHandlers.
     *
     * @return true if the view can reference hidden (e.g. deleted) entities.
     */
    @Override
    public boolean isAllowHiddenEntities() {
        return allowHiddenEntities;
    }

    @Override
    public boolean setAllowHiddenEntities(boolean allowHiddenEntities) {
        boolean prev = this.allowHiddenEntities;
        this.allowHiddenEntities = allowHiddenEntities;
        return prev;
    }

    /**
     * Returns true if this view was created by internal JOE processing.  This is intended
     * to be used by the browser to ignore views that weren't created by the user.
     *
     * @return Returns true if this view was created by internal JOE processing.
     */
    @Override
    public boolean isInternal() {
        return isInternal;
    }

    @Override
    public View setInternal(boolean isInternal) {
        this.isInternal = isInternal;
        return this;
    }

    @Override
    public void addViewCleanupWork(DropViewCleanup work) {
        if (cleanupWork == null)
            cleanupWork = new ArrayList<>();

        cleanupWork.add(work);
    }
}