/*
* © Copyright IBM Corp. 2012
*
* Licensed 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.
*/
/*
* Created on May 29, 2005
*
*/
package com.ibm.commons.xml.xpath;
import java.util.ArrayList;
import com.ibm.commons.util.StringUtil;
import com.ibm.commons.util.SystemCache;
import com.ibm.commons.xml.xpath.part.AttributePart;
import com.ibm.commons.xml.xpath.part.ElementPart;
import com.ibm.commons.xml.xpath.part.IndexedElementPart;
import com.ibm.commons.xml.xpath.part.Part;
/**
* @author Mark Wallace
* @author Philippe Riand
*/
abstract public class AbstractExpressionFactory implements XPathExpressionFactory {
protected SystemCache _expressionCache;
public static final String FORBIDDEN_IN_CREATE = "()[]{}*="; //$NON-NLS-1$
public static final String FORBIDDEN_IN_SIMPLE = "(){}*="; //$NON-NLS-1$
/**
* Construct a AbstractXPathExpressionFactory.
*/
public AbstractExpressionFactory() {
this(null);
}
/**
* Construct a AbstractXPathExpressionFactory and create a cache with the specified name
* and a maximum size of 400.
*
* @param name
*/
public AbstractExpressionFactory(String name) {
this(name, 400);
}
/**
* Construct a AbstractXPathExpressionFactory and create a cache with the specified name
* and the specified maximum size.
*
* @param name
* @param maxSize
*/
public AbstractExpressionFactory(String name, int maxSize) {
name = (name == null) ? getClass().getName() : name;
_expressionCache = new SystemCache(name, 400, "ibm.xpath.cachesize"); // $NON-NLS-1$
}
/* (non-Javadoc)
* @see com.ibm.commons.xml.xpath.XPathExpressionFactory#createExpression(java.lang.Object, java.lang.String, boolean)
*/
public XPathExpression createExpression(Object data, String expression, boolean useCache) throws XPathException {
// try the cache
XPathExpression pathExpression;
if(useCache) {
pathExpression = (XPathExpression)_expressionCache.get(expression);
if (pathExpression != null) {
return pathExpression;
}
}
// try to create a simple xpath if possible
pathExpression = createSimpleExpression(expression);
if (pathExpression == null) {
// not a simple xpath so create a complex one
pathExpression = createComplexExpression(expression);
}
// cache the created xpath expression
if(useCache) {
cacheExpression(expression, pathExpression);
}
return pathExpression;
}
/**
* Create a simple XPathExpression object for the specified expression. This uses the
* rules for identifying a simple Xpath expression:
*
* <li>the expression does not contain any of the following characters (){}*=</li>
* <li>the expression does not start with //</li>
* <li>each part of the expression starts with /</li>
* <li>each part starts with a valid XML start character</li>
* <li>each part contains only valid XML characters</li>
*
* @param expression
* @return
*/
protected XPathExpression createSimpleExpression(String expression) throws XPathException {
// reserved key words
if ("true".equals(expression)) { //$NON-NLS-1$
return null;
}
if ("false".equals(expression)) { //$NON-NLS-1$
return null;
}
// first check for forbidden characters
char[] chars = expression.toCharArray();
for (int index=0; index < chars.length; index++) {
if (FORBIDDEN_IN_SIMPLE.indexOf(chars[index]) != -1) {
return null;
}
}
// next check that each part is simple
boolean isFromRoot = false;
ArrayList partList = new ArrayList();
for (int index=0; index < chars.length; ) {
// each part should start with a leading '/'
// except the first one...
if (index == 0) {
if (chars[index]=='/') {
isFromRoot = true;
index++;
if (chars.length> index && chars[index]=='/' ) { // case of '//'
return null;
}
}
}
else {
if (chars[index] != '/') {
return null;
}
index++;
}
// check if the path points to an attribute
if (index < chars.length && chars[index] == '@') {
index++;
int start = index;
// the first character must be valid
if (index >= chars.length || !isXMLNameStartCharacter(chars[index])) {
return null;
}
index++;
// get all the other characters
while (index < chars.length && isXMLNameCharacter(chars[index])) {
index++;
}
String attrName = new String(chars, start, index-start);
// and compose the attribute part
AttributePart part = new AttributePart(attrName);
partList.add(part);
continue;
}
// else, check if it is an element
if (index < chars.length && isXMLNameStartCharacter(chars[index])) {
int start = index++;
// get all the other characters
while (index < chars.length && isXMLNameCharacter(chars[index]) && chars[index] != ':') {
index++;
}
String nsPrefix = null;
String eltName = new String(chars, start, index-start);
// check if it was a namespace prefix
if (index < chars.length && chars[index]==':' ) {
index++;
nsPrefix = eltName;
start = index;
if (index >= chars.length || !isXMLNameStartCharacter(chars[index]) ||
chars[index]==':') {
// if "::" or not valid namespace character then it is not a simple path
return null;
}
index++;
// Get all the other characters
while (index < chars.length && isXMLNameCharacter(chars[index])) {
index++;
}
eltName = new String(chars, start, index-start);
}
// check if the element is indexed
if (index<chars.length && chars[index] == '[') {
index++;
start = index;
while (index<chars.length && chars[index]!=']') {
index++;
}
String strIdx = (new String(chars, start, index-start)).trim();
if( StringUtil.isEmpty(strIdx) ) {
return null; // throw new InvalidXPath()
}
index++; // skip ']'
try {
int idx = Integer.parseInt(strIdx);
// and compose the element part
IndexedElementPart part = new IndexedElementPart(nsPrefix, eltName, idx);
partList.add(part);
} catch( Exception e ) {
return null;
}
}
else {
// and compose the element part
ElementPart part = new ElementPart(nsPrefix, eltName);
partList.add(part);
}
continue;
}
else if (index<chars.length && chars[index] == '.') {
index++;
if (index<chars.length && chars[index] == '.') {
index++;
// and compose the element part
ElementPart part = new ElementPart(null, "..");
partList.add(part);
}
else {
// and compose the element part
ElementPart part = new ElementPart(null, ".");
partList.add(part);
}
continue;
}
// it is an error!
if (index < chars.length)
return null;
}
// create the simple xpath expression object
Part[] parts = (Part[])partList.toArray(new Part[partList.size()]);
return createSimpleExpression(expression, isFromRoot, parts);
}
/**
* Create a simple XPathExpression object for the specified properties.
*
* @param expression
* @param isFromRoot
* @param parts
* @return
*/
abstract protected XPathExpression createSimpleExpression(String expression, boolean isFromRoot, Part[] parts) throws XPathException;
/**
* Create a complex XPathExpression object for the specified expression.
* This creates the expression using whateverthe underlying XPath engine is
* appropriate for this expression.
*
* @param expression
* @return
*/
abstract protected XPathExpression createComplexExpression(String expression) throws XPathException ;
/**
* This is a utility function for determining whether a specified
* character is a legal name start character according to the XML 1.0 specification.
*
* @param c <code>char</code> to check for XML name start compliance.
* @return <code>boolean</code> true if it's a name start character, false otherwise.
*/
abstract protected boolean isXMLNameStartCharacter(char ch);
/**
* This is a utility function for determining whether a specified
* character is a name character according to the XML 1.0 specification.
*
* @param c <code>char</code> to check for XML name compliance.
* @return <code>boolean</code> true if it's a name character, false otherwise.
*/
abstract protected boolean isXMLNameCharacter(char ch);
/**
* Cache the specified XPathExpression object as long as it is not a simple expression
* with only one part.
*/
protected void cacheExpression(String expression, XPathExpression pathExpression) {
if (pathExpression == null)
return;
// PHIL: cache them anyway
// Indexed XPath were heavily used in FlowBuilder, but Designer is not generating them anymore as
// it is now using XPathContext.
// if (pathExpression.isSimple()) {
// AbstractSimpleExpression simpleExpression = (AbstractSimpleExpression)pathExpression;
// if (!simpleExpression.isFromRoot() && (simpleExpression.getPartCount() < 2))
// return;
// // Only cache non indexed path
// // This is because a lot of XPath in UI are build with such an index when applied in a
// // repeat context.
// if(expression.indexOf('[')>=0) {
// return;
// }
// }
// cache the expression
_expressionCache.put(expression, pathExpression);
}
}