/* * © 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. */ package com.ibm.sbt.util; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import org.w3c.dom.Attr; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.ibm.commons.util.StringUtil; import com.ibm.commons.util.io.json.JsonException; import com.ibm.commons.util.io.json.JsonFactory; import com.ibm.commons.util.io.json.JsonJavaFactory; import com.ibm.commons.xml.DOMUtil; import com.ibm.commons.xml.NamespaceContext; import com.ibm.commons.xml.util.XMIConverter; /** * Data Navigator. * Supported XPath: * // * [prefix:]name * [xx] * [ * @author Philippe Riand */ public abstract class DataNavigator implements Cloneable /*extends DataObject*/ { private static final int TYPE_NODE = -1; private static final int TYPE_OBJECT = 0; private static final int TYPE_STRING = 1; private static final int TYPE_INT = 2; private static final int TYPE_LONG = 3; private static final int TYPE_DOUBLE = 4; private static final int TYPE_BOOLEAN = 5; private static final int TYPE_DATE = 6; public static interface ISelect { public boolean matches(Object object); } public static class Xml extends DataNavigator { private NamespaceContext nsContext; private Node root; private List<Object> currentNodes; public Xml(Node root) { this.root = root; if(root!=null) { this.currentNodes = Collections.singletonList((Object)root); this.nsContext = DOMUtil.getSelectionNamespaces(DOMUtil.getOwnerDocument(root)); } else { this.currentNodes = Collections.emptyList(); } } protected Xml(Xml copy, List<Object> nodes) { this.root = copy.root; this.nsContext = copy.nsContext; this.currentNodes = nodes; } @Override public List<Object> getCurrentNodes() { return currentNodes; } @Override protected DataNavigator create(List<Object> nodes) { return new Xml(this,nodes); } @Override protected Node root() { return root; } @Override protected List<Object> children() { return currentNodes; } @Override protected void extract(Object node, List<Object> result, String name, int type, boolean global) { // Split the name between the namespace and the local name String uri = null; String localName = null; if(StringUtil.isNotEmpty(name)) { int pos = name.indexOf(':'); if(pos>=0) { if(nsContext==null) { return; } uri = nsContext.getNamespaceURI(name.substring(0,pos)); if(uri==null) { return; } localName = name.substring(pos+1); } else { localName = name; } } else { localName = name; } // Then find the children for all the current nodes if(type==TYPE_NODE) { extractChildren(result, (Node)node, uri, localName, global); } else { extractValues(result, (Node)node, uri, localName, type, global); } } private void extractChildren(List<Object> result, Node parent, String uri, String name, boolean global) { // Look for an attribute if(name.startsWith("@")) { if(parent.getNodeType()==Node.ELEMENT_NODE) { Element e = (Element)parent; String attrName = name.substring(1); Attr attr = (uri==null) ? e.getAttributeNode(attrName) : e.getAttributeNodeNS(uri,attrName); if(attr!=null) { result.add(attr); } } } else { if(name.equals(".")) { if(!result.contains(parent)) { result.add(parent); } } else { NodeList children = parent.getChildNodes(); for(int i=0; i<children.getLength(); i++) { Node node = children.item(i); if(node.getNodeType()==Node.ELEMENT_NODE && matches(node,uri,name)) { if(!result.contains(node)) { result.add(node); } } } } } if(global) { NodeList list = parent.getChildNodes(); for(int i=0; i<list.getLength(); i++) { Node node = list.item(i); if(node.getNodeType()==Node.ELEMENT_NODE) { extractChildren(result, node, uri, name, global); } } } } private void extractValues(List<Object> result, Node parent, String uri, String name, int type, boolean global) { // Look for an attribute if(name.startsWith("@")) { if(parent.getNodeType()==Node.ELEMENT_NODE) { Element e = (Element)parent; String attrName = name.substring(1); Attr attr = (uri==null) ? e.getAttributeNode(attrName) : e.getAttributeNodeNS(uri,attrName); if(attr!=null) { Object v = convertTo(attr.getValue(),type); if(v!=null) { result.add(v); } } } return; } else { if(name.equals(".")) { if(parent.getNodeType()==Node.ELEMENT_NODE) { String text = DOMUtil.getText(parent); if(StringUtil.isNotEmpty(text)) { Object v = convertTo(text,type); result.add(v); } } } else { NodeList children = parent.getChildNodes(); for(int i=0; i<children.getLength(); i++) { Node node = children.item(i); if(node.getNodeType()==Node.ELEMENT_NODE && matches(node,uri,name)) { String text = DOMUtil.getText(node); if(StringUtil.isNotEmpty(text)) { Object v = convertTo(text,type); result.add(v); } } } } } if(global) { NodeList list = parent.getChildNodes(); for(int i=0; i<list.getLength(); i++) { Node node = list.item(i); if(node.getNodeType()==Node.ELEMENT_NODE) { extractValues(result, node, uri, name, type, global); } } } } private boolean matches(Node node, String uri, String name) { if(name.equals("*")) { return true; } if(uri==null) { return name.equals(node.getLocalName()); } else { return uri.equals(node.getNamespaceURI()) && name.equals(node.getLocalName()); } } private Object convertTo(String value, int type) { try { switch(type) { case TYPE_INT: return Integer.parseInt(value); case TYPE_LONG: return Long.parseLong(value); case TYPE_DOUBLE: return Double.parseDouble(value); case TYPE_BOOLEAN: return Boolean.parseBoolean(value); case TYPE_DATE: return XMIConverter.parseUtilDate(value); } return value; } catch(NumberFormatException ex) { return null; } } } public static class Json extends DataNavigator { private JsonFactory factory; private Object root; private List<Object> currentNodes; public Json(Object root) { this(null,root); } public Json(JsonFactory factory, Object root) { this.root = root; if(root!=null) { this.currentNodes = Collections.singletonList(root); } else { this.currentNodes = Collections.emptyList(); } if(factory==null) { factory=findFactory(root); } this.factory = factory; } protected JsonFactory findFactory(Object root){ return JsonJavaFactory.instanceEx; } protected Json(Json copy, List<Object> nodes) { this.root = copy.root; this.factory = copy.factory; this.currentNodes = nodes; } @Override public List<Object> getCurrentNodes() { return currentNodes; } @Override protected DataNavigator create(List<Object> nodes) { return new Json(this,nodes); } @Override protected Object root() { return root; } @Override protected List<Object> children() { return currentNodes; } @Override protected void extract(Object node, List<Object> result, String name, int type, boolean global) { // Find the children for all the current nodes if(type==TYPE_NODE) { extractChildren(result, node, name, global); } else { extractValues(result, node, name, type, global); } } private void extractChildren(List<Object> result, Object parent, String name, boolean global) { try { if(name.equals("*")) { for(Iterator<String> it=factory.iterateObjectProperties(parent); it.hasNext(); ) { String prop = it.next(); _extractChildren(result, parent, prop, global); } } if(name.equals(".")) { if(factory.isObject(parent)) { if(!result.contains(parent)) { result.add(parent); } } } else { _extractChildren(result, parent, name, global); } } catch(JsonException ex) {} } private void _extractChildren(List<Object> result, Object parent, String name, boolean global) throws JsonException { Object prop = factory.getProperty(parent,name); if(factory.isObject(prop)) { if(!result.contains(prop)) { result.add(prop); } } else if(prop!=null && factory.isArray(prop)) { // Until commons fixed for( Iterator<Object> it=factory.iterateArrayValues(prop); it.hasNext(); ) { Object val = it.next(); if(factory.isObject(val) && !result.contains(val)) { result.add(val); } } } if(global) { _extractChildrenGlobal(result, parent, name); } } private void _extractChildrenGlobal(List<Object> result, Object parent, String name) throws JsonException { for(Iterator<String> it=factory.iterateObjectProperties(parent); it.hasNext(); ) { Object o = factory.getProperty(parent, it.next()); if(factory.isObject(o)) { extractChildren(result, o, name, true); } else if(o!=null && factory.isArray(o)) { // Until commons fixed for( Iterator<Object> it2=factory.iterateArrayValues(o); it2.hasNext(); ) { Object val = it2.next(); extractChildren(result, val, name, true); } } } } private void extractValues(List<Object> result, Object parent, String name, int type, boolean global) { try { _extractValues(result, parent, name, type, global); } catch(JsonException ex) {} } private void _extractValues(List<Object> result, Object parent, String name, int type, boolean global) throws JsonException { if(name.equals("*")) { for(Iterator<String> it=factory.iterateObjectProperties(parent); it.hasNext(); ) { String prop = it.next(); Object value = factory.getProperty(parent,prop); _addValues(result, value, type); } } if(name.equals(".")) { // Not supported here... Only for XML! } else { Object value = factory.getProperty(parent,name); _addValues(result, value, type); } if(global) { _extractValuesGlobal(result, parent, name, type); } } private void _extractValuesGlobal(List<Object> result, Object parent, String name, int type) throws JsonException { for(Iterator<String> it=factory.iterateObjectProperties(parent); it.hasNext(); ) { Object o = factory.getProperty(parent, it.next()); if(factory.isObject(o)) { _extractValues(result, o, name, type, true); } else if(o!=null && factory.isArray(o)) { // Until commons fixed for( Iterator<Object> it2=factory.iterateArrayValues(o); it2.hasNext(); ) { Object val = it2.next(); _extractValuesGlobal(result, val, name, type); } } } } private void _addValues(List<Object> result, Object value, int type) throws JsonException { if(factory.isString(value)) { result.add(convertTo(factory.getString(value), type)); } else if(factory.isNumber(value)) { result.add(convertTo(factory.getNumber(value), type)); } else if(factory.isBoolean(value)) { result.add(convertTo(factory.getBoolean(value), type)); } else if(value!=null && factory.isArray(value)) { // Until commons fixed for( Iterator<Object> it=factory.iterateArrayValues(value); it.hasNext(); ) { Object val = it.next(); _addValues(result, val, type); } } } private Object convertTo(String value, int type) { try { switch(type) { case TYPE_INT: return Integer.parseInt(value); case TYPE_LONG: return Long.parseLong(value); case TYPE_DOUBLE: return Double.parseDouble(value); case TYPE_BOOLEAN: return Boolean.parseBoolean(value); case TYPE_DATE: return XMIConverter.parseUtilDate(value); } return value; } catch(NumberFormatException ex) { return null; } } private Object convertTo(double value, int type) { try { switch(type) { case TYPE_INT: return (int)value; case TYPE_LONG: return (long)value; case TYPE_DOUBLE: return value; case TYPE_BOOLEAN: return value!=0.0; case TYPE_DATE: return new Date((long)value); } return value; } catch(NumberFormatException ex) { return null; } } private Object convertTo(boolean value, int type) { try { switch(type) { case TYPE_INT: return Integer.valueOf(value?1:0); case TYPE_LONG: return Long.valueOf(value?1L:0L); case TYPE_DOUBLE: return Double.valueOf(value?1.0:0.0); case TYPE_BOOLEAN: return value; case TYPE_DATE: return new Date(0L); } return value; } catch(NumberFormatException ex) { return null; } } } public DataNavigator() { } @Override public DataNavigator clone() { try { return (DataNavigator)super.clone(); } catch(CloneNotSupportedException ex){return null;} } /** * Get the current nodes from this navigator. */ public abstract List<Object> getCurrentNodes(); /** * Get the current first node from this navigator. */ public Object getCurrentNode() { List<Object> l = getCurrentNodes(); if(l!=null && !l.isEmpty()) { return l.get(0); } return null; } /** * Get the number of elements in this navigator. * @return */ public int getCount() { return children().size(); } /** * Create a navigator from one indexed entry. * @param index * @return */ public DataNavigator get(int index) { List<Object> l = children(); if(index>=0 && index<l.size()) { List<Object> singleton = Collections.singletonList(l.get(index)); return create(singleton); } return create(Collections.emptyList()); } /** * Extract a subpath from the current navigator. * @param path * @return */ public DataNavigator get(String path) { List<Object> result = xpath(path, TYPE_NODE); return create(result); } /** * Select. * @param path * @return */ public DataNavigator select(ISelect select) { List<Object> l = children(); int size = l.size(); if(size==1) { Object o = l.get(0); if(select==null || select.matches(o)) { return create(Collections.singletonList(o)); } } else if(size>1) { List<Object> result = new ArrayList<Object>(); for(int i=0; i<size; i++) { Object o = l.get(i); if(select==null || select.matches(o)) { result.add(o); } } return create(result); } return this; } public DataNavigator selectEq(final String path, final String value) { return select(new ISelect() { @Override public boolean matches(Object object) { List<Object> res = DataNavigator.this.xpath(object,path,TYPE_STRING); for(int i=0; i<res.size(); i++) { if(StringUtil.equals((String)res.get(i), value)) { return true; } } return false; } }); } public DataNavigator selectEq(final String path, final double value) { return select(new ISelect() { @Override public boolean matches(Object object) { List<Object> res = DataNavigator.this.xpath(object,path,TYPE_DOUBLE); for(int i=0; i<res.size(); i++) { if(((Number)res.get(i)).doubleValue()==value) { return true; } } return false; } }); } public DataNavigator selectEq(final String path, final boolean value) { return select(new ISelect() { @Override public boolean matches(Object object) { List<Object> res = DataNavigator.this.xpath(object,path,TYPE_BOOLEAN); for(int i=0; i<res.size(); i++) { if(((Boolean)res.get(i)).booleanValue()==value) { return true; } } return false; } }); } public List<Object> nodes(String path) { return xpath(path, TYPE_NODE); } public Object node(String path) { return _value(path,TYPE_NODE,null); } public Object node(String path, Object def) { return _value(path,TYPE_NODE,def); } public List<Object> values(String path) { return xpath(path, TYPE_OBJECT); } public Object value(String path) { return _value(path,TYPE_OBJECT,null); } public Object value(String path, Object def) { return _value(path,TYPE_OBJECT,def); } @SuppressWarnings({ "unchecked", "rawtypes" }) public List<String> stringValues(String path) { return (List)xpath(path, TYPE_STRING); } public String stringValue(String path) { return (String)_value(path,TYPE_STRING,null); } public String stringValue(String path, String def) { return (String)_value(path,TYPE_STRING,def); } @SuppressWarnings({ "unchecked", "rawtypes" }) public List<Integer> intValues(String path) { return (List)xpath(path, TYPE_INT); } public int intValue(String path) { return (Integer)_value(path,TYPE_INT,null); } public int intValue(String path, String def) { return (Integer)_value(path,TYPE_INT,def); } @SuppressWarnings({ "unchecked", "rawtypes" }) public List<Long> longValues(String path) { return (List)xpath(path, TYPE_LONG); } public long longValue(String path) { return (Long)_value(path,TYPE_LONG,null); } public long longValue(String path, String def) { return (Long)_value(path,TYPE_LONG,def); } @SuppressWarnings({ "unchecked", "rawtypes" }) public List<Double> doubleValues(String path) { return (List)xpath(path, TYPE_DOUBLE); } public double doubleValue(String path) { return (Double)_value(path,TYPE_DOUBLE,null); } public double doubleValue(String path, String def) { return (Double)_value(path,TYPE_DOUBLE,def); } @SuppressWarnings({ "unchecked", "rawtypes" }) public List<Boolean> booleanValues(String path) { return (List)xpath(path, TYPE_BOOLEAN); } public boolean booleanValue(String path) { return (Boolean)_value(path,TYPE_BOOLEAN,false); } public boolean booleanValue(String path, String def) { return (Boolean)_value(path,TYPE_BOOLEAN,def); } @SuppressWarnings({ "unchecked", "rawtypes" }) public List<Date> dateValues(String path) { return (List)xpath(path, TYPE_DATE); } public Date dateValue(String path) { return (Date)_value(path,TYPE_DATE,null); } public Date dateValue(String path, String def) { return (Date)_value(path,TYPE_DATE,def); } // // Abstract methods that must be implemented // protected abstract Object root(); protected abstract List<Object> children(); protected abstract DataNavigator create(List<Object> nodes); protected abstract void extract(Object node, List<Object> result, String name, int type, boolean global); // // Utility: access to the values // private Object _value(String path, int type, Object def) { List<Object> l = xpath(path, type); if(l.size()==1) { return l.get(0); } return def; } // // Internal path execution // private List<Object> xpath(String path, int type) { return xpath(children(), path, type); } private List<Object> xpath(List<Object> current, String path, int type) { if(StringUtil.isNotEmpty(path)) { for(int start=0; start<path.length(); ) { int end = path.indexOf('/',start); if(end!=start) { List<Object> result = new ArrayList<Object>(); boolean global = start>=2 && path.charAt(start-1)=='/' && path.charAt(start-2)=='/'; if(end>=0) { String part = path.substring(start,end); start = end+1; int size = current.size(); for(int i=0; i<size; i++) { extract(current.get(i),result,part,TYPE_NODE,global); } current = result; } else { String part = start>0 ? path.substring(start) : path; int size = current.size(); for(int i=0; i<size; i++) { extract(current.get(i),result,part,type,global); } return result; } } else { start++; } } } return Collections.emptyList(); } private List<Object> xpath(Object current, String path, int type) { if(StringUtil.isNotEmpty(path)) { for(int start=0; start<path.length(); ) { List<Object> result = new ArrayList<Object>(); int end = path.indexOf('/',start); if(end!=start) { boolean global = start>=2 && path.charAt(start-1)=='/' && path.charAt(start-2)=='/'; if(end>=0) { String part = path.substring(start,end); start = end+1; extract(current,result,part,TYPE_NODE,global); } else { String part = start>0 ? path.substring(start) : path; extract(current,result,part,type,global); return result; } } else { start++; } } } return Collections.emptyList(); } }