/* * Copyright (C) 2011 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program 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. * This program 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 this program; if not, see http://www.gnu.org/licenses/ */ package org.esa.snap.rcp.preferences; import com.bc.ceres.binding.Property; import com.bc.ceres.binding.PropertyContainer; import com.bc.ceres.binding.PropertyDescriptor; import com.bc.ceres.binding.PropertySet; import com.bc.ceres.binding.ValidationException; import com.bc.ceres.binding.Validator; import com.bc.ceres.binding.ValueRange; import com.bc.ceres.binding.ValueSet; import com.bc.ceres.core.Assert; import com.bc.ceres.swing.binding.BindingContext; import org.esa.snap.core.util.StringUtils; import org.esa.snap.core.util.SystemUtils; import org.esa.snap.rcp.SnapApp; import org.esa.snap.runtime.Config; import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.Lookup; import javax.swing.JComponent; import javax.swing.JPanel; import java.beans.PropertyChangeListener; import java.util.HashSet; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; /** * Abstract superclass for preferences pages. Subclasses need to be annotated with either * {@link OptionsPanelController.TopLevelRegistration} or {@link OptionsPanelController.SubRegistration}. * * @author thomas */ public abstract class DefaultConfigController extends OptionsPanelController { private PreferencesPanel panel; private BindingContext bindingContext; /** * Create a {@link PropertySet} object instance that holds all parameters. * Clients that want to maintain properties need to overwrite this method. * * @return An instance of {@link PropertySet}, holding all configuration parameters. * * @see #createPropertySet(Object) */ protected abstract PropertySet createPropertySet(); /** * Create a panel that allows the user to set the parameters in the given {@link BindingContext}. Clients that want * to create their own panel representation on the given properties need to overwrite this method. * * @param context The {@link BindingContext} for the panel. * * @return A JPanel instance for the given {@link BindingContext}, never {@code null}. */ protected JPanel createPanel(BindingContext context) { Assert.state(isInitialised()); return new PreferencesPanel(null, bindingContext).getComponent(); } /** * Configure the passed binding context. This is intended to be used to create {@code enablements} in order to * add dependencies between property states. The default implementation does nothing. * * @param context The {@link BindingContext} to configure. * * @see com.bc.ceres.swing.binding.Enablement * @see com.bc.ceres.swing.binding.BindingContext#bindEnabledState(String, boolean, com.bc.ceres.swing.binding.Enablement.Condition) * @see com.bc.ceres.swing.binding.BindingContext#bindEnabledState(String, boolean, String, Object) */ protected void configure(BindingContext context) { } protected BindingContext getBindingContext() { return bindingContext; } /** * Creates a PropertyContainer for any bean. The bean parameters need to be annotated with {@link Preference}. * * @param bean a bean with fields annoted with {@link Preference}. * * @return an instance of {@link PropertyContainer}, fit for passing within overridden * {@link #createPropertySet()}. */ protected final PropertyContainer createPropertySet(Object bean) { return PropertyContainer.createObjectBacked(bean, field -> { Class<Preference> annotationClass = Preference.class; Preference annotation = field.getAnnotation(annotationClass); if (annotation == null) { throw new IllegalStateException("Field '" + field.getName() + "' must be annotated with '" + annotationClass.getSimpleName() + "'."); } String label = annotation.label(); String key = annotation.key(); String[] valueSet = annotation.valueSet(); String valueRange = annotation.interval(); String description = annotation.description(); Validator validator = createValidator(annotation.validatorClass()); Assert.state(StringUtils.isNotNullAndNotEmpty(label), "Label of field '" + field.getName() + "' must not be null or empty."); Assert.state(StringUtils.isNotNullAndNotEmpty(key), "Key of field '" + field.getName() + "' must not be null or empty."); boolean isDeprecated = field.getAnnotation(Deprecated.class) != null; PropertyDescriptor valueDescriptor = new PropertyDescriptor(key, field.getType()); valueDescriptor.setDeprecated(isDeprecated); valueDescriptor.setAttribute("key", key); valueDescriptor.setAttribute("displayName", label); valueDescriptor.setAttribute("configName", annotation.config()); valueDescriptor.setAttribute("propertyValidator", validator); valueDescriptor.setDescription(description); if (valueSet.length > 0) { valueDescriptor.setValueSet(new ValueSet(valueSet)); } if (StringUtils.isNotNullAndNotEmpty(valueRange)) { valueDescriptor.setValueRange(ValueRange.parseValueRange(valueRange)); } return valueDescriptor; }); } @Override public void update() { if (isInitialised()) { for (Property property : bindingContext.getPropertySet().getProperties()) { String key = property.getDescriptor().getAttribute("key").toString(); String preferencesValue = getPreferences(property.getDescriptor()).get(key, null); if (preferencesValue != null) { try { property.setValueFromText(preferencesValue); SystemUtils.LOG.fine(String.format("Bean property value change: %s = %s", property.getName(), property.getValueAsText())); } catch (ValidationException e) { SystemUtils.LOG.severe("Failed to set bean value from preferences: " + e.getMessage()); } } } } } @Override public void applyChanges() { if (isInitialised()) { HashSet<Preferences> set = new HashSet<>(); for (Property property : bindingContext.getPropertySet().getProperties()) { String key = property.getDescriptor().getAttribute("key").toString(); String value = property.getValueAsText(); Preferences preferences = getPreferences(property.getDescriptor()); preferences.put(key, value); set.add(preferences); SystemUtils.LOG.fine(String.format("Preferences value change: %s = %s", key, preferences.get(key, null))); } for (Preferences preferences : set) { try { preferences.flush(); } catch (BackingStoreException e) { SnapApp.getDefault().handleError("Failed to store user preferences.", e); } } setChanged(false); } } @Override public void cancel() { if (isInitialised()) { setChanged(false); } } @Override public boolean isValid() { if (!isInitialised()) { return false; } for (Property property : bindingContext.getPropertySet().getProperties()) { Validator validator = (Validator) property.getDescriptor().getAttribute("propertyValidator"); try { validator.validateValue(property, property.getValue()); } catch (ValidationException e) { return false; } } return true; } protected void setChanged(boolean changed) { panel.setChanged(changed); } @Override public boolean isChanged() { return isInitialised() && panel.isChanged(); } @Override public JComponent getComponent(Lookup lookup) { if (!isInitialised()) { initialize(); } return panel.getComponent(); } @Override public void addPropertyChangeListener(PropertyChangeListener propertyChangeListener) { if (bindingContext != null) { bindingContext.addPropertyChangeListener(propertyChangeListener); } } @Override public void removePropertyChangeListener(PropertyChangeListener propertyChangeListener) { if (bindingContext != null) { bindingContext.removePropertyChangeListener(propertyChangeListener); } } protected boolean isInitialised() { return bindingContext != null; } private void initialize() { bindingContext = new BindingContext(createPropertySet()); panel = new PreferencesPanel(createPanel(bindingContext), bindingContext); panel.getComponent(); // trigger component initialisation configure(bindingContext); } private Preferences getPreferences(PropertyDescriptor propertyDescriptor) { Object configNameValue = propertyDescriptor.getAttribute("configName"); String configName = configNameValue != null ? configNameValue.toString().trim() : null; if (configName == null || configName.isEmpty()) { return SnapApp.getDefault().getPreferences(); } return Config.instance(configName).load().preferences(); } private Validator createValidator(Class<? extends Validator> validatorClass) { Validator validator; try { validator = validatorClass.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new IllegalStateException(e); } return validator; } }