Package org.thechiselgroup.choosel.core.client.visualization.model.implementation

Source Code of org.thechiselgroup.choosel.core.client.visualization.model.implementation.DefaultVisualizationModel

/*******************************************************************************
* Copyright (C) 2011 Lars Grammel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*    
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. 
*******************************************************************************/
package org.thechiselgroup.choosel.core.client.visualization.model.implementation;

import static org.thechiselgroup.choosel.core.client.util.DisposeUtil.safelyDispose;
import static org.thechiselgroup.choosel.core.client.util.collections.CollectionFactory.createLightweightList;
import static org.thechiselgroup.choosel.core.client.util.collections.Delta.createAddedRemovedDelta;
import static org.thechiselgroup.choosel.core.client.util.collections.Delta.createDelta;
import static org.thechiselgroup.choosel.core.client.util.collections.Delta.createUpdatedDelta;
import static org.thechiselgroup.choosel.core.client.util.collections.LightweightCollections.copy;
import static org.thechiselgroup.choosel.core.client.util.collections.LightweightCollections.emptyCollection;
import static org.thechiselgroup.choosel.core.client.util.collections.LightweightCollections.getRelativeComplement;
import static org.thechiselgroup.choosel.core.client.util.collections.LightweightCollections.toCollection;

import java.util.Collection;
import java.util.EnumMap;
import java.util.Map;
import java.util.Set;

import org.thechiselgroup.choosel.core.client.error_handling.ErrorHandler;
import org.thechiselgroup.choosel.core.client.resources.CategorizableResourceGroupingChange;
import org.thechiselgroup.choosel.core.client.resources.CategorizableResourceGroupingChange.ChangeType;
import org.thechiselgroup.choosel.core.client.resources.DefaultResourceSet;
import org.thechiselgroup.choosel.core.client.resources.Resource;
import org.thechiselgroup.choosel.core.client.resources.ResourceGrouping;
import org.thechiselgroup.choosel.core.client.resources.ResourceGroupingChangedEvent;
import org.thechiselgroup.choosel.core.client.resources.ResourceGroupingChangedHandler;
import org.thechiselgroup.choosel.core.client.resources.ResourceMultiCategorizer;
import org.thechiselgroup.choosel.core.client.resources.ResourceSet;
import org.thechiselgroup.choosel.core.client.resources.ResourceSetChangedEvent;
import org.thechiselgroup.choosel.core.client.resources.ResourceSetChangedEventHandler;
import org.thechiselgroup.choosel.core.client.resources.ResourceSetFactory;
import org.thechiselgroup.choosel.core.client.util.Disposable;
import org.thechiselgroup.choosel.core.client.util.HandlerRegistrationSet;
import org.thechiselgroup.choosel.core.client.util.Initializable;
import org.thechiselgroup.choosel.core.client.util.collections.CollectionFactory;
import org.thechiselgroup.choosel.core.client.util.collections.CombinedIterable;
import org.thechiselgroup.choosel.core.client.util.collections.Delta;
import org.thechiselgroup.choosel.core.client.util.collections.LightweightCollection;
import org.thechiselgroup.choosel.core.client.util.collections.LightweightCollections;
import org.thechiselgroup.choosel.core.client.util.collections.LightweightList;
import org.thechiselgroup.choosel.core.client.visualization.model.Slot;
import org.thechiselgroup.choosel.core.client.visualization.model.SlotMappingChangedHandler;
import org.thechiselgroup.choosel.core.client.visualization.model.ViewContentDisplay;
import org.thechiselgroup.choosel.core.client.visualization.model.ViewContentDisplayCallback;
import org.thechiselgroup.choosel.core.client.visualization.model.VisualItem;
import org.thechiselgroup.choosel.core.client.visualization.model.VisualItem.Subset;
import org.thechiselgroup.choosel.core.client.visualization.model.VisualItemBehavior;
import org.thechiselgroup.choosel.core.client.visualization.model.VisualItemContainer;
import org.thechiselgroup.choosel.core.client.visualization.model.VisualItemContainerChangeEventHandler;
import org.thechiselgroup.choosel.core.client.visualization.model.VisualItemValueResolver;
import org.thechiselgroup.choosel.core.client.visualization.model.VisualizationModel;

import com.google.gwt.event.shared.HandlerRegistration;

/**
* <p>
* <b>Robustness</b>: If {@link Exception}s are thrown by the
* {@link ViewContentDisplay} during <code>update</code> operations, these
* Exceptions are logged and not forwarded. This is to ensure robustness of the
* framework against failures in 3rd party components.
* </p>
*
* @author Lars Grammel
* @author Patrick Gorman
*/
/*
* TODO introduce VisualItemContainer decorator that is filtered to the valid
* view items from the error model
*
* TODO remove highlighted / selected code duplication (similar to
* DefaultVisualItem)
*/
public class DefaultVisualizationModel implements VisualizationModel,
        Disposable {

    final class DefaultVisualItemContainer extends AbstractVisualItemContainer
            implements Disposable {

        /**
         * Maps ids (representing the resource sets that are calculated by the
         * resource grouping, also used as view item ids) to the
         * {@link DefaultVisualItem}s that display the resource sets in the
         * view.
         */
        private Map<String, DefaultVisualItem> visualItemsById = CollectionFactory
                .createStringMap();

        public DefaultVisualItemContainer(ErrorHandler errorHandler) {
            super(errorHandler);
        }

        public void addDefaultVisualItem(DefaultVisualItem visualItem) {
            assertDoesNotContainVisualItem(visualItem.getId());

            visualItemsById.put(visualItem.getId(), visualItem);
        }

        private void assertContainsVisualItem(String id) {
            assert id != null : "id must not be null";
            assert containsVisualItem(id) : "no visual item with id " + id;
        }

        private void assertDoesNotContainVisualItem(String id) {
            assert !containsVisualItem(id) : "visual item with id " + id
                    + " is contained in " + visualItemsById;
        }

        @Override
        public boolean containsVisualItem(String visualItemId) {
            return visualItemsById.containsKey(visualItemId);
        }

        @Override
        public void dispose() {
            // fire event that all view items were removed
            fireVisualItemContainerChangeEvent(Delta
                    .createRemovedDelta(getVisualItems()));
            for (DefaultVisualItem visualItem : getDefaultVisualItems()) {
                safelyDispose(visualItem, errorHandler);
            }
        }

        public DefaultVisualItem getDefaultVisualItem(String id) {
            return visualItemsById.get(id);
        }

        public Collection<DefaultVisualItem> getDefaultVisualItems() {
            return visualItemsById.values();
        }

        @Override
        public VisualItem getVisualItem(String visualItemId) {
            assertContainsVisualItem(visualItemId);
            return getDefaultVisualItem(visualItemId);
        }

        // TODO these view items should be cached & the cache should be updated
        @Override
        public LightweightCollection<VisualItem> getVisualItems() {
            return LightweightCollections
                    .<VisualItem> toCollection(visualItemsById.values());
        }

        /**
         * @return List of {@link VisualItem}s that contain at least one of the
         *         {@link Resource}s.
         */
        @Override
        public LightweightList<VisualItem> getVisualItems(
                Iterable<Resource> resources) {

            assert resources != null;

            LightweightList<VisualItem> result = CollectionFactory
                    .createLightweightList();
            Set<String> groupIds = resourceGrouping.getGroupIds(resources);
            for (String groupId : groupIds) {
                assertContainsVisualItem(groupId);
                result.add(visualItemsById.get(groupId));
            }
            return result;
        }

        public DefaultVisualItem removeDefaultVisualItem(String id) {
            assertContainsVisualItem(id);
            DefaultVisualItem visualItem = visualItemsById.remove(id);
            assertDoesNotContainVisualItem(id);
            return visualItem;
        }
    }

    private final class ErrorFreeVisualItemContainerDecorator extends
            AbstractVisualItemContainer {

        public ErrorFreeVisualItemContainerDecorator(ErrorHandler errorHandler) {
            super(errorHandler);
        }

        @Override
        public boolean containsVisualItem(String visualItemId) {
            assert visualItemId != null;
            return fullVisualItemContainer.containsVisualItem(visualItemId)
                    && !hasErrors(fullVisualItemContainer
                            .getVisualItem(visualItemId));
        }

        @Override
        public VisualItem getVisualItem(String visualItemId) {
            assert visualItemId != null;

            VisualItem visualItem = fullVisualItemContainer
                    .getVisualItem(visualItemId);

            assert !hasErrors(visualItem) : "VisualItem with id "
                    + visualItemId
                    + " contains errors and cannot be retrieved.";

            return visualItem;
        }

        @Override
        public LightweightCollection<VisualItem> getVisualItems() {
            LightweightList<VisualItem> validVisualItems = createLightweightList();
            for (VisualItem visualItem : fullVisualItemContainer
                    .getVisualItems()) {
                if (!hasErrors(visualItem)) {
                    validVisualItems.add(visualItem);
                }
            }
            return validVisualItems;
        }

        @Override
        public LightweightCollection<VisualItem> getVisualItems(
                Iterable<Resource> resources) {

            return getRelativeComplement(
                    fullVisualItemContainer.getVisualItems(resources),
                    getVisualItemsWithErrors());
        }
    }

    /*
     * TODO cache intersection with content, minimize intersection updates.
     * Using an IntersectionResourceSet does not work out of the box because of
     * the event notification order. The intersection updates need to be
     * triggered manually in the correct order (e.g. before creating visual
     * items).
     */
    private class SubsetContainer implements Disposable {

        private Subset subset;

        private ResourceSet resources;

        private HandlerRegistration handlerRegistration;

        public SubsetContainer(final Subset subset, ResourceSet resources) {
            assert subset != null;
            assert resources != null;

            this.subset = subset;
            this.resources = resources;

            handlerRegistration = resources
                    .addEventHandler(new ResourceSetChangedEventHandler() {
                        @Override
                        public void onResourceSetChanged(
                                ResourceSetChangedEvent event) {

                            updateSubset(subset, event.getAddedResources(),
                                    event.getRemovedResources());
                        }
                    });
        }

        @Override
        public void dispose() {
            handlerRegistration.removeHandler();
            handlerRegistration = null;

            resources = null;
        }

        private void updateVisualItemOnResourcesChange(
                DefaultVisualItem visualItem,
                CategorizableResourceGroupingChange change) {

            LightweightList<Resource> addedResourcesInSubset = resources
                    .getIntersection(change.getAddedResources());
            LightweightList<Resource> removedResourcesInSubset = resources
                    .getIntersection(change.getRemovedResources());

            if (!addedResourcesInSubset.isEmpty()
                    || !removedResourcesInSubset.isEmpty()) {
                visualItem.updateSubset(subset, addedResourcesInSubset,
                        removedResourcesInSubset);
            }
        }

    }

    private DefaultSlotMappingConfiguration slotMappingConfiguration;

    private ViewContentDisplay contentDisplay;

    private ResourceGrouping resourceGrouping;

    private final VisualItemBehavior visualItemBehavior;

    private HandlerRegistrationSet handlerRegistrations = new HandlerRegistrationSet();

    private final ErrorHandler errorHandler;

    private DefaultVisualItemResolutionErrorModel errorModel = new DefaultVisualItemResolutionErrorModel();

    private Map<Subset, SubsetContainer> subsets = new EnumMap<Subset, SubsetContainer>(
            Subset.class);

    private DefaultVisualItemContainer fullVisualItemContainer;

    private ErrorFreeVisualItemContainerDecorator errorFreeVisualItemContainer;

    /**
     * @param errorHandler
     *            Exceptions that occur in other modules, e.g.
     *            {@link ViewContentDisplay}s or
     *            {@link VisualItemContainerChangeEventHandler}s are caught and
     *            reported to this {@link ErrorHandler}.
     */
    public DefaultVisualizationModel(ViewContentDisplay contentDisplay,
            ResourceSet selectedResources, ResourceSet highlightedResources,
            VisualItemBehavior visualItemBehavior, ErrorHandler errorHandler,
            ResourceSetFactory resourceSetFactory,
            ResourceMultiCategorizer multiCategorizer) {

        assert contentDisplay != null;
        assert selectedResources != null;
        assert highlightedResources != null;
        assert visualItemBehavior != null;
        assert errorHandler != null;
        assert resourceSetFactory != null;
        assert multiCategorizer != null;

        this.contentDisplay = contentDisplay;
        this.visualItemBehavior = visualItemBehavior;
        this.errorHandler = errorHandler;

        this.fullVisualItemContainer = new DefaultVisualItemContainer(
                errorHandler);
        this.errorFreeVisualItemContainer = new ErrorFreeVisualItemContainerDecorator(
                errorHandler);
        this.resourceGrouping = new ResourceGrouping(multiCategorizer,
                resourceSetFactory);
        this.slotMappingConfiguration = new DefaultSlotMappingConfiguration(
                contentDisplay.getSlots());

        addSubset(Subset.SELECTED, selectedResources);
        addSubset(Subset.HIGHLIGHTED, highlightedResources);

        // TODO should be external...
        errorFreeVisualItemContainer.addHandler(visualItemBehavior);

        init(selectedResources);

        initResourceGrouping();
        initContentDisplay();

        assert getContentResourceSet().isEmpty();
    }

    @Override
    public HandlerRegistration addHandler(SlotMappingChangedHandler handler) {
        return slotMappingConfiguration.addHandler(handler);
    }

    private void addSubset(Subset subset, ResourceSet resources) {
        this.subsets.put(subset, new SubsetContainer(subset, resources));
    }

    private void assertWithoutErrors(
            LightweightCollection<VisualItem> visualItems) {

        for (VisualItem visualItem : visualItems) {
            assert !hasErrors(visualItem) : visualItem + " has errors";
        }
    }

    private Delta<VisualItem> calculateErrorFreeDelta(Delta<VisualItem> delta,
            LightweightCollection<VisualItem> visualItemsThatHadErrors) {

        LightweightList<VisualItem> addedVisualItems = CollectionFactory
                .createLightweightList();
        LightweightList<VisualItem> removedVisualItems = CollectionFactory
                .createLightweightList();
        LightweightList<VisualItem> updatedVisualItems = CollectionFactory
                .createLightweightList();

        // check if added view items should be added or ignored
        for (VisualItem added : delta.getAddedElements()) {
            boolean hasErrorsNow = errorModel.hasErrors(added);

            if (!hasErrorsNow) {
                addedVisualItems.add(added);
            }
        }

        // check if removed resources should to be removed or ignored
        for (VisualItem removed : delta.getRemovedElements()) {
            boolean hadErrorsBefore = visualItemsThatHadErrors
                    .contains(removed);

            if (!hadErrorsBefore) {
                removedVisualItems.add(removed);
            }
        }

        // check if updated resources should to be added, removed, updated, or
        // ignored
        for (VisualItem updated : delta.getUpdatedElements()) {
            boolean hasErrorsNow = errorModel.hasErrors(updated);
            boolean hadErrorsBefore = visualItemsThatHadErrors
                    .contains(updated);

            if (!hasErrorsNow && !hadErrorsBefore) {
                updatedVisualItems.add(updated);
            } else if (!hasErrorsNow && hadErrorsBefore) {
                addedVisualItems.add(updated);
            } else if (!hadErrorsBefore && hasErrorsNow) {
                removedVisualItems.add(updated);
            }
        }

        return createDelta(addedVisualItems, updatedVisualItems,
                removedVisualItems);
    }

    /**
     * Clears the cached {@link VisualItem} values for {@code slot} and all
     * dependent slots.
     */
    private void clearVisualItemValueCache(Slot slot) {
        assert slot != null;

        LightweightList<Slot> dependentSlots = slotMappingConfiguration
                .getDependentSlots(slot);

        for (DefaultVisualItem visualItem : fullVisualItemContainer
                .getDefaultVisualItems()) {
            visualItem.clearValueCache(slot);
            for (Slot dependentSlot : dependentSlots) {
                visualItem.clearValueCache(dependentSlot);
            }
        }
    }

    @Override
    public boolean containsSlot(Slot slot) {
        return slotMappingConfiguration.containsSlot(slot);
    }

    private DefaultVisualItem createVisualItem(String groupID,
            ResourceSet resources,
            LightweightCollection<Resource> highlightedResources,
            LightweightCollection<Resource> selectedResources) {

        DefaultVisualItem visualItem = new DefaultVisualItem(groupID,
                resources, slotMappingConfiguration, visualItemBehavior);

        visualItem.updateSubset(Subset.HIGHLIGHTED, highlightedResources,
                LightweightCollections.<Resource> emptyCollection());

        visualItem.updateSubset(Subset.SELECTED, selectedResources,
                LightweightCollections.<Resource> emptyCollection());

        fullVisualItemContainer.addDefaultVisualItem(visualItem);

        return visualItem;
    }

    @Override
    public void dispose() {
        /*
         * NOTE Shared objects should not be disposed. Instead, our event
         * handlers should get removed and references should be set to null.
         */

        fullVisualItemContainer = safelyDispose(fullVisualItemContainer,
                errorHandler);

        resourceGrouping = safelyDispose(resourceGrouping, errorHandler);
        contentDisplay = safelyDispose(contentDisplay, errorHandler);

        handlerRegistrations = safelyDispose(handlerRegistrations, errorHandler);

        for (SubsetContainer subsetContainer : subsets.values()) {
            subsetContainer.dispose();
        }
        subsets = null;
    }

    @Override
    public ResourceMultiCategorizer getCategorizer() {
        return resourceGrouping.getCategorizer();
    }

    @Override
    public ResourceSet getContentResourceSet() {
        return resourceGrouping.getResourceSet();
    }

    @Override
    public VisualItemContainer getErrorFreeVisualItemContainer() {
        return errorFreeVisualItemContainer;
    }

    @Override
    public VisualItemContainer getFullVisualItemContainer() {
        return fullVisualItemContainer;
    }

    @Override
    public ResourceSet getHighlightedResources() {
        return getSubset(Subset.HIGHLIGHTED).resources;
    }

    /**
     * Calculates the intersection with the resources that are displayed in this
     * view.
     * <p>
     * <b>PERFORMANCE</b>: returns ResourceSet to enable fast containment checks
     * in DefaultResourceItem.
     * </p>
     */
    private ResourceSet getIntersectionWithVisualizationResources(
            LightweightCollection<Resource> resources) {

        ResourceSet resourcesInThisView = new DefaultResourceSet();
        resourcesInThisView.addAll(getContentResourceSet().getIntersection(
                resources));
        return resourcesInThisView;
    }

    @Override
    public VisualItemValueResolver getResolver(Slot slot) {
        return slotMappingConfiguration.getResolver(slot);
    }

    @Override
    public ResourceSet getSelectedResources() {
        return getSubset(Subset.SELECTED).resources;
    }

    @Override
    public Slot getSlotById(String slotId) {
        return slotMappingConfiguration.getSlotById(slotId);
    }

    @Override
    public Slot[] getSlots() {
        return slotMappingConfiguration.getSlots();
    }

    @Override
    public LightweightCollection<Slot> getSlotsWithErrors() {
        return errorModel.getSlotsWithErrors();
    }

    @Override
    public LightweightCollection<Slot> getSlotsWithErrors(VisualItem visualItem) {
        return errorModel.getSlotsWithErrors(visualItem);
    }

    private SubsetContainer getSubset(Subset subset) {
        return subsets.get(subset);
    }

    @Override
    public LightweightCollection<Slot> getUnconfiguredSlots() {
        return slotMappingConfiguration.getUnconfiguredSlots();
    }

    @Override
    public ViewContentDisplay getViewContentDisplay() {
        return contentDisplay;
    }

    @Override
    public LightweightCollection<VisualItem> getVisualItemsWithErrors() {
        return errorModel.getVisualItemsWithErrors();
    }

    @Override
    public LightweightCollection<VisualItem> getVisualItemsWithErrors(Slot slot) {
        return errorModel.getVisualItemsWithErrors(slot);
    }

    @Override
    public boolean hasErrors() {
        return errorModel.hasErrors();
    }

    @Override
    public boolean hasErrors(Slot slot) {
        return errorModel.hasErrors(slot);
    }

    @Override
    public boolean hasErrors(VisualItem visualItem) {
        return errorModel.hasErrors(visualItem);
    }

    private void init(Object target) {
        if (target instanceof Initializable) {
            ((Initializable) target).init();
        }
    }

    private void initContentDisplay() {
        contentDisplay.init(errorFreeVisualItemContainer,
                new ViewContentDisplayCallback() {
                    @Override
                    public VisualItemValueResolver getResolver(Slot slot) {
                        return DefaultVisualizationModel.this.getResolver(slot);
                    }

                    @Override
                    public String getSlotResolverDescription(Slot slot) {
                        if (!slotMappingConfiguration.isConfigured(slot)) {
                            return "N/A";
                        }

                        return slotMappingConfiguration.getResolver(slot)
                                .toString();
                    }
                });
    }

    private void initResourceGrouping() {
        resourceGrouping.addHandler(new ResourceGroupingChangedHandler() {
            @Override
            public void onResourceCategoriesChanged(
                    ResourceGroupingChangedEvent e) {
                processResourceGroupingChange(e);
            }
        });
    }

    @Override
    public boolean isConfigured(Slot slot) {
        return slotMappingConfiguration.isConfigured(slot);
    }

    /**
     * Processes the added resource groups when the grouping changes.
     */
    private LightweightCollection<VisualItem> processAddChanges(
            LightweightCollection<CategorizableResourceGroupingChange> changes) {

        if (changes.isEmpty()) {
            return emptyCollection();
        }

        LightweightList<VisualItem> addedVisualItems = createLightweightList();

        /*
         * PERFORMANCE: cache highlighted resources and use resource set to
         * enable fast containment checks in DefaultResourceItem
         *
         * TODO refactor: use intersection resource sets instead.
         *
         * TODO extract
         */
        LightweightList<Resource> highlightedResources = getContentResourceSet()
                .getIntersection(getSubset(Subset.HIGHLIGHTED).resources);
        LightweightList<Resource> selectedResources = getContentResourceSet()
                .getIntersection(getSubset(Subset.SELECTED).resources);

        for (CategorizableResourceGroupingChange change : changes) {
            assert change.getDelta() == ChangeType.GROUP_CREATED;

            addedVisualItems.add(createVisualItem(change.getGroupID(),
                    change.getResourceSet(), highlightedResources,
                    selectedResources));
        }

        return addedVisualItems;
    }

    private LightweightCollection<VisualItem> processRemoveChanges(
            LightweightCollection<CategorizableResourceGroupingChange> changes) {

        if (changes.isEmpty()) {
            return emptyCollection();
        }

        LightweightList<VisualItem> removedVisualItems = createLightweightList();
        for (CategorizableResourceGroupingChange change : changes) {
            assert change.getDelta() == ChangeType.GROUP_REMOVED;

            DefaultVisualItem visualItem = fullVisualItemContainer
                    .removeDefaultVisualItem(change.getGroupID());

            // XXX dispose should be done after method call / event firing etc.
            visualItem.dispose();

            removedVisualItems.add(visualItem);
        }
        return removedVisualItems;
    }

    private void processResourceGroupingChange(
            ResourceGroupingChangedEvent event) {

        assert event != null;

        LightweightCollection<VisualItem> visualItemsThatHadErrors = copy(getVisualItemsWithErrors());

        Delta<VisualItem> delta = updateVisualItemsOnGroupingChange(event);

        updateErrorModel(delta);

        Delta<VisualItem> errorFreeDelta = calculateErrorFreeDelta(delta,
                visualItemsThatHadErrors);

        updateViewContentDisplay(errorFreeDelta,
                LightweightCollections.<Slot> emptyCollection());

        fullVisualItemContainer.fireVisualItemContainerChangeEvent(delta);
        errorFreeVisualItemContainer
                .fireVisualItemContainerChangeEvent(errorFreeDelta);
    }

    private LightweightCollection<VisualItem> processUpdates(
            LightweightCollection<CategorizableResourceGroupingChange> changes) {

        if (changes.isEmpty()) {
            return emptyCollection();
        }

        LightweightList<VisualItem> updatedVisualItems = createLightweightList();
        for (CategorizableResourceGroupingChange change : changes) {
            assert change.getDelta() == ChangeType.GROUP_CHANGED;
            DefaultVisualItem visualItem = fullVisualItemContainer
                    .getDefaultVisualItem(change.getGroupID());
            for (SubsetContainer subsetContainer : subsets.values()) {
                subsetContainer.updateVisualItemOnResourcesChange(visualItem,
                        change);
            }

            updatedVisualItems.add(visualItem);
        }

        return updatedVisualItems;
    }

    @Override
    public void setCategorizer(ResourceMultiCategorizer newCategorizer) {
        resourceGrouping.setCategorizer(newCategorizer);
    }

    @Override
    public void setContentResourceSet(ResourceSet resources) {
        resourceGrouping.setResourceSet(resources);
    }

    /*
     * IMPLEMENTATION NOTE: We updated the error model and view content display
     * directly after the resolver has been set, because this keeps the
     * algorithm simpler (compared to an event based approach, which would
     * require prioritizing event handlers etc.)
     *
     * TODO this can cause problems, if you actively try to set a fixed resolver
     */
    @Override
    public void setResolver(Slot slot, VisualItemValueResolver resolver) {
        assert slot != null;
        assert resolver != null;

        // keep information (currently valid / invalid stuff)
        LightweightCollection<VisualItem> visualItemsThatHadErrors = copy(getVisualItemsWithErrors());
        LightweightCollection<VisualItem> visualItemsThatWereValid = copy(errorFreeVisualItemContainer
                .getVisualItems());

        clearVisualItemValueCache(slot);

        // actually change the slot mapping
        slotMappingConfiguration.setResolver(slot, resolver);

        updateErrorModel(slot);

        // CASE 1: VisualItems that went from invalid to valid get added
        LightweightCollection<VisualItem> visualItemsToAdd = getRelativeComplement(
                visualItemsThatHadErrors, getVisualItemsWithErrors());

        // CASE 2: VisualItems that went from valid to invalid get removed
        LightweightCollection<VisualItem> visualItemsToRemove = getRelativeComplement(
                visualItemsThatWereValid,
                errorFreeVisualItemContainer.getVisualItems());

        Delta<VisualItem> errorFreeDelta = createAddedRemovedDelta(
                visualItemsToAdd, visualItemsToRemove);

        updateViewContentDisplay(errorFreeDelta, toCollection(slot));

        errorFreeVisualItemContainer
                .fireVisualItemContainerChangeEvent(errorFreeDelta);
    }

    protected void updateErrorModel(Delta<VisualItem> delta) {
        assert delta != null;

        updateErrorModel(delta.getAddedElements());
        updateErrorModel(delta.getUpdatedElements());
        errorModel.clearErrors(delta.getRemovedElements());
    }

    private void updateErrorModel(LightweightCollection<VisualItem> visualItems) {
        assert visualItems != null;

        errorModel.clearErrors(visualItems);
        for (Slot slot : getSlots()) {
            updateErrorModel(slot, visualItems);
        }
    }

    private void updateErrorModel(Slot changedSlot) {
        assert changedSlot != null;

        errorModel.clearErrors(changedSlot);
        updateErrorModel(changedSlot, fullVisualItemContainer.getVisualItems());
    }

    private void updateErrorModel(Slot slot,
            LightweightCollection<VisualItem> visualItems) {

        if (!slotMappingConfiguration.isConfigured(slot)) {
            /*
             * TODO potential optimization: have all invalid state for slot in
             * error model (would return errors for all view items)
             */
            errorModel.reportErrors(slot, visualItems);
            // XXX update calling delegates
            return;
        }

        VisualItemValueResolver resolver = slotMappingConfiguration
                .getResolver(slot);

        // check if target slots are configured & can resolve
        for (Slot targetSlot : resolver.getTargetSlots()) {
            if (!slotMappingConfiguration.isConfigured(targetSlot)) {
                errorModel.reportErrors(slot, visualItems);
                // XXX update calling delegates
                return;
            }

            // XXX also need to check if delegate can resolve...
        }

        for (VisualItem visualItem : visualItems) {
            if (!resolver.canResolve(visualItem, this)) {
                /*
                 * TODO potential optimization: only change error model if state
                 * for view item has changed (delta update).
                 */
                errorModel.reportError(slot, visualItem);
            }
        }

        // update error model for delegating slots
        for (Slot delegatingSlot : slotMappingConfiguration
                .getDependentSlots(slot)) {

            updateErrorModel(delegatingSlot);
        }
    }

    /*
     * TODO introduce Delta/Change object with added/removed --> just use full
     * delta?
     */
    private void updateSubset(Subset subset,
            LightweightCollection<Resource> addedResources,
            LightweightCollection<Resource> removedResources) {

        // TODO remove once subset intersection sets are available
        ResourceSet addedResourcesInThisVisualization = getIntersectionWithVisualizationResources(addedResources);
        ResourceSet removedResourcesInThisVisualization = getIntersectionWithVisualizationResources(removedResources);

        if (addedResourcesInThisVisualization.isEmpty()
                && removedResourcesInThisVisualization.isEmpty()) {
            return;
        }

        /*
         * XXX What if VisualItem subsets are not updated and then it becomes
         * valid again?
         */

        LightweightCollection<VisualItem> affectedVisualItems = errorFreeVisualItemContainer
                .getVisualItems(new CombinedIterable<Resource>(addedResources,
                        removedResources));

        for (VisualItem visualItem : affectedVisualItems) {
            ((DefaultVisualItem) visualItem).updateSubset(subset,
                    addedResourcesInThisVisualization,
                    removedResourcesInThisVisualization);
        }

        updateViewContentDisplay(createUpdatedDelta(affectedVisualItems),
                LightweightCollections.<Slot> emptyCollection());
    }

    /**
     * NOTE: Exceptions are reported and not thrown to ensure robustness.
     */
    private void updateViewContentDisplay(Delta<VisualItem> delta,
            LightweightCollection<Slot> changedSlots) {

        if (delta.isEmpty() && changedSlots.isEmpty()) {
            return;
        }

        assertWithoutErrors(delta.getAddedElements());
        assertWithoutErrors(delta.getUpdatedElements());

        try {
            contentDisplay.update(delta, changedSlots);
        } catch (Throwable ex) {
            errorHandler.handleError(ex);
        }
    }

    private Delta<VisualItem> updateVisualItemsOnGroupingChange(
            ResourceGroupingChangedEvent event) {

        assert event != null;

        /*
         * IMPORTANT: remove old items before adding new ones (there might be
         * conflicts, i.e. groups with the same id)
         */
        LightweightCollection<VisualItem> removedVisualItems = processRemoveChanges(event
                .getChanges(ChangeType.GROUP_REMOVED));
        LightweightCollection<VisualItem> addedVisualItems = processAddChanges(event
                .getChanges(ChangeType.GROUP_CREATED));
        LightweightCollection<VisualItem> updatedVisualItems = processUpdates(event
                .getChanges(ChangeType.GROUP_CHANGED));

        return createDelta(addedVisualItems, updatedVisualItems,
                removedVisualItems);
    }

}
TOP

Related Classes of org.thechiselgroup.choosel.core.client.visualization.model.implementation.DefaultVisualizationModel

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.