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.bindingMessages; import java.util.ArrayList; import java.util.List; import org.eclipse.core.databinding.Binding; import org.eclipse.core.databinding.observable.ChangeEvent; import org.eclipse.core.databinding.observable.IChangeListener; import org.eclipse.core.databinding.observable.IObservable; import org.eclipse.core.databinding.observable.list.IObservableList; import org.eclipse.core.databinding.observable.list.WritableList; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.runtime.Assert; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.fieldassist.FieldDecoration; import org.eclipse.jface.fieldassist.FieldDecorationRegistry; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import com.rcpcompany.uibindings.BindingMessageSeverity; import com.rcpcompany.uibindings.BindingState; import com.rcpcompany.uibindings.Constants; import com.rcpcompany.uibindings.IBindingMessage; import com.rcpcompany.uibindings.IBindingMessage.FeatureMatchingAlgorithm; import com.rcpcompany.uibindings.IDisposable; import com.rcpcompany.uibindings.IManager; import com.rcpcompany.uibindings.IQuickfixProposal; import com.rcpcompany.uibindings.IUIBindingDecoratorExtender; import com.rcpcompany.uibindings.IUIBindingDecoratorExtenderContext; import com.rcpcompany.uibindings.IUIBindingsPackage; import com.rcpcompany.uibindings.IValueBinding; import com.rcpcompany.uibindings.contextAdapters.ContextMessageDecorator; import com.rcpcompany.uibindings.decorators.extenders.AbstractUIBindingDecoratorExtender; import com.rcpcompany.uibindings.internal.Activator; import com.rcpcompany.uibindings.internal.Messages; import com.rcpcompany.uibindings.internal.bindingMessages.messageAdapters.DataBindingDecoratorMessageObservableValue; import com.rcpcompany.uibindings.internal.observables.DelayedChangeEvent; import com.rcpcompany.uibindings.internal.observables.IDelayedChangeListener; import com.rcpcompany.uibindings.internal.observables.IDelayedChangeObservable; import com.rcpcompany.uibindings.internal.observables.TextObservableValue; import com.rcpcompany.uibindings.model.utils.BasicUtils; import com.rcpcompany.uibindings.observables.IKeyedObservable; import com.rcpcompany.uibindings.utils.IManagerRunnable; import com.rcpcompany.uibindings.validators.IValidatorAdapterManager; import com.rcpcompany.uibindings.validators.IValidatorAdapterMessageDecorator; import com.rcpcompany.utils.logging.LogUtils; /** * Message decorator for {@link IValueBinding} objects. * <p> * One of these exists for each {@link IValueBinding} object so some care must be taken to limit the * amount of data in the object. * * @author Tonny Madsen, The RCP Company */ public class ValueBindingMessageImageDecorator extends AdapterImpl implements IDisposable, IContextMessageProvider, IValidatorAdapterMessageDecorator, Adapter, IChangeListener, IDelayedChangeListener, Listener { /** * Extender for {@link ValueBindingMessageImageDecorator}. */ public static class Extender extends AbstractUIBindingDecoratorExtender implements IUIBindingDecoratorExtender { @Override public void extend(IUIBindingDecoratorExtenderContext context) { final IValueBinding binding = context.getBinding(); ValueBindingMessageImageDecorator data = binding.getService(ValueBindingMessageImageDecorator.class); if (data == null) { data = new ValueBindingMessageImageDecorator(binding); } data.extend(context); } } /** * The {@link IValueBinding value binding} of this decorator. */ private final IValueBinding myBinding; /** * Constructs and returns a new decorator. * * @param binding the value binding */ public ValueBindingMessageImageDecorator(IValueBinding binding) { myBinding = binding; myObservedObject = getBinding().getModelObject(); init(); } /** * Completes the initialization of this message decorator. Only invoked when the state of the * binding is OK. */ protected void init() { if (getBinding().getState() != BindingState.OK) { /* * If not in state OK, then wait until we get there... */ getBinding().eAdapters().add(this); return; } Assert.isTrue(getBinding().getState() == BindingState.OK); if (Activator.getDefault().TRACE_LIFECYCLE_VALUE_BINDING_MESSAGE_DECORATOR) { LogUtils.debug(this, "init " + hashCode() + ": " + getBinding()); //$NON-NLS-1$ //$NON-NLS-2$ } getBinding().registerService(this); IObservable observable = getBinding().getUIObservable(); observable.addChangeListener(this); if (observable instanceof IDelayedChangeObservable) { ((IDelayedChangeObservable) observable).addDelayedChangeListener(this); } observable = getBinding().getModelObservable(); observable.addChangeListener(this); final IObservableList decoratorMessages = getBinding().getDecorator().getMessages(); if (decoratorMessages != null) { decoratorMessages.addChangeListener(this); } /* * The list change listener will ensure that the change listener is added to all the message * providers */ final List<Binding> bindings = getBinding().getMonitoredDBBindings(); myMessageProviders = new IObservableValue[bindings.size()]; for (int i = 0; i < bindings.size(); i++) { // TODO TMTM reduce the extra observable! myMessageProviders[i] = new DataBindingDecoratorMessageObservableValue(getBinding(), bindings.get(i)); myMessageProviders[i].addChangeListener(this); } /* * Register this decorator with the ValidatorAdapterManager. */ VALIDATION_MANAGER.addDecorator(this); /* * Add ourself as a message decorator to the context if present */ final ContextMessageDecorator contextMessageDecorator = myBinding.getContext() .getService(ContextMessageDecorator.class); if (contextMessageDecorator != null) { contextMessageDecorator.addMessageProvider(this); } /* * For bindings with controls, register a focus listener, so we can change the decoration at * focus in/out. */ final Control control = getBinding().getControl(); if (control != null) { control.addListener(SWT.FocusIn, this); control.addListener(SWT.FocusOut, this); } /* * Register for configuration changes */ IManager.Factory.getManager().eAdapters().add(this); } /** * Disposes of this binding message decoration. */ @Override public void dispose() { IManagerRunnable.Factory.cancelAsyncExec("updateDecorations", this); VALIDATION_MANAGER.removeDecorator(this); IObservable observable = getBinding().getUIObservable(); observable.removeChangeListener(this); if (observable instanceof IDelayedChangeObservable) { ((IDelayedChangeObservable) observable).removeDelayedChangeListener(this); } observable = getBinding().getModelObservable(); observable.removeChangeListener(this); getBinding().unregisterService(this); final Control control = getBinding().getControl(); if (control != null) { control.removeListener(SWT.FocusIn, this); control.removeListener(SWT.FocusOut, this); } /* * Clear all messages and update the context decorator... */ myMessagesOL.clear(); // Remove all listeners for (final Object v : myMessageProviders) { ((IObservableValue) v).removeChangeListener(this); } final IObservableList decoratorMessages = getBinding().getDecorator().getMessages(); if (decoratorMessages != null) { decoratorMessages.removeChangeListener(this); } myMessageProviders = null; final ContextMessageDecorator contextMessageDecorator = myBinding.getContext() .getService(ContextMessageDecorator.class); if (contextMessageDecorator != null) { contextMessageDecorator.removeMessageProvider(this); } IManager.Factory.getManager().eAdapters().remove(this); if (Activator.getDefault().TRACE_LIFECYCLE_VALUE_BINDING_MESSAGE_DECORATOR) { LogUtils.debug(this, "dispose " + hashCode() + ": " + getBinding()); //$NON-NLS-1$ //$NON-NLS-2$ } } @Override public void notifyChanged(Notification msg) { if (msg.isTouch()) return; /* * React to change is the configuration used by this decorator. */ if ((msg.getFeature() == IUIBindingsPackage.Literals.MANAGER__MESSAGE_DECORATION_POSITION) || (msg.getFeature() == IUIBindingsPackage.Literals.MANAGER__ALTERNATIVE_DECORATION_POSITION) || (msg.getFeature() == IUIBindingsPackage.Literals.MANAGER__MESSAGE_DECORATION_MINIMUM_SEVERITY) || (msg.getFeature() == IUIBindingsPackage.Literals.MANAGER__REQUIRED_VB_IMAGE_DECORATION_SHOWN) || (msg.getFeature() == IUIBindingsPackage.Literals.MANAGER__ASSIST_VB_IMAGE_DECORATION_SHOWN) || (msg.getFeature() == IUIBindingsPackage.Literals.MANAGER__QUICKFIX_VB_IMAGE_DECORATION_SHOWN)) { updateDecoration(); return; } /* * When the state of the underlying binding changes to OK or DISPOSED, then react to this. */ if (msg.getFeature() == IUIBindingsPackage.Literals.BINDING__STATE) { switch (getBinding().getState()) { case OK: init(); //$FALL-THROUGH$ fallthrough case DISPOSED: getBinding().eAdapters().remove(this); break; default: break; } return; } } @Override public void handleEvent(Event event) { updateDecoration(); } /** * Common change listener used to update the decoration whenever any of the individual elements * of {@link #myMessageProviders} is changed. */ @Override public void handleChange(ChangeEvent event) { updateDecoration(); } /** * Common change listener used to update the decoration whenever changes are initiated for the * UI Observable. See {@link TextObservableValue} for more information. */ @Override public void handleDelayedChange(DelayedChangeEvent event) { updateDecoration(); } @Override public IValueBinding getBinding() { return myBinding; } /** * Array with all the added message providers for this messages decorator. * <p> * The list elements are observable values that returns an {@link IBindingMessage} or * <code>null</code>. * <p> * The array is static - once it is created, it does not change. */ private IObservableValue[] myMessageProviders = null; /** * The current list of outstanding messages for this decorator. * <p> * These messages comes from the outside. The party that adds a message must also remove it * again. * * @see #addMessage(IBindingMessage) * @see #removeMessage(IBindingMessage) */ private final List<IBindingMessage> myOutstandingMessages = new ArrayList<IBindingMessage>(); /** * The current list of shown messages for this decorator. * <p> * The messages all have the same message type. */ private final List<IBindingMessage> myMessages = new ArrayList<IBindingMessage>(); @Override public IObservableList getMessages() { return myMessagesOL; } private final IObservableList myMessagesOL = new WritableList(myMessages, IBindingMessage.class); @Override public void addMessage(IBindingMessage message) { if (Activator.getDefault().TRACE_LIFECYCLE_VALUE_BINDING_MESSAGE_DECORATOR) { // LogUtils.DEBUG_STRACK_LEVELS = 10; LogUtils.debug(this, hashCode() + ": " + getBinding() + "\n" + message + "@" + message.hashCode()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } myOutstandingMessages.add(message); updateDecoration(); } @Override public void removeMessage(IBindingMessage message) { if (Activator.getDefault().TRACE_LIFECYCLE_VALUE_BINDING_MESSAGE_DECORATOR) { LogUtils.debug(this, hashCode() + ": " + getBinding() + "\n" + message + "@" + message.hashCode()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } myOutstandingMessages.remove(message); updateDecoration(); } /** * Returns the current list of quick fixes.. * <p> * A new list is returned for every call. * * @return the list */ public List<IQuickfixProposal> getQuickfixes() { final IManager manager = IManager.Factory.getManager(); final List<IQuickfixProposal> quickfixes = new ArrayList<IQuickfixProposal>(); for (final IBindingMessage m : myMessages) { manager.getQuickfixes(m, quickfixes); } return quickfixes; } /** * Updates the message decoration of this decorator. */ protected void updateDecoration() { if (getBinding().getState() != BindingState.OK) return; IManagerRunnable.Factory.asyncExec("updateDecorations", this, new Runnable() { @Override public void run() { updateDecorationDelayed(); } }); } /** * The object observed by this decorator. Used to track changes in master-detail bindings. */ private EObject myObservedObject = null; private Image myMessageDecorationImage; private String myMessageDecorationMessage; private Image myAlternativeDecorationImage; private String myAlternativeDecorationMessage; /** * The VAM... */ public static final IValidatorAdapterManager VALIDATION_MANAGER = IValidatorAdapterManager.Factory.getManager(); /** * Decoration used to display <em>a value is required</em> for the binding. */ public static final FieldDecoration REQUIRED_FIELD_DECORATOR = FieldDecorationRegistry.getDefault() .getFieldDecoration(FieldDecorationRegistry.DEC_REQUIRED); /** * Decoration used to display <em>additional information</em> is available for the binding. */ public static final FieldDecoration INFORMATION_FIELD_DECORATOR = FieldDecorationRegistry.getDefault() .getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION); /** * Decoration used to display <em>a warning</em> is detected for the binding. */ public static final FieldDecoration WARNING_FIELD_DECORATOR = FieldDecorationRegistry.getDefault() .getFieldDecoration(FieldDecorationRegistry.DEC_WARNING); /** * Decoration used to display <em>an error</em> is detected for the binding. */ public static final FieldDecoration ERROR_FIELD_DECORATOR = FieldDecorationRegistry.getDefault() .getFieldDecoration(FieldDecorationRegistry.DEC_ERROR); /** * Decoration used to display <em>a content proposal</em> is detected for the binding. */ public static final FieldDecoration CONTENT_PROPOSAL_FIELD_DECORATOR = FieldDecorationRegistry.getDefault() .getFieldDecoration(FieldDecorationRegistry.DEC_CONTENT_PROPOSAL); /** * Decoration used to display <em>a quick fix proposal</em> is detected for the binding. */ public static final FieldDecoration QUICKFIX_FIELD_DECORATOR = FieldDecorationRegistry.getDefault() .getFieldDecoration(FieldDecorationRegistry.DEC_ERROR_QUICKFIX); /** * @see #updateDecoration() */ protected void updateDecorationDelayed() { if (getBinding().getState() != BindingState.OK) return; /* * As this operation is delayed, the widget might be disposed in the mean time... */ if (getBinding().getUIObservable().isDisposed()) return; if (Activator.getDefault().TRACE_LIFECYCLE_VALUE_BINDING_MESSAGE_DECORATOR) { LogUtils.debug(this, "update delayed " + hashCode() + ": " + getBinding()); //$NON-NLS-1$ //$NON-NLS-2$ } if (VALIDATION_MANAGER != null) { if (getBinding().getModelObservable().isDisposed()) { LogUtils.debug(this, "value is disposed"); return; } /* * Check if the observed object of the binding has changed. If so, then reset the * decoration for the validation manager - this will remove all current messages and set * up a new set... */ final EObject newObservedObject = getBinding().getModelObject(); if (myObservedObject != newObservedObject) { myObservedObject = newObservedObject; VALIDATION_MANAGER.resetDecorator(this); } } /* * Make a list of all the potential messages */ final List<IBindingMessage> ml = new ArrayList<IBindingMessage>(); final BindingMessageSeverity minimumSeverity = IManager.Factory.getManager() .getMessageDecorationMinimumSeverity(); for (final Object v : myMessageProviders) { final IBindingMessage message = (IBindingMessage) ((IObservableValue) v).getValue(); if (message.getSeverity().compareTo(minimumSeverity) < 0) { continue; } ml.add(message); } final IObservableList decoratorMessages = getBinding().getDecorator().getMessages(); if (decoratorMessages != null) { for (final Object m : decoratorMessages) { final IBindingMessage message = (IBindingMessage) m; if (message.getSeverity().compareTo(minimumSeverity) < 0) { continue; } ml.add(message); } } for (final IBindingMessage message : myOutstandingMessages) { if (message.getSeverity().compareTo(minimumSeverity) < 0) { continue; } ml.add(message); } /* * Find the current max type and the combined messages to use... */ int maxType = IMessageProvider.NONE; final List<IBindingMessage> topList = new ArrayList<IBindingMessage>(); for (final IBindingMessage message : ml) { final int type = message.getMessageType(); if (type > maxType) { topList.clear(); maxType = type; } if (type == maxType) { topList.add(message); } } /* * Weed out any superseded messages. * * There has to be at least two messages before there any anything to weed out... */ if (topList.size() > 1) { final IBindingMessage[] ma = topList.toArray(new IBindingMessage[topList.size()]); for (int i = 0; i < ma.length; i++) { final IBindingMessage a = ma[i]; for (int j = i + 1; j < ma.length; j++) { final IBindingMessage b = ma[j]; if (a.supersedes(b)) { topList.remove(b); continue; } if (b.supersedes(a)) { topList.remove(a); break; } } } } /* * Updated the messages */ for (final IBindingMessage m : topList) { if (myMessages.contains(m)) { continue; } myMessagesOL.add(m); } for (final IBindingMessage m : myMessages.toArray(new IBindingMessage[myMessages.size()])) { if (topList.contains(m)) { continue; } myMessagesOL.remove(m); } final StringBuilder sb = new StringBuilder(100); for (final IBindingMessage message : myMessages) { if (sb.length() > 0) { sb.append('\n'); } sb.append(message.getMessage()); } /* * Show the appropriate message decorations */ final Image oldMessageDecorationImage = myMessageDecorationImage; final String oldMessageDecorationMessage = myMessageDecorationMessage; switch (maxType) { case IMessageProvider.NONE: myMessageDecorationImage = null; break; case IMessageProvider.ERROR: myMessageDecorationImage = ERROR_FIELD_DECORATOR.getImage(); break; case IMessageProvider.WARNING: myMessageDecorationImage = WARNING_FIELD_DECORATOR.getImage(); break; case IMessageProvider.INFORMATION: myMessageDecorationImage = INFORMATION_FIELD_DECORATOR.getImage(); break; default: break; } myMessageDecorationMessage = sb.toString(); /* * The alternative stuff is only relevant if the binding is changeable... */ final Image oldAlternativeDecorationImage = myAlternativeDecorationImage; final String oldAlternativeDecorationMessage = myAlternativeDecorationMessage; if (getBinding().isChangeable()) { final IManager manager = IManager.Factory.getManager(); /* * Only show the alternative decorations for controls if they have the focus - but not * for checkboxes */ final Control control = getBinding().getControl(); boolean showAlternativeDecorations = control != null && control.isFocusControl(); if (control instanceof Button && (control.getStyle() & SWT.CHECK) == SWT.CHECK) { showAlternativeDecorations = false; } // TODO TMTM add key bindings if (getQuickfixes().size() > 0 && manager.isQuickfixVBImageDecorationShown()) { myAlternativeDecorationImage = QUICKFIX_FIELD_DECORATOR.getImage(); myAlternativeDecorationMessage = QUICKFIX_FIELD_DECORATOR.getDescription(); } else if (showAlternativeDecorations && getBinding().getDataType().isRequired() && manager.isRequiredVBImageDecorationShown()) { myAlternativeDecorationImage = REQUIRED_FIELD_DECORATOR.getImage(); myAlternativeDecorationMessage = Messages.ValueBindingMessageImageDecorator_ValueRequired; } else if (showAlternativeDecorations && getBinding().getDecorator().getValidUIList() != null && getBinding().getUIAttribute().getFieldAssistAdapter() != null && manager.isAssistVBImageDecorationShown()) { myAlternativeDecorationImage = CONTENT_PROPOSAL_FIELD_DECORATOR.getImage(); myAlternativeDecorationMessage = Messages.ValueBindingMessageImageDecorator_ContentAssistAvailanble; } else { myAlternativeDecorationImage = null; myAlternativeDecorationMessage = null; } } /* * If everything is the same, then do nothing */ if (BasicUtils.equals(oldMessageDecorationImage, myMessageDecorationImage) && BasicUtils.equals(oldMessageDecorationMessage, myMessageDecorationMessage) && BasicUtils.equals(oldAlternativeDecorationImage, myAlternativeDecorationImage) && BasicUtils.equals(oldAlternativeDecorationMessage, myAlternativeDecorationMessage)) return; /* * Update the binding */ getBinding().updateBinding(); } /** * Updates the context based on the current images and message values. * * @param context the extender context to update */ public void extend(IUIBindingDecoratorExtenderContext context) { if (myMessageDecorationImage != null) { // LogUtils.debug(this, myMessageDecorationMessage); context.setDecoratingImage(IManager.Factory.getManager().getMessageDecorationPosition(), true, myMessageDecorationImage, myMessageDecorationMessage); } if (myAlternativeDecorationImage != null) { // LogUtils.debug(this, myAlternativeDecorationMessage); context.setDecoratingImage(IManager.Factory.getManager().getAlternativeDecorationPosition(), true, myAlternativeDecorationImage, myAlternativeDecorationMessage); } } @Override public boolean accept(IBindingMessage unboundMessage) { if (myObservedObject == null) return false; final IValueBinding binding = getBinding(); final IObservable modelObservable = binding.getModelObservable(); Object key = null; if (modelObservable instanceof IKeyedObservable) { key = ((IKeyedObservable) modelObservable).getObservableKey(); } FeatureMatchingAlgorithm matchingAlgorithm; if (binding.getArgument(Constants.ARG_MODEL_OBJECT_MESSAGES, Boolean.class, Boolean.FALSE) == Boolean.TRUE) { matchingAlgorithm = FeatureMatchingAlgorithm.EXACT_OR_NULL; } else { matchingAlgorithm = FeatureMatchingAlgorithm.EXACT; } if (unboundMessage.matches(myObservedObject, binding.getModelFeature(), key, matchingAlgorithm)) return true; if (binding.getArgument(Constants.ARG_VALUE_OBJECT_MESSAGES, Boolean.class, Boolean.FALSE) == Boolean.TRUE && unboundMessage.matches(myObservedObject, null, null, FeatureMatchingAlgorithm.EXACT)) return true; return false; } }