com.rcpcompany.uibindings.internal.validators.ValidatorAdapterManager.java Source code

Java tutorial

Introduction

Here is the source code for com.rcpcompany.uibindings.internal.validators.ValidatorAdapterManager.java

Source

/*******************************************************************************
 * Copyright (c) 2006-2013 The RCP Company and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     The RCP Company - initial API and implementation
 *******************************************************************************/
package com.rcpcompany.uibindings.internal.validators;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.commands.common.EventManager;
import org.eclipse.core.databinding.observable.DisposeEvent;
import org.eclipse.core.databinding.observable.IDisposeListener;
import org.eclipse.core.databinding.observable.Observables;
import org.eclipse.core.databinding.observable.list.IListChangeListener;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.ListChangeEvent;
import org.eclipse.core.databinding.observable.list.ListDiffVisitor;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.core.runtime.Assert;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.swt.widgets.Display;

import com.rcpcompany.uibindings.BindingMessageSeverity;
import com.rcpcompany.uibindings.IBindingMessage;
import com.rcpcompany.uibindings.IBindingMessage.FeatureMatchingAlgorithm;
import com.rcpcompany.uibindings.IBindingMessageTarget;
import com.rcpcompany.uibindings.IDisposable;
import com.rcpcompany.uibindings.IManager;
import com.rcpcompany.uibindings.IUIBindingsPackage;
import com.rcpcompany.uibindings.IValueBinding;
import com.rcpcompany.uibindings.bindingMessages.AbstractBindingMessage;
import com.rcpcompany.uibindings.internal.Activator;
import com.rcpcompany.uibindings.validators.IValidationAdapterManagerChangeEvent;
import com.rcpcompany.uibindings.validators.IValidationAdapterManagerChangeListener;
import com.rcpcompany.uibindings.validators.IValidatorAdapter;
import com.rcpcompany.uibindings.validators.IValidatorAdapterManager;
import com.rcpcompany.uibindings.validators.IValidatorAdapterMessageDecorator;
import com.rcpcompany.utils.logging.LogUtils;

/**
 * Implementation of {@link IValidatorAdapterManager}.
 * 
 * @author Tonny Madsen, The RCP Company
 */
public class ValidatorAdapterManager extends EventManager implements IValidatorAdapterManager, IDisposable {

    /**
     * The base manager...
     */
    private static final IManager THE_MANAGER = IManager.Factory.getManager();

    /**
     * Returns the validation adapter manager.
     * 
     * @return the manager
     */
    public static IValidatorAdapterManager getManager() {
        IValidatorAdapterManager mng = THE_MANAGER.getService(IValidatorAdapterManager.class);
        if (mng == null) {
            mng = new ValidatorAdapterManager();
        }
        return mng;
    }

    private final IDisposeListener myDisposeListener = new IDisposeListener() {
        @Override
        public void handleDispose(DisposeEvent event) {
            LogUtils.DEBUG_STRACK_LEVELS = 10;
            LogUtils.debug(event.getObservable(), "DISPOSED");
            LogUtils.DEBUG_STRACK_LEVELS = 0;
        }
    };

    /**
     * Contructs and returns a new manager.
     */
    protected ValidatorAdapterManager() {
        THE_MANAGER.registerService(this);

        myUnboundMessagesOLUnmodifiable.addDisposeListener(myDisposeListener);
        myUnboundMessagesOL.addDisposeListener(myDisposeListener);
    }

    @Override
    public void dispose() {
        THE_MANAGER.unregisterService(this);

        reset();

        for (final IValidatorAdapterMessageDecorator d : myDecorators
                .toArray(new IValidatorAdapterMessageDecorator[myDecorators.size()])) {
            removeDecorator(d);
        }

        myUnboundMessagesOLUnmodifiable.removeDisposeListener(myDisposeListener);
        myUnboundMessagesOL.removeDisposeListener(myDisposeListener);

        myUnboundMessagesOLUnmodifiable.dispose();
        myUnboundMessagesOL.dispose();
    }

    @Override
    public void addValidationAdapterManagerChangeListener(IValidationAdapterManagerChangeListener listener) {
        addListenerObject(listener);
    }

    @Override
    public void removeValidationAdapterManagerChangeListener(IValidationAdapterManagerChangeListener listener) {
        removeListenerObject(listener);
    }

    /**
     * Map of all the current validation roots indexed by their object roots.
     */
    /* package */final Map<EObject, ValidationRoot> myValidationRoots = new HashMap<EObject, ValidatorAdapterManager.ValidationRoot>();

