/*******************************************************************************
* This file is part of Goko.
*
* Goko is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Goko is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Goko. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package org.goko.common.bindings;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.databinding.AggregateValidationStatus;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.beans.BeansObservables;
import org.eclipse.core.databinding.beans.PojoObservables;
import org.eclipse.core.databinding.conversion.IConverter;
import org.eclipse.core.databinding.conversion.NumberToStringConverter;
import org.eclipse.core.databinding.conversion.StringToNumberConverter;
import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.core.internal.databinding.BindingStatus;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.databinding.fieldassist.ControlDecorationSupport;
import org.eclipse.jface.databinding.swt.ISWTObservable;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.jface.databinding.viewers.IViewerObservableValue;
import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
import org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider;
import org.eclipse.jface.databinding.viewers.ViewerProperties;
import org.eclipse.jface.databinding.viewers.ViewersObservables;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Widget;
import org.goko.common.bindings.converters.NegateBooleanConverter;
import org.goko.common.bindings.toolbar.ToolBarObservable;
import org.goko.common.bindings.validator.StringBigDecimalValidator;
import org.goko.common.bindings.validator.StringRealNumberValidator;
import org.goko.common.elements.combo.GkCombo;
import org.goko.common.elements.combo.LabeledValue;
import org.goko.common.elements.combo.v2.GkCombo2;
import org.goko.common.preferences.fieldeditor.ui.UiFieldEditor;
import org.goko.core.common.applicative.logging.IApplicativeLogService;
import org.goko.core.common.event.EventDispatcher;
import org.goko.core.common.exception.GkException;
import org.goko.core.common.exception.GkFunctionalException;
import org.goko.core.common.exception.GkTechnicalException;
import org.goko.core.log.GkLog;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.DecimalFormatSymbols;
public abstract class AbstractController<T extends AbstractModelObject> extends EventDispatcher{
private static final GkLog LOG = GkLog.getLogger(AbstractController.class);
private DataBindingContext bindingContext;
private T dataModel;
private List<Binding> bindings;
@Inject
private IApplicativeLogService applicativeLogService;
private List<UiFieldEditor<?>> lstFieldEditor;
public AbstractController(T binding) {
bindingContext = new DataBindingContext();
this.dataModel = binding;
this.bindings = new ArrayList<Binding>();
lstFieldEditor = new ArrayList<UiFieldEditor<?>>();
}
/**
* Initialization method
* @throws GkException GkException
*/
public abstract void initialize() throws GkException;
public void addBigDecimalModifyBinding(Object source, String property) throws GkException{
addBigDecimalModifyBinding(source, getDataModel(), property);
}
public void addBigDecimalModifyBinding(Object source, Object model, String property) throws GkException{
DecimalFormat format = new DecimalFormat();
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator('.');
format.setDecimalFormatSymbols(symbols);
StringToNumberConverter stringConverter = StringToNumberConverter.toBigDecimal(format);
NumberToStringConverter numberConverter = NumberToStringConverter.fromBigDecimal(format);
addTextModifyBinding(source, model, property,new StringBigDecimalValidator(), stringConverter, numberConverter);
}
public void addDoubleModifyBinding(Object source, String property) throws GkException{
DecimalFormat format = new DecimalFormat();
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator('.');
format.setDecimalFormatSymbols(symbols);
StringToNumberConverter stringConverter = StringToNumberConverter.toDouble(format, false);
NumberToStringConverter numberConverter = NumberToStringConverter.fromDouble(format, false);
addTextModifyBinding(source, property,new StringRealNumberValidator(), stringConverter, numberConverter);
}
public void addIntegerModifyBinding(Object source, String property) throws GkException{
StringToNumberConverter converter = StringToNumberConverter.toInteger(false);
addTextModifyBinding(source, property,new StringRealNumberValidator(), converter);
}
/**
* Binding between an object and a property, based on text modification
* @param source the UI object
* @param property the name of the property
* @throws GkException GkException
*/
public void addTextModifyBinding(Object source, String property) throws GkException{
addTextModifyBinding(source, property,null);
}
public void addTextModifyBinding(Object source, String property, IValidator validator) throws GkException{
addTextModifyBinding(source, property, validator, null);
}
public void addTextModifyBinding(Object source, String property, IValidator validator, IConverter targetToModel ) throws GkException{
addTextModifyBinding(source, property, validator, targetToModel, null);
}
public void addTextModifyBinding(Object source, String property, IValidator validator, IConverter targetToModel, IConverter modelToTarget) throws GkException{
addTextModifyBinding(source, dataModel, property, validator, targetToModel, modelToTarget);
}
protected void addTextModifyBinding(Object source, Object model, String property, IValidator validator, IConverter targetToModel, IConverter modelToTarget) throws GkException{
verifyGetter(model,property);
verifySetter(model,property);
IObservableValue widgetObserver = WidgetProperties.text(SWT.Modify).observe(source);
IObservableValue modelObserver = BeanProperties.value(property).observe(model);
UpdateValueStrategy targetToModelUpdateStrategy = new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE);
if(validator != null){
targetToModelUpdateStrategy.setAfterGetValidator(validator);
}
if(targetToModel != null){
targetToModelUpdateStrategy.setConverter(targetToModel);
}
UpdateValueStrategy modelToTargetUpdateStrategy = new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE);
if(modelToTarget != null){
modelToTargetUpdateStrategy.setConverter(modelToTarget);
} //
Binding binding = bindingContext.bindValue(widgetObserver, modelObserver, targetToModelUpdateStrategy, modelToTargetUpdateStrategy);
bindings.add(binding);
if(validator != null){
ControlDecorationSupport.create(binding, SWT.RIGHT | SWT.TOP);
}
}
/**
* Binding between an source and a property to change the text of the source
* @param source the widget use to display the text
* @param property the property name
* @throws GkException GkException
*/
public void addTextDisplayBinding(Object source, String property) throws GkException{
verifyGetter(dataModel,property);
verifySetter(dataModel,property);
IObservableValue widgetObserver = WidgetProperties.text().observe(source);
IObservableValue modelObserver = BeanProperties.value(property).observe(dataModel);
Binding binding = bindingContext.bindValue(widgetObserver, modelObserver, null, new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE));
bindings.add(binding);
}
/**
* Binding between an object and a property, based on items list
* @param source the UI object
* @param property the name of the property
* @throws GkException GkException
*/
public void addItemsBinding(Object source, String property) throws GkException{
verifyGetter(dataModel,property);
verifySetter(dataModel,property);
IObservableList itemsComboSerialPortObserveWidget = WidgetProperties.items().observe(source);
IObservableList comPortListBindingsObserveList = BeanProperties.list(property).observe(dataModel);
Binding binding = bindingContext.bindList(itemsComboSerialPortObserveWidget, comPortListBindingsObserveList, null, null);
bindings.add(binding);
}
/**
* Binding between an object and a property, based on items list
* @param source the UI object
* @param property the name of the property
* @throws GkException GkException
*/
public void addItemsBinding(Viewer source, String property) throws GkException{
verifyGetter(dataModel,property);
verifySetter(dataModel,property);
IViewerObservableValue itemsComboSerialPortObserveWidget = ViewerProperties.input().observe(source);
IObservableValue comPortListBindingsObserveList = BeanProperties.value(property).observe(dataModel);
Binding binding = bindingContext.bindValue(itemsComboSerialPortObserveWidget, comPortListBindingsObserveList, new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE), new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE));
bindings.add(binding);
}
/**
* Binding between an GkCombo and a property, based on items list
* @param source the UI object
* @param property the name of the property
* @throws GkException GkException
*/
public void addItemsBinding(GkCombo<?> source, String property) throws GkException{
verifyGetter(dataModel,property);
verifySetter(dataModel,property);
ObservableListContentProvider listContentProvider = new ObservableListContentProvider();
IObservableMap observeMap = PojoObservables.observeMap(listContentProvider.getKnownElements(), LabeledValue.class, "label");
source.setLabelProvider(new ObservableMapLabelProvider(observeMap));
source.setContentProvider(listContentProvider);
source.setInput(BeanProperties.list(property).observe(dataModel));
}
/**
* Binding between an GkCombo and a property, based on items list
* @param source the UI object
* @param property the name of the property
* @throws GkException GkException
*/
public void addItemsBinding(GkCombo2<?> source, String property) throws GkException{
verifyGetter(dataModel,property);
verifySetter(dataModel,property);
ObservableListContentProvider listContentProvider = new ObservableListContentProvider();
IObservableMap observeMap = PojoObservables.observeMap(listContentProvider.getKnownElements(), LabeledValue.class, "label");
source.setLabelProvider(new ObservableMapLabelProvider(observeMap));
source.setContentProvider(listContentProvider);
source.setInput(BeanProperties.list(property).observe(dataModel));
}
/**
* Binding between an object and a property, based on item selection
* @param source the UI object
* @param property the name of the property
* @throws GkException GkException
*/
public void addItemSelectionBinding(ComboViewer source, String property) throws GkException{
addItemSelectionBinding(source, dataModel, property);
}
/**
* Binding between an object and a property, based on item selection
* @param source the UI object
* @param property the name of the property
* @throws GkException GkException
*/
public void addItemSelectionBinding(ComboViewer source, Object modelObject, String property) throws GkException{
verifyGetter(modelObject,property);
verifySetter(modelObject,property);
IObservableValue target = ViewersObservables.observeSingleSelection(source);
IObservableValue model = BeanProperties.value(property).observe(dataModel);
Binding binding = bindingContext.bindValue(target, model,null,null);
bindings.add(binding);
}
public <T> void addItemValueSelectionBinding(final GkCombo2<T> source, Object modelObject, String property) throws GkException{
verifyGetter(modelObject,property);
verifySetter(modelObject,property);
IObservableValue target = ViewersObservables.observeSingleSelection(source);
IObservableValue model = BeansObservables.observeValue(modelObject, property);
Binding binding = bindingContext.bindValue(target, model,null,null);
bindings.add(binding);
}
public void addTableViewerInputBinding(){
}
public void addSelectionBinding(ToolItem target, String property) throws GkException {
verifyGetter(dataModel,property);
verifySetter(dataModel,property);
IObservableValue observeSelectionBtnCheckButtonObserveWidget = ToolBarObservable.observeSelection(target);
IObservableValue enabledBindingsObserveValue = BeanProperties.value(property).observe(dataModel);
bindingContext.bindValue(observeSelectionBtnCheckButtonObserveWidget, enabledBindingsObserveValue, null, null);
}
public void addSelectionBinding(Object target, String property) throws GkException {
verifyGetter(dataModel,property);
verifySetter(dataModel,property);
IObservableValue observeSelectionBtnCheckButtonObserveWidget = WidgetProperties.selection().observe(target);
IObservableValue enabledBindingsObserveValue = BeanProperties.value(property).observe(dataModel);
bindingContext.bindValue(observeSelectionBtnCheckButtonObserveWidget, enabledBindingsObserveValue, null, null);
}
public void addSelectionBinding(Viewer target, String property) throws GkException {
verifyGetter(dataModel,property);
verifySetter(dataModel,property);
IObservableValue observeSelectionObserveWidget = ViewersObservables.observeSingleSelection(target);
IObservableValue enabledBindingsObserveValue = BeanProperties.value(property).observe(dataModel);
bindingContext.bindValue(observeSelectionObserveWidget, enabledBindingsObserveValue, null, null);
}
public void addTableSelectionBinding(TableViewer target, String property) throws GkException {
verifyGetter(dataModel,property);
verifySetter(dataModel,property);
IObservableValue observeSelectionTableObserveWidget = ViewersObservables.observeSingleSelection(target);
IObservableValue enabledBindingsObserveValue = BeanProperties.value(property).observe(dataModel);
bindingContext.bindValue(observeSelectionTableObserveWidget, enabledBindingsObserveValue, null, null);
}
public void addPropertyBinding(Object target, String targetProperty, String modelProperty) throws GkException {
verifyGetter(dataModel,modelProperty);
verifySetter(dataModel,modelProperty);
verifyGetter(target,targetProperty);
verifySetter(target,targetProperty);
IObservableValue observeSelectionBtnCheckButtonObserveWidget = PojoObservables.observeValue(target,targetProperty);
IObservableValue enabledBindingsObserveValue = BeanProperties.value(modelProperty).observe(dataModel);
bindingContext.bindValue(observeSelectionBtnCheckButtonObserveWidget, enabledBindingsObserveValue);
}
/**
* Enables/disable the source when the property is respectively true/false
* @param source the UI object
* @param property the name of the property
* @throws GkException GkException
*/
public void addEnableBinding(Widget source, String property) throws GkException{
verifyGetter(dataModel,property);
verifySetter(dataModel,property);
IObservableValue target = WidgetProperties.enabled().observe(source);
IObservableValue model = BeansObservables.observeValue(dataModel, property);
Binding binding = bindingContext.bindValue(target, model);
bindings.add(binding);
}
/**
* Enables/disable the source when the property is respectively true/false
* @param source the UI object
* @param property the name of the property
* @throws GkException GkException
*/
public void addVisibleBinding(Widget source, String property) throws GkException{
verifyGetter(dataModel,property);
verifySetter(dataModel,property);
IObservableValue target = WidgetProperties.visible().observe(source);
IObservableValue model = BeansObservables.observeValue(dataModel, property);
Binding binding = bindingContext.bindValue(target, model);
bindings.add(binding);
}
/**
* Enables/disable the source when the property is respectively true/false
* @param source the UI object
* @param property the name of the property
* @throws GkException GkException
*/
public void addEnableBinding(ComboViewer source, String property) throws GkException{
addEnableBinding(source.getCombo(), property);
}
/**
* Enables/disable the source when the property is respectively false/true
* @param source the UI object
* @param property the name of the property
* @throws GkException GkException
*/
public void addEnableReverseBinding(Widget source, String property) throws GkException{
verifyGetter(dataModel,property);
verifySetter(dataModel,property);
IObservableValue target = WidgetProperties.enabled().observe(source);
IObservableValue model = BeanProperties.value(property).observe(dataModel);
UpdateValueStrategy strategy = new UpdateValueStrategy();
strategy.setConverter(new NegateBooleanConverter());
Binding binding = bindingContext.bindValue(target, model,new UpdateValueStrategy(UpdateValueStrategy.POLICY_NEVER), strategy);
bindings.add(binding);
}
/**
* Enables/disable the source when the property is respectively false/true
* @param source the UI object
* @param property the name of the property
* @throws GkException GkException
*/
public void addEnableReverseBinding(ComboViewer source, String property) throws GkException{
addEnableReverseBinding(source.getCombo(), property);
}
/**
* Binding between a button and a property, based on selection
* @param source the UI object
* @param property the name of the property
* @throws GkException GkException
*/
public void addCheckboxSelectionBinding(Button button, String property) {
IObservableValue widgetValue = WidgetProperties.selection().observe(button);
IObservableValue modelValue = BeansObservables.observeValue(dataModel, property);
Binding binding = bindingContext.bindValue(widgetValue, modelValue);
bindings.add(binding);
}
/**
* Make sure the getter for the given property exists
* @param source the source object
* @param property the property to search for
* @return the {@link Method}
* @throws GkException GkException
*/
private Method verifyGetter(Object source, String property) throws GkException{
String firstLetter = StringUtils.substring(property, 0,1);
String otherLetters = StringUtils.substring(property, 1);
String getGetterName = "^get"+ StringUtils.upperCase(firstLetter)+otherLetters+"$";
String isGetterName = "^is"+ StringUtils.upperCase(firstLetter)+otherLetters+"$";
Method[] methodArray = source.getClass().getMethods();
for (Method method : methodArray) {
//if(StringUtils.equals(getterName, method.getName())){
if(method.getName().matches(getGetterName) || method.getName().matches(isGetterName)){
return method;
}
}
String getterNameDisplay = "get"+ StringUtils.upperCase(firstLetter)+otherLetters+"/is"+ StringUtils.upperCase(firstLetter)+otherLetters;
throw new GkTechnicalException("Cannot find getter (looking for '"+getterNameDisplay+"') for property '"+property+"' on object "+source.getClass()+". Make sure it's public and correctly spelled");
}
/**
* Make sure the setter for the given property exists
* @param source the source object
* @param property the property to search for
* @throws GkException GkException
*/
private void verifySetter(Object source, String property) throws GkException{
String firstLetter = StringUtils.substring(property, 0,1);
String otherLetters = StringUtils.substring(property, 1);
String setterName = "set"+ StringUtils.upperCase(firstLetter)+otherLetters;
boolean found = false;
Method getMethod = verifyGetter(source, property);
Method[] methodArray = source.getClass().getMethods();
for (Method method : methodArray) {
if(StringUtils.equals(setterName, method.getName())){
Class<?>[] paramsArray = method.getParameterTypes();
if(paramsArray != null && paramsArray.length == 1 && paramsArray[0] == getMethod.getReturnType()){
found = true;
break;
}
}
}
//source.getClass().getDeclaredMethod(setterName, getMethod.getReturnType());
if(!found){
throw new GkTechnicalException("Cannot find setter (looking for '"+setterName+"') for property '"+property+"' on object "+source.getClass()+". Make sure it's public and correctly spelled");
}
}
/**
* Validates
* @return
* @throws GkException
*/
public boolean validate() {
List<BindingStatus> lstStatus = new ArrayList<BindingStatus>();
for(Binding binding : bindings){
IObservableValue status = binding.getValidationStatus();
if(status != null){
BindingStatus statusValue = (BindingStatus) status.getValue();
if(statusValue.getSeverity() == BindingStatus.ERROR){
lstStatus.add(statusValue);
}
}
}
getDataModel().setValidationMessages(lstStatus);
return CollectionUtils.isEmpty(lstStatus);
}
public void log(GkException e){
LOG.error(e);
if(applicativeLogService != null){
if(e instanceof GkFunctionalException){
applicativeLogService.error(e.getMessage(), StringUtils.EMPTY);
}
}
}
@Deprecated
public void notifyException(Exception e){
if(e instanceof GkFunctionalException){
notifyWarning(((GkFunctionalException)e).getLocalizedMessage());
return;
}
notifyError(e);
}
/**
* display an error dialog
* @param message the exception to display
*/
@Deprecated
private void notifyError(Exception e){
notifyListeners(new ErrorEvent(e,"DEFAULT"));
}
/**
* display a warning dialog
* @param message the message to display
*/
private void notifyWarning(String message){
notifyListeners(new WarningEvent(message));
}
/**
* Adds binding for the header displaying validation messages
* @param source the label in which the errors will be displayed
* @throws GkException GkException
*/
public void addValidationMessagesBinding(final Label source) throws GkException {
AggregateValidationStatus aggValidationStatus = new AggregateValidationStatus(bindingContext.getBindings(), AggregateValidationStatus.MAX_SEVERITY);
aggValidationStatus.addChangeListener(new IChangeListener() {
@Override
public void handleChange(ChangeEvent event) {
StringBuffer errorBuffer = new StringBuffer();
source.setText(StringUtils.EMPTY);
source.setVisible(false);
for (Object o : bindingContext.getBindings()) {
Binding binding = (Binding) o;
IStatus status = (IStatus) binding .getValidationStatus().getValue();
Control control = null;
if (binding.getTarget() instanceof ISWTObservable) {
ISWTObservable swtObservable = (ISWTObservable) binding.getTarget();
control = (Control) swtObservable.getWidget();
}
if (!status.isOK()) {
if(StringUtils.isNotEmpty(status.getMessage())){
errorBuffer.append(status.getMessage());
errorBuffer.append(System.lineSeparator());
source.setText(errorBuffer.toString().trim());
source.setVisible(true);
return;
}
}
}
}
});
bindingContext.bindValue(SWTObservables.observeText(source),
aggValidationStatus, null, null);
}
/**
* @return the bindings
*/
public T getDataModel() {
return dataModel;
}
/**
* @param bindings the bindings to set
*/
protected void setDataModel(T bindings) {
this.dataModel = bindings;
}
public void addBinding(Binding binding){
bindings.add(binding);
}
/**
* @return the bindings
*/
public List<Binding> getBindings() {
return bindings;
}
/**
* @param bindings the bindings to set
*/
public void setBindings(List<Binding> bindings) {
this.bindings = bindings;
}
/**
* @return the bindingContext
*/
public DataBindingContext getBindingContext() {
return bindingContext;
}
/**
* @param bindingContext the bindingContext to set
*/
public void setBindingContext(DataBindingContext bindingContext) {
this.bindingContext = bindingContext;
}
public void addFieldEditor(UiFieldEditor<?> fieldEditor) throws GkException{
this.lstFieldEditor.add(fieldEditor);
bindings.add(fieldEditor.getBinding(bindingContext, dataModel));
}
}