Project: ceres
/*
 * Copyright (C) 2010 Brockmann Consult GmbH ([email protected]
 * 
 * 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 com.bc.ceres.binding; 
 
import com.bc.ceres.binding.dom.DomConverter; 
import com.bc.ceres.binding.validators.ArrayValidator; 
import com.bc.ceres.binding.validators.IntervalValidator; 
import com.bc.ceres.binding.validators.MultiValidator; 
import com.bc.ceres.binding.validators.NotEmptyValidator; 
import com.bc.ceres.binding.validators.NotNullValidator; 
import com.bc.ceres.binding.validators.PatternValidator; 
import com.bc.ceres.binding.validators.TypeValidator; 
import com.bc.ceres.binding.validators.ValueSetValidator; 
import com.bc.ceres.core.Assert; 
 
import java.beans.PropertyChangeEvent; 
import java.beans.PropertyChangeListener; 
import java.beans.PropertyChangeSupport; 
import java.lang.reflect.Field; 
import java.util.ArrayList; 
import java.util.Enumeration; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 
import java.util.regex.Pattern; 
 
/**
 * Describes a property by its name, type and a set of optional (mutable) attributes. 
 * Examples for such attributes are a {@link ValueSet}, a {@link Pattern} or 
 * an {@link ValueRange}. 
 * Attribute changes may be observed by adding a property (attribute) change listeners 
 * to instances of this class. 
 * 
 * @author Norman Fomferra 
 * @since 0.6 
 */
 
public class PropertyDescriptor { 
 
    private final String name; 
    private final Class<?> type; 
    private volatile Validator effectiveValidator; 
 
    private Map<String, Object> attributes; 
    private PropertyChangeSupport attributeChangeSupport; 
 
    public PropertyDescriptor(String name, Class<?> type) { 
        this(name, type, new HashMap<String, Object>(8)); 
    } 
 
    public PropertyDescriptor(PropertyDescriptor propertyDescriptor) { 
        this(propertyDescriptor.getName(), propertyDescriptor.getType(), propertyDescriptor.attributes); 
    } 
 
    public PropertyDescriptor(String name, Class<?> type, Map<String, Object> attributes) { 
        Assert.notNull(name, "name"); 
        Assert.notNull(type, "type"); 
        Assert.notNull(attributes, "attributes"); 
        this.name = name; 
        this.type = type; 
        this.attributes = new HashMap<String, Object>(attributes); 
        if (type.isPrimitive()) { 
            setNotNull(true); 
        } 
        setDisplayName(createDisplayName(name)); 
        if (type.isEnum() && getValueSet() == null)  { 
            setValueSet(new ValueSet(type.getEnumConstants())); 
        } 
    } 
 
    public String getName() { 
        return name; 
    } 
 
    public Class<?> getType() { 
        return type; 
    } 
 
    public String getDisplayName() { 
        return (String) getAttribute("displayName"); 
    } 
 
    public void setDisplayName(String displayName) { 
        Assert.notNull(displayName, "displayName"); 
        setAttribute("displayName", displayName); 
    } 
 
    public String getAlias() { 
        return (String) getAttribute("alias"); 
    } 
 
    public void setAlias(String alias) { 
        setAttribute("alias", alias); 
    } 
 
    public String getUnit() { 
        return (String) getAttribute("unit"); 
    } 
 
    public void setUnit(String unit) { 
        setAttribute("unit", unit); 
    } 
 
    public String getDescription() { 
        return (String) getAttribute("description"); 
    } 
 
    public void setDescription(String description) { 
        setAttribute("description", description); 
    } 
 
    public boolean isNotNull() { 
        return getBooleanProperty("notNull"); 
    } 
 
    public void setNotNull(boolean notNull) { 
        setAttribute("notNull", notNull); 
    } 
 
    public boolean isNotEmpty() { 
        return getBooleanProperty("notEmpty"); 
    } 
 
    public void setNotEmpty(boolean notEmpty) { 
        setAttribute("notEmpty", notEmpty); 
    } 
 
    public boolean isTransient() { 
        return getBooleanProperty("transient"); 
    } 
 
    public void setTransient(boolean b) { 
        setAttribute("transient", b); 
    } 
 
    public String getFormat() { 
        return (String) getAttribute("format"); 
    } 
 
    public void setFormat(String format) { 
        setAttribute("format", format); 
    } 
 
    public ValueRange getValueRange() { 
        return (ValueRange) getAttribute("valueRange"); 
    } 
 
    public void setValueRange(ValueRange valueRange) { 
        setAttribute("valueRange", valueRange); 
    } 
 
    public Pattern getPattern() { 
        return (Pattern) getAttribute("pattern"); 
    } 
 
    public Object getDefaultValue() { 
        return getAttribute("defaultValue"); 
    } 
 
    public void setDefaultValue(Object defaultValue) { 
        setAttribute("defaultValue", defaultValue); 
    } 
 
    public void setPattern(Pattern pattern) { 
        setAttribute("pattern", pattern); 
    } 
 
    public ValueSet getValueSet() { 
        return (ValueSet) getAttribute("valueSet"); 
    } 
 
    public void setValueSet(ValueSet valueSet) { 
        setAttribute("valueSet", valueSet); 
    } 
 
    public Converter<?> getConverter() { 
        return getConverter(false); 
    } 
 
    public Converter<?> getConverter(boolean notNull) { 
        final Converter<?> converter = (Converter<?>) getAttribute("converter"); 
        if (converter == null && notNull) { 
            throw new IllegalStateException("no converter defined for value '" + getName() + "'"); 
        } 
        return converter; 
    } 
 
    public void setDefaultConverter() { 
        Class<?> type = getType(); 
        if (getItemAlias() != null && type.isArray()) { 
            type = type.getComponentType(); 
        } 
        setConverter(ConverterRegistry.getInstance().getConverter(type)); 
    } 
 
    public void setConverter(Converter<?> converter) { 
        setAttribute("converter", converter); 
    } 
 
    public DomConverter getDomConverter() { 
        return (DomConverter) getAttribute("domConverter"); 
    } 
 
    public void setDomConverter(DomConverter converter) { 
        setAttribute("domConverter", converter); 
    } 
 
    public Validator getValidator() { 
        return (Validator) getAttribute("validator"); 
    } 
 
    public void setValidator(Validator validator) { 
        setAttribute("validator", validator); 
    } 
 
    Validator getEffectiveValidator() { 
        if (effectiveValidator == null) { 
            synchronized (this) { 
                if (effectiveValidator == null) { 
                    effectiveValidator = createEffectiveValidator(); 
                } 
            } 
        } 
        return effectiveValidator; 
    } 
 
 
    ////////////////////////////////////////////////////////////////////////////// 
    // Array/List item attributes 
 
    public String getItemAlias() { 
        return (String) getAttribute("itemAlias"); 
    } 
 
    public void setItemAlias(String alias) { 
        setAttribute("itemAlias", alias); 
    } 
 
    public boolean getItemsInlined() { 
        return getBooleanProperty("itemsInlined"); 
    } 
 
    public void setItemsInlined(boolean inlined) { 
        setAttribute("itemsInlined", inlined); 
    } 
 
    ////////////////////////////////////////////////////////////////////////////// 
    // Generic attributes 
 
    public Object getAttribute(String name) { 
        return attributes.get(name); 
    } 
 
    public void setAttribute(String name, Object value) { 
        Object oldValue = getAttribute(name); 
        if (value != null) { 
            attributes.put(name, value); 
        } else { 
            attributes.remove(name); 
        } 
        if (!equals(oldValue, value)) { 
            firePropertyChange(name, oldValue, value); 
        } 
    } 
 
    public final void addAttributeChangeListener(PropertyChangeListener listener) { 
        if (attributeChangeSupport == null) { 
            attributeChangeSupport = new PropertyChangeSupport(this); 
        } 
        attributeChangeSupport.addPropertyChangeListener(listener); 
    } 
 
    public final void removeAttributeChangeListener(PropertyChangeListener listener) { 
        if (attributeChangeSupport != null) { 
            attributeChangeSupport.removePropertyChangeListener(listener); 
        } 
    } 
 
    public PropertyChangeListener[] getAttributeChangeListeners() { 
        if (attributeChangeSupport == null) { 
            return new PropertyChangeListener[0]; 
        } 
        return this.attributeChangeSupport.getPropertyChangeListeners(); 
    } 
 
 
    ///////////////////////////////////////////////////////////////////////// 
    // Package Local 
 
    static PropertyDescriptor createPropertyDescriptor(String name, Class<?> type) { 
        final PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name, type); 
        propertyDescriptor.initialize(); 
        return propertyDescriptor; 
    } 
 
    static PropertyDescriptor createPropertyDescriptor(Field field, 
                                                       PropertyDescriptorFactory descriptorFactory) { 
        final PropertyDescriptor propertyDescriptor = descriptorFactory.createValueDescriptor(field); 
        if (propertyDescriptor == null) { 
            return null
        } 
        propertyDescriptor.initialize(); 
        return propertyDescriptor; 
    } 
 
    ///////////////////////////////////////////////////////////////////////// 
    // Private 
 
    private void initialize() { 
        if (getConverter() == null) { 
            setDefaultConverter(); 
        } 
        if (getDefaultValue() == null && getType().isPrimitive()) { 
            setDefaultValue(Property.PRIMITIVE_ZERO_VALUES.get(getType())); 
        } 
    } 
 
    private void firePropertyChange(String propertyName, Object newValue, Object oldValue) { 
        if (attributeChangeSupport == null) { 
            return
        } 
        PropertyChangeListener[] propertyChangeListeners = getAttributeChangeListeners(); 
        PropertyChangeEvent evt = new PropertyChangeEvent(this, propertyName, oldValue, newValue); 
        for (PropertyChangeListener propertyChangeListener : propertyChangeListeners) { 
            propertyChangeListener.propertyChange(evt); 
        } 
    } 
 
    private static boolean equals(Object a, Object b) { 
        return a == b || !(a == null || b == null) && a.equals(b); 
    } 
 
    private boolean getBooleanProperty(String name) { 
        Object v = getAttribute(name); 
        return v != null && (Boolean) v; 
    } 
 
    private Validator createEffectiveValidator() { 
        List<Validator> validators = new ArrayList<Validator>(3); 
 
        if (isNotNull()) { 
            validators.add(new NotNullValidator()); 
        } 
 
        validators.add(new TypeValidator()); 
 
        if (isNotEmpty()) { 
            validators.add(new NotEmptyValidator()); 
        } 
        if (getPattern() != null) { 
            validators.add(new PatternValidator(getPattern())); 
        } 
        if (getValueSet() != null) { 
            Validator valueSetValidator = new ValueSetValidator(this); 
            if (getType().isArray()) { 
                valueSetValidator = new ArrayValidator(valueSetValidator); 
            } 
            validators.add(valueSetValidator); 
        } 
        if (getValueRange() != null) { 
            validators.add(new IntervalValidator(getValueRange())); 
        } 
        if (getValidator() != null) { 
            validators.add(getValidator()); 
        } 
        Validator validator; 
        if (validators.isEmpty()) { 
            validator = null
        } else if (validators.size() == 1) { 
            validator = validators.get(0); 
        } else { 
            validator = new MultiValidator(validators); 
        } 
        return validator; 
    } 
 
    public static String getDisplayName(PropertyDescriptor propertyDescriptor) { 
        String label = propertyDescriptor.getDisplayName(); 
        if (label != null) { 
            return label; 
        } 
        String name = propertyDescriptor.getName().replace("_"" "); 
        return createDisplayName(name); 
    } 
 
    public static String createDisplayName(String name) { 
        StringBuilder sb = new StringBuilder(name.length()); 
        for (int i = 0; i < name.length(); i++) { 
            char ch = name.charAt(i); 
            if (i == 0) { 
                sb.append(Character.toUpperCase(ch)); 
            } else if (i > 0 && i < name.length() - 1 
                    && Character.isUpperCase(ch) && 
                    Character.isLowerCase(name.charAt(i + 1))) { 
                sb.append(' '); 
                sb.append(Character.toLowerCase(ch)); 
            } else if (ch == '_'){ 
                sb.append(' '); 
            } else { 
                sb.append(ch); 
            } 
        } 
        return sb.toString(); 
    } 
}