    @Override
    public void reset() {
        for (final ValidationRoot root : myValidationRoots.values()
                .toArray(new ValidationRoot[myValidationRoots.values().size()])) {
            removeRoot(root.getRoot());
        }

        myUnboundMessages.clear();
    }

    @Override
    public void addRoot(EObject root, IValidatorAdapter validationAdapter) {
        Assert.isNotNull(root);
        Assert.isNotNull(validationAdapter);

        ValidationRoot vr = myValidationRoots.get(root);
        if (vr == null) {
            vr = new ValidationRoot(root);
        }
        new ValidationRootAdapter(vr, validationAdapter);
        vr.markForValidation();
        delayValidation();
    }

    @Override
    public void removeRoot(EObject root) {
        Assert.isNotNull(root);

        final ValidationRoot vr = myValidationRoots.get(root);
        if (vr == null)
            return;
        for (final ValidationRootAdapter vra : vr.getAdapters()
                .toArray(new ValidationRootAdapter[vr.getAdapters().size()])) {
            vra.dispose();
        }
        vr.dispose();
        validate();
    }

    @Override
    public void removeRoot(EObject root, IValidatorAdapter validationAdapter) {
        Assert.isNotNull(root);
        Assert.isNotNull(validationAdapter);

        final ValidationRoot vr = myValidationRoots.get(root);
        if (vr == null)
            return;
        for (final ValidationRootAdapter vra : vr.getAdapters()) {
            if (vra.getValidationAdapter() != validationAdapter) {
                continue;
            }
            vra.dispose();
            break;
        }
        if (vr.getAdapters().isEmpty()) {
            vr.dispose();
        }
        validate();
    }

    @Override
    public List<IBindingMessage> getUnboundMessages() {
        return myUnboundMessagesUnmodifiable;
    }

    @Override
    public IObservableList getUnboundMessagesOL() {
        return myUnboundMessagesOLUnmodifiable;
    }

    /**
     * The time when the next validation will be performed.
     */
    private long myNextValidation = System.currentTimeMillis();

    @Override
    public void delayValidation() {
        if (validationPaused)
            return;
        /*
         * If a new validation has just been scheduled, then ignore this change
         */
        if (System.currentTimeMillis() < myNextValidation - THE_MANAGER.getValidationDelay()
                + THE_MANAGER.getValidationDelayWindow())
            return;
        myNextValidation = System.currentTimeMillis() + THE_MANAGER.getValidationDelay();
        Display.getDefault().asyncExec(new Runnable() {
            @Override
            public void run() {
                Display.getDefault().timerExec(THE_MANAGER.getValidationDelay(), myDelayRunnable);
            }
        });
    }

    private final Runnable myDelayRunnable = new Runnable() {
        @Override
        public void run() {
            if (myNextValidation > System.currentTimeMillis() + 50)
                return;
            validate();
        }
    };

    /**
     * Whether validation is currently paused.
     */
    private boolean validationPaused = false;

    @Override
    public void validate() {
        myNextValidation = System.currentTimeMillis();
        if (validationPaused)
            return;
        myChangedObjects.clear();
        myUnboundMessages.clear();
        for (final ValidationRoot r : myValidationRoots.values()) {
            if (!r.isValidationNeeded()) {
                continue;
            }
            r.markValidationDone();
            for (final ValidationRootAdapter vra : r.getAdapters()) {
                vra.validate();
            }
        }
        if (Activator.getDefault().TRACE_VALIDATION_RESULT) {
            final StringBuilder sb = new StringBuilder(200);
            for (final IBindingMessage m : getUnboundMessages()) {
                sb.append("\n  " + m);
            }
            LogUtils.debug(this, "Results:" + sb);
        }
        if (myChangedObjects.size() > 0) {
            myCurrentObjects.clear();
            for (final IBindingMessage m : getUnboundMessages()) {
                for (final IBindingMessageTarget t : m.getTargets()) {
                    myCurrentObjects.add(t.getModelObject());
                }
            }
            for (final Object l : getListeners()) {
                try {
                    ((IValidationAdapterManagerChangeListener) l).affectedObjectsChanged(myChangeEvent);
                } catch (final Exception ex) {
                    LogUtils.error(l, ex);
                }
            }
        }
    }

    /**
     * 
     */
    @Override
    public void forceValidate() {
        myNextValidation = System.currentTimeMillis();
        if (validationPaused)
            return;
        myChangedObjects.clear();
        myUnboundMessages.clear();
        for (final ValidationRoot r : myValidationRoots.values()) {
            r.markValidationDone();
            for (final ValidationRootAdapter vra : r.getAdapters()) {
                vra.validate();
            }
        }
        if (Activator.getDefault().TRACE_VALIDATION_RESULT) {
            final StringBuilder sb = new StringBuilder(200);
            for (final IBindingMessage m : getUnboundMessages()) {
                sb.append("\n  " + m);
            }
            LogUtils.debug(this, "Results:" + sb);
        }
        if (myChangedObjects.size() > 0) {
            myCurrentObjects.clear();
            for (final IBindingMessage m : getUnboundMessages()) {
                for (final IBindingMessageTarget t : m.getTargets()) {
                    myCurrentObjects.add(t.getModelObject());
                }
            }
            for (final Object l : getListeners()) {
                try {
                    ((IValidationAdapterManagerChangeListener) l).affectedObjectsChanged(myChangeEvent);
                } catch (final Exception ex) {
                    LogUtils.error(l, ex);
                }
            }
        }
    }

    @Override
    public void executeWithoutValidation(Runnable runnable) {
        try {
            validationPaused = true;
            runnable.run();
        } finally {
            validationPaused = false;
            delayValidation();
        }
    }

    /**
     * Map with all created bound messages index by unbound message.
     * <p>
     * This way it is easy to remove the bound messages when the unbound message is removed.
     */
    private final Map<IBindingMessage, List<IBindingMessage>> myBoundMessages = new HashMap<IBindingMessage, List<IBindingMessage>>();

    /**
     * Added message decorators.
     * 
     * @see #addDecorator(IValidatorAdapterMessageDecorator)
     * @see #removeDecorator(IValidatorAdapterMessageDecorator)
     */
    private final List<IValidatorAdapterMessageDecorator> myDecorators = new ArrayList<IValidatorAdapterMessageDecorator>();

    /**
     * Adds a new message to the manager, and populates all relevant decorators.
     * 
     * @param unboundMessage the new message
     */
    protected void addUnboundMessage(IBindingMessage unboundMessage) {
        for (final IBindingMessageTarget t : unboundMessage.getTargets()) {
            myChangedObjects.add(t.getModelObject());
        }
        for (final IValidatorAdapterMessageDecorator decorator : myDecorators) {
            createMessageIfNeeded(unboundMessage, decorator);
        }
    }

    /**
     * Removes an unbound existing message from the manager, and removes all messages from the
     * relevant decorators.
     * 
     * @param unboundMessage the message to remove
     */
    protected void removeUnboundMessage(IBindingMessage unboundMessage) {
        for (final IBindingMessageTarget t : unboundMessage.getTargets()) {
            myChangedObjects.add(t.getModelObject());
        }

        final List<IBindingMessage> messageList = myBoundMessages.get(unboundMessage);
        if (messageList == null)
            return;

        for (final IBindingMessage m : messageList) {
            final IValueBinding binding = m.getBinding();
            final IValidatorAdapterMessageDecorator decorator = binding
                    .getService(IValidatorAdapterMessageDecorator.class);
            if (decorator == null) {
                continue;
            }
            decorator.removeMessage(m);
        }
        myBoundMessages.remove(unboundMessage);
    }

    @Override
    public void addDecorator(IValidatorAdapterMessageDecorator decorator) {
        myDecorators.add(decorator);
        for (final ValidationRoot r : myValidationRoots.values()) {
            for (final ValidationRootAdapter vra : r.getAdapters()) {
                for (final Object o : vra.getFoundMessages()) {
                    final IBindingMessage message = (IBindingMessage) o;
                    createMessageIfNeeded(message, decorator);
                }
            }
        }
    }

    @Override
    public void removeDecorator(IValidatorAdapterMessageDecorator decorator) {
        myDecorators.remove(decorator);
        final IValueBinding binding = decorator.getBinding();
        for (final List<IBindingMessage> messages : myBoundMessages.values()) {
            for (final IBindingMessage m : messages) {
                if (m.getBinding() == binding) {
                    messages.remove(m);
                    decorator.removeMessage(m);
                    /*
                     * Each unbound message will only have one bound message for each decorator...
                     */
                    break;
                }
            }
        }
    }

    @Override
    public void resetDecorator(IValidatorAdapterMessageDecorator decorator) {
        removeDecorator(decorator);
        addDecorator(decorator);
    }

    /**
     * Matches the specified unbound message against the specified binding and creates a bound
     * message in case of a match.
     * <p>
     * The new bound message is added to the decorator of the binding.
     * 
     * @param unboundMessage the message
     * @param decorator the binding
     */
    private void createMessageIfNeeded(final IBindingMessage unboundMessage,
            IValidatorAdapterMessageDecorator decorator) {
        if (!decorator.accept(unboundMessage))
            return;
        final IValueBinding binding = decorator.getBinding();

        List<IBindingMessage> messageList = myBoundMessages.get(unboundMessage);
        if (messageList == null) {
            messageList = new ArrayList<IBindingMessage>();
            myBoundMessages.put(unboundMessage, messageList);
        }
        final IBindingMessage boundMessage = new BoundMessage(unboundMessage, binding);

        messageList.add(boundMessage);
        decorator.addMessage(boundMessage);
    }

    /**
     * Change listener that see the changes in unbound messages.
     */
    private final IListChangeListener myFoundMessageChangeListener = new IListChangeListener() {
        @Override
        public void handleListChange(ListChangeEvent event) {
            event.diff.accept(myUnboundMessageChangeVisitor);
        }
    };

    /**
     * Set used to collect the object with an associated unbound message.
     * <p>
     * Only used in {@link #validate()}.
     */
    private final Set<EObject> myCurrentObjects = new HashSet<EObject>();

    /**
     * Unmodifiable version of {@link #myCurrentObjects}.
     */
    private final Set<EObject> myCurrentObjectsUnmodifiable = Collections.unmodifiableSet(myCurrentObjects);

    @Override
    public Set<EObject> getCurrentObjects() {
        return myCurrentObjectsUnmodifiable;
    }

    /**
     * Set used to collect the changed objects of the last validate.
     * <p>
     * Only used in {@link #validate()}.
     */
    private final Set<EObject> myChangedObjects = new HashSet<EObject>();

    /**
     * Unmodifiable version of {@link #myChangedObjects}.
     */
    private final Set<EObject> myChangedObjectsUnmodifiable = Collections.unmodifiableSet(myChangedObjects);

    /**
     * The list of unbound messages across all roots.
     * <p>
     * Here for optimization purposes...
     */
    private final List<IBindingMessage> myUnboundMessages = new ArrayList<IBindingMessage>();

    /**
     * Unmodifiable version of {@link #myUnboundMessages}.
     */
    private final List<IBindingMessage> myUnboundMessagesUnmodifiable = Collections
            .unmodifiableList(myUnboundMessages);

    /**
     * Observable version of {@link #myUnboundMessages}
     * <p>
     * Here for optimization purposes...
     */
    private final IObservableList myUnboundMessagesOL = WritableList
            .withElementType(IUIBindingsPackage.Literals.BINDING_MESSAGE);

    /**
     * Unmodifiable version of {@link #myUnboundMessagesOL}.
     */
    private final IObservableList myUnboundMessagesOLUnmodifiable = Observables
            .unmodifiableObservableList(myUnboundMessagesOL);

    private final IValidationAdapterManagerChangeEvent myChangeEvent = new IValidationAdapterManagerChangeEvent() {
        @Override
        public Set<EObject> getCurrentObjects() {
            return myCurrentObjectsUnmodifiable;
        }

        @Override
        public Set<EObject> getChangedObjects() {
            return myChangedObjectsUnmodifiable;
        }
    };

    private final ListDiffVisitor myUnboundMessageChangeVisitor = new ListDiffVisitor() {
        @Override
        public void handleAdd(int index, Object element) {
            final IBindingMessage m = (IBindingMessage) element;
            myUnboundMessagesOL.add(m);
            addUnboundMessage(m);
        }

        @Override
        public void handleRemove(int index, Object element) {
            final IBindingMessage m = (IBindingMessage) element;
            myUnboundMessagesOL.remove(m);
            removeUnboundMessage(m);
        }
    };

    /**
     * The record of one object root added via
     * {@link ValidatorAdapterManager#addRoot(EObject, IValidatorAdapter)}.
     * <p>
     * Each of these roots have its own adapter - the object itself...
     */
    private class ValidationRoot extends EContentAdapter implements IDisposable {
        private final EObject myRoot;
        private final List<ValidationRootAdapter> myAdapters = new ArrayList<ValidatorAdapterManager.ValidationRootAdapter>();

        protected ValidationRoot(EObject root) {
            Assert.isNotNull(root);
            myRoot = root;
            myValidationRoots.put(root, this);
            myRoot.eAdapters().add(this);
        }

        public boolean isValidationNeeded() {
            return validationNeeded;
        }

        @Override
        public void dispose() {
            myValidationRoots.remove(getRoot());
            myRoot.eAdapters().remove(this);
        }

        public EObject getRoot() {
            return myRoot;
        }

        public List<ValidationRootAdapter> getAdapters() {
            return myAdapters;
        }

        @Override
        public void notifyChanged(Notification msg) {
            super.notifyChanged(msg);
            if (msg.isTouch())
                return;
            // LogUtils.debug(this, ToStringUtils.toString(msg));
            markForValidation();
        }

        private boolean validationNeeded;

        public void markForValidation() {
            if (validationNeeded)
                return;
            validationNeeded = true;
            delayValidation();
        }

        public void markValidationDone() {
            validationNeeded = false;
        }

    }

    /**
     * The record of one root added via
     * {@link ValidatorAdapterManager#addRoot(EObject, IValidatorAdapter)}.
     */
    private class ValidationRootAdapter implements IDisposable {
        public IValidatorAdapter getValidationAdapter() {
            return myValidationAdapter;
        }

        public IObservableList getFoundMessages() {
            return myFoundMessages;
        }

        private final ValidationRoot myRoot;

        public ValidationRoot getRoot() {
            return myRoot;
        }

        private final IValidatorAdapter myValidationAdapter;
        private final IObservableList myFoundMessages = WritableList.withElementType(IBindingMessage.class);

        protected ValidationRootAdapter(ValidationRoot root, IValidatorAdapter validationAdapter) {
            myRoot = root;
            myRoot.getAdapters().add(this);
            myValidationAdapter = validationAdapter;
            myFoundMessages.addListChangeListener(myFoundMessageChangeListener);
        }

        @Override
        public void dispose() {
            myFoundMessages.clear();
            myFoundMessages.removeListChangeListener(myFoundMessageChangeListener);
            myRoot.getAdapters().remove(this);
        }

        public void validate() {
            try {
                myValidationAdapter.validateObjectTree(myRoot.getRoot(), myFoundMessages);
            } catch (final Exception ex) {
                LogUtils.error(myValidationAdapter, ex);
            }
            myUnboundMessages.addAll(myFoundMessages);
        }
    }

    @Override
    public int getObjectSeverity(EObject object) {
        int severity = IMessageProvider.NONE;
        for (final IBindingMessage message : getUnboundMessages()) {
            if (message.matches(object, null, null, FeatureMatchingAlgorithm.IGNORE)) {
                final int s = message.getMessageType();
                if (s > severity) {
                    severity = s;
                }
            }
            if (severity == IMessageProvider.ERROR)
                return severity;
        }
        return severity;
    }

    /**
     * A bound message based on an unbound message and a specific binding.
     */
    private static class BoundMessage extends AbstractBindingMessage {

        private final IBindingMessage myParentMessage;

        private BoundMessage(IBindingMessage message, IValueBinding binding) {
            super(binding);
            myParentMessage = message;
        }

        @Override
        public int getCode() {
            return myParentMessage.getCode();
        }

        @Override
        public String getPrefix() {
            if (getTargets().size() != 1)
                return null;
            if (getTargets().get(0).getModelFeature() == null)
                return null;
            return super.getPrefix();
        }

        @Override
        public BindingMessageSeverity getSeverity() {
            return myParentMessage.getSeverity();
        }

        @Override
        public Object getData() {
            return myParentMessage.getData();
        }

        @Override
        public String getMessage() {
            return myParentMessage.getMessage();
        }

        @Override
        public boolean supersedes(IBindingMessage otherMessage) {
            if (otherMessage instanceof BoundMessage) {
                final BoundMessage m = (BoundMessage) otherMessage;
                if (myParentMessage.supersedes(m.myParentMessage))
                    return true;

            }
            return myParentMessage.supersedes(otherMessage);
        }

        @Override
        public boolean matches(EObject obj, EStructuralFeature feature, Object key,
                FeatureMatchingAlgorithm algorithm) {
            return myParentMessage.matches(obj, feature, key, algorithm);
        }

        @Override
        public EList<IBindingMessageTarget> getTargets() {
            return myParentMessage.getTargets();
        }
    }

}