/******************************************************************************* * Copyright 2015 xWic group (http://www.xwic.de) * * Licensed under the Apache License, Version 2.0 (the "License"). * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *******************************************************************************/ package de.jwic.samples.controls.propeditor; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import de.jwic.base.Control; import de.jwic.base.ControlContainer; import de.jwic.base.IControlContainer; import de.jwic.controls.InputBox; import de.jwic.controls.ListBoxControl; import de.jwic.events.ElementSelectedEvent; import de.jwic.events.ElementSelectedListener; import de.jwic.events.SessionAdapter; import de.jwic.events.SessionEvent; import de.jwic.events.ValueChangedEvent; import de.jwic.events.ValueChangedListener; /** * Used to edit the properties of a bean. * @author Florian Lippisch */ public class PropertyEditorView extends ControlContainer { private Object bean = null; private transient List<String> propertyNames = null; private transient Map<String, PropInfo> controlMap = null; private String errorMessage = null; private ValueChangedListener changeListener; private ElementSelectedListener elementSelectedListener; private class PropInfo implements Serializable { PropertyDescriptor descriptor; String controlName; IPropertyMapper mapper; } /** * @param container * @param name */ public PropertyEditorView(IControlContainer container, String name) { super(container, name); changeListener = new ValueChangedListener() { public void valueChanged(ValueChangedEvent event) { onValueChange(event); } }; elementSelectedListener = new ElementSelectedListener() { public void elementSelected(ElementSelectedEvent event) { onElementSelected(event); } }; // listen to deserialization to restore data... getSessionContext().addSessionListener(new SessionAdapter() { /* (non-Javadoc) * @see de.jwic.events.SessionAdapter#afterDeserialization(de.jwic.events.SessionEvent) */ @Override public void afterDeserialization(SessionEvent event) { autodetectProperties(); } }); } /** * @param event */ protected void onElementSelected(ElementSelectedEvent event) { Control source = (Control)event.getEventSource(); updateBySource(source); } /** * @param event */ protected void onValueChange(ValueChangedEvent event) { Control source = (Control)event.getEventSource(); updateBySource(source); } /** * @param source */ private void updateBySource(Control source) { for (PropInfo pi : controlMap.values()) { if (pi.controlName.equals(source.getName())) { updateBean(pi, pi.mapper.getControlValue(source)); } } } /** * @param controlValue */ private void updateBean(PropInfo pi, Object value) { try { Method writeMethod = pi.descriptor.getWriteMethod(); if (Integer.class.equals(pi.descriptor.getPropertyType()) || int.class.equals(pi.descriptor.getPropertyType())) { if (value instanceof String) { value = new Integer(Integer.parseInt((String)value)); } } if (Double.class.equals(pi.descriptor.getPropertyType()) || double.class.equals(pi.descriptor.getPropertyType())) { if (value instanceof String) { value = new Double(Double.parseDouble((String)value)); } } if (Character.class.equals(pi.descriptor.getPropertyType()) || char.class.equals(pi.descriptor.getPropertyType())) { if (value instanceof String) { value = new Character(((String)value).charAt(0)); } } writeMethod.invoke(bean, value); } catch (Exception e) { setErrorMessage("Error updating value: " + e); } } /** * @return the bean */ public Object getBean() { return bean; } /** * @param bean the bean to set */ public void setBean(Object bean) { clearOldProperties(); this.bean = bean; autodetectProperties(); } /** * Remove the previous created controls. */ private void clearOldProperties() { errorMessage = null; if (propertyNames != null) { propertyNames = null; } if (controlMap != null) { for(PropInfo pi : controlMap.values()) { removeControl(pi.controlName); } controlMap = null; } requireRedraw(); } /** * */ private void autodetectProperties() { if (bean != null) { propertyNames = new ArrayList<String>(); controlMap = new HashMap<String, PropInfo>(); List<String> SKIP_NAMES = new ArrayList<String>(); SKIP_NAMES.add("controls"); SKIP_NAMES.add("sessionContext"); SKIP_NAMES.add("requireRedraw"); try { BeanInfo bi = Introspector.getBeanInfo(bean.getClass()); PropertyDescriptor descriptors[] = bi.getPropertyDescriptors(); for (int i = 0; i < descriptors.length; i++) { PropertyDescriptor prop = descriptors[i]; if (SKIP_NAMES.contains(prop.getName())) { continue; } propertyNames.add(prop.getName()); // create a control for each property PropInfo pi = new PropInfo(); pi.descriptor = prop; Method writeMethod = prop.getWriteMethod(); boolean readOnly = writeMethod == null || !Modifier.isPublic(writeMethod.getModifiers()); Control control = null; if (!readOnly) { if (String.class.equals(prop.getPropertyType()) || Integer.class.equals(prop.getPropertyType()) || int.class.equals(prop.getPropertyType()) || Double.class.equals(prop.getPropertyType()) || double.class.equals(prop.getPropertyType()) || Character.class.equals(prop.getPropertyType()) || char.class.equals(prop.getPropertyType())) { TextEditor ctrl = new TextEditor(this, null); ctrl.addValueChangedListener(changeListener); control = ctrl; pi.mapper = new TextEditorMapper(); } else if (Boolean.class.equals(prop.getPropertyType()) || boolean.class.equals(prop.getPropertyType())) { ListBoxControl lbc = new ListBoxControl(this, null); lbc.addElement("True", "true"); lbc.addElement("False", "false"); lbc.setChangeNotification(true); lbc.setFillWidth(false); lbc.setWidth(100); lbc.addElementSelectedListener(elementSelectedListener); lbc.setCssClass("small"); control = lbc; pi.mapper = new BooleanMapper(); }else if(prop.getPropertyType().isEnum()){ EnumListBoxControl<Enum<?>> lbc = new EnumListBoxControl<Enum<?>>(this, null,(Class<Enum<?>>) prop.getPropertyType()); lbc.setChangeNotification(true); lbc.setFillWidth(false); lbc.setWidth(100); lbc.addElementSelectedListener(elementSelectedListener); lbc.setCssClass("small"); control = lbc; pi.mapper = new EnumMapper(); } } if (control == null) { // property is readonly or no suiteable editor is available. InputBox inp = new InputBox(this); inp.setWidth(270); inp.setReadonly(true); inp.setCssClass("j-inputbox-intable"); control = inp; pi.mapper = new InputBoxControlMapper(); } pi.controlName = control.getName(); controlMap.put(prop.getName(), pi); } Collections.sort(propertyNames); loadValues(); } catch (IntrospectionException e) { log.error("Error reading bean properties.", e); setErrorMessage("Introspection error: " + e); } } } /** * Action that is invoked from the refresh link on the page. */ public void actionRefresh() { loadValues(); } /** * */ public void loadValues() { for (PropInfo pi : controlMap.values()) { try { Method mRead = pi.descriptor.getReadMethod(); Object value = mRead.invoke(bean, new Object[0]); pi.mapper.updateControlValue(getControl(pi.controlName), value); } catch (Exception e) { setErrorMessage("Error reading property '" + pi.descriptor.getName() + "': " + e); log.error("Error reading property", e); //break; } } } /** * @return the propertyNames */ public List<String> getPropertyNames() { return propertyNames; } /** * Returns the name of the control that handles this property. * @param propertyName * @return */ public String getControlName(String propertyName) { if (controlMap == null) { return null; } return controlMap.get(propertyName).controlName; } /** * @return the errorMessage */ public String getErrorMessage() { return errorMessage; } /** * @param errorMessage the errorMessage to set */ private void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; requireRedraw(); } }