Java tutorial
/** * Copyright (C) 2005 - 2012 Eric Van Dewoestine * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.eclim.plugin.core.util; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.regex.Pattern; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathFactory; import org.apache.commons.lang.SystemUtils; import org.apache.commons.vfs.FileObject; import org.apache.commons.vfs.FileSystemManager; import org.apache.commons.vfs.VFS; import org.eclim.Services; import org.eclim.command.Error; import org.eclim.logging.Logger; import org.eclim.plugin.core.util.ProjectUtils; import org.eclim.util.IOUtils; import org.eclim.util.file.FileUtils; import org.w3c.dom.Element; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; /** * Some xml utility methods. * * @author Eric Van Dewoestine */ public class XmlUtils { private static final Logger logger = Logger.getLogger(XmlUtils.class); private static final Pattern WIN_BUG = Pattern.compile("^/[a-zA-Z]:/.*"); private static XPath XPATH; /** * Create an XPathExpression from the supplied xpath string. * * @param xpath The xpath string. * @return An XPathExpression. */ public static XPathExpression createXPathExpression(String xpath) throws Exception { if (XPATH == null) { XPATH = XPathFactory.newInstance().newXPath(); } return XPATH.compile(xpath); } /** * Validate the supplied xml file. * * @param project The project name. * @param filename The file path to the xml file. * @return A possibly empty array of errors. */ public static List<Error> validateXml(String project, String filename) throws Exception { return validateXml(project, filename, false, null); } /** * Validate the supplied xml file. * * @param project The project name. * @param filename The file path to the xml file. * @param schema True to use schema validation relying on the * xsi:schemaLocation attribute of the document. * @return A possibly empty array of errors. */ public static List<Error> validateXml(String project, String filename, boolean schema) throws Exception { return validateXml(project, filename, schema, null); } /** * Validate the supplied xml file. * * @param project The project name. * @param filename The file path to the xml file. * @param schema True to use schema validation relying on the * xsi:schemaLocation attribute of the document. * @param handler The content handler to use while parsing the file. * @return A possibly empty list of errors. */ public static List<Error> validateXml(String project, String filename, boolean schema, DefaultHandler handler) throws Exception { SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); factory.setValidating(true); if (schema) { factory.setFeature("http://apache.org/xml/features/validation/schema", true); factory.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true); } SAXParser parser = factory.newSAXParser(); filename = ProjectUtils.getFilePath(project, filename); filename = filename.replace('\\', '/'); ErrorAggregator errorHandler = new ErrorAggregator(filename); EntityResolver entityResolver = new EntityResolver(FileUtils.getFullPath(filename)); try { parser.parse(new File(filename), getHandler(handler, errorHandler, entityResolver)); } catch (SAXParseException spe) { ArrayList<Error> errors = new ArrayList<Error>(); errors.add(new Error(spe.getMessage(), filename, spe.getLineNumber(), spe.getColumnNumber(), false)); return errors; } return errorHandler.getErrors(); } /** * Validate the supplied xml file against the specified xsd. * * @param project The project name. * @param filename The file path to the xml file. * @param schema The file path to the xsd. * @return A possibly empty array of errors. */ public static List<Error> validateXml(String project, String filename, String schema) throws Exception { SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); factory.setValidating(true); factory.setFeature("http://apache.org/xml/features/validation/schema", true); factory.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true); SAXParser parser = factory.newSAXParser(); parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema"); if (!schema.startsWith("file:")) { schema = "file://" + schema; } parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource", schema); parser.setProperty("http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation", schema.replace('\\', '/')); filename = ProjectUtils.getFilePath(project, filename); filename = filename.replace('\\', '/'); ErrorAggregator errorHandler = new ErrorAggregator(filename); EntityResolver entityResolver = new EntityResolver(FileUtils.getFullPath(filename)); try { parser.parse(new File(filename), getHandler(null, errorHandler, entityResolver)); } catch (SAXParseException spe) { ArrayList<Error> errors = new ArrayList<Error>(); errors.add(new Error(spe.getMessage(), filename, spe.getLineNumber(), spe.getColumnNumber(), false)); return errors; } return errorHandler.getErrors(); } /** * Gets the value of a named child element. * * @param element The parent element. * @param name The name of the child element to retrieve the value from. * @return The text value of the child element. */ public static String getElementValue(Element element, String name) { return ((Element) element.getElementsByTagName(name).item(0)).getFirstChild().getNodeValue(); } /** * Gets an aggregate handler which delegates accordingly to the supplied * handlers. * * @param handler Main DefaultHandler to delegate to (may be null). * @param errorHandler DefaultHandler to delegate errors to (may be null). * @param entityResolver EntityResolver to delegate to (may be null). * @return */ private static DefaultHandler getHandler(DefaultHandler handler, DefaultHandler errorHandler, EntityResolver entityResolver) { DefaultHandler hdlr = handler != null ? handler : new DefaultHandler(); return new AggregateHandler(hdlr, errorHandler, entityResolver); } /** * Aggregate DefaultHandler which delegates to other handlers. */ private static class AggregateHandler extends DefaultHandler { private DefaultHandler handler; private DefaultHandler errorHandler; private org.xml.sax.EntityResolver entityResolver; /** * Constructs a new instance. * * @param handler The handler for this instance. * @param errorHandler The errorHandler for this instance. * @param entityResolver The entityResolver for this instance. */ public AggregateHandler(DefaultHandler handler, DefaultHandler errorHandler, EntityResolver entityResolver) { this.handler = handler; this.errorHandler = errorHandler != null ? errorHandler : handler; this.entityResolver = entityResolver != null ? entityResolver : handler; } /** * @see DefaultHandler#resolveEntity(String,String) */ public InputSource resolveEntity(String publicId, String systemId) throws IOException, SAXException { return entityResolver.resolveEntity(publicId, systemId); } /** * @see DefaultHandler#notationDecl(String,String,String) */ public void notationDecl(String name, String publicId, String systemId) throws SAXException { handler.notationDecl(name, publicId, systemId); } /** * @see DefaultHandler#unparsedEntityDecl(String,String,String,String) */ public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException { handler.unparsedEntityDecl(name, publicId, systemId, notationName); } /** * @see DefaultHandler#setDocumentLocator(Locator) */ public void setDocumentLocator(Locator locator) { handler.setDocumentLocator(locator); } /** * @see DefaultHandler#startDocument() */ public void startDocument() throws SAXException { handler.startDocument(); } /** * @see DefaultHandler#endDocument() */ public void endDocument() throws SAXException { handler.endDocument(); } /** * @see DefaultHandler#startPrefixMapping(String,String) */ public void startPrefixMapping(String prefix, String uri) throws SAXException { handler.startPrefixMapping(prefix, uri); } /** * @see DefaultHandler#endPrefixMapping(String) */ public void endPrefixMapping(String prefix) throws SAXException { handler.endPrefixMapping(prefix); } /** * @see DefaultHandler#startElement(String,String,String,Attributes) */ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { handler.startElement(uri, localName, qName, attributes); } /** * @see DefaultHandler#endElement(String,String,String) */ public void endElement(String uri, String localName, String qName) throws SAXException { handler.endElement(uri, localName, qName); } /** * @see DefaultHandler#characters(char[],int,int) */ public void characters(char[] ch, int start, int length) throws SAXException { handler.characters(ch, start, length); } /** * @see DefaultHandler#ignorableWhitespace(char[],int,int) */ public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { handler.ignorableWhitespace(ch, start, length); } /** * @see DefaultHandler#processingInstruction(String,String) */ public void processingInstruction(String target, String data) throws SAXException { handler.processingInstruction(target, data); } /** * @see DefaultHandler#skippedEntity(String) */ public void skippedEntity(String name) throws SAXException { handler.skippedEntity(name); } /** * @see DefaultHandler#warning(SAXParseException) */ public void warning(SAXParseException e) throws SAXException { errorHandler.warning(e); } /** * @see DefaultHandler#error(SAXParseException) */ public void error(SAXParseException e) throws SAXException { errorHandler.error(e); } /** * @see DefaultHandler#fatalError(SAXParseException) */ public void fatalError(SAXParseException e) throws SAXException { errorHandler.fatalError(e); } } /** * Handler for collecting errors durring parsing and validation of a xml * file. */ private static class ErrorAggregator extends DefaultHandler { private ArrayList<Error> errors = new ArrayList<Error>(); private String filename; /** * Constructs a new instance. * * @param filename The filename for this instance. */ public ErrorAggregator(String filename) { this.filename = filename; } @Override public void warning(SAXParseException ex) throws SAXException { addError(ex, true); } @Override public void error(SAXParseException ex) throws SAXException { addError(ex, false); } @Override public void fatalError(SAXParseException ex) throws SAXException { addError(ex, false); } /** * Adds the supplied SAXException as an Error. * * @param ex The SAXException. */ private void addError(SAXParseException ex, boolean warning) { String location = ex.getSystemId(); if (location != null) { if (location.startsWith("file://")) { location = location.substring("file://".length()); } else if (location.startsWith("file:")) { location = location.substring("file:".length()); } } // bug where window paths start with /C:/... if (location != null && WIN_BUG.matcher(location).matches()) { location = location.substring(1); } if (location == null) { location = filename; } try { errors.add(new Error(ex.getMessage(), URLDecoder.decode(location, "utf-8"), ex.getLineNumber(), ex.getColumnNumber(), warning)); } catch (Exception e) { throw new RuntimeException(e); } } /** * Gets the possibly empty array of errors. * * @return Array of Error. */ public List<Error> getErrors() { return errors; } } /** * EntityResolver extension. */ private static class EntityResolver implements org.xml.sax.EntityResolver { private static String TEMP_PREFIX = "file://" + SystemUtils.JAVA_IO_TMPDIR.replace('\\', '/'); static { if (TEMP_PREFIX.endsWith("\\") || TEMP_PREFIX.endsWith("/")) { TEMP_PREFIX = TEMP_PREFIX.substring(0, TEMP_PREFIX.length() - 1); } } private String path; private String lastPath; // cache missing sources to avoiding hitting the same url repeatedly private static HashMap<String, IOException> missingSources = new HashMap<String, IOException>(); /** * Constructs a new instance. * * @param path The path for all relative entities to be relative to. */ public EntityResolver(String path) { this.path = path; } @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { String location = systemId; // rolling the dice to fix windows issue where parser, or something, is // turning C:/... into C/. This would cause problems if someone acutally // has a single letter directory, but that is doubtful. location = location.replaceFirst("^file://([a-zA-Z])/", "file://$1:/"); if (location.startsWith(TEMP_PREFIX)) { location = location.substring(TEMP_PREFIX.length()); return resolveEntity(publicId, lastPath + location); } else if (location.startsWith("http://")) { if (missingSources.containsKey(systemId)) { throw missingSources.get(systemId); } int index = location.indexOf('/', 8); lastPath = location.substring(0, index + 1); location = location.substring(index); location = TEMP_PREFIX + location; FileSystemManager fsManager = VFS.getManager(); FileObject tempFile = fsManager.resolveFile(location); // check if temp file already exists. if (!tempFile.exists() || tempFile.getContent().getSize() == 0) { InputStream in = null; OutputStream out = null; try { if (!tempFile.exists()) { tempFile.createFile(); } // download and save remote file. URL remote = new URL(systemId); URLConnection conn = remote.openConnection(); conn.setConnectTimeout(3000); conn.setReadTimeout(5000); conn.connect(); in = conn.getInputStream(); out = tempFile.getContent().getOutputStream(); IOUtils.copy(in, out); } catch (Exception e) { logger.warn(e.getMessage()); IOUtils.closeQuietly(out); try { tempFile.delete(); } catch (Exception ignore) { } IOException ex = new IOException(e.getMessage()); ex.initCause(e); missingSources.put(systemId, ex); throw ex; } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(out); } } InputSource source = new InputSource(tempFile.getContent().getInputStream()); source.setSystemId(location); return source; } else if (location.startsWith("file:")) { location = location.substring("file:".length()); if (location.startsWith("//")) { location = location.substring(2); } if (FileUtils.getFullPath(location).equals(FileUtils.getPath(location))) { location = FileUtils.concat(path, location); } if (!new File(location).exists()) { StringBuffer resource = new StringBuffer().append("/resources/") .append(FileUtils.getExtension(location)).append('/') .append(FileUtils.getFileName(location)).append('.') .append(FileUtils.getExtension(location)); URL url = Services.getResource(resource.toString()); if (url != null) { return new InputSource(url.toString()); } } return new InputSource(location); } return new InputSource(systemId); } } }