/*
 * 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 org.joox;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import javax.xml.bind.DataBindingException;
import javax.xml.bind.JAXB;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.dom.DOMResult;

import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

Author:Lukas Eder
/** * @author Lukas Eder */
public final class JOOX { // --------------------------------------------------------------------- // $ wrapper methods // ---------------------------------------------------------------------
Wrap a new empty document
/** * Wrap a new empty document */
public static Match $() { return $(builder().newDocument()); }
Wrap a JAXB-marshallable element in a jOOX Match element set
See Also:
/** * Wrap a JAXB-marshallable element in a jOOX {@link Match} element set * * @see #content(Object) * @see Match#content(Object) */
public static Match $(Object object) { Document document = builder().newDocument(); if (object != null) { Result result = new DOMResult(document); JAXB.marshal(object, result); } return $(document); }
Create a new DOM element in an independent document
/** * Create a new DOM element in an independent document */
public static Match $(String name) { Document document = builder().newDocument(); DocumentFragment fragment = Util.createContent(document, name); if (fragment != null) document.appendChild(fragment); else document.appendChild(document.createElement(name)); return $(document); }
Create a new DOM element in an independent document
/** * Create a new DOM element in an independent document */
public static Match $(String name, String content) { return $(name).append(content); }
Create a new DOM element in an independent document

The added content is cloned into the new document

/** * Create a new DOM element in an independent document * <p> * The added content is cloned into the new document */
public static Match $(String name, Element... content) { return $(name).append(content); }
Create a new DOM element in an independent document

The added content is cloned into the new document

/** * Create a new DOM element in an independent document * <p> * The added content is cloned into the new document */
public static Match $(String name, Match... content) { return $(name).append(content); }
Wrap a DOM document in a jOOX Match element set
/** * Wrap a DOM document in a jOOX {@link Match} element set */
public static Match $(Document document) { if (document == null) return $(); else if (document.getDocumentElement() == null) return new Impl(document, null); else return $(document.getDocumentElement()); }
Wrap a DOM element in a jOOX Match element set
/** * Wrap a DOM element in a jOOX {@link Match} element set */
public static Match $(Element element) { if (element == null) return $(); else return new Impl(element.getOwnerDocument(), null).addElements(element); }
Wrap a DOM Node in a jOOX Match element set

Supported node types are

If the supplied Node is of any other type, then an empty Match is created
/** * Wrap a DOM {@link Node} in a jOOX {@link Match} element set * <p> * Supported node types are * <ul> * <li> {@link Document} : see {@link #$(Document)}</li> * <li> {@link Element} : see {@link #$(Element)}</li> * </ul> * If the supplied Node is of any other type, then an empty Match is created */
public static Match $(Node node) { if (node instanceof Document) return $((Document) node); else if (node instanceof Element) return $((Element) node); else return $(); }
Wrap a DOM NodeList in a jOOX Match element set

If the supplied NodeList is empty or null, then an empty Match is created

/** * Wrap a DOM {@link NodeList} in a jOOX {@link Match} element set * <p> * If the supplied NodeList is empty or null, then an empty Match is created */
public static Match $(NodeList list) { if (list != null && list.getLength() > 0) return new Impl(list.item(0).getOwnerDocument(), null).addNodeList(list); return $(); }
Convenience method for calling $(context.element())
/** * Convenience method for calling <code>$(context.element())</code> */
public static Match $(Context context) { if (context == null) return $(); else return $(context.element()); }
Convenience method for calling $(match)
/** * Convenience method for calling <code>$(match)</code> */
public static Match $(Match match) { if (match == null) return $(); else return match; }
Convenience method for calling $(url.openStream())
/** * Convenience method for calling <code>$(url.openStream())</code> */
public static Match $(URL url) throws SAXException, IOException { return $(url.openStream()); }
Convenience method for calling $(new File(uri))
/** * Convenience method for calling <code>$(new File(uri))</code> */
public static Match $(URI uri) throws SAXException, IOException { return $(new File(uri)); }
Read a DOM document from a file into a Match element set
Throws:
/** * Read a DOM document from a file into a {@link Match} element set * * @throws IOException * @throws SAXException */
public static Match $(File file) throws SAXException, IOException { return $(builder().parse(file)); }
Read a DOM document from a stream into a Match element set
Throws:
/** * Read a DOM document from a stream into a {@link Match} element set * * @throws IOException * @throws SAXException */
public static Match $(InputStream stream) throws SAXException, IOException { return $(builder().parse(stream)); }
Read a DOM document from a reader into a Match element set
Throws:
/** * Read a DOM document from a reader into a {@link Match} element set * * @throws IOException * @throws SAXException */
public static Match $(Reader reader) throws SAXException, IOException { return $(builder().parse(new InputSource(reader))); }
Read a DOM document from a file into a Match element set
Throws:
/** * Read a DOM document from a file into a {@link Match} element set * * @throws IOException * @throws SAXException */
public static Match $(InputSource source) throws SAXException, IOException { return $(builder().parse(source)); } // --------------------------------------------------------------------- // Filter factories // ---------------------------------------------------------------------
A filter that always returns false
/** * A filter that always returns false */
public static FastFilter none() { return NONE; }
A filter that always returns true
/** * A filter that always returns true */
public static FastFilter all() { return ALL; }
A filter that returns true on all even iteration indexes (starting with 0!)
/** * A filter that returns true on all even iteration indexes (starting with * 0!) */
public static FastFilter even() { return EVEN; }
A filter that returns true on all odd iteration indexes (starting with 0!)
/** * A filter that returns true on all odd iteration indexes (starting with * 0!) */
public static FastFilter odd() { return ODD; }
A filter that returns true on leaf elements
/** * A filter that returns true on leaf elements */
public static FastFilter leaf() { return LEAF; }
A filter that returns true on elements at given iteration indexes
/** * A filter that returns true on elements at given iteration indexes */
public static FastFilter at(final int... indexes) { return new FastFilter() { @Override public boolean filter(Context context) { for (int i : indexes) if (i == context.elementIndex()) return true; return false; } }; }
A filter that returns all elements matched by a given selector.

In most cases, this is the same as calling tag(String). In Match.find(String), the following CSS-style selector syntax elements are also supported:

Selector pattern meaning
* any element
E an element of type E
E[foo] an E element with a "foo" attribute
E[foo="bar"] an E element whose "foo" attribute value is exactly equal to "bar"
E[foo~="bar"] an E element whose "foo" attribute value is a list of whitespace-separated values, one of which is exactly equal to "bar"
E[foo^="bar"] an E element whose "foo" attribute value begins exactly with the string "bar"
E[foo$="bar"] an E element whose "foo" attribute value ends exactly with the string "bar"
E[foo*="bar"] an E element whose "foo" attribute value contains the substring "bar"
E[foo|="en"] an E element whose "foo" attribute has a hyphen-separated list of values beginning (from the left) with "en"
E:root an E element, root of the document
E:first-child an E element, first child of its parent
E:last-child an E element, last child of its parent
E:only-child an E element, only child of its parent
E:empty an E element that has no children (including text nodes)
E#myid an E element with ID equal to "myid".
E F an F element descendant of an E element
E > F an F element child of an E element
E + F an F element immediately preceded by an E element
E ~ F an F element preceded by an E element

Note that due to the presence of pseudo selectors, such as :root, :empty, etc, namespaces are not supported in selectors. Use jOOX's XPath functionality provided in Match.xpath(String) along with Match.namespaces(Map<String,String>) if your XML document contains namespaces

See Also:
/** * A filter that returns all elements matched by a given selector. * <p> * In most cases, this is the same as calling {@link #tag(String)}. In * {@link Match#find(String)}, the following CSS-style selector syntax * elements are also supported: * <table border="1"> * <tr> * <th>Selector pattern</th> * <th>meaning</th> * </tr> * <tr> * <td>*</td> * <td>any element</td> * </tr> * <tr> * <td>E</td> * <td>an element of type E</td> * </tr> * <tr> * <td>E[foo]</td> * <td>an E element with a "foo" attribute</td> * </tr> * <tr> * <td>E[foo="bar"]</td> * <td>an E element whose "foo" attribute value is exactly equal to "bar"</td> * </tr> * <tr> * <td>E[foo~="bar"]</td> * <td>an E element whose "foo" attribute value is a list of * whitespace-separated values, one of which is exactly equal to "bar"</td> * </tr> * <tr> * <td>E[foo^="bar"]</td> * <td>an E element whose "foo" attribute value begins exactly with the * string "bar"</td> * </tr> * <tr> * <td>E[foo$="bar"]</td> * <td>an E element whose "foo" attribute value ends exactly with the string * "bar"</td> * </tr> * <tr> * <td>E[foo*="bar"]</td> * <td>an E element whose "foo" attribute value contains the substring "bar" * </td> * </tr> * <tr> * <td>E[foo|="en"]</td> * <td>an E element whose "foo" attribute has a hyphen-separated list of * values beginning (from the left) with "en"</td> * </tr> * <tr> * <td>E:root</td> * <td>an E element, root of the document</td> * </tr> * <tr> * <td>E:first-child</td> * <td>an E element, first child of its parent</td> * </tr> * <tr> * <td>E:last-child</td> * <td>an E element, last child of its parent</td> * </tr> * <tr> * <td>E:only-child</td> * <td>an E element, only child of its parent</td> * </tr> * <tr> * <td>E:empty</td> * <td>an E element that has no children (including text nodes)</td> * </tr> * <tr> * <td>E#myid</td> * <td>an E element with ID equal to "myid".</td> * </tr> * <tr> * <td>E F</td> * <td>an F element descendant of an E element</td> * </tr> * <tr> * <td>E > F</td> * <td>an F element child of an E element</td> * </tr> * <tr> * <td>E + F</td> * <td>an F element immediately preceded by an E element</td> * </tr> * <tr> * <td>E ~ F</td> * <td>an F element preceded by an E element</td> * </tr> * </table> * <p> * Note that due to the presence of pseudo selectors, such as * <code>:root</code>, <code>:empty</code>, etc, namespaces are not * supported in selectors. Use jOOX's XPath functionality provided in * {@link Match#xpath(String)} along with * {@link Match#namespaces(java.util.Map)} if your XML document contains * namespaces * * @see <a * href="http://www.w3.org/TR/selectors/#selectors">http://www.w3.org/TR/selectors/#selectors</a> */
public static Filter selector(final String selector) { return tag(selector); }
A filter that returns all elements with a given tag name

This is the same as calling tag(tagName, true)

See Also:
  • tag(String, boolean)
/** * A filter that returns all elements with a given tag name * <p> * This is the same as calling <code>tag(tagName, true)</code> * * @see #tag(String, boolean) */
public static FastFilter tag(final String tagName) { return tag(tagName, true); }
A filter that returns all elements with a given tag name

This method allows for specifying whether namespace prefixes should be ignored. This is particularly useful in DOM Level 1 documents, which are namespace-unaware. In those methods Document.getElementsByTagNameNS(String, String) will not work, as elements do not contain any localName.

Params:
  • tagName – The tag name to match. Use * as a special tag name to match all tag names
  • ignoreNamespace – Whether namespace prefixes can be ignored. When set to true, then the namespace prefix is ignored. When set to false, then tagName must include the actual namespace prefix.
/** * A filter that returns all elements with a given tag name * <p> * This method allows for specifying whether namespace prefixes should be * ignored. This is particularly useful in DOM Level 1 documents, which are * namespace-unaware. In those methods * {@link Document#getElementsByTagNameNS(String, String)} will not work, as * elements do not contain any <code>localName</code>. * * @param tagName The tag name to match. Use <strong>*</strong> as a special * tag name to match all tag names * @param ignoreNamespace Whether namespace prefixes can be ignored. When * set to <code>true</code>, then the namespace prefix is * ignored. When set to <code>false</code>, then * <code>tagName</code> must include the actual namespace prefix. */
public static FastFilter tag(final String tagName, final boolean ignoreNamespace) { if (tagName == null || tagName.equals("")) return none(); // [#104] The special * operator is also supported else if ("*".equals(tagName)) return all(); else return new FastFilter() { @Override public boolean filter(Context context) { String localName = context.element().getTagName(); // [#103] If namespaces are ignored, consider only local // part of possibly namespace-unaware Element if (ignoreNamespace) localName = Util.stripNamespace(localName); return tagName.equals(localName); } }; }
A filter that returns all elements with a given namespace prefix

null and the empty string are treated equally to indicate that no namespace prefix should be present.

/** * A filter that returns all elements with a given namespace prefix * <p> * <code>null</code> and the empty string are treated equally to indicate * that no namespace prefix should be present. */
public static FastFilter namespacePrefix(final String namespacePrefix) { // The special * operator is also supported if ("*".equals(namespacePrefix)) return all(); else return new FastFilter() { @Override public boolean filter(Context context) { String match = $(context).namespacePrefix(); if (match == null || "".equals(match)) return namespacePrefix == null || "".equals(namespacePrefix); else return match.equals(namespacePrefix); } }; }
A filter that returns all elements with a given namespace URI

null and the empty string are treated equally to indicate that no namespace URI should be present.

This only works if the underlying document is namespace-aware

/** * A filter that returns all elements with a given namespace URI * <p> * <code>null</code> and the empty string are treated equally to indicate * that no namespace URI should be present. * <p> * This only works if the underlying document is namespace-aware */
public static FastFilter namespaceURI(final String namespaceURI) { // The special * operator is also supported if ("*".equals(namespaceURI)) return all(); else return new FastFilter() { @Override public boolean filter(Context context) { String match = $(context).namespaceURI(); if (match == null || "".equals(match)) return namespaceURI == null || "".equals(namespaceURI); else return match.equals(namespaceURI); } }; }
A filter that returns all elements whose text content matches a given regex
See Also:
  • matches.matches(String, CharSequence)
/** * A filter that returns all elements whose text content matches a given * regex * * @see Pattern#matches(String, CharSequence) */
public static FastFilter matchText(final String regex) { if (regex == null || regex.equals("")) return none(); else return new FastFilter() { private final Pattern pattern = Pattern.compile(regex); @Override public boolean filter(Context context) { return pattern.matcher($(context).text()).matches(); } }; }
A filter that returns all elements whose text content matches a given regex
See Also:
  • matches.matches(String, CharSequence)
/** * A filter that returns all elements whose text content matches a given * regex * * @see Pattern#matches(String, CharSequence) */
public static FastFilter matchAttr(final String name, final String valueRegex) { if (name == null || name.equals("") || valueRegex == null || valueRegex.equals("")) return none(); else return new FastFilter() { private final Pattern pattern = Pattern.compile(valueRegex); @Override public boolean filter(Context context) { String value = $(context).attr(name); if (value == null) return false; return pattern.matcher(value).matches(); } }; }
A filter that returns all elements whose tag name matches a given regex

This is the same as calling matchTag(regex, true)

See Also:
  • matches.matches(String, CharSequence)
/** * A filter that returns all elements whose tag name matches a given regex * <p> * This is the same as calling <code>matchTag(regex, true)</code> * * @see Pattern#matches(String, CharSequence) */
public static FastFilter matchTag(final String regex) { return matchTag(regex, true); }
A filter that returns all elements whose tag name matches a given regex

This method allows for specifying whether namespace prefixes should be ignored. This is particularly useful in DOM Level 1 documents, which are namespace-unaware. In those methods Document.getElementsByTagNameNS(String, String) will not work, as elements do not contain any localName.

Params:
  • regex – The regular expression to use for matching tag names.
  • ignoreNamespace – Whether namespace prefixes can be ignored. When set to true, then the namespace prefix is ignored. When set to false, then regex must also match potential namespace prefixes.
See Also:
/** * A filter that returns all elements whose tag name matches a given regex * <p> * This method allows for specifying whether namespace prefixes should be * ignored. This is particularly useful in DOM Level 1 documents, which are * namespace-unaware. In those methods * {@link Document#getElementsByTagNameNS(String, String)} will not work, as * elements do not contain any <code>localName</code>. * * @param regex The regular expression to use for matching tag names. * @param ignoreNamespace Whether namespace prefixes can be ignored. When * set to <code>true</code>, then the namespace prefix is * ignored. When set to <code>false</code>, then * <code>regex</code> must also match potential namespace * prefixes. * @see Pattern#matches(String, CharSequence) */
public static FastFilter matchTag(final String regex, final boolean ignoreNamespace) { if (regex == null || regex.equals("")) return none(); else return new FastFilter() { private final Pattern pattern = Pattern.compile(regex); @Override public boolean filter(Context context) { String localName = context.element().getTagName(); // [#106] If namespaces are ignored, consider only local // part of possibly namespace-unaware Element if (ignoreNamespace) localName = Util.stripNamespace(localName); return pattern.matcher(localName).matches(); } }; }
A filter that returns all elements with a given attribute
/** * A filter that returns all elements with a given attribute */
public static FastFilter attr(final String name) { if (name == null || name.equals("")) return new FastFilter() { @Override public boolean filter(Context context) { return context.element().getAttributes().getLength() == 0; } }; else return new FastFilter() { @Override public boolean filter(Context context) { return $(context).attr(name) != null; } }; }
A filter that returns all elements with a given attribute being set to a given value
/** * A filter that returns all elements with a given attribute being set to a * given value */
public static FastFilter attr(final String name, final String... values) { final List<String> list = Arrays.asList(values); if (name == null || name.equals("")) return attr(name); else return new FastFilter() { @Override public boolean filter(Context context) { return list.contains($(context).attr(name)); } }; }
Combine filters
/** * Combine filters */
public static Filter and(final Filter... filters) { return new Filter() { @Override public boolean filter(Context context) { for (Filter filter : filters) if (!filter.filter(context)) return false; return true; } }; }
Combine filters
/** * Combine filters */
public static Filter or(final Filter... filters) { return new Filter() { @Override public boolean filter(Context context) { for (Filter filter : filters) if (filter.filter(context)) return true; return false; } }; }
Inverse a filter
/** * Inverse a filter */
public static Filter not(final Filter filter) { return new Filter() { @Override public boolean filter(Context context) { return !filter.filter(context); } }; }
Create a filter matching id attributes
/** * Create a filter matching id attributes */
public static FastFilter ids(String... ids) { final Set<String> set = new HashSet<String>(Arrays.asList(ids)); return new FastFilter() { @Override public boolean filter(Context context) { return set.contains($(context).attr("id")); } }; } // --------------------------------------------------------------------- // Content factories // ---------------------------------------------------------------------
Get a constant content that returns the same value for all elements.
/** * Get a constant content that returns the same <code>value</code> for all * elements. */
public static Content content(final String value) { return new Content() { @Override public String content(Context context) { return value; } }; }
Get a constant content that returns a marshalled, JAXB-annotated value for all elements.
See Also:
/** * Get a constant content that returns a marshalled, JAXB-annotated * <code>value</code> for all elements. * * @see #$(Object) * @see Match#content(Object) */
public static Content content(final Object value) { if (value == null) return content(""); return new Content() { private String marshalled; @Override public String content(Context context) { if (marshalled == null) { try { JAXBContext jaxb = JAXBContext.newInstance(value.getClass()); Marshaller marshaller = jaxb.createMarshaller(); marshaller.setProperty("jaxb.fragment", true); StringWriter writer = new StringWriter(); marshaller.marshal(value, writer); marshalled = writer.toString(); } catch (JAXBException e) { throw new DataBindingException(e); } } return marshalled; } }; } // --------------------------------------------------------------------- // Mapper factories // ---------------------------------------------------------------------
Create a mapper that returns all id attributes
/** * Create a mapper that returns all <code>id</code> attributes */
public static Mapper<String> ids() { return attrs("id"); }
Create a mapper that returns all attributes with a given name
/** * Create a mapper that returns all attributes with a given name */
public static Mapper<String> attrs(final String attributeName) { return new Mapper<String>() { @Override public String map(Context context) { return $(context).attr(attributeName); } }; }
Create a mapper that returns all paths to given elements
/** * Create a mapper that returns all paths to given elements */
public static Mapper<String> paths() { return new Mapper<String>() { @Override public String map(Context context) { return Util.path(context.element()); } }; } // --------------------------------------------------------------------- // API utilities // ---------------------------------------------------------------------
Chain several instances of Each into a single one.

The resulting chained Each produces a new Each that can be used in the Match.each(Each) method. I.e. every node in a set of matched nodes will be passed to every chained Each, sequentially.

/** * Chain several instances of {@link Each} into a single one. * <p> * The resulting chained <code>Each</code> produces a new <code>Each</code> * that can be used in the {@link Match#each(Each)} method. I.e. every node * in a set of matched nodes will be passed to every chained * <code>Each</code>, sequentially. */
public static Each chain(final Each... each) { return chain(Arrays.asList(each)); }
Chain several instances of Each into a single one.

The resulting chained Each produces a new Each that can be used in the Match.each(Each) method. I.e. every node in a set of matched nodes will be passed to every chained Each, sequentially.

/** * Chain several instances of {@link Each} into a single one. * <p> * The resulting chained <code>Each</code> produces a new <code>Each</code> * that can be used in the {@link Match#each(Each)} method. I.e. every node * in a set of matched nodes will be passed to every chained * <code>Each</code>, sequentially. */
public static Each chain(final Iterable<? extends Each> each) { return new Each() { @Override public void each(Context context) { if (each != null) for (Each e : each) e.each(context); } }; } // --------------------------------------------------------------------- // DOM utilities // ---------------------------------------------------------------------
Wrap a NodeList into an Iterable
/** * Wrap a {@link NodeList} into an {@link Iterable} */
public static Iterable<Element> iterable(NodeList elements) { return new Elements(elements); }
Wrap a NodeList into an Iterator
/** * Wrap a {@link NodeList} into an {@link Iterator} */
public static Iterator<Element> iterator(NodeList elements) { return new Elements(elements).iterator(); }
Wrap a NodeList into an List
/** * Wrap a {@link NodeList} into an {@link List} */
public static List<Element> list(NodeList elements) { List<Element> list = new ArrayList<Element>(); for (Element element : iterable(elements)) list.add(element); return list; }
Get a namespace-aware document builder
/** * Get a namespace-aware document builder */
public static DocumentBuilder builder() { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // ----------------------------------------------------------------- // [#136] FIX START: Prevent OWASP attack vectors try { factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); } catch (ParserConfigurationException ignore) {} try { factory.setFeature("http://xml.org/sax/features/external-general-entities", false); } catch (ParserConfigurationException ignore) {} try { factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); } catch (ParserConfigurationException ignore) {} // [#149] Not implemented on Android try { factory.setXIncludeAware(false); } catch (UnsupportedOperationException ignore) {} factory.setExpandEntityReferences(false); // [#136] FIX END // ----------------------------------------------------------------- // [#9] [#107] In order to take advantage of namespace-related DOM // features, the internal builder should be namespace-aware factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); return builder; } catch (Exception e) { throw new RuntimeException(e); } } // --------------------------------------------------------------------- // Other utilities // --------------------------------------------------------------------- private static final Set<String> TRUE_VALUES; private static final Set<String> FALSE_VALUES; static { TRUE_VALUES = new HashSet<String>(); FALSE_VALUES = new HashSet<String>(); TRUE_VALUES.add("1"); TRUE_VALUES.add("y"); TRUE_VALUES.add("yes"); TRUE_VALUES.add("true"); TRUE_VALUES.add("on"); TRUE_VALUES.add("enabled"); FALSE_VALUES.add("0"); FALSE_VALUES.add("n"); FALSE_VALUES.add("no"); FALSE_VALUES.add("false"); FALSE_VALUES.add("off"); FALSE_VALUES.add("disabled"); }
Convert a string value to any of these types:
  • String: The conversion has no effect
  • Byte: Numeric conversion. NaN will return null
  • Short: Numeric conversion. NaN will return null
  • Integer: Numeric conversion. NaN will return null
  • Long: Numeric conversion. NaN will return null
  • Float: Numeric conversion. NaN will return null
  • Double: Numeric conversion. NaN will return null
  • BigDecimal: Numeric conversion. NaN will return null
  • BigInteger: Numeric conversion. NaN will return null
  • Boolean: Boolean conversion. Boolean values for true are any of these case-insensitive strings:
    • 1
    • y
    • yes
    • true
    • on
    • enabled
    Boolean values for false are any of these case-insensitive strings:
    • 0
    • n
    • no
    • false
    • off
    • disabled
  • Primitive types: Numeric or boolean conversion, except that null and illegal values will result in 0 or false
  • Date: Datetime conversion.
  • Calendar: Datetime conversion.
  • GregorianCalendar: Datetime conversion.
  • Timestamp: Datetime conversion. Possible patterns for datetime conversion are
    • yyyy: Only the year is parsed
    • yyyy[-/]MM: Year and month are parsed. Separator characters are optional
    • yyyy[-/]MM[-/]dd: Date is parsed. Separator characters are optional
    • dd[-/.]MM[-/.]yyyy: Date is parsed. Separator characters are mandatory
    • yyyy[-/]MM[-/]dd[T ]HH: Date and hour are parsed. Separator characters are optional
    • yyyy[-/]MM[-/]dd[T ]HH[:]mm: Date and time are parsed. Separator characters are optional
    • yyyy[-/]MM[-/]dd[T ]HH[:]mm[:]ss: Date and time are parsed. Separator characters are optional
    • yyyy[-/]MM[-/]dd[T ]HH[:]mm[:]ss.SSS: Date and time are parsed. Separator characters are optional
  • Date: Date conversion. Possible patterns for date conversion are
    • yyyy: Only the year is parsed
    • yyyy[-/]MM: Year and month are parsed. Separator characters are optional
    • yyyy[-/]MM[-/]dd: Date is parsed. Separator characters are optional
    • dd[-/.]MM[-/.]yyyy: Date is parsed. Separator characters are mandatory
  • Time: Time conversion. Possible patterns for time conversion are
    • HH: Hour is parsed. Separator characters are optional
    • HH[:]mm: Hour and minute are parsed. Separator characters are optional
    • HH[:]mm[:]ss: Time is parsed. Separator characters are optional
  • Any of the above as array. Arrays of any type are split by any whitespace character, comma or semi-colon. String literals may be delimited by quotes as well.

All other values evaluate to null

/** * Convert a string value to any of these types: * <ul> * <li> {@link String}: The conversion has no effect</li> * <li> {@link Byte}: Numeric conversion. NaN will return null</li> * <li> {@link Short}: Numeric conversion. NaN will return null</li> * <li> {@link Integer}: Numeric conversion. NaN will return null</li> * <li> {@link Long}: Numeric conversion. NaN will return null</li> * <li> {@link Float}: Numeric conversion. NaN will return null</li> * <li> {@link Double}: Numeric conversion. NaN will return null</li> * <li> {@link BigDecimal}: Numeric conversion. NaN will return null</li> * <li> {@link BigInteger}: Numeric conversion. NaN will return null</li> * <li> {@link Boolean}: Boolean conversion. Boolean values for * <code>true</code> are any of these case-insensitive strings: * <ul> * <li><code>1</code></li> * <li><code>y</code></li> * <li><code>yes</code></li> * <li><code>true</code></li> * <li><code>on</code></li> * <li><code>enabled</code></li> * </ul> * Boolean values for <code>false</code> are any of these case-insensitive * strings: * <ul> * <li><code>0</code></li> * <li><code>n</code></li> * <li><code>no</code></li> * <li><code>false</code></li> * <li><code>off</code></li> * <li><code>disabled</code></li> * </ul> * </li> * <li>Primitive types: Numeric or boolean conversion, except that * <code>null</code> and illegal values will result in <code>0</code> or * <code>false</code></li> * <li> {@link java.util.Date}: Datetime conversion.</li> * <li> {@link java.util.Calendar}: Datetime conversion.</li> * <li> {@link java.util.GregorianCalendar}: Datetime conversion.</li> * <li> {@link java.sql.Timestamp}: Datetime conversion. Possible patterns * for datetime conversion are * <ul> * <li><code>yyyy</code>: Only the year is parsed</li> * <li><code>yyyy[-/]MM</code>: Year and month are parsed. Separator * characters are optional</li> * <li><code>yyyy[-/]MM[-/]dd</code>: Date is parsed. Separator characters * are optional</li> * <li><code>dd[-/.]MM[-/.]yyyy</code>: Date is parsed. Separator characters * are mandatory</li> * <li><code>yyyy[-/]MM[-/]dd[T ]HH</code>: Date and hour are parsed. * Separator characters are optional</li> * <li><code>yyyy[-/]MM[-/]dd[T ]HH[:]mm</code>: Date and time are parsed. * Separator characters are optional</li> * <li><code>yyyy[-/]MM[-/]dd[T ]HH[:]mm[:]ss</code>: Date and time are * parsed. Separator characters are optional</li> * <li><code>yyyy[-/]MM[-/]dd[T ]HH[:]mm[:]ss.SSS</code>: Date and time are * parsed. Separator characters are optional</li> * </ul> * </li> * <li> {@link java.sql.Date}: Date conversion. Possible patterns for date * conversion are * <ul> * <li><code>yyyy</code>: Only the year is parsed</li> * <li><code>yyyy[-/]MM</code>: Year and month are parsed. Separator * characters are optional</li> * <li><code>yyyy[-/]MM[-/]dd</code>: Date is parsed. Separator characters * are optional</li> * <li><code>dd[-/.]MM[-/.]yyyy</code>: Date is parsed. Separator characters * are mandatory</li> * </ul> * </li> * <li> {@link java.sql.Time}: Time conversion. Possible patterns for time * conversion are * <ul> * <li><code>HH</code>: Hour is parsed. Separator characters are optional</li> * <li><code>HH[:]mm</code>: Hour and minute are parsed. Separator * characters are optional</li> * <li><code>HH[:]mm[:]ss</code>: Time is parsed. Separator characters are * optional</li> * </ul> * </li> * <li>Any of the above as array. Arrays of any type are split by any * whitespace character, comma or semi-colon. String literals may be * delimited by quotes as well.</li> * </ul> * <p> * All other values evaluate to <code>null</code> */
@SuppressWarnings("unchecked") public static <T> T convert(String value, Class<T> type) { if (value == null && type.isPrimitive()) { value = "0"; } if (value == null) { return null; } // [#24] TODO: base64-decode binary data // else if (type == byte[].class) { // } // [#28] Array conversion will recurse for split values else if (type.isArray()) { Class<?> component = type.getComponentType(); List<String> split = Util.split(value); return (T) convert(split, component).toArray((Object[]) Array.newInstance(component, split.size())); } // Strings are not converted else if (type == String.class) { return (T) value; } // All type can be converted to Object else if (type == Object.class) { return (T) value; } // Various number types else if (type == Byte.class || type == byte.class) { try { return (T) Byte.valueOf(new BigDecimal(value).byteValue()); } catch (Exception e) { return (T) ((type == Byte.class) ? null : (byte) 0); } } else if (type == Short.class || type == short.class) { try { return (T) Short.valueOf(new BigDecimal(value).shortValue()); } catch (Exception e) { return (T) ((type == Short.class) ? null : (short) 0); } } else if (type == Integer.class || type == int.class) { try { return (T) Integer.valueOf(new BigDecimal(value).intValue()); } catch (Exception e) { return (T) ((type == Integer.class) ? null : 0); } } else if (type == Long.class || type == long.class) { try { return (T) Long.valueOf(new BigDecimal(value).longValue()); } catch (Exception e) { return (T) ((type == Long.class) ? null : 0L); } } else if (type == Float.class || type == float.class) { try { return (T) Float.valueOf(value); } catch (Exception e) { return (T) ((type == Float.class) ? null : 0.0f); } } else if (type == Double.class || type == double.class) { try { return (T) Double.valueOf(value); } catch (Exception e) { return (T) ((type == Double.class) ? null : 0.0); } } else if (type == BigDecimal.class) { try { return (T) new BigDecimal(value); } catch (Exception e) { return null; } } else if (type == BigInteger.class) { try { return (T) new BigDecimal(value).toBigInteger(); } catch (Exception e) { return null; } } // Booleans have a set of allowed values else if (type == Boolean.class || type == boolean.class) { String s = value.toLowerCase(); if (TRUE_VALUES.contains(s)) { return (T) Boolean.TRUE; } else if (FALSE_VALUES.contains(s)) { return (T) Boolean.FALSE; } else { return (T) ((type == Boolean.class) ? null : false); } } // [#29] TODO: Date-time types else if (type == java.util.Date.class) { try { return (T) Util.parseDate(value); } catch (Exception e) { return null; } } else if (type == java.util.Calendar.class) { try { Calendar cal = Calendar.getInstance(); cal.setTime(Util.parseDate(value)); return (T) cal; } catch (Exception e) { return null; } } else if (type == java.util.GregorianCalendar.class) { try { Calendar cal = new GregorianCalendar(); cal.setTime(Util.parseDate(value)); return (T) cal; } catch (Exception e) { return null; } } else if (type == java.sql.Timestamp.class) { try { return (T) new java.sql.Timestamp(Util.parseDate(value).getTime()); } catch (Exception e) { return null; } } else if (type == java.sql.Date.class) { try { return (T) new java.sql.Date(Util.parseDate(value).getTime()); } catch (Exception e) { return null; } } else if (type == java.sql.Time.class) { try { return (T) new java.sql.Time(Util.parseDate(value).getTime()); } catch (Exception e) { return null; } } // All other types are ignored return null; }
Convert several values
See Also:
  • convert(String, Class)
/** * Convert several values * * @see #convert(String, Class) */
public static <T> List<T> convert(List<String> values, Class<T> type) { List<T> result = new ArrayList<T>(); for (String value : values) result.add(convert(value, type)); return result; } // --------------------------------------------------------------------- // Static utilities // --------------------------------------------------------------------- private static final FastFilter NONE = new FastFilter() { @Override public boolean filter(Context context) { return false; } }; private static final FastFilter ALL = new FastFilter() { @Override public boolean filter(Context context) { return true; } }; private static final FastFilter EVEN = new FastFilter() { @Override public boolean filter(Context context) { return context.elementIndex() % 2 == 0; } }; private static final FastFilter ODD = new FastFilter() { @Override public boolean filter(Context context) { return context.elementIndex() % 2 != 0; } }; private static final FastFilter LEAF = new FastFilter() { @Override public boolean filter(Context context) { NodeList children = context.element().getChildNodes(); for (int i = 0;;i++) { Node item = children.item(i); if (item == null) return true; else if (item.getNodeType() == Node.ELEMENT_NODE) return false; } } }; }