All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xmind.core.internal.dom.RevisionManagerImpl Maven / Gradle / Ivy

/* ******************************************************************************
 * Copyright (c) 2006-2012 XMind Ltd. and others.
 * 
 * This file is a part of XMind 3. XMind releases 3 and
 * above are dual-licensed under the Eclipse Public License (EPL),
 * which is available at http://www.eclipse.org/legal/epl-v10.html
 * and the GNU Lesser General Public License (LGPL), 
 * which is available at http://www.gnu.org/licenses/lgpl.html
 * See http://www.xmind.net/license.html for details.
 * 
 * Contributors:
 *     XMind Ltd. - initial API and implementation
 *******************************************************************************/

package org.xmind.core.internal.dom;

import static org.xmind.core.internal.dom.DOMConstants.ATTR_MEDIA_TYPE;
import static org.xmind.core.internal.dom.DOMConstants.ATTR_NEXT_REVISION_NUMBER;
import static org.xmind.core.internal.dom.DOMConstants.ATTR_RESOURCE;
import static org.xmind.core.internal.dom.DOMConstants.ATTR_RESOURCE_ID;
import static org.xmind.core.internal.dom.DOMConstants.ATTR_REVISION_NUMBER;
import static org.xmind.core.internal.dom.DOMConstants.ATTR_TIMESTAMP;
import static org.xmind.core.internal.dom.DOMConstants.TAG_REVISION;
import static org.xmind.core.internal.dom.DOMConstants.TAG_REVISION_CONTENT;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xmind.core.Core;
import org.xmind.core.CoreException;
import org.xmind.core.IAdaptable;
import org.xmind.core.IFileEntry;
import org.xmind.core.IRevision;
import org.xmind.core.IWorkbook;
import org.xmind.core.event.ICoreEventListener;
import org.xmind.core.event.ICoreEventRegistration;
import org.xmind.core.event.ICoreEventSource;
import org.xmind.core.event.ICoreEventSupport;
import org.xmind.core.internal.RevisionManager;
import org.xmind.core.internal.zip.ArchiveConstants;
import org.xmind.core.util.DOMUtils;

/**
 * @author Frank Shaka
 * 
 */
public class RevisionManagerImpl extends RevisionManager implements
        ICoreEventSource, INodeAdaptableFactory {

    private Document implementation;

    private WorkbookImpl ownedWorkbook;

    private NodeAdaptableRegistry registry;

    private ICoreEventSupport coreEventSupport;

    private String dirPath;

    /**
     * 
     */
    public RevisionManagerImpl(Document implementation,
            WorkbookImpl ownedWorkbook, String dirPath) {
        this.implementation = implementation;
        this.ownedWorkbook = ownedWorkbook;
        this.dirPath = dirPath;
        this.registry = new NodeAdaptableRegistry(implementation, this);
    }

    @Override
    public int hashCode() {
        return implementation.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (obj == null || !(obj instanceof RevisionManagerImpl))
            return false;
        RevisionManagerImpl that = (RevisionManagerImpl) obj;
        return implementation.equals(that.implementation);
    }

    @Override
    public Object getAdapter(Class adapter) {
        if (adapter == Node.class || adapter == Document.class)
            return getImplementation();
        return super.getAdapter(adapter);
    }

    public Document getImplementation() {
        return implementation;
    }

    protected Element getRevisionsElement() {
        return implementation.getDocumentElement();
    }

    protected INodeAdaptableFactory getAdaptableFactory() {
        return ownedWorkbook;
    }

    public IAdaptable createAdaptable(Node node) {
        return new RevisionImpl((Element) node, this);
    }

    protected String getDirPath() {
        return dirPath;
    }

    public String getResourceId() {
        return getRevisionsElement().getAttribute(ATTR_RESOURCE_ID);
    }

    public String getContentType() {
        return getRevisionsElement().getAttribute(ATTR_MEDIA_TYPE);
    }

    public Iterator iterRevisions() {
        return new DOMUtils.AdaptableIterator(getRevisionsElement(),
                TAG_REVISION, registry, false);
    }

    public Iterator iterRevisionsReversed() {
        return new DOMUtils.AdaptableIterator(getRevisionsElement(),
                TAG_REVISION, registry, true);
    }

    public IRevision getLatestRevision() {
        Node node = getLastRevisionNode();
        return node == null ? null : (IRevision) registry.getAdaptable(node);
    }

    protected Node getLastRevisionNode() {
        Node node = getRevisionsElement().getLastChild();
        while (node != null && !DOMUtils.isElementByTag(node, TAG_REVISION)) {
            node = node.getPreviousSibling();
        }
        return node;
    }

    public int getNextRevisionNumber() {
        String num = getRevisionsElement().getAttribute(
                ATTR_NEXT_REVISION_NUMBER);
        return NumberUtils.safeParseInt(num, 1);
    }

    public IRevision addRevision(IAdaptable content) throws IOException,
            CoreException {
        Node node = (Node) content.getAdapter(Node.class);
        if (node == null)
            throw new CoreException(Core.ERROR_INVALID_ARGUMENT,
                    "Invalid content for content type: " + getContentType()); //$NON-NLS-1$

        IRevision latest = getLatestRevision();
        if (latest != null) {
            IAdaptable latestContent = latest.getContent();
            if (latestContent != null) {
                Object latestNode = latestContent.getAdapter(Node.class);
                if (latestNode != null && latestNode.equals(node))
                    return null;
            }
        }

        int revNum = getNextRevisionNumber();
        long timestamp = System.currentTimeMillis();
        String path = takeSnapshot(node, revNum, timestamp);
        RevisionImpl revision = createRevision(revNum, timestamp, path);
        setNextRevisionNumber(revNum + 1);
        revision.addNotify(ownedWorkbook);
        fireTargetEvent(Core.RevisionAdd, revision);
        return revision;
    }

    private void setNextRevisionNumber(int nextRevNum) {
        getRevisionsElement().setAttribute(ATTR_NEXT_REVISION_NUMBER,
                String.valueOf(nextRevNum));
    }

    private RevisionImpl createRevision(int revNum, long timestamp, String path) {
        Element ele = implementation.createElement(TAG_REVISION);
        ele.setAttribute(ATTR_REVISION_NUMBER, String.valueOf(revNum));
        ele.setAttribute(ATTR_TIMESTAMP, String.valueOf(timestamp));
        ele.setAttribute(ATTR_RESOURCE, path);
        getRevisionsElement().appendChild(ele);
        return new RevisionImpl(ele, this);
    }

    private String takeSnapshot(Node source, int revNum, long timestamp)
            throws IOException, CoreException {
        Document doc = DOMUtils.createDocument(TAG_REVISION_CONTENT);
        Element docEle = doc.getDocumentElement();
        NS.setNS(NS.Revision, docEle, NS.Xhtml, NS.Xlink, NS.SVG, NS.Fo);
        Node snapshot = doc.importNode(source, true);
        docEle.appendChild(snapshot);

        String path = getDirPath() + getFileName(revNum, timestamp, "xml"); //$NON-NLS-1$
        IFileEntry entry = ownedWorkbook.getManifest().createFileEntry(path);
        OutputStream stream = entry.openOutputStream();
        DOMUtils.save(doc, stream, true);
        return path;
    }

    private String getFileName(int revNum, long timestamp, String extName) {
        return String.format("rev-%s-%s.%s", revNum, timestamp, extName); //$NON-NLS-1$
    }

    public Object removeRevision(IRevision revision) {
        RevisionImpl rev = (RevisionImpl) revision;
        Element ele = rev.getImplementation();
        Element parentEle = getRevisionsElement();
        int index = DOMUtils.getNodeIndex(parentEle, ele);
        if (index < 0)
            return null;
        rev.removeNotify(ownedWorkbook);
        parentEle.removeChild(ele);
        fireTargetEvent(Core.RevisionRemove, revision);
        return Integer.valueOf(index);
    }

    public void restoreRevision(IRevision revision, Object removal) {
        if (!(removal instanceof Integer))
            throw new IllegalArgumentException("Invalid removal object"); //$NON-NLS-1$
        int index = ((Integer) removal).intValue();
        RevisionImpl rev = (RevisionImpl) revision;
        Element ele = rev.getImplementation();
        Element parentEle = getRevisionsElement();
        NodeList childNodes = parentEle.getChildNodes();
        if (index < childNodes.getLength()) {
            parentEle.insertBefore(ele, childNodes.item(index));
        } else {
            parentEle.appendChild(ele);
        }
        ((RevisionImpl) revision).addNotify(ownedWorkbook);
        fireTargetEvent(Core.RevisionAdd, revision);
    }

    public IWorkbook getOwnedWorkbook() {
        return ownedWorkbook;
    }

    public boolean isOrphan() {
        IFileEntry metaEntry = ownedWorkbook.getManifest().getFileEntry(
                getDirPath() + ArchiveConstants.REVISIONS_XML);
        return metaEntry == null || metaEntry.isOrphan();
    }

    protected void addNotify(WorkbookImpl workbook) {
        increaseFileEntryReference();
        Iterator it = iterRevisions();
        while (it.hasNext()) {
            ((RevisionImpl) it.next()).addNotify(workbook);
        }
    }

    protected void removeNotify(WorkbookImpl workbook) {
        Iterator it = iterRevisionsReversed();
        while (it.hasNext()) {
            ((RevisionImpl) it.next()).removeNotify(workbook);
        }
        decreaseFileEntryReference();
    }

    private void increaseFileEntryReference() {
        IFileEntry entry = getMetaFileEntry();
        if (entry != null) {
            entry.increaseReference();
        }
    }

    private void decreaseFileEntryReference() {
        IFileEntry entry = getMetaFileEntry();
        if (entry != null) {
            entry.decreaseReference();
        }
    }

    private IFileEntry getMetaFileEntry() {
        String path = dirPath + ArchiveConstants.REVISIONS_XML;
        return ownedWorkbook.getManifest().getFileEntry(path);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.xmind.core.event.ICoreEventSource#getCoreEventSupport()
     */
    public ICoreEventSupport getCoreEventSupport() {
        if (coreEventSupport == null) {
            coreEventSupport = ownedWorkbook.getCoreEventSupport();
        }
        return coreEventSupport;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.xmind.core.event.ICoreEventSource#registerCoreEventListener(java.
     * lang.String, org.xmind.core.event.ICoreEventListener)
     */
    public ICoreEventRegistration registerCoreEventListener(String type,
            ICoreEventListener listener) {
        return getCoreEventSupport().registerGlobalListener(type, listener);
    }

    private void fireTargetEvent(String eventType, IRevision revision) {
        getCoreEventSupport().dispatchTargetChange(this, eventType, revision);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy