Java tutorial
/******************************************************************************* * 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(); } } }