在Spring中,我们有很多配置,如果一个个设置的话比较繁琐,参考Spring Boot的
@ConfigurationProperties(prefix = "spring.nbp")的方式类动态将spring.nbp前缀的配置全部绑定到我们的配置类就比较方便了。
好了,我们先上个实际使用示例,假设我们需要将spring.nbp前缀的配置绑定到DispatchProperty:
DispatchProperty property = BinderUtils.relaxedDataBinderBind(this.environment, DispatchProperty.PREFIX, DispatchProperty.class);
一行代码就搞定,那么这个类是如何帮我们来实现配置动态绑定的呢?我们抽丝剥茧来看一下关键类:
package com.xxx.arch.mw.nbp.client.spring.bind;
import org.springframework.core.env.ConfigurableEnvironment;
import java.lang.reflect.Constructor;
/**
* @created 2022-11-24 12:38 AM
* @description:
*/
public class BinderUtils {
public static T relaxedDataBinderBind(ConfigurableEnvironment environment, String prefix, Class type) {
T instance;
try {
Constructor extends T> constructor = type.getDeclaredConstructor();
instance = constructor.newInstance();
} catch (Throwable e) {
throw new IllegalArgumentException(e);
}
if (environment != null) {
new RelaxedDataBinder(instance, prefix)
.bind(new PropertySourcesPropertyValues(environment.getPropertySources()));
}
return instance;
}
}
package com.xxx.arch.mw.nbp.client.spring.bind;
import com.xxx.commons.data.constant.CommonConstants;
import org.springframework.beans.*;
import org.springframework.beans.propertyeditors.FileEditor;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.validation.DataBinder;
import java.beans.PropertyEditor;
import java.util.*;
/**
* Binder implementation that allows caller to bind to maps and also allows property names
* to match a bit loosely (if underscores or dashes are removed and replaced with camel
* case for example).
*
* @author Dave Syer
* @author Phillip Webb
* @author Stephane Nicoll
* @author Andy Wilkinson
* @see RelaxedNames
*/
public class RelaxedDataBinder extends DataBinder {
private static final Set> EXCLUDED_EDITORS;
private static final Object BLANK = new Object();
private static final String REGEX = "[_\\-\\.]";
private static final String EMPTY = "";
static {
Set> excluded = new HashSet>();
excluded.add(FileEditor.class);
EXCLUDED_EDITORS = Collections.unmodifiableSet(excluded);
}
private String namePrefix;
private boolean ignoreNestedProperties;
private MultiValueMap nameAliases = new LinkedMultiValueMap();
/**
* Create a new {@link RelaxedDataBinder} instance.
*
* @param target the target into which properties are bound
*/
public RelaxedDataBinder(Object target) {
super(wrapTarget(target));
}
/**
* Create a new {@link RelaxedDataBinder} instance.
*
* @param target the target into which properties are bound
* @param namePrefix An optional prefix to be used when reading properties
*/
public RelaxedDataBinder(Object target, String namePrefix) {
super(wrapTarget(target), (StringUtils.hasLength(namePrefix) ? namePrefix : DEFAULT_OBJECT_NAME));
this.namePrefix = cleanNamePrefix(namePrefix);
}
private static Object wrapTarget(Object target) {
if (target instanceof Map) {
@SuppressWarnings("unchecked") Map map = (Map) target;
target = new MapHolder(map);
}
return target;
}
private String cleanNamePrefix(String namePrefix) {
if (!StringUtils.hasLength(namePrefix)) {
return null;
}
return (namePrefix.endsWith(CommonConstants.DOT) ? namePrefix : namePrefix + CommonConstants.DOT);
}
/**
* Flag to disable binding of nested properties (i.e. those with period separators in
* their paths). Can be useful to disable this if the name prefix is empty and you
* don't want to ignore unknown fields.
*
* @param ignoreNestedProperties the flag to set (default false)
*/
public void setIgnoreNestedProperties(boolean ignoreNestedProperties) {
this.ignoreNestedProperties = ignoreNestedProperties;
}
/**
* Set name aliases.
*
* @param aliases a map of property name to aliases
*/
public void setNameAliases(Map> aliases) {
this.nameAliases = new LinkedMultiValueMap(aliases);
}
/**
* Add aliases to the {@link org.springframework.validation.DataBinder}.
*
* @param name the property name to alias
* @param alias aliases for the property names
* @return this instance
*/
public RelaxedDataBinder withAlias(String name, String... alias) {
for (String value : alias) {
this.nameAliases.add(name, value);
}
return this;
}
@Override
protected void doBind(MutablePropertyValues propertyValues) {
super.doBind(modifyProperties(propertyValues, getTarget()));
}
/**
* Modify the property values so that period separated property paths are valid for
* map keys. Also creates new maps for properties of map type that are null (assuming
* all maps are potentially nested). The standard bracket {@code[...]} dereferencing
* is also accepted.
*
* @param propertyValues the property values
* @param target the target object
* @return modified property values
*/
private MutablePropertyValues modifyProperties(MutablePropertyValues propertyValues, Object target) {
propertyValues = getPropertyValuesForNamePrefix(propertyValues);
if (target instanceof MapHolder) {
propertyValues = addMapPrefix(propertyValues);
}
BeanWrapper wrapper = new BeanWrapperImpl(target);
wrapper.setAutoGrowNestedPaths(true);
List sortedValues = new ArrayList();
Set modifiedNames = new HashSet();
List sortedNames = getSortedPropertyNames(propertyValues);
for (String name : sortedNames) {
PropertyValue propertyValue = propertyValues.getPropertyValue(name);
PropertyValue modifiedProperty = modifyProperty(wrapper, propertyValue);
if (modifiedNames.add(modifiedProperty.getName())) {
sortedValues.add(modifiedProperty);
}
}
return new MutablePropertyValues(sortedValues);
}
private List getSortedPropertyNames(MutablePropertyValues propertyValues) {
List names = new LinkedList();
for (PropertyValue propertyValue : propertyValues.getPropertyValueList()) {
names.add(propertyValue.getName());
}
sortPropertyNames(names);
return names;
}
/**
* Sort by name so that parent properties get processed first (e.g. 'foo.bar' before
* 'foo.bar.spam'). Don't use Collections.sort() because the order might be
* significant for other property names (it shouldn't be but who knows what people
* might be relying on, e.g. HSQL has a JDBCXADataSource where "databaseName" is a
* synonym for "url").
*
* @param names the names to sort
*/
private void sortPropertyNames(List names) {
for (String name : new ArrayList(names)) {
int propertyIndex = names.indexOf(name);
BeanPath path = new BeanPath(name);
for (String prefix : path.prefixes()) {
int prefixIndex = names.indexOf(prefix);
if (prefixIndex >= propertyIndex) {
// The child property has a parent in the list in the wrong order
names.remove(name);
names.add(prefixIndex, name);
}
}
}
}
private MutablePropertyValues addMapPrefix(MutablePropertyValues propertyValues) {
MutablePropertyValues rtn = new MutablePropertyValues();
for (PropertyValue pv : propertyValues.getPropertyValues()) {
rtn.add("map." + pv.getName(), pv.getValue());
}
return rtn;
}
private MutablePropertyValues getPropertyValuesForNamePrefix(MutablePropertyValues propertyValues) {
if (!StringUtils.hasText(this.namePrefix) && !this.ignoreNestedProperties) {
return propertyValues;
}
MutablePropertyValues rtn = new MutablePropertyValues();
for (PropertyValue value : propertyValues.getPropertyValues()) {
String name = value.getName();
for (String prefix : new RelaxedNames(stripLastDot(this.namePrefix))) {
for (String separator : new String[]{
CommonConstants.DOT,
CommonConstants.UNDERLINE_SEPARATOR
}) {
String candidate = (StringUtils.hasLength(prefix) ? prefix + separator : prefix);
if (name.startsWith(candidate)) {
name = name.substring(candidate.length());
if (!(this.ignoreNestedProperties && name.contains(CommonConstants.DOT))) {
rtn.addPropertyValue(new PropertyValue(name, value.getValue()));
}
}
}
}
}
return rtn;
}
private String stripLastDot(String string) {
if (StringUtils.hasLength(string) && string.endsWith(CommonConstants.DOT)) {
string = string.substring(0, string.length() - 1);
}
return string;
}
private PropertyValue modifyProperty(BeanWrapper target, PropertyValue propertyValue) {
String name = propertyValue.getName();
String normalizedName = normalizePath(target, name);
if (!normalizedName.equals(name)) {
return new PropertyValue(normalizedName, propertyValue.getValue());
}
return propertyValue;
}
/**
* Normalize a bean property path to a format understood by a BeanWrapper. This is
* used so that
*
* - Fuzzy matching can be employed for bean property names
* - Period separators can be used instead of indexing ([...]) for map keys
*
*
* @param wrapper a bean wrapper for the object to bind
* @param path the bean path to bind
* @return a transformed path with correct bean wrapper syntax
*/
protected String normalizePath(BeanWrapper wrapper, String path) {
return initializePath(wrapper, new BeanPath(path), 0);
}
private String initializePath(BeanWrapper wrapper, BeanPath path, int index) {
String prefix = path.prefix(index);
String key = path.name(index);
if (path.isProperty(index)) {
key = getActualPropertyName(wrapper, prefix, key);
path.rename(index, key);
}
if (path.name(++index) == null) {
return path.toString();
}
String name = path.prefix(index);
TypeDescriptor descriptor = wrapper.getPropertyTypeDescriptor(name);
if (descriptor == null || descriptor.isMap()) {
if (isMapValueStringType(descriptor) || isBlanked(wrapper, name, path.name(index))) {
path.collapseKeys(index);
}
path.mapIndex(index);
extendMapIfNecessary(wrapper, path, index);
} else if (descriptor.isCollection()) {
extendCollectionIfNecessary(wrapper, path, index);
} else if (descriptor.getType().equals(Object.class)) {
if (isBlanked(wrapper, name, path.name(index))) {
path.collapseKeys(index);
}
path.mapIndex(index);
if (path.isLastNode(index)) {
wrapper.setPropertyValue(path.toString(), BLANK);
} else {
String next = path.prefix(index + 1);
if (wrapper.getPropertyValue(next) == null) {
wrapper.setPropertyValue(next, new LinkedHashMap());
}
}
}
return initializePath(wrapper, path, index);
}
private boolean isMapValueStringType(TypeDescriptor descriptor) {
if (descriptor == null || descriptor.getMapValueTypeDescriptor() == null) {
return false;
}
if (Properties.class.isAssignableFrom(descriptor.getObjectType())) {
// Properties is declared as Map
package com.xxx.arch.mw.nbp.client.spring.bind;
import org.springframework.core.env.PropertySource;
/**
* The origin of a property, specifically its source and its name before any prefix was
* removed.
*
* @author Andy Wilkinson
* @since 1.3.0
*/
public class PropertyOrigin {
private final PropertySource> source;
private final String name;
PropertyOrigin(PropertySource> source, String name) {
this.name = name;
this.source = source;
}
public PropertySource> getSource() {
return this.source;
}
public String getName() {
return this.name;
}
}
package com.xxx.arch.mw.nbp.client.spring.bind;
import org.springframework.beans.PropertyValue;
import org.springframework.core.env.PropertySource;
public class OriginCapablePropertyValue extends PropertyValue {
private static final String ATTRIBUTE_PROPERTY_ORIGIN = "propertyOrigin";
private final PropertyOrigin origin;
OriginCapablePropertyValue(PropertyValue propertyValue) {
this(propertyValue.getName(), propertyValue.getValue(),
(PropertyOrigin) propertyValue.getAttribute(ATTRIBUTE_PROPERTY_ORIGIN));
}
OriginCapablePropertyValue(String name, Object value, String originName,
PropertySource> originSource) {
this(name, value, new PropertyOrigin(originSource, originName));
}
OriginCapablePropertyValue(String name, Object value, PropertyOrigin origin) {
super(name, value);
this.origin = origin;
setAttribute(ATTRIBUTE_PROPERTY_ORIGIN, origin);
}
public PropertyOrigin getOrigin() {
return this.origin;
}
@Override
public String toString() {
String name = (this.origin != null ? this.origin.getName() : this.getName());
String source = (this.origin.getSource() != null
? this.origin.getSource().getName() : "unknown");
return "'" + name + "' from '" + source + "'";
}
public static PropertyOrigin getOrigin(PropertyValue propertyValue) {
if (propertyValue instanceof OriginCapablePropertyValue) {
return ((OriginCapablePropertyValue) propertyValue).getOrigin();
}
return new OriginCapablePropertyValue(propertyValue).getOrigin();
}
}
package com.xxx.arch.mw.nbp.client.spring.bind;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import java.util.Iterator;
/**
* Convenience class to flatten out a tree of property sources without losing the
* reference to the backing data (which can therefore be updated in the background).
*/
public class FlatPropertySources implements PropertySources {
private PropertySources propertySources;
public FlatPropertySources(PropertySources propertySources) {
this.propertySources = propertySources;
}
@Override
public Iterator> iterator() {
MutablePropertySources result = getFlattened();
return result.iterator();
}
@Override
public boolean contains(String name) {
return get(name) != null;
}
@Override
public PropertySource> get(String name) {
return getFlattened().get(name);
}
private MutablePropertySources getFlattened() {
MutablePropertySources result = new MutablePropertySources();
for (PropertySource> propertySource : this.propertySources) {
flattenPropertySources(propertySource, result);
}
return result;
}
private void flattenPropertySources(PropertySource> propertySource, MutablePropertySources result) {
Object source = propertySource.getSource();
if (source instanceof ConfigurableEnvironment) {
ConfigurableEnvironment environment = (ConfigurableEnvironment) source;
for (PropertySource> childSource : environment.getPropertySources()) {
flattenPropertySources(childSource, result);
}
} else {
result.addLast(propertySource);
}
}
}
package com.xxx.arch.mw.nbp.client.spring.bind;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.util.Assert;
import org.springframework.util.PatternMatchUtils;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
/**
* A {@link org.springframework.beans.PropertyValues} implementation backed by a {@link org.springframework.core.env.PropertySources}, bridging
* the two abstractions and allowing (for instance) a regular {@link org.springframework.validation.DataBinder} to be
* used with the latter.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class PropertySourcesPropertyValues implements PropertyValues {
private static final Pattern COLLECTION_PROPERTY = Pattern.compile("\\[(\\d+)\\](\\.\\S+)?");
private final PropertySources propertySources;
private final Collection nonEnumerableFallbackNames;
private final PropertyNamePatternsMatcher includes;
private final Map propertyValues = new LinkedHashMap();
private final ConcurrentHashMap> collectionOwners = new ConcurrentHashMap>();
private final boolean resolvePlaceholders;
/**
* Create a new PropertyValues from the given PropertySources.
*
* @param propertySources a PropertySources instance
*/
public PropertySourcesPropertyValues(PropertySources propertySources) {
this(propertySources, true);
}
/**
* Create a new PropertyValues from the given PropertySources that will optionally
* resolve placeholders.
*
* @param propertySources a PropertySources instance
* @param resolvePlaceholders {@code true} if placeholders should be resolved.
* @since 1.5.2
*/
public PropertySourcesPropertyValues(PropertySources propertySources, boolean resolvePlaceholders) {
this(propertySources, (Collection) null, PropertyNamePatternsMatcher.ALL, resolvePlaceholders);
}
/**
* Create a new PropertyValues from the given PropertySources.
*
* @param propertySources a PropertySources instance
* @param includePatterns property name patterns to include from system properties and
* environment variables
* @param nonEnumerableFallbackNames the property names to try in lieu of an
* {@link org.springframework.core.env.EnumerablePropertySource}.
*/
public PropertySourcesPropertyValues(PropertySources propertySources, Collection includePatterns,
Collection nonEnumerableFallbackNames) {
this(propertySources, nonEnumerableFallbackNames, new PatternPropertyNamePatternsMatcher(includePatterns),
true);
}
/**
* Create a new PropertyValues from the given PropertySources.
*
* @param propertySources a PropertySources instance
* @param nonEnumerableFallbackNames the property names to try in lieu of an
* {@link org.springframework.core.env.EnumerablePropertySource}.
* @param includes the property name patterns to include
* @param resolvePlaceholders flag to indicate the placeholders should be resolved
*/
PropertySourcesPropertyValues(PropertySources propertySources, Collection nonEnumerableFallbackNames,
PropertyNamePatternsMatcher includes, boolean resolvePlaceholders) {
Assert.notNull(propertySources, "PropertySources must not be null");
Assert.notNull(includes, "Includes must not be null");
this.propertySources = propertySources;
this.nonEnumerableFallbackNames = nonEnumerableFallbackNames;
this.includes = includes;
this.resolvePlaceholders = resolvePlaceholders;
PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
for (PropertySource> source : propertySources) {
processPropertySource(source, resolver);
}
}
private void processPropertySource(PropertySource> source, PropertySourcesPropertyResolver resolver) {
if (source instanceof EnumerablePropertySource) {
processEnumerablePropertySource((EnumerablePropertySource>) source, resolver, this.includes);
} else {
processNonEnumerablePropertySource(source, resolver);
}
}
private void processEnumerablePropertySource(EnumerablePropertySource> source,
PropertySourcesPropertyResolver resolver, PropertyNamePatternsMatcher includes) {
if (source.getPropertyNames().length > 0) {
for (String propertyName : source.getPropertyNames()) {
if (includes.matches(propertyName)) {
Object value = getEnumerableProperty(source, resolver, propertyName);
putIfAbsent(propertyName, value, source);
}
}
}
}
private Object getEnumerableProperty(EnumerablePropertySource> source, PropertySourcesPropertyResolver resolver,
String propertyName) {
try {
if (this.resolvePlaceholders) {
return resolver.getProperty(propertyName, Object.class);
}
} catch (RuntimeException ex) {
// Probably could not resolve placeholders, ignore it here
}
return source.getProperty(propertyName);
}
private void processNonEnumerablePropertySource(PropertySource> source,
PropertySourcesPropertyResolver resolver) {
// We can only do exact matches for non-enumerable property names, but
// that's better than nothing...
if (this.nonEnumerableFallbackNames == null) {
return;
}
for (String propertyName : this.nonEnumerableFallbackNames) {
if (!source.containsProperty(propertyName)) {
continue;
}
Object value = null;
try {
value = resolver.getProperty(propertyName, Object.class);
} catch (RuntimeException ex) {
// Probably could not convert to Object, weird, but ignorable
}
if (value == null) {
value = source.getProperty(propertyName.toUpperCase(Locale.ENGLISH));
}
putIfAbsent(propertyName, value, source);
}
}
@Override
public PropertyValue[] getPropertyValues() {
Collection values = this.propertyValues.values();
return values.toArray(new PropertyValue[values.size()]);
}
@Override
public PropertyValue getPropertyValue(String propertyName) {
PropertyValue propertyValue = this.propertyValues.get(propertyName);
if (propertyValue != null) {
return propertyValue;
}
for (PropertySource> source : this.propertySources) {
Object value = source.getProperty(propertyName);
propertyValue = putIfAbsent(propertyName, value, source);
if (propertyValue != null) {
return propertyValue;
}
}
return null;
}
private PropertyValue putIfAbsent(String propertyName, Object value, PropertySource> source) {
if (value != null && !this.propertyValues.containsKey(propertyName)) {
PropertySource> collectionOwner = this.collectionOwners.putIfAbsent(
COLLECTION_PROPERTY.matcher(propertyName).replaceAll("[]"), source);
if (collectionOwner == null || collectionOwner == source) {
PropertyValue propertyValue = new OriginCapablePropertyValue(propertyName, value, propertyName, source);
this.propertyValues.put(propertyName, propertyValue);
return propertyValue;
}
}
return null;
}
@Override
public PropertyValues changesSince(PropertyValues old) {
MutablePropertyValues changes = new MutablePropertyValues();
// for each property value in the new set
for (PropertyValue newValue : getPropertyValues()) {
// if there wasn't an old one, add it
PropertyValue oldValue = old.getPropertyValue(newValue.getName());
if (oldValue == null || !oldValue.equals(newValue)) {
changes.addPropertyValue(newValue);
}
}
return changes;
}
@Override
public boolean contains(String propertyName) {
return getPropertyValue(propertyName) != null;
}
@Override
public boolean isEmpty() {
return this.propertyValues.isEmpty();
}
static interface PropertyNamePatternsMatcher {
PropertyNamePatternsMatcher ALL = new PropertyNamePatternsMatcher() {
@Override
public boolean matches(String propertyName) {
return true;
}
};
PropertyNamePatternsMatcher NONE = new PropertyNamePatternsMatcher() {
@Override
public boolean matches(String propertyName) {
return false;
}
};
/**
* Return {@code true} of the property name matches.
*
* @param propertyName the property name
* @return {@code true} if the property name matches
*/
boolean matches(String propertyName);
}
static class PatternPropertyNamePatternsMatcher implements PropertyNamePatternsMatcher {
private final String[] patterns;
PatternPropertyNamePatternsMatcher(Collection patterns) {
this.patterns = (patterns != null ? patterns.toArray(new String[patterns.size()]) : new String[] {});
}
@Override
public boolean matches(String propertyName) {
return PatternMatchUtils.simpleMatch(this.patterns, propertyName);
}
}
}
package com.xxx.arch.mw.nbp.client.spring.bind;
import org.springframework.util.StringUtils;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Generates relaxed name variations from a given source.
*
* @author Phillip Webb
* @author Dave Syer
* @see RelaxedDataBinder
*/
public final class RelaxedNames implements Iterable {
private static final Pattern CAMEL_CASE_PATTERN = Pattern.compile("([^A-Z-])([A-Z])");
private static final Pattern SEPARATED_TO_CAMEL_CASE_PATTERN = Pattern
.compile("[_\\-.]");
private final String name;
private final Set values = new LinkedHashSet();
/**
* Create a new {@link RelaxedNames} instance.
*
* @param name the source name. For the maximum number of variations specify the name
* using dashed notation (e.g. {@literal my-property-name}
*/
public RelaxedNames(String name) {
this.name = (name != null ? name : "");
initialize(RelaxedNames.this.name, this.values);
}
@Override
public Iterator iterator() {
return this.values.iterator();
}
private void initialize(String name, Set values) {
if (values.contains(name)) {
return;
}
for (Variation variation : Variation.values()) {
for (Manipulation manipulation : Manipulation.values()) {
String result = name;
result = manipulation.apply(result);
result = variation.apply(result);
values.add(result);
initialize(result, values);
}
}
}
/**
* Name variations.
*/
enum Variation {
NONE {
@Override
public String apply(String value) {
return value;
}
},
LOWERCASE {
@Override
public String apply(String value) {
return (value.isEmpty() ? value : value.toLowerCase(Locale.ENGLISH));
}
},
UPPERCASE {
@Override
public String apply(String value) {
return (value.isEmpty() ? value : value.toUpperCase(Locale.ENGLISH));
}
};
public abstract String apply(String value);
}
/**
* Name manipulations.
*/
enum Manipulation {
NONE {
@Override
public String apply(String value) {
return value;
}
},
HYPHEN_TO_UNDERSCORE {
@Override
public String apply(String value) {
return (value.indexOf('-') != -1 ? value.replace('-', '_') : value);
}
},
UNDERSCORE_TO_PERIOD {
@Override
public String apply(String value) {
return (value.indexOf('_') != -1 ? value.replace('_', '.') : value);
}
},
PERIOD_TO_UNDERSCORE {
@Override
public String apply(String value) {
return (value.indexOf('.') != -1 ? value.replace('.', '_') : value);
}
},
CAMELCASE_TO_UNDERSCORE {
@Override
public String apply(String value) {
if (value.isEmpty()) {
return value;
}
Matcher matcher = CAMEL_CASE_PATTERN.matcher(value);
if (!matcher.find()) {
return value;
}
matcher = matcher.reset();
StringBuffer result = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(result, matcher.group(1) + '_'
+ StringUtils.uncapitalize(matcher.group(2)));
}
matcher.appendTail(result);
return result.toString();
}
},
CAMELCASE_TO_HYPHEN {
@Override
public String apply(String value) {
if (value.isEmpty()) {
return value;
}
Matcher matcher = CAMEL_CASE_PATTERN.matcher(value);
if (!matcher.find()) {
return value;
}
matcher = matcher.reset();
StringBuffer result = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(result, matcher.group(1) + '-'
+ StringUtils.uncapitalize(matcher.group(2)));
}
matcher.appendTail(result);
return result.toString();
}
},
SEPARATED_TO_CAMELCASE {
@Override
public String apply(String value) {
return separatedToCamelCase(value, false);
}
},
CASE_INSENSITIVE_SEPARATED_TO_CAMELCASE {
@Override
public String apply(String value) {
return separatedToCamelCase(value, true);
}
};
private static final char[] SUFFIXES = new char[]{'_', '-', '.'};
public abstract String apply(String value);
private static String separatedToCamelCase(String value,
boolean caseInsensitive) {
if (value.isEmpty()) {
return value;
}
StringBuilder builder = new StringBuilder();
for (String field : SEPARATED_TO_CAMEL_CASE_PATTERN.split(value)) {
field = (caseInsensitive ? field.toLowerCase(Locale.ENGLISH) : field);
builder.append(
builder.length() != 0 ? StringUtils.capitalize(field) : field);
}
char lastChar = value.charAt(value.length() - 1);
for (char suffix : SUFFIXES) {
if (lastChar == suffix) {
builder.append(suffix);
break;
}
}
return builder.toString();
}
}
/**
* Return a {@link com.guahao.mrpc.config.bind.RelaxedNames} for the given source camelCase source name.
*
* @param name the source name in camelCase
* @return the relaxed names
*/
public static RelaxedNames forCamelCase(String name) {
StringBuilder result = new StringBuilder();
for (char c : name.toCharArray()) {
result.append(Character.isUpperCase(c) && result.length() > 0
&& result.charAt(result.length() - 1) != '-'
? "-" + Character.toLowerCase(c) : c);
}
return new RelaxedNames(result.toString());
}
}