package org.anodyneos.xpImpl.util;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.xml.sax.SAXException;
public class Util {
public static String escapeStringQuoted(String string) {
if (null != string) {
return "\"" + escapeString(string) + "\"";
} else {
return "null";
}
}
/**
* Escapes '"' and '\' characters in a String (add a '\' before them) so that it can
* be inserted in java source.
*/
public static String escapeString(String string) {
char chr[] = string.toCharArray();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < chr.length; i++) {
switch (chr[i]) {
case '\t':
buffer.append("\\t");
break;
case '\r':
buffer.append("\\r");
break;
case '\n':
buffer.append("\\n");
break;
case '\b':
buffer.append("\\b");
break;
case '\f':
buffer.append("\\f");
break;
case '"':
case '\\':
buffer.append('\\');
buffer.append(chr[i]);
break;
default:
if (' ' <= chr[i] && chr[i] < 0x7F) {
buffer.append(chr[i]);
} else {
buffer.append("\\u");
buffer.append(int2digit(chr[i] >> 12));
buffer.append(int2digit(chr[i] >> 8));
buffer.append(int2digit(chr[i] >> 4));
buffer.append(int2digit(chr[i]));
}
break;
}
}
return buffer.toString();
}
private static char int2digit(int x) {
x &= 0xF;
if (x <= 9) return (char)(x + '0');
else return (char)(x - 10 + 'A');
}
public static String toSetMethod(String name) {
if (name.length() == 1) {
return "set" + name.toUpperCase();
} else {
return "set" + name.substring(0,1).toUpperCase() + name.substring(1);
}
}
/**
* Returns true if arg contains an EL. The backslash char '\\' escapes only $.
* It does not escape itself. This is as defined in JSP 2.0 for XML
* content.
*
* @param s
* @return
*/
public static boolean hasEL(String s) {
for(int start = s.indexOf("${"); start != -1; start = s.indexOf("${", start+1)) {
if (0 == start || s.charAt(start-1) != '\\') {
// if not escaped, return true
return true;
}
}
return false;
}
/**
* Unescapes a regular string using EL escaping conventions and return the
* result. This method uses elSplit to do the dirty work.
*
* @param s
* Must be regular text without an EL expression.
* @return The unescaped String or "" if s is null.
* @throws SAXException if the String contains an unescaped EL.
*/
public static String unescapeEL(String s) throws SAXException {
if (null == s || "".equals(s)) {
return "";
}
TextPart[] parts = elSplit(s);
if (parts != null && parts[0].isEL || parts.length > 1) {
throw new SAXException("EL found where not expected.");
}
return parts[0].part;
}
/**
* Like escapeStringQuoted() except that the string is first run through
* unescapeEL to remove extra backslashes.
*
* @param s
* must satisfy null != s && hasEL(s) == false
* @return the quoted String
* @throws SAXException if thrown by unescapeEL
*/
public static String escapeStringQuotedEL(String s) throws SAXException {
return escapeStringQuoted(unescapeEL(s));
}
/**
* Utility to call elExpressionCode(Util.TextPart[]...) with a String
* instead of TextPart[].
*
* @param inExpr
* A String containing plain text and/or EL expressions
* @param xpContextVar
* @param targetClass
* @return @throws
* SAXException if thrown by elSplit(inExpr) or
* elExpressionCode(Util.TextPart[]...)
*/
public static String elExpressionCode(String inExpr, String targetClass)
throws SAXException {
return elExpressionCode(elSplit(inExpr), targetClass);
}
public static String elExpressionCode(Util.TextPart[] parts, String type)
throws SAXException {
// normalize type
type = CoerceUtil.simplifyType(type);
if(parts.length == 1 && ! parts[0].isEL) {
// one part, non-EL. Convert to an expression representing the given type.
try {
return CoerceUtil.javaExpression(parts[0].part, type);
} catch (NumberFormatException e) {
throw new SAXException("Number out of range or cannot parse.", e);
} catch (IllegalArgumentException e) {
throw new SAXException("Invalid type for operation: " + type, e);
}
} else if(parts.length == 1 && parts[0].isEL) {
if(! CoerceUtil.isNativeType(type)) {
return "(" + type + ") "
+ "elEvaluator.evaluate("
+ escapeStringQuoted(parts[0].part)
+ ", " + type + ".class"
+ ", varResolver"
+ ", fResolver.getFunctionMapper(xpCH))";
} else {
// need to box/unbox
String boxClass = CoerceUtil.boxClass(type);
StringBuffer expr = new StringBuffer(
"((" + boxClass + ") "
+ "elEvaluator.evaluate("
+ escapeStringQuoted(parts[0].part)
+ ", " + boxClass + ".class"
+ ", varResolver"
+ ", fResolver.getFunctionMapper(xpCH)))");
// for boolean, byte, char, double, int, float, long, short
expr.append("." + type + "Value()");
return expr.toString();
}
} else { // multiple parts
StringBuffer expr = new StringBuffer();
expr.append ("(String) ");
for (int i = 0; i < parts.length; i++) {
if(i > 0) {
expr.append(" + ");
}
if (! parts[i].isEL) {
expr.append(escapeStringQuoted(parts[i].part));
} else { // this part is an EL
expr.append(
"elEvaluator.evaluate("
+ escapeStringQuoted(parts[i].part)
+ ", String.class"
+ ", varResolver"
+ ", fResolver.getFunctionMapper(xpCH))");
}
}
if("String".equals(type) || "Object".equals(type)) { // multiple parts, String
return expr.toString();
} else { // multiple parts, not String
// type must be native type, boxClass, or Object
// Same as string, but run-time pass through Coerce utility
if (CoerceUtil.isNativeType(type)) {
String type2 = type.substring(0,1).toUpperCase() + type.substring(1);
expr.insert(0, "org.anodyneos.xp.util.XpCoerce.coerceTo" + type2 + "Type(");
expr.append(")");
} else if (CoerceUtil.isBoxClass(type)) {
expr.insert(0, "org.anodyneos.xp.util.XpCoerce.coerceTo" + type + "(");
expr.append(")");
} else {
throw new SAXException("Expression invalid for type: " + type);
}
return expr.toString();
}
}
}
/**
* Returns a String containing Java code that can be used to represent the
* result of processing the given expression.
*
* @param parts
* @param xpContextVar
* The name of a variable that contains the XpContext
* @param targetClass
* The Java class name for the result. If not java.lang.String,
* inExpr must contain only an EL expression.
* @return Java expression
*/
public static String elExpressionCodeOld(Util.TextPart[] parts, String targetClass)
throws SAXException {
// @TODO: what about FunctionMapper() - probably needs to be passed in since
// the page defines mappings using namespaces.
if("java.lang.String".equals(targetClass)) {
targetClass = "String";
}
// not returning a String
if (! "String".equals(targetClass)) {
if (parts.length != 1 || ! parts[0].isEL) {
// if not a String, entire inExpr must be an EL expression
throw new SAXException("expression is not a java.lang.String, must only have EL");
} else {
return "(" + targetClass + ") "
+ "elEvaluator.evaluate("
+ escapeStringQuoted(parts[0].part)
+ ", " + targetClass + ".class"
+ ", varResolver"
+ ", null)";
}
} else {
if(parts.length == 1 && ! parts[0].isEL) {
// dispose of simple cases
return escapeStringQuoted(parts[0].part);
}
StringBuffer code = new StringBuffer();
code.append ("(String) ");
for (int i = 0; i < parts.length; i++) {
if(i > 0) {
code.append(" + ");
}
if (! parts[i].isEL) {
code.append(escapeStringQuoted(parts[i].part));
} else { // this part is an EL
code.append(
"elEvaluator.evaluate("
+ escapeStringQuoted(parts[i].part)
+ ", String.class"
+ ", varResolver"
+ ", null)");
}
}
return code.toString();
}
}
public static void outputCharactersCode(String raw, CodeWriter out) throws SAXException {
// this will output on multiple lines to make generated code easier to read.
// TODO split up the regular text lines at line breaks
// NOTE: we should really only expect "\n" from the input because of:
// http://www.w3.org/TR/1998/REC-xml-19980210#sec-line-ends which
// requires \r\n and \r to be converted to a single \n. We can safely
// replace \r crap with just \n although this doesn't necesarily apply
// to text mode if that will be supported.
// Well, not really the case: xml can have
which is not converted as above. However,
// should make it safe to split on \n (\r characters will not break lines.)
// note: cannot use part.part.split("\n|\r\n|\r"); because split()
// throws away
// adjacent matches so the result will not have the right # of
// linebreaks.
Util.TextPart[] parts = Util.elSplit(raw);
for(int i = 0; i < parts.length; i++) {
Util.TextPart part = parts[i];
if (part.isEL) {
String codeValue = elExpressionCode(new TextPart[] { part }, "String");
// We use xpOut.write instead of xpCH.characters since EL expression
// may have invalid CRLF convention and xpOut.write can normalize correctly.
out.printIndent().println("xpOut.write(" + codeValue + ");");
} else {
// since we are getting the String after elSplit, it does not need further
// unescaping, so we will use the regular "escapeStringQuoted"
String codeValue = escapeStringQuoted(part.part);
out.printIndent().println("xpCH.characters(" + codeValue + ");");
}
}
}
/**
* Splits a string that contains regular text and EL expressions into an
* array of TextParts where each part holds either literal text or a
* complete EL expression. Literal text never occurs as adjacent Strings
* within the array. Literal text returned by this function has been
* unescaped.
*
* @param str
* @return the array of <code>TextPart</code> or null if <code>str==null</code>.
*/
public static TextPart[] elSplit(String str) throws SAXException {
// @TODO: determine null or empty array convention, adjust code, and fix all
// code that uses this method.
// Handle easy cases first
if (null == str) {
return null;
}
if (str.indexOf("$") == -1) {
// if no "$", then there are no EL and no backslashed to remove.
return new TextPart[] { new TextPart(str, false) };
}
StringBuffer sb = new StringBuffer();
List list = new ArrayList();
StringCharacterIterator it = new StringCharacterIterator(str);
char buf = ' '; // may be '\\', '$', or ' '
for(char cur = it.first(); cur != CharacterIterator.DONE; cur = it.next()) {
switch (cur) {
case '$':
if('\\' == buf) {
// escaped, output
sb.append('$');
buf = ' ';
} else {
// not escaped, may be start of EL
buf = '$';
}
break;
case '\\':
if('\\' == buf || '$' == buf) {
// output buf
sb.append(buf);
}
// we may be escaping $, lets wait and see
buf = '\\';
break;
case '{':
if('\\' == buf) {
// output buf and this
sb.append(buf);
sb.append(cur);
buf = ' ';
} else if (' ' == buf) {
// output this
sb.append(cur);
// buf = ' '; // buf is already ' '
} else { // buf == '$'
// we are starting an EL
buf = ' ';
if(sb.length() != 0) {
list.add(new TextPart(sb.toString(), false));
sb.setLength(0);
}
sb.append("${");
// read in rest of EL including trailing }
char elBuf = ' '; // equals ' ' or '\'
boolean done = false;
char quoteType = ' '; // equals '\'' or '"'
for(cur = it.next(); cur != CharacterIterator.DONE; cur = it.next()) {
sb.append(cur); // we don't mangle inside EL
if (' ' == quoteType) {
// enter quote, end EL, or do nothing
if (cur == '\'' || cur == '"') {
quoteType = cur;
elBuf = ' ';
} else if (cur == '}') {
// end EL
done = true;
break;
}
} else if (quoteType == '\'' || quoteType == '"') {
if ('\\' == elBuf) {
// cur is being escaped
elBuf = ' ';
} else if ('\\' == cur) {
// escape next
elBuf = '\\';
} else if (quoteType == cur) {
// out of quote now
quoteType = ' ';
}
}
}
if (done) {
list.add(new TextPart(sb.toString(), true));
sb.setLength(0);
} else {
throw new SAXException("EL did not end in }");
}
}
break;
default:
if('\\' == buf || '$' == buf) {
sb.append(buf);
buf = ' ';
}
sb.append(cur);
break;
}
}
if(sb.length() != 0) {
list.add(new TextPart(sb.toString(), false));
}
return (TextPart[]) list.toArray(new TextPart[list.size()]);
}
public static void main(String[] args) throws Exception {
// test splitEl
String[] stringArray;
// test only text
System.out.println(
Arrays.asList(elSplit("some_text_slash\\_slashslash\\\\_dollar$_dollar\\$_literal\\${")).toString()
);
// text only expr
System.out.println(
Arrays.asList(elSplit("${myexpr_slash\\_quote_'${}{}\\''_quote\"${}{}\\\"\"}")).toString()
);
// test start and end text
System.out.println(
Arrays.asList(elSplit("xxx${yyy}xxx")).toString()
);
// test start and end expr
System.out.println(
Arrays.asList(elSplit("${yyy}xxx${yyy}")).toString()
);
// more testing would include exceptions for non-ending EL due to missing } or endless quote
}
public static class TextPart {
public String part;
public boolean isEL;
public TextPart(String part, boolean isEL) {
this.part = part;
this.isEL = isEL;
}
public boolean equals(Object thatObj) {
if(thatObj == null || ! (thatObj instanceof TextPart)) {
return false;
}
TextPart that = (TextPart) thatObj;
if(this.isEL != that.isEL) {
return false;
}
if(this.part == null && that.part != null) {
return false;
}
if(this.part == null && that.part == null) {
return true;
}
return(this.part.equals(that.part));
}
}
}