org.pentaho.pms.ui.concept.editor.ConceptTreeModel.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.pms.ui.concept.editor.ConceptTreeModel.java

Source

/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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 Lesser General Public License for more details.
*
* Copyright (c) 2002-2013 Pentaho Corporation..  All rights reserved.
*/

package org.pentaho.pms.ui.concept.editor;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.commons.collections.BidiMap;
import org.apache.commons.collections.MapIterator;
import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.bidimap.DualHashBidiMap;
import org.apache.commons.collections.map.MultiValueMap;
import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.pms.schema.SchemaMeta;
import org.pentaho.pms.schema.concept.Concept;
import org.pentaho.pms.schema.concept.ConceptInterface;
import org.pentaho.pms.schema.concept.ConceptPropertyInterface;
import org.pentaho.pms.schema.concept.DefaultPropertyID;
import org.pentaho.pms.schema.concept.DeleteNotAllowedException;
import org.pentaho.pms.schema.concept.types.bool.ConceptPropertyBoolean;
import org.pentaho.pms.schema.concept.types.columnwidth.ColumnWidth;
import org.pentaho.pms.schema.concept.types.columnwidth.ConceptPropertyColumnWidth;
import org.pentaho.pms.schema.concept.types.localstring.ConceptPropertyLocalizedString;
import org.pentaho.pms.schema.concept.types.localstring.LocalizedStringSettings;
import org.pentaho.pms.schema.concept.types.string.ConceptPropertyString;
import org.pentaho.pms.util.ObjectAlreadyExistsException;
import org.pentaho.pms.util.Settings;

/**
 * This is actually a model of a forest (set of trees).
 * @author mlowery
 */
public class ConceptTreeModel implements IConceptTreeModel {

    // ~ Static fields/initializers ======================================================================================

    private static final Log logger = LogFactory.getLog(ConceptTreeModel.class);

    // ~ Instance fields =================================================================================================

    EventSupport eventSupport = new EventSupport();

    SchemaMeta schemaMeta;

    /**
     * Used to synchronize original tree with modified tree when user saves changes in concept editor. Keys and values are
     * instances of <code>ConceptInterface</code>.
     */
    BidiMap origModBidiMap = new DualHashBidiMap();

    /**
     * List of concepts marked for deletion since either instantiation of this class or last <code>save</code> call.
     * (Cleared on save.)
     */
    List<ConceptInterface> deletedConcepts = new ArrayList<ConceptInterface>();

    /**
     * List of concepts marked for creation since either instantiation of this class or last <code>save</code> call.
     * (Cleared on save.)
     */
    List<ConceptInterface> newConcepts = new ArrayList<ConceptInterface>();

    /**
     * Stores children of concepts. (Concepts already know about their parents.) Keys are instances of
     * <code>ConceptInterface</code>. Values are Collections of (<code>ConceptInterface</code>s)--the children.
     */
    MultiMap parentToChildrenMap = new MultiValueMap();

    /**
     * Points of entry into the trees.
     */
    List roots = new ArrayList();

    // ~ Constructors ====================================================================================================

    public ConceptTreeModel(final SchemaMeta schemaMeta) {
        super();
        Validate.notNull(schemaMeta);
        this.schemaMeta = schemaMeta;
        buildTree();
    }

    // ~ Methods =========================================================================================================

    private void buildTree() {
        // clone all concepts and remember how to get from modified to original and vice versa
        List<ConceptInterface> clones = new ArrayList<ConceptInterface>();
        Iterator iter1 = schemaMeta.getConcepts().iterator();
        while (iter1.hasNext()) {
            ConceptInterface orig = (ConceptInterface) iter1.next();
            ConceptInterface mod = (ConceptInterface) orig.clone();
            clones.add(mod);
            origModBidiMap.put(mod, orig);
        }

        // if cloned concepts have parent links to original concepts, re-point them to cloned concepts
        Iterator iter2 = clones.iterator();
        while (iter2.hasNext()) {
            ConceptInterface clone = (ConceptInterface) iter2.next();
            if (origModBidiMap.inverseBidiMap().containsKey(clone.getParentInterface())) {
                clone.setParentInterface(
                        (ConceptInterface) origModBidiMap.inverseBidiMap().get(clone.getParentInterface()));
            }
            // remember the children...they are our future
            parentToChildrenMap.put(clone.getParentInterface(), clone);
        }
    }

    public void addConcept(final ConceptInterface parent, final ConceptInterface newChild)
            throws ObjectAlreadyExistsException {
        // parent can be null but concept cannot
        Validate.notNull(newChild);
        // newChild name cannot be null
        Validate.notNull(newChild.getName());

        // do not add newChild if its name already exists; need to check both orig and new concepts
        Iterator iter = origModBidiMap.keySet().iterator();
        while (iter.hasNext()) {
            if (newChild.getName().equals(((ConceptInterface) iter.next()).getName())) {
                throw new ObjectAlreadyExistsException(newChild.getName());
            }
        }
        Iterator iter1 = newConcepts.iterator();
        while (iter1.hasNext()) {
            if (newChild.getName().equals(((ConceptInterface) iter1.next()).getName())) {
                throw new ObjectAlreadyExistsException();
            }
        }

        newChild.setParentInterface(parent);
        parentToChildrenMap.put(parent, newChild);
        newConcepts.add(newChild);
        fireConceptTreeModificationEvent(new ConceptTreeModificationEvent(this));
    }

    public ConceptInterface[] getChildren(final ConceptInterface parent) {
        if (parentToChildrenMap.containsKey(parent)) {
            @SuppressWarnings("unchecked")
            Collection<ConceptInterface> c = (Collection<ConceptInterface>) parentToChildrenMap.get(parent);
            return (ConceptInterface[]) c.toArray(new ConceptInterface[0]);
        } else {
            return new ConceptInterface[0];
        }
    }

    public ConceptInterface getParent(final ConceptInterface concept) {
        Validate.notNull(concept);
        return concept.getParentInterface();
    }

    private void removeDescendants(ConceptInterface parent, List<ConceptInterface> forRemoval) {
        ConceptInterface[] children = getChildren(parent);
        for (int i = 0; i < children.length; i++) {
            ConceptInterface child = children[i];
            forRemoval.add(child);
            // Are you anyone's parent? 
            removeDescendants(child, forRemoval);
        }
    }

    public void removeConcept(final ConceptInterface concept) throws DeleteNotAllowedException {
        Validate.notNull(concept);

        if (concept.getName().equalsIgnoreCase(Settings.getConceptNameBase())) {
            throw new DeleteNotAllowedException();
        }

        List<ConceptInterface> forRemoval = new ArrayList<ConceptInterface>();
        forRemoval.add(concept);

        // trigger removal from tree
        ConceptInterface parent = concept.getParentInterface();
        Collection children = (Collection) parentToChildrenMap.get(parent);
        children.remove(concept);

        // Now collect all descendants and remove from model and mark for removal from CWM repository
        removeDescendants(concept, forRemoval);

        for (Iterator iter = forRemoval.iterator(); iter.hasNext();) {
            ConceptInterface conceptToRemove = (ConceptInterface) iter.next();
            ConceptInterface orig = (ConceptInterface) origModBidiMap.remove(conceptToRemove);
            if (null != orig) {
                // if this concept exists in schema meta, it needs to be marked for removal
                markForRemoval(orig);
            } else {
                // concept has been added since last save; simply remove it from the list of concepts to be added
                newConcepts.remove(concept);
            }
        }
        fireConceptTreeModificationEvent(new ConceptTreeModificationEvent(this));
    }

    private void markForRemoval(final ConceptInterface concept) {
        deletedConcepts.add(concept);
    }

    public void save() throws ObjectAlreadyExistsException {
        // process additions
        Iterator iter1 = newConcepts.iterator();
        while (iter1.hasNext()) {
            ConceptInterface mod = (ConceptInterface) iter1.next();
            ConceptInterface orig = (ConceptInterface) mod.clone();
            schemaMeta.addConcept(orig);
            origModBidiMap.put(mod, orig);
        }
        newConcepts.clear();

        // process deletions
        Iterator iter2 = deletedConcepts.iterator();
        while (iter2.hasNext()) {

            ConceptInterface mod = (ConceptInterface) iter2.next();
            // we removed this object from the list in the delete execution above... 
            //ConceptInterface orig = (ConceptInterface) origModBidiMap.get(mod);
            // origModBidiMap.remove(mod);
            removeConceptFromSchemaMeta(mod);
        }
        deletedConcepts.clear();

        // process mods
        MapIterator iter = origModBidiMap.mapIterator();
        while (iter.hasNext()) {
            ConceptInterface mod = (ConceptInterface) iter.next();
            ConceptInterface orig = (ConceptInterface) iter.getValue();
            orig.clearChildProperties();
            orig.getChildPropertyInterfaces().putAll(mod.getChildPropertyInterfaces());
            orig.setName(mod.getName());
            orig.setParentInterface((ConceptInterface) origModBidiMap.get(mod.getParentInterface()));
        }
    }

    /**
     * The only way to remove the concept is via index. Find the index by comparing object identity.
     */
    private void removeConceptFromSchemaMeta(final ConceptInterface concept) {
        String[] names = schemaMeta.getConceptNames();
        for (int i = 0; i < schemaMeta.nrConcepts(); i++) {
            if (schemaMeta.getConcept(i).getName().equalsIgnoreCase(concept.getName())) {
                schemaMeta.removeConcept(i);
                return;
            }
        }
    }

    public static void main(String[] args) throws ObjectAlreadyExistsException, DeleteNotAllowedException {
        ConceptInterface c = new Concept();
        IConceptModel conceptModel = new ConceptModel(c);
        LocalizedStringSettings s1 = new LocalizedStringSettings();
        s1.setLocaleString("en_US", "chicken");
        s1.setLocaleString("es_ES", "pollo");
        conceptModel.setProperty(new ConceptPropertyLocalizedString(DefaultPropertyID.NAME.getId(), s1));

        LocalizedStringSettings s2 = new LocalizedStringSettings();
        s2.setLocaleString("en_US", "Where is the library?");
        s2.setLocaleString("es_ES", "Dnde est la biblioteca?");
        conceptModel.setProperty(new ConceptPropertyLocalizedString(DefaultPropertyID.DESCRIPTION.getId(), s2));
        conceptModel.setProperty(
                new ConceptPropertyColumnWidth(DefaultPropertyID.COLUMN_WIDTH.getId(), ColumnWidth.INCHES));
        conceptModel.setProperty(
                new ConceptPropertyString(DefaultPropertyID.TARGET_SCHEMA.getId(), "overridden_table"));
        Concept parentConcept = new Concept();
        ConceptPropertyInterface prop1 = new ConceptPropertyString(DefaultPropertyID.FORMULA.getId(), "e=mc2");
        parentConcept.addProperty(prop1);
        ConceptPropertyInterface prop2 = new ConceptPropertyString(DefaultPropertyID.TARGET_SCHEMA.getId(),
                "test_schema");
        parentConcept.addProperty(prop2);
        conceptModel.setRelatedConcept(parentConcept, IConceptModel.REL_PARENT);

        Concept secConcept = new Concept();
        ConceptPropertyInterface sec2 = new ConceptPropertyString(DefaultPropertyID.TARGET_TABLE.getId(),
                "test_table");
        secConcept.addProperty(sec2);
        conceptModel.setRelatedConcept(secConcept, IConceptModel.REL_INHERITED);

        Concept inConcept = new Concept();
        ConceptPropertyInterface in1 = new ConceptPropertyBoolean(DefaultPropertyID.EXACT.getId(), true);
        inConcept.addProperty(in1);
        ConceptPropertyInterface in2 = new ConceptPropertyString(DefaultPropertyID.TARGET_TABLE.getId(),
                "test_table");
        inConcept.addProperty(in2);
        new ConceptModel(parentConcept).setRelatedConcept(inConcept, IConceptModel.REL_PARENT);

        SchemaMeta schemaMeta = new SchemaMeta();
        schemaMeta.addConcept(parentConcept);
        schemaMeta.addConcept(c);
        schemaMeta.addConcept(inConcept);
        schemaMeta.addConcept(secConcept);

        IConceptTreeModel model = new ConceptTreeModel(schemaMeta);

        ConceptInterface[] childrenOfRoots = model.getChildren(null);
        for (int i = 0; i < childrenOfRoots.length; i++) {
            ConceptInterface[] childrenOfRoot = model.getChildren(childrenOfRoots[i]);
        }

        model.addConcept(model.getChildren(null)[0], secConcept);

        childrenOfRoots = model.getChildren(null);
        for (int i = 0; i < childrenOfRoots.length; i++) {
            ConceptInterface[] childrenOfRoot = model.getChildren(childrenOfRoots[i]);
        }

        model.removeConcept(secConcept);

        childrenOfRoots = model.getChildren(null);
        for (int i = 0; i < childrenOfRoots.length; i++) {
            ConceptInterface[] childrenOfRoot = model.getChildren(childrenOfRoots[i]);
        }

    }

    private void fireConceptTreeModificationEvent(final ConceptTreeModificationEvent e) {
        Set listeners = eventSupport.getListeners();
        for (Iterator iter = listeners.iterator(); iter.hasNext();) {
            ((IConceptTreeModificationListener) iter.next()).conceptTreeModified(e);
        }
    }

    public void addConceptTreeModificationListener(final IConceptTreeModificationListener listener) {
        eventSupport.addListener(listener);
    }

    public void removeConceptTreeModificationListener(final IConceptTreeModificationListener listener) {
        eventSupport.removeListener(listener);
    }

    public SchemaMeta getSchemaMeta() {
        return schemaMeta;
    }

}