/***************************************************************************** * Copyright (c) 2006-2013, Cloudsmith Inc. * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the copyright holder * listed above, as the Initial Contributor under such license. The text of * such license is available at www.eclipse.org. *****************************************************************************/ package org.eclipse.buckminster.sax; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.AttributesImpl; /** * @author Thomas Hallgren */ public class Utils { private static class ByteInputOutputBuffer extends ByteArrayOutputStream { public InputStream getInputStream() { return new ByteArrayInputStream(buf, 0, count); } } private static SAXTransformerFactory saxTransformerFactory; private static final Class<?>[] emptyArgTypes = new Class[] {}; private static final Object[] emptyArgs = new Object[] {}; private static final SAXParserFactory parserFactory = SAXParserFactory.newInstance(); /** * Adds a CDATA type attribute using the default namespace * * @param attrs * The attribtue collection receiveing the attribute * @param name * The name of the attribute * @param value * The attribute value */ public static void addAttribute(AttributesImpl attrs, String name, String value) { attrs.addAttribute("", name, name, "CDATA", value); //$NON-NLS-1$ //$NON-NLS-2$ } public static <T> List<T> createUnmodifiableList(Collection<T> coll) { List<T> aList; if (coll == null || coll.size() == 0) aList = Collections.emptyList(); else { List<T> newList; if (coll.size() == 1) { T value = (coll instanceof List<?>) ? ((List<T>) coll).get(0) : coll.iterator().next(); newList = Collections.singletonList(value); } else newList = new ArrayList<T>(coll); aList = Collections.unmodifiableList(newList); } return aList; } public static <K, V> Map<K, V> createUnmodifiableMap(Map<K, V> aMap) { if (aMap == null || aMap.size() == 0) aMap = Collections.emptyMap(); else { if (aMap.size() == 1) { Map.Entry<K, V> entry = aMap.entrySet().iterator().next(); aMap = Collections.singletonMap(entry.getKey(), entry.getValue()); } else aMap = new HashMap<K, V>(aMap); aMap = Collections.unmodifiableMap(aMap); } return aMap; } public static <T> Set<T> createUnmodifiableSet(Collection<T> coll) { Set<T> aSet; if (coll == null || coll.size() == 0) aSet = Collections.emptySet(); else { Set<T> newSet; if (coll.size() == 1) { T value = (coll instanceof List<?>) ? ((List<T>) coll).get(0) : coll.iterator().next(); newSet = Collections.singleton(value); } else newSet = new HashSet<T>(coll); aSet = Collections.unmodifiableSet(newSet); } return aSet; } /** * Create an XMLReader instance. * * @param validating * true if a validating parser is desired * @param withNamespace * true if the parser is namespace aware * @return The created instance. * @throws SAXException * If no XMLReader could be created. */ public static XMLReader createXMLReader(boolean validating, boolean withNamespace) throws SAXException { try { synchronized (parserFactory) { parserFactory.setValidating(validating); parserFactory.setNamespaceAware(withNamespace); return parserFactory.newSAXParser().getXMLReader(); } } catch (ParserConfigurationException e) { throw new SAXException(e); } } public static <T extends ISaxableElement> void emitCollection(String namespace, String prefix, String localName, String elemName, Attributes attrs, Collection<T> collection, ContentHandler handler) throws SAXException { if (collection.isEmpty() && attrs.getLength() == 0) return; String qName = makeQualifiedName(prefix, localName); handler.startElement(namespace, localName, qName, attrs); for (T elem : collection) elem.toSax(handler, namespace, prefix, elemName == null ? elem.getDefaultTag() : elemName); handler.endElement(namespace, localName, qName); } public static <T extends ISaxableElement> void emitCollection(String namespace, String prefix, String localName, String elemName, Attributes attrs, T[] array, ContentHandler handler) throws SAXException { if (array.length == 0) return; String qName = makeQualifiedName(prefix, localName); handler.startElement(namespace, localName, qName, attrs); for (T elem : array) elem.toSax(handler, namespace, prefix, elemName == null ? elem.getDefaultTag() : elemName); handler.endElement(namespace, localName, qName); } public static <T extends ISaxableElement> void emitCollection(String namespace, String prefix, String localName, String elemName, Collection<T> collection, ContentHandler handler) throws SAXException { emitCollection(namespace, prefix, localName, elemName, ISaxableElement.EMPTY_ATTRIBUTES, collection, handler); } /** * Using J2SE 5.0 or higher, we would not need this since the * <code>Locator</code> then can be casted to a <code>Locator2</code>. With * 1.4 we know that the method is there but we can't get to it without using * reflection. * * @param locator * The locator to extract the encoding from * @return The encoding as stated in the entity or <code>null</code> if not * found. */ public static String getEncoding(Locator locator) { String enc = null; if (locator != null) { try { enc = (String) locator.getClass().getMethod("getEncoding", emptyArgTypes).invoke(locator, emptyArgs); //$NON-NLS-1$ } catch (Exception e) { // either this locator object doesn't have this // method, or we're on an old JDK } } if (enc == null) enc = "UTF-8"; //$NON-NLS-1$ return enc; } /** * Obtain the byte image that corresponds to the <code>UTF-8</code> encoded * XML image of the <code>saxable</code> argument. The method will make no * attempt to nice indenting and the newline character used will be unix * style always. * * @param saxable * The element to be emited. Must not be <code>null</code>. * @return the byte image */ public static byte[] getImage(ISaxable saxable) { try { ByteArrayOutputStream builder = new ByteArrayOutputStream(); serializeUgly(saxable, builder); return builder.toByteArray(); } catch (SAXException e) { throw new RuntimeException(e); } } /** * Obtain the input stream to the <code>UTF-8</code> encoded XML image of * the <code>saxable</code> argument. * * @param saxable * The element to be emited. Must not be <code>null</code>. * @return the input stream */ public static InputStream getInputStream(ISaxable saxable) throws SAXException { ByteInputOutputBuffer builder = new ByteInputOutputBuffer(); serialize(saxable, builder); return builder.getInputStream(); } /** * Creates a qualified name by concatenating the <code>prefix</code>, a * colon, and the <code>localName</code>. If <code>prefix</code> is * <code>null</code> or an empty string, the <code>localName</code> is * returned. * * @param prefix * The prefix for the qualified name * @param localName * The localName * @return The qualified name. */ public static String makeQualifiedName(String prefix, String localName) { return (prefix == null || prefix.length() == 0) ? localName : prefix + ':' + localName; } /** * Instantiates and returns a new ContentHandler that will print its output * to a stream. * * @param file * The name of the output file. Use for diagnostic only. * @param stream * The stream that will receive the output. * @param indent * The indentation to use when pretty-printing or -1 if no * pretty-printing is desired. * @return A ContentHandler that will act as a serializer. * @throws SAXException * if a problem occured creating the serializer. */ public static ContentHandler newSerializer(File file, OutputStream stream, String encoding, int indent, boolean useSysLinesep) throws SAXException { TransformerHandler serializer = createTransformerHandler(indent); Transformer t = serializer.getTransformer(); t.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$ if (encoding != null) t.setOutputProperty(OutputKeys.ENCODING, encoding); if (indent >= 0) t.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ StreamResult out = new StreamResult(); if (file != null) out.setSystemId(file); try { if (encoding == null) encoding = t.getOutputProperty(OutputKeys.ENCODING); out.setWriter(new OutputStreamWriter(stream, encoding)); } catch (UnsupportedEncodingException e) { throw new SAXException(e.getMessage()); } String lineSep = System.getProperty("line.separator"); //$NON-NLS-1$ if (useSysLinesep || "\n".equals(lineSep)) //$NON-NLS-1$ serializer.setResult(out); else { // Someone forgot to expose the ToXMLStream.setLineSepUse(boolean) // so there's // no way we can do that in a nice way. This has been known to work // though // System.setProperty("line.separator", "\n"); //$NON-NLS-1$ //$NON-NLS-2$ try { serializer.setResult(out); } finally { System.setProperty("line.separator", lineSep); //$NON-NLS-1$ } } return serializer; } public static void serialize(ISaxable saxable, OutputStream outputStream) throws SAXException { ContentHandler serializer = newSerializer(null, outputStream, "UTF-8", 4, true); //$NON-NLS-1$ saxable.toSax(serializer); } public static void serializeUgly(ISaxable saxable, OutputStream outputStream) throws SAXException { ContentHandler serializer = newSerializer(null, outputStream, "UTF-8", -1, false); //$NON-NLS-1$ saxable.toSax(serializer); } private static synchronized TransformerHandler createTransformerHandler(int indent) throws SAXException { if (saxTransformerFactory == null) { TransformerFactory tf = TransformerFactory.newInstance(); if (!tf.getFeature(SAXTransformerFactory.FEATURE)) throw new SAXException("The TransformerFactory is not a SAXTransformerFactory"); //$NON-NLS-1$ saxTransformerFactory = (SAXTransformerFactory) tf; } try { saxTransformerFactory.setAttribute("indent-number", Integer.toString(indent)); //$NON-NLS-1$ } catch (IllegalArgumentException e) { // This transformer doesn't support ident-number. We dont' consider // that an error } try { return saxTransformerFactory.newTransformerHandler(); } catch (TransformerConfigurationException e) { throw new SAXException(e.getMessage()); } } }