/* * Rapid Beans Framework: EditorProperty.java * * Copyright (C) 2009 Martin Bluemel * * Creation Date: 02/15/2006 * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser 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 Lesser General Public License for more details. * You should have received a copies of the GNU Lesser General Public License and the * GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>. */ package org.rapidbeans.presentation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.rapidbeans.core.basic.Property; import org.rapidbeans.core.basic.PropertyCollection; import org.rapidbeans.core.basic.PropertyString; import org.rapidbeans.core.basic.RapidBean; import org.rapidbeans.core.basic.ThreadLocalValidationSettings; import org.rapidbeans.core.common.RapidBeansLocale; import org.rapidbeans.core.common.ReadonlyListCollection; import org.rapidbeans.core.event.PropertyChangeEvent; import org.rapidbeans.core.event.PropertyChangeListener; import org.rapidbeans.core.exception.RapidBeansRuntimeException; import org.rapidbeans.core.exception.ValidationException; import org.rapidbeans.core.type.PropertyType; import org.rapidbeans.core.type.TypePropertyChoice; import org.rapidbeans.core.type.TypePropertyCollection; import org.rapidbeans.core.type.TypePropertyQuantity; import org.rapidbeans.core.type.TypePropertyString; import org.rapidbeans.core.type.TypeRapidBean; import org.rapidbeans.core.util.StringHelper; import org.rapidbeans.datasource.event.AddedEvent; import org.rapidbeans.datasource.event.ChangedEvent; import org.rapidbeans.datasource.event.DocumentChangeListener; import org.rapidbeans.datasource.event.RemovedEvent; import org.rapidbeans.domain.finance.Currency; import org.rapidbeans.presentation.config.ConfigEditorBean; import org.rapidbeans.presentation.config.ConfigPropEditor; import org.rapidbeans.presentation.config.ConfigPropEditorBean; import org.rapidbeans.presentation.config.EditorPropNullBehaviour; import org.rapidbeans.security.User; /** * the bean property editor GUI. * * @author Martin Bluemel */ public abstract class EditorProperty implements View, DocumentChangeListener, PropertyChangeListener { /** * The null behavior. */ private EditorPropNullBehaviour nullBehaviour = EditorPropNullBehaviour.always_empty; /** * @return the null behavior */ public EditorPropNullBehaviour getNullBehaviour() { return nullBehaviour; } /** * @return the property editors label widget */ public abstract Object getLabelWidget(); /** * set the focus to the input field's widget. */ public abstract void setFocus(); /** * the bean property to edit. */ private Property property; /** * @return the property currently edited */ public Property getProperty() { return this.property; } /** * setter. * * @param prop * the property to set. */ protected void setProperty(final Property prop) { this.property = prop; } /** * the bean property with it's original values. */ private Property propertyBak; /** * @return the backup property. */ protected Property getPropertyBak() { return this.propertyBak; } /** * @param bakprop * the propertyBak to set */ protected void setPropertyBak(final Property bakprop) { this.propertyBak = bakprop; } /** * the locale. */ private RapidBeansLocale locale = null; /** * @return the locale */ protected RapidBeansLocale getLocale() { return this.locale; } /** * the editor's UI event lock to avoid input event feedback during updating * the UI accoring to a bean's contents. */ private int uiEventLock = 0; /** * @return the UI event lock */ public boolean getUIEventLock() { return this.uiEventLock > 0; } /** * increase the UI event lock. */ public void setUIEventLock() { this.uiEventLock++; } /** * decrease the UI event lock. */ public void releaseUIEventLock() { if (this.uiEventLock > 0) { this.uiEventLock--; } } /** * the editor's specific UI update lock to avoid unwanted presentation of * normalized forms before having completed the input (usually used text * editors). */ private int uiUpdateLock = 0; /** * @return the UI update lock */ public boolean getUIUpdateLock() { return this.uiUpdateLock > 0; } /** * increase the UI update lock. */ public void setUIUpdateLock() { this.uiUpdateLock++; } /** * decrease the UI update lock. */ public void releaseUIUpdateLock() { if (this.uiUpdateLock > 0) { this.uiUpdateLock--; } } /** * the parent bean editor. */ private EditorBean beanEditor = null; /** * @return the parent bean editor */ public EditorBean getBeanEditor() { return this.beanEditor; } public ConfigPropEditorBean getConfig() { ConfigPropEditorBean cfg = null; if (this.beanEditor != null) { final ConfigEditorBean beanCfg = this.beanEditor.getConfiguration(); if (beanCfg != null) { cfg = beanCfg.getPropertycfg(this.property.getName()); } } return cfg; } /** * the listeners. */ private List<EditorPropertyListener> listeners = new ArrayList<EditorPropertyListener>(); /** * @param listener * the listener to add. */ public void addPropertyEditorListener(final EditorPropertyListener listener) { this.listeners.add(listener); } /** * @param listener * the listener to remove. */ public void removePropertyEditorListener(final EditorPropertyListener listener) { this.listeners.remove(listener); } /** * fire an input file changed event. */ public void fireInputFieldChanged() { if (this.getUIEventLock()) { return; } // write the value into the bean // before performing any further checks boolean modifiesBefore = false; Object oldValue = null; try { ThreadLocalEventLock.set(this); modifiesBefore = this.beanEditor.getModifies(); this.beanEditor.setModifies(true); oldValue = this.property.getValue(); if (this.property instanceof PropertyCollection) { ((PropertyCollection) this.property).setValue(this.getInputFieldValue(), true, false); } else { this.property.setValue(this.getInputFieldValue()); } if (this.property.getType().isKeyCandidate()) { this.property.getBean().clearId(); } } catch (ValidationException e) { // Do not validate here at that point of time. // Simply rewrite the old value. try { ThreadLocalValidationSettings.validationOff(); this.property.setValue(oldValue); } finally { ThreadLocalValidationSettings.remove(); } } finally { ThreadLocalEventLock.release(); this.beanEditor.setModifies(modifiesBefore); } for (EditorPropertyListener listener : this.listeners) { listener.inputFieldChanged(this); } } /** * handler for added bean. * * @param e * the removed event */ public void beanRemoved(final RemovedEvent e) { } /** * @return the view's title. */ public String getTitle() { return null; } /** * display a value on the GUI. */ public abstract void updateUI(); /** * check if the input field has changed. * * @return if the input field has a different value than the property. */ public boolean isInputFieldChanged() { boolean equals = true; Object newValue = null; try { final Object val = this.getInputFieldValue(); newValue = this.getProperty().convertValue(val); } catch (ValidationException e) { equals = false; } if (equals) { equals = false; Object oldValue = this.propertyBak.getValue(); if (newValue == null && oldValue == null) { equals = true; } else if (newValue != null && oldValue != null) { if (oldValue instanceof ReadonlyListCollection<?> && newValue instanceof Collection<?>) { equals = true; final Collection<?> newValueCol = (Collection<?>) newValue; final ReadonlyListCollection<?> oldValueCol = (ReadonlyListCollection<?>) oldValue; if (newValueCol.size() != oldValueCol.size()) { equals = false; } else { for (Object o1 : newValueCol) { if (!oldValueCol.contains(o1)) { equals = false; break; } else { final Object o2 = oldValueCol.get(oldValueCol.indexOf(o1)); if (o1 == null && o2 == null) { // equals stays true, do nothing } else if (o1 == null || o2 == null || (!(o1.equals(o2)))) { equals = false; break; } } } } } else if (oldValue instanceof RapidBean && newValue instanceof Collection<?>) { final Collection<?> newValueCol = (Collection<?>) newValue; if (newValueCol.size() != 1) { throw new RapidBeansRuntimeException("Can't compare a bean with a" + " Collection with size != 1 (size == " + newValueCol.size() + ")"); } final RapidBean newBean = (RapidBean) newValueCol.iterator().next(); equals = oldValue.equals(newBean); } else if (oldValue instanceof Collection<?> && newValue instanceof RapidBean) { final Collection<?> oldValueCol = (Collection<?>) oldValue; if (oldValueCol.size() != 1) { throw new RapidBeansRuntimeException("Can't compare a" + " Collection with size != 1 (size == " + oldValueCol.size() + ") with a bean"); } final RapidBean oldBean = (RapidBean) oldValueCol.iterator().next(); equals = oldBean.equals(newValue); } else { equals = newValue.equals(oldValue); } } } return !equals; } /** * @return the input field value */ public abstract Object getInputFieldValue(); /** * @return the input field value as text. */ public abstract Object getInputFieldValueString(); /** * validate an input field. * * @return the value object if the value is valid */ public abstract Object validateInputField(); /** * validate an input field. * * @return if the string in the input field is valid or at least could at * least get after appending additional characters. * * @param ex * the validation exception */ protected boolean hasPotentiallyValidInputField(final ValidationException ex) { return false; } /** * create a new editor. * * @param client * the client * @param bizBeanEditor * the parent bean editor * @param prop * the bean property to edit * @param bakProp * the bean backup property used instead of a transaction * * @return the editor object */ public static EditorProperty createInstance(final Application client, final EditorBean bizBeanEditor, final Property prop, final Property bakProp) { EditorProperty editor = null; final PropertyType proptype = prop.getType().getProptype(); // String proptypename = proptype.name(); String widgetname = null; String classname = null; // try to find the property editor class out ouf the UI configuration final ConfigEditorBean editorConfig = bizBeanEditor.getConfiguration(); if (editorConfig != null) { final ConfigPropEditorBean propEditorConfig = editorConfig.getPropertycfg(prop.getType().getPropName()); if (propEditorConfig != null) { final ConfigPropEditor propEditorConfigEditor = propEditorConfig.getEditor(); if (propEditorConfigEditor != null) { // 1st try: editorclass if (propEditorConfigEditor.getEditorclass() != null) { classname = propEditorConfigEditor.getEditorclass(); } else if (propEditorConfigEditor.getBasepackage() != null && propEditorConfigEditor.getClassnamepart() != null) { classname = propEditorConfigEditor.getBasepackage() + "." + client.getConfiguration().getGuitype().name() + ".EditorProperty" + propEditorConfigEditor.getClassnamepart() + StringHelper.upperFirstCharacter(client.getConfiguration().getGuitype().name()); } } } } if (classname == null) { if ((prop.getBean() instanceof User || TypeRapidBean.isSameOrSubtype( TypeRapidBean.forName("org.rapidbeans.security.User"), prop.getBean().getType())) && prop instanceof PropertyString && prop.getType().getPropName().equals("pwd")) { classname = "org.rapidbeans.presentation.swing.EditorPropertyPwd"; } } // if a property editor is not configured use the default editor if (classname == null) { switch (proptype) { case bool: widgetname = "Checkbox"; break; case choice: if (((TypePropertyChoice) prop.getType()).getMultiple()) { widgetname = "List"; } else { widgetname = "Combobox"; } break; case date: widgetname = "Date"; break; case collection: final TypePropertyCollection colProptype = (TypePropertyCollection) prop.getType(); if (colProptype.getMaxmult() == 1) { widgetname = "Combobox"; } else { widgetname = "List"; } break; case quantity: final TypePropertyQuantity qProptype = (TypePropertyQuantity) prop.getType(); if ((Currency.dollar).getType() == qProptype.getQuantitytype().getUnitInfo()) { widgetname = "Money"; } else { widgetname = "Quantity"; } break; case file: widgetname = "File"; break; case string: final TypePropertyString sProptype = (TypePropertyString) prop.getType(); if (sProptype.getMultiline()) { widgetname = "TextArea"; } else { widgetname = "Text"; } break; default: widgetname = "Text"; break; } classname = "org.rapidbeans.presentation." + client.getConfiguration().getGuitype().name() + ".EditorProperty" + widgetname + StringHelper.upperFirstCharacter(client.getConfiguration().getGuitype().name()); } // instantiate the property editor Class<?> clazz; try { clazz = Class.forName(classname); } catch (ClassNotFoundException e) { throw new RapidBeansRuntimeException("class " + classname + " not found", e); } try { Constructor<?> constr = clazz.getConstructor(BBEDITORPROP_CONSTRUCTOR_TYPES); Object[] oa = { client, bizBeanEditor, prop, bakProp }; editor = (EditorProperty) constr.newInstance(oa); } catch (NoSuchMethodException e) { throw new RapidBeansRuntimeException("failed to initialize BBProp of Class \"" + clazz.getName() + "\".\n" + "Constructor (Application, Property) not found."); } catch (IllegalAccessException e) { throw new RapidBeansRuntimeException("failed to initialize BBProp of Class \"" + clazz.getName() + "\".\n" + "IllegalAccessException while calling the constructor."); } catch (InstantiationException e) { throw new RapidBeansRuntimeException("failed to initialize BBProp of Class \"" + clazz.getName() + "\".\n" + "InstatiationException while calling the constructor."); } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); if (t instanceof ExceptionInInitializerError) { t = ((ExceptionInInitializerError) t).getException(); } if (t instanceof RuntimeException) { throw ((RuntimeException) t); } throw new RapidBeansRuntimeException("failed to initialize EditorProperty of Class \"" + clazz.getName() + "\".\n" + "InvocationTargetException caused by " + t.getClass().getName() + " \"" + t.getMessage() + "\"" + " while calling the constructor."); } return editor; } /** * constructor. * * @param prop * the bean property to edit * @param bakProp * the backup property used instead of a transaction * @param beanEditor * the parent bean editor * @param client * the client */ protected EditorProperty(final Application client, final EditorBean beanEditor, final Property prop, final Property bakProp) { this.beanEditor = beanEditor; this.property = prop; this.propertyBak = bakProp; this.locale = client.getCurrentLocale(); if (this.beanEditor != null) { final ConfigEditorBean editorConfig = this.beanEditor.getConfiguration(); if (editorConfig != null) { final ConfigPropEditorBean propEditorConfig = editorConfig.getPropertycfg(prop.getType().getPropName()); if (propEditorConfig != null) { if (propEditorConfig.getNullbehaviour() != null) { this.nullBehaviour = propEditorConfig.getNullbehaviour(); } } } } this.getProperty().getBean().addPropertyChangeListener(this); } /** * the document view's name is the document's name. * * @return the document view's name */ public String getName() { return "documentview.beaneditor.propertyeditor." + this.property.getBean().getIdString() + "." + this.property.getType().getPropName(); } /** * validate the input field and mark wrong fields. * * @return the validated value */ protected Object validateInputFieldInternal() { Object value = null; try { ThreadLocalValidationSettings.readonlyOff(); value = this.getProperty().validate(this.getInputFieldValue()); } catch (ValidationException e) { throw e; } catch (RuntimeException e) { Throwable t = e.getCause(); while (t != null) { if (t instanceof ValidationException) { throw (ValidationException) t; } t = t.getCause(); } throw e; } finally { ThreadLocalValidationSettings.remove(); } return value; } /** * constant for constructor arguments of class TypeProperty. */ private static final Class<?>[] BBEDITORPROP_CONSTRUCTOR_TYPES = { Application.class, EditorBean.class, Property.class, Property.class }; /** * close the bean property editor. * * @return if cancelling is desired */ public boolean close() { getProperty().getBean().removePropertyChangeListener(this); return false; } /** * bean created event. * * @param e * added event */ public void beanAddPre(final AddedEvent e) { } /** * bean created event. * * @param e * added event */ public void beanAdded(final AddedEvent e) { } /** * bean deleted event. * * @param e * the deleted event */ public void beanRemovePre(final RemovedEvent e) { } /** * bean changed event. * * @param e * changed event */ public void beanChangePre(final ChangedEvent e) { } /** * bean changed event. * * @param e * changed event */ public void beanChanged(final ChangedEvent e) { boolean interestedForEvent = false; for (PropertyChangeEvent propEv : e.getPropertyEvents()) { final Property prop = propEv.getProperty(); if (prop == this.getProperty()) { interestedForEvent = true; break; } } if (!interestedForEvent) { return; } this.updateUI(); } @Override public void propertyChangePre(PropertyChangeEvent e) { } @Override public void propertyChanged(PropertyChangeEvent e) { if (getUIEventLock()) { return; } if (e.getProperty() == this.getProperty()) { this.updateUI(); } } /** * document save event. */ public void documentSaved() { } }