
jaxx.runtime.JXPathDecorator Maven / Gradle / Ivy
The newest version!
package jaxx.runtime;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* JXPath decorator based on {@link String#format(String, Object[])} method.
*
* To use it, give to him a expression where all jxpath to apply on bean are boxed in ${}
.
*
* After the jxpath token you must specifiy the formatter to apply of the jxpath token.
*
* For example :
*
* Decorator<Object> d = JXPathDecorator.newDecorator(JXPathDecorator.class,"expr = ${expressions}$s");
* assert "expr = %1$s" == d.getExpression();
* assert 1 == d.getNbToken();
* assert java.util.Arrays.asList("expression") == d.getTokens();
* assert "expr = %1$s" == d.toString(d);
*
*
* @param type of data
* @author chemit
* @see Decorator
*/
public class JXPathDecorator extends Decorator {
/** to use log facility, just put in your code: log.info(\"...\"); */
private static final Log log = LogFactory.getLog(JXPathDecorator.class);
private static final long serialVersionUID = 1L;
/**
* Factory method to instanciate a new {@link JXPathDecorator} for the given class {@link O} and expression.
*
* @param internalClass the class of the objects decorated by the new decorator
* @param expression the expression to use to decorated objects
* @param the generic type of class to be decorated by the new decorator
* @return the new instanciated decorator
* @throws IllegalArgumentException if the expression is not valid, says:
*
* - a missing right brace was detected.
*
* - a ${ was found in a jxpath token.
* @throws NullPointerException if internalClass parameter is null.
*/
public static JXPathDecorator newDecorator(Class internalClass, String expression)
throws IllegalArgumentException, NullPointerException {
return new JXPathDecorator(internalClass, expression, true);
}
/**
* Sort a list of data based on the first token property of a given context
* in a given decorator.
*
* @param type of data to sort
* @param decorator the decorator to use to sort
* @param datas the list of data to sort
* @param pos the index of context to used in decorator to obtain sorted property.
*/
public static void sort(JXPathDecorator decorator, List datas, int pos) {
Comparator c = null;
boolean cachedComparator = false;
try {
c = decorator.getComparator(pos);
cachedComparator = c instanceof JXPathComparator>;
if (cachedComparator) {
((JXPathComparator) c).init(decorator, datas);
}
Collections.sort(datas, c);
} finally {
if (cachedComparator) {
((JXPathComparator>) c).clear();
}
}
}
public static class JXPathComparator implements Comparator {
protected Map>> valueCache;
private final String expression;
public JXPathComparator(String expression) {
this.expression = expression;
this.valueCache = new HashMap>>();
}
@Override
public int compare(O o1, O o2) {
Comparable> c1 = valueCache.get(o1);
Comparable> c2 = valueCache.get(o2);
return c1.compareTo(c2);
}
public void clear() {
valueCache.clear();
}
public void init(JXPathDecorator decorator, List datas) {
clear();
for (O data : datas) {
JXPathContext jxcontext = JXPathContext.newContext(data);
Comparable> key = decorator.getTokenValue(jxcontext, expression);
valueCache.put(data, key);
}
}
}
public static class Context implements java.io.Serializable {
/**
* expression to format using {@link String#format(String, Object[])}, all variables are compute
* using using the jxpath tokens.
*/
protected String expression;
/** list of jxpath tokens to apply on expression */
protected String[] tokens;
protected transient Comparator comparator;
private static final long serialVersionUID = 1L;
public Context(String expression, String[] tokens) {
this.expression = expression;
this.tokens = tokens;
}
public String getFirstProperty() {
return tokens[0];
}
public Comparator getComparator(int pos) {
if (comparator == null) {
comparator = new JXPathComparator(tokens[pos]);
}
return comparator;
}
public void setComparator(Comparator comparator) {
this.comparator = comparator;
}
@Override
public String toString() {
return "";
}
}
/** the computed context of the decorator */
protected Context context;
/** nb jxpath tokens to compute */
protected int nbToken;
/** the initial expression used to compute the decorator context. */
protected String initialExpression;
@Override
public String toString(Object bean) {
if (bean == null) {
return null;
}
JXPathContext jxcontext = JXPathContext.newContext(bean);
Object[] args = new Object[nbToken];
for (int i = 0; i < nbToken; i++) {
try {
args[i] = getTokenValue(jxcontext, context.tokens[i]);
} catch (Exception e) {
log.error("can not obtain token " + context.tokens[i] + "on object " + bean + " for reason " + e.getMessage(), e);
}
}
return String.format(context.expression, args);
}
@SuppressWarnings({"unchecked"})
protected Comparable> getTokenValue(JXPathContext jxcontext, String token) {
// assume all values are comparable
return (Comparable>) jxcontext.getValue(token);
}
public String getProperty(int pos) {
return getTokens()[pos];
}
public String getExpression() {
return context.expression;
}
public String[] getTokens() {
return context.tokens;
}
public int getNbToken() {
return nbToken;
}
public String getInitialExpression() {
return initialExpression;
}
@Override
public String toString() {
return super.toString() + "<" + context + ">";
}
public void setContext(Context context) {
this.context = context;
this.nbToken = context.tokens.length;
// always reset comparator
//this.context.comparator = null;
if (log.isDebugEnabled()) {
log.debug(context);
}
}
public JXPathDecorator(Class internalClass, String expression, boolean creatContext) throws IllegalArgumentException, NullPointerException {
super(internalClass);
this.initialExpression = expression;
if (creatContext) {
setContext(JXPathDecorator.createInitialContext(expression));
if (log.isDebugEnabled()) {
log.debug(expression + " --> " + this.context);
}
}
}
@SuppressWarnings({"unchecked"})
protected Comparator getComparator(int pos) {
ensureTokenIndex(this, pos);
return context.getComparator(pos);
}
public static Context createInitialContext(String expression) {
List lTokens = new ArrayList();
StringBuilder buffer = new StringBuilder();
int size = expression.length();
int end = -1;
int start;
while ((start = expression.indexOf("${", end + 1)) > -1) {
if (start > end + 1) {
// prefix of next jxpath token
buffer.append(expression.substring(end + 1, start));
}
// seek end of jxpath
end = expression.indexOf("}", start + 1);
if (end == -1) {
throw new IllegalArgumentException("could not find the rigth brace starting at car " + start + " : " + expression.substring(start + 2));
}
String jxpath = expression.substring(start + 2, end);
// not allowed ${ inside a jxpath token
if (jxpath.indexOf("${") > -1) {
throw new IllegalArgumentException("could not find a ${ inside a jxpath expression at car " + (start + 2) + " : " + jxpath);
}
// save the jxpath token
lTokens.add(jxpath);
// replace jxpath token in expresion with a string format variable
buffer.append("%").append(lTokens.size());
}
if (size > (end + 1)) {
// suffix after end jxpath (or all expression if no jxpath)
buffer.append(expression.substring(end + 1));
}
return new Context(buffer.toString(), lTokens.toArray(new String[lTokens.size()]));
}
protected static void ensureTokenIndex(JXPathDecorator> decorator, int pos) {
if (pos < -1 || pos > decorator.getNbToken()) {
throw new ArrayIndexOutOfBoundsException("token index " + pos + " is out of bound, can be inside [" + 0 + "," + decorator.nbToken + "]");
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy