/*******************************************************************************
* 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.validators;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import com.rcpcompany.uibindings.BindingMessageSeverity;
import com.rcpcompany.uibindings.Constants;
import com.rcpcompany.uibindings.IBindingMessage;
import com.rcpcompany.uibindings.IConstraintValidatorAdapterConstraintDescriptor;
import com.rcpcompany.uibindings.IDecoratorProvider;
import com.rcpcompany.uibindings.IManager;
import com.rcpcompany.uibindings.IUIBindingsFactory;
import com.rcpcompany.uibindings.IValueBinding;
import com.rcpcompany.uibindings.ModelValueKind;
import com.rcpcompany.uibindings.bindingMessages.AbstractBindingMessage;
import com.rcpcompany.uibindings.internal.Activator;
import com.rcpcompany.uibindings.uiAttributes.VirtualUIAttribute;
import com.rcpcompany.utils.logging.LogUtils;
/**
* This class provides an adapter interface between the constraint specified in the uibindings
* extension point and {@link IValidatorAdapterManager}.
*
* @author Tonny Madsen, The RCP Company
*/
public class ConstraintValidatorAdapter extends AbstractValidatorAdapter {
/**
* A provider for a new constraint that can then be added to the constraints to be checked.
*/
public interface IConstraintProvider {
}
/**
* Map with all found constraints indexed by class. Extended ALAP in
* {@link #getConstraints(EObject)}.
*/
private final Map<EClass, List<IConstraintValidatorAdapterConstraint>> myConstraints = new HashMap<EClass, List<IConstraintValidatorAdapterConstraint>>();
@Override
public void validateObjectTree(EObject root, IObservableList messages) {
final List<Message> toRemoveMessages = new ArrayList<Message>(messages);
validateOneEObject(root, messages, toRemoveMessages);
final TreeIterator<Object> iterator = EcoreUtil.getAllContents(root, false);
for (; iterator.hasNext();) {
final Object o = iterator.next();
if (!(o instanceof EObject)) {
continue;
}
validateOneEObject((EObject) o, messages, toRemoveMessages);
}
messages.removeAll(toRemoveMessages);
}
/**
* Validates the given object. Returns messages and possible messages to be removed.
*
* @param obj the object to validate
* @param messages the list of current messages
* @param toRemoveMessages list of messages to be removed at the end of the validation
*/
private void validateOneEObject(final EObject obj, IObservableList messages, final List<Message> toRemoveMessages) {
/*
* Find the constraints for this particular object
*/
final List<IConstraintValidatorAdapterConstraint> constraints = getConstraints(obj);
if (constraints == null) return;
for (final IConstraintValidatorAdapterConstraint c : constraints) {
toRemoveMessages.remove(c.validate(obj, messages));
}
}
/**
* Returns the list of constraints for the specified object or <code>null</code>.
* <p>
* If the class of the object has not been tested before, new constraints are constructed as
* needed.
*
* @param obj the object to test
* @return the found constraints or <code>null</code>
*/
public List<IConstraintValidatorAdapterConstraint> getConstraints(EObject obj) {
final EClass eClass = obj.eClass();
if (myConstraints.containsKey(eClass)) return myConstraints.get(eClass);
List<IConstraintValidatorAdapterConstraint> cs = new ArrayList<IConstraintValidatorAdapterConstraint>();
final IManager manager = IManager.Factory.getManager();
for (final EStructuralFeature sf : eClass.getEAllStructuralFeatures()) {
/*
* Forget about -to-many features...
*/
if ((sf.getUpperBound() > 1) || (sf.getUpperBound() == -1)) {
continue;
}
/*
* We temporary create a new value binding (needed for the argument handling).
*/
final IValueBinding vb = IUIBindingsFactory.eINSTANCE.createValueBinding();
vb.model(obj, sf).ui(new VirtualUIAttribute(String.class));
/*
* Allow the user to turn off validation for specified feature
*/
if (!vb.getArgument(Constants.ARG_CONSTRAINTS_VALIDATE, Boolean.class, true)) {
continue;
}
final IDecoratorProvider provider = manager.getProvider(vb.getModelType(), ModelValueKind.VALUE,
String.class, vb.getType());
vb.setDecoratorProvider(provider);
for (final IConstraintValidatorAdapterConstraintDescriptor cpd : IManager.Factory.getManager()
.getConstraintProviders()) {
final IConstraintValidatorAdapterConstraintProvider cp = cpd.getProvider().getObject();
try {
final IConstraintValidatorAdapterConstraint c = cp.getConstraint(vb);
if (c != null) {
cs.add(c);
}
} catch (final Exception ex) {
LogUtils.error(cpd.getProvider().getConfigurationElement(), ex);
}
}
/*
* TODO Handle binding with ARG_VALID_VALUES
*/
}
if (cs.size() == 0) {
cs = null;
}
myConstraints.put(eClass, cs);
return cs;
}
/**
* Message implementation for a constraint violation.
*
* @author Tonny Madsen, The RCP Company
*/
public static class Message extends AbstractBindingMessage {
public Message(EObject object, EStructuralFeature feature, String message, BindingMessageSeverity severity,
int code) {
super(null);
myObject = object;
myFeature = feature;
myMessage = message;
mySeverity = severity;
myCode = code;
addTarget(object, feature, null);
}
public Message(EObject object, EStructuralFeature feature, IStatus status) {
super(null);
myObject = object;
myFeature = feature;
myMessage = status.getMessage();
switch (status.getSeverity()) {
case IStatus.OK:
mySeverity = BindingMessageSeverity.NONE;
break;
case IStatus.INFO:
mySeverity = BindingMessageSeverity.INFORMATION;
break;
case IStatus.WARNING:
mySeverity = BindingMessageSeverity.WARNING;
break;
case IStatus.ERROR:
mySeverity = BindingMessageSeverity.ERROR;
break;
default:
mySeverity = BindingMessageSeverity.NONE;
break;
}
myCode = status.getCode();
addTarget(object, feature, null);
}
private final EObject myObject;
private final EStructuralFeature myFeature;
private final String myMessage;
private final BindingMessageSeverity mySeverity;
private final int myCode;
@Override
public String getSource() {
return Activator.ID;
}
@Override
public int getCode() {
return myCode;
}
@Override
public String getMessage() {
return myMessage;
}
@Override
public BindingMessageSeverity getSeverity() {
return mySeverity;
}
@Override
public boolean supersedes(IBindingMessage otherMessage) {
if (!matches(getObject(), getFeature(), null, FeatureMatchingAlgorithm.EXACT)) return false;
if (otherMessage.getSeverity() != getSeverity()) return false;
if (getSource() != null && !getSource().equals(otherMessage.getSource())) return false;
if (otherMessage.getCode() != getCode()) return false;
return true;
}
public EObject getObject() {
return myObject;
}
public EStructuralFeature getFeature() {
return myFeature;
}
}
}