/*
* Copyright (C) 2010 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 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.util.ArrayList;
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;
private PropertySetDescriptor propertySetDescriptor;
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<>(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 isDeprecated() {
return getBooleanProperty("deprecated");
}
public void setDeprecated(boolean deprecated) {
setAttribute("deprecated", deprecated);
}
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() {
boolean hasItemAlias = getItemAlias() != null && !getItemAlias().isEmpty();
boolean useItemConverter = getType().isArray() && hasItemAlias;
if (!useItemConverter) {
setConverter(ConverterRegistry.getInstance().getConverter(getType()));
}
}
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;
}
public PropertySetDescriptor getPropertySetDescriptor() {
return propertySetDescriptor;
}
public void setPropertySetDescriptor(PropertySetDescriptor propertySetDescriptor) {
this.propertySetDescriptor = propertySetDescriptor;
}
//////////////////////////////////////////////////////////////////////////////
// Array/List item attributes
public String getItemAlias() {
return (String) getAttribute("itemAlias");
}
public void setItemAlias(String alias) {
setAttribute("itemAlias", alias);
}
/**
* @deprecated since BEAM 5
*/
@Deprecated
public boolean getItemsInlined() {
return getBooleanProperty("itemsInlined");
}
/**
* @deprecated since BEAM 5
*/
@Deprecated
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
void initDefaults() {
if (getConverter() == null) {
setDefaultConverter();
}
if (getDefaultValue() == null && getType().isPrimitive()) {
setDefaultValue(Property.PRIMITIVE_ZERO_VALUES.get(getType()));
}
}
/////////////////////////////////////////////////////////////////////////
// Private
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<>(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();
}
}