com.feilong.lib.digester3.SetPropertiesRule Maven / Gradle / Ivy
Show all versions of feilong Show documentation
package com.feilong.lib.digester3;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import static java.lang.String.format;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import com.feilong.lib.beanutils.BeanUtils;
import com.feilong.lib.beanutils.PropertyUtils;
/**
*
* Rule implementation that sets properties on the object at the top of the stack, based on attributes with
* corresponding names.
*
*
* This rule supports custom mapping of attribute names to property names. The default mapping for particular attributes
* can be overridden by using {@link #SetPropertiesRule(String[] attributeNames, String[] propertyNames)}. This allows
* attributes to be mapped to properties with different names. Certain attributes can also be marked to be ignored.
*
*/
public class SetPropertiesRule extends Rule{
/** The Constant log. */
private static final Logger LOGGER = LoggerFactory.getLogger(SetPropertiesRule.class);
// ----------------------------------------------------------- Constructors
/**
* Base constructor.
*/
public SetPropertiesRule(){
// nothing to set up
}
/**
*
* Convenience constructor overrides the mapping for just one property.
*
*
* For details about how this works, see {@link #SetPropertiesRule(String[] attributeNames, String[] propertyNames)}
* .
*
*
* @param attributeName
* map this attribute
* @param propertyName
* to a property with this name
*/
public SetPropertiesRule(String attributeName, String propertyName){
aliases.put(attributeName, propertyName);
}
/**
*
* Constructor allows attribute->property mapping to be overriden.
*
*
* Two arrays are passed in. One contains the attribute names and the other the property names. The attribute name /
* property name pairs are match by position In order words, the first string in the attribute name list matches to
* the first string in the property name list and so on.
*
*
* If a property name is null or the attribute name has no matching property name, then this indicates that the
* attibute should be ignored.
*
* Example One
*
* The following constructs a rule that maps the alt-city
attribute to the city
property
* and the alt-state
to the state
property. All other attributes are mapped as usual using
* exact name matching.
* SetPropertiesRule(
* new String[] {"alt-city", "alt-state"},
* new String[] {"city", "state"});
*
*
Example Two
*
* The following constructs a rule that maps the class
attribute to the className
* property. The attribute ignore-me
is not mapped. All other attributes are mapped as usual using
* exact name matching.
* SetPropertiesRule(
* new String[] {"class", "ignore-me"},
* new String[] {"className"});
*
*
* @param attributeNames
* names of attributes to map
* @param propertyNames
* names of properties mapped to
*/
public SetPropertiesRule(String[] attributeNames, String[] propertyNames){
for (int i = 0, size = attributeNames.length; i < size; i++){
String propName = null;
if (i < propertyNames.length){
propName = propertyNames[i];
}
aliases.put(attributeNames[i], propName);
}
}
/**
* Constructor allows attribute->property mapping to be overriden.
*
* @param aliases
* attribute->property mapping
* @since 3.0
*/
public SetPropertiesRule(Map aliases){
if (aliases != null && !aliases.isEmpty()){
this.aliases.putAll(aliases);
}
}
// ----------------------------------------------------- Instance Variables
private final Map aliases = new HashMap<>();
/**
* Used to determine whether the parsing should fail if an property specified in the XML is missing from the bean.
* Default is true for backward compatibility.
*/
private boolean ignoreMissingProperty = true;
// --------------------------------------------------------- Public Methods
/**
* {@inheritDoc}
*/
@Override
public void begin(String namespace,String name,Attributes attributes) throws Exception{
// Build a set of attribute names and corresponding values
Map values = new HashMap<>();
for (int i = 0; i < attributes.getLength(); i++){
String attributeName = attributes.getLocalName(i);
if ("".equals(attributeName)){
attributeName = attributes.getQName(i);
}
String value = attributes.getValue(i);
// alias lookup has complexity O(1)
if (aliases.containsKey(attributeName)){
attributeName = aliases.get(attributeName);
}
if (LOGGER.isDebugEnabled()){
LOGGER.debug(
format(
"[SetPropertiesRule]{%s} Setting property '%s' to '%s'",
getDigester().getMatch(),
attributeName,
attributeName));
}
if ((!ignoreMissingProperty) && (attributeName != null)){
// The BeanUtils.populate method silently ignores items in
// the map (ie xml entities) which have no corresponding
// setter method, so here we check whether each xml attribute
// does have a corresponding property before calling the
// BeanUtils.populate method.
//
// Yes having the test and set as separate steps is ugly and
// inefficient. But BeanUtils.populate doesn't provide the
// functionality we need here, and changing the algorithm which
// determines the appropriate setter method to invoke is
// considered too risky.
//
// Using two different classes (PropertyUtils vs BeanUtils) to
// do the test and the set is also ugly; the codepaths
// are different which could potentially lead to trouble.
// However the BeanUtils/ProperyUtils code has been carefully
// compared and the PropertyUtils functionality does appear
// compatible so we'll accept the risk here.
Object top = getDigester().peek();
boolean test = PropertyUtils.isWriteable(top, attributeName);
if (!test){
throw new NoSuchMethodException("Property " + attributeName + " can't be set");
}
}
if (attributeName != null){
values.put(attributeName, value);
}
}
// Populate the corresponding properties of the top object
Object top = getDigester().peek();
if (LOGGER.isDebugEnabled()){
if (top != null){
LOGGER.debug(format("[SetPropertiesRule]{%s} Set '%s' properties", getDigester().getMatch(), top.getClass().getName()));
}else{
LOGGER.debug(format("[SetPropertiesRule]{%s} Set NULL properties", getDigester().getMatch()));
}
}
BeanUtils.populate(top, values);
}
/**
* Add an additional attribute name to property name mapping. This is intended to be used from the xml rules.
*
* @param attributeName
* the attribute name has to be mapped
* @param propertyName
* the target property name
*/
public void addAlias(String attributeName,String propertyName){
aliases.put(attributeName, propertyName);
}
/**
* {@inheritDoc}
*/
@Override
public String toString(){
return format("SetPropertiesRule[aliases=%s, ignoreMissingProperty=%s]", aliases, ignoreMissingProperty);
}
/**
*
* Are attributes found in the xml without matching properties to be ignored?
*
*
* If false, the parsing will interrupt with an NoSuchMethodException
if a property specified in the
* XML is not found. The default is true.
*
*
* @return true if skipping the unmatched attributes.
*/
public boolean isIgnoreMissingProperty(){
return this.ignoreMissingProperty;
}
/**
* Sets whether attributes found in the xml without matching properties should be ignored. If set to false, the
* parsing will throw an NoSuchMethodException
if an unmatched attribute is found. This allows to trap
* misspellings in the XML file.
*
* @param ignoreMissingProperty
* false to stop the parsing on unmatched attributes.
*/
public void setIgnoreMissingProperty(boolean ignoreMissingProperty){
this.ignoreMissingProperty = ignoreMissingProperty;
}
}