diff options
Diffstat (limited to 'org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/content')
4 files changed, 479 insertions, 0 deletions
diff --git a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/content/PomFileContentDescriber.java b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/content/PomFileContentDescriber.java new file mode 100644 index 00000000..49c9a868 --- /dev/null +++ b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/content/PomFileContentDescriber.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2008-2010 Sonatype, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sonatype, Inc. - initial API and implementation + *******************************************************************************/ + +package org.eclipse.m2e.core.internal.content; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +import javax.xml.parsers.ParserConfigurationException; + +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import org.eclipse.core.runtime.content.IContentDescription; + +import org.eclipse.m2e.core.internal.Messages; + + +/** + * A content describer for POM files. + * + * @see org.eclipse.ant.internal.core.contentDescriber.AntBuildfileContentDescriber + * @author Herve Boutemy + * @since 0.9.6 + */ +public final class PomFileContentDescriber extends XMLContentDescriber { + /** + * Determines the validation status for the given contents. + * + * @param contents the contents to be evaluated + * @return one of the following:<ul> + * <li><code>VALID</code></li>, + * <li><code>INVALID</code></li>, + * <li><code>INDETERMINATE</code></li> + * </ul> + * @throws IOException + */ + private int checkCriteria(InputSource contents) throws IOException { + PomHandler pomHandler = new PomHandler(); + try { + if(!pomHandler.parseContents(contents)) { + return INDETERMINATE; + } + } catch(SAXException e) { + // we may be handed any kind of contents... it is normal we fail to parse + return INDETERMINATE; + } catch(ParserConfigurationException e) { + // some bad thing happened - force this describer to be disabled + throw new RuntimeException(Messages.PomFileContentDescriber_error); + } + + // Check to see if we matched our criteria. + if(pomHandler.hasRootProjectElement()) { + if(pomHandler.hasArtifactIdElement()) { + //project and artifactId element + return VALID; + } + //only a top level project element: maybe a POM file, but maybe an Ant buildfile, a site descriptor, ... + return INDETERMINATE; + } + return INDETERMINATE; + } + + @Override + public int describe(InputStream contents, IContentDescription description) throws IOException { + // call the basic XML describer to do basic recognition + if(super.describe(contents, description) == INVALID) { + return INVALID; + } + // super.describe will have consumed some chars, need to rewind + contents.reset(); + // Check to see if we matched our criteria. + return checkCriteria(new InputSource(contents)); + } + + @Override + public int describe(Reader contents, IContentDescription description) throws IOException { + // call the basic XML describer to do basic recognition + if(super.describe(contents, description) == INVALID) { + return INVALID; + } + // super.describe will have consumed some chars, need to rewind + contents.reset(); + // Check to see if we matched our criteria. + return checkCriteria(new InputSource(contents)); + } +} diff --git a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/content/PomHandler.java b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/content/PomHandler.java new file mode 100644 index 00000000..3ff2db65 --- /dev/null +++ b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/content/PomHandler.java @@ -0,0 +1,166 @@ +/******************************************************************************* + * Copyright (c) 2008-2010 Sonatype, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sonatype, Inc. - initial API and implementation + *******************************************************************************/ + +package org.eclipse.m2e.core.internal.content; + +import java.io.IOException; +import java.io.StringReader; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + + +/** + * An xml event handler for detecting the project top-level element in a POM file. Also records whether a default + * attribute is present for the project and if any typical Maven elements are present. + * + * @see org.eclipse.ant.internal.core.contentDescriber.AntHandler + * @author Herve Boutemy + * @since 0.9.6 + */ +public final class PomHandler extends DefaultHandler { + /** + * An exception indicating that the parsing should stop. + */ + private class StopParsingException extends SAXException { + /** + * All serializable objects should have a stable serialVersionUID + */ + private static final long serialVersionUID = 1L; + + /** + * Constructs an instance of <code>StopParsingException</code> with a <code>null</code> detail message. + */ + public StopParsingException() { + super((String) null); + } + } + + private static final String PROJECT = "project"; //$NON-NLS-1$ + + private static final String ARTIFACTID = "artifactId"; //$NON-NLS-1$ + + /** + * This is the name of the top-level element found in the XML file. This member variable is <code>null</code> unless + * the file has been parsed successful to the point of finding the top-level element. + */ + private String fTopElementFound = null; + + private SAXParserFactory fFactory; + + private boolean fArtifactIdFound = false; + + private int fLevel = -1; + + /** + * Creates a new SAX parser for use within this instance. + * + * @return The newly created parser. + * @throws ParserConfigurationException If a parser of the given configuration cannot be created. + * @throws SAXException If something in general goes wrong when creating the parser. + */ + private final SAXParser createParser(SAXParserFactory parserFactory) throws ParserConfigurationException, + SAXException, SAXNotRecognizedException, SAXNotSupportedException { + // Initialize the parser. + final SAXParser parser = parserFactory.newSAXParser(); + final XMLReader reader = parser.getXMLReader(); + // disable DTD validation + try { + // be sure validation is "off" or the feature to ignore DTD's will not apply + reader.setFeature("http://xml.org/sax/features/validation", false); //$NON-NLS-1$ + reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); //$NON-NLS-1$ + } catch(SAXNotRecognizedException e) { + // not a big deal if the parser does not recognize the features + } catch(SAXNotSupportedException e) { + // not a big deal if the parser does not support the features + } + return parser; + } + + private SAXParserFactory getFactory() { + synchronized(this) { + if(fFactory != null) { + return fFactory; + } + fFactory = SAXParserFactory.newInstance(); + fFactory.setNamespaceAware(true); + } + return fFactory; + } + + protected boolean parseContents(InputSource contents) throws IOException, ParserConfigurationException, SAXException { + // Parse the file into we have what we need (or an error occurs). + try { + fFactory = getFactory(); + if(fFactory == null) { + return false; + } + final SAXParser parser = createParser(fFactory); + // to support external entities specified as relative URIs (see bug 63298) + contents.setSystemId("/"); //$NON-NLS-1$ + parser.parse(contents, this); + } catch(StopParsingException e) { + // Abort the parsing normally. Fall through... + } + return true; + } + + /* + * Resolve external entity definitions to an empty string. This is to speed + * up processing of files with external DTDs. Not resolving the contents + * of the DTD is ok, as only the System ID of the DTD declaration is used. + * @see org.xml.sax.helpers.DefaultHandler#resolveEntity(java.lang.String, java.lang.String) + */ + @Override + public InputSource resolveEntity(String publicId, String systemId) { + return new InputSource(new StringReader("")); //$NON-NLS-1$ + } + + + @Override + public final void startElement(final String uri, final String elementName, final String qualifiedName, + final Attributes attributes) throws SAXException { + fLevel++ ; + if(fTopElementFound == null) { + fTopElementFound = elementName; + if(!hasRootProjectElement()) { + throw new StopParsingException(); + } + } + if(fLevel == 1 && ARTIFACTID.equals(elementName)) { + fArtifactIdFound = true; + throw new StopParsingException(); + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + super.endElement(uri, localName, qName); + fLevel-- ; + } + + protected boolean hasRootProjectElement() { + return PROJECT.equals(fTopElementFound); + } + + protected boolean hasArtifactIdElement() { + return fArtifactIdFound; + } +} diff --git a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/content/TextContentDescriber.java b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/content/TextContentDescriber.java new file mode 100644 index 00000000..bfe4da15 --- /dev/null +++ b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/content/TextContentDescriber.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2008-2010 Sonatype, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sonatype, Inc. - initial API and implementation + *******************************************************************************/ + +package org.eclipse.m2e.core.internal.content; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.core.runtime.content.IContentDescription; +import org.eclipse.core.runtime.content.ITextContentDescriber; + +/** + * A copy of org.eclipse.core.internal.content.TextContentDescriber to avoid internal API use. + * + * This class provides internal basis for text-based content describers. + * + * <p> + * Note: do not add protected/public members to this class if you don't intend to + * make them public API. + * </p> + * + * @see org.eclipse.core.runtime.content.XMLRootElementContentDescriber2 + * @since 3.0 + */ +class TextContentDescriber implements ITextContentDescriber { + + private final static QualifiedName[] SUPPORTED_OPTIONS = {IContentDescription.BYTE_ORDER_MARK}; + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.content.ITextContentDescriber#describe(java.io.Reader, org.eclipse.core.runtime.content.IContentDescription) + */ + @SuppressWarnings("unused") + public int describe(Reader contents, IContentDescription description) throws IOException { + // we want to be pretty loose on detecting the text content type + return INDETERMINATE; + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.content.IContentDescriber#describe(java.io.InputStream, org.eclipse.core.runtime.content.IContentDescription) + */ + public int describe(InputStream contents, IContentDescription description) throws IOException { + if (description == null || !description.isRequested(IContentDescription.BYTE_ORDER_MARK)) + return INDETERMINATE; + byte[] bom = getByteOrderMark(contents); + if (bom != null) + description.setProperty(IContentDescription.BYTE_ORDER_MARK, bom); + // we want to be pretty loose on detecting the text content type + return INDETERMINATE; + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.content.IContentDescriber#getSupportedOptions() + */ + public QualifiedName[] getSupportedOptions() { + return SUPPORTED_OPTIONS; + } + + byte[] getByteOrderMark(InputStream input) throws IOException { + int first = input.read(); + if (first == 0xEF) { + //look for the UTF-8 Byte Order Mark (BOM) + int second = input.read(); + int third = input.read(); + if (second == 0xBB && third == 0xBF) + return IContentDescription.BOM_UTF_8; + } else if (first == 0xFE) { + //look for the UTF-16 BOM + if (input.read() == 0xFF) + return IContentDescription.BOM_UTF_16BE; + } else if (first == 0xFF) { + if (input.read() == 0xFE) + return IContentDescription.BOM_UTF_16LE; + } + return null; + } +} diff --git a/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/content/XMLContentDescriber.java b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/content/XMLContentDescriber.java new file mode 100644 index 00000000..ed115a0a --- /dev/null +++ b/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/content/XMLContentDescriber.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2008-2010 Sonatype, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sonatype, Inc. - initial API and implementation + *******************************************************************************/ + +package org.eclipse.m2e.core.internal.content; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.core.runtime.content.IContentDescription; +import org.eclipse.core.runtime.content.ITextContentDescriber; + +/** + * A copy of org.eclipse.core.internal.content.XMLContentDescriber to avoid internal API use. + * + * A content interpreter for XML files. + * This class provides internal basis for XML-based content describers. + * <p> + * Note: do not add protected/public members to this class if you don't intend to + * make them public API. + * </p> + * + * @see org.eclipse.core.runtime.content.XMLRootElementContentDescriber2 + * @see "http://www.w3.org/TR/REC-xml *" + */ +class XMLContentDescriber extends TextContentDescriber implements ITextContentDescriber { + private static final QualifiedName[] SUPPORTED_OPTIONS = new QualifiedName[] {IContentDescription.CHARSET, IContentDescription.BYTE_ORDER_MARK}; + private static final String ENCODING = "encoding="; //$NON-NLS-1$ + private static final String XML_PREFIX = "<?xml "; //$NON-NLS-1$ + + public int describe(InputStream input, IContentDescription description) throws IOException { + byte[] bom = getByteOrderMark(input); + String xmlDeclEncoding = "UTF-8"; //$NON-NLS-1$ + input.reset(); + if (bom != null) { + if (bom == IContentDescription.BOM_UTF_16BE) + xmlDeclEncoding = "UTF-16BE"; //$NON-NLS-1$ + else if (bom == IContentDescription.BOM_UTF_16LE) + xmlDeclEncoding = "UTF-16LE"; //$NON-NLS-1$ + // skip BOM to make comparison simpler + input.skip(bom.length); + // set the BOM in the description if requested + if (description != null && description.isRequested(IContentDescription.BYTE_ORDER_MARK)) + description.setProperty(IContentDescription.BYTE_ORDER_MARK, bom); + } + byte[] xmlPrefixBytes = XML_PREFIX.getBytes(xmlDeclEncoding); + byte[] prefix = new byte[xmlPrefixBytes.length]; + if (input.read(prefix) < prefix.length) + // there is not enough info to say anything + return INDETERMINATE; + for (int i = 0; i < prefix.length; i++) + if (prefix[i] != xmlPrefixBytes[i]) + // we don't have a XMLDecl... there is not enough info to say anything + return INDETERMINATE; + if (description == null) + return VALID; + // describe charset if requested + if (description.isRequested(IContentDescription.CHARSET)) { + String fullXMLDecl = readFullXMLDecl(input, xmlDeclEncoding); + if (fullXMLDecl != null) { + String charset = getCharset(fullXMLDecl); + if (charset != null && !"UTF-8".equalsIgnoreCase(charset)) //$NON-NLS-1$ + // only set property if value is not default (avoid using a non-default content description) + description.setProperty(IContentDescription.CHARSET, getCharset(fullXMLDecl)); + } + } + return VALID; + } + + private String readFullXMLDecl(InputStream input, String unicodeEncoding) throws IOException { + byte[] xmlDecl = new byte[100]; + int c = 0; + // looks for XMLDecl ending char (?) + int read = 0; + while (read < xmlDecl.length && (c = input.read()) != -1 && c != '?') + xmlDecl[read++] = (byte) c; + return c == '?' ? new String(xmlDecl, 0, read, unicodeEncoding) : null; + } + + public int describe(Reader input, IContentDescription description) throws IOException { + BufferedReader reader = new BufferedReader(input); + String line = reader.readLine(); + // end of stream + if (line == null) + return INDETERMINATE; + // XMLDecl should be the first string (no blanks allowed) + if (!line.startsWith(XML_PREFIX)) + return INDETERMINATE; + if (description == null) + return VALID; + // describe charset if requested + if ((description.isRequested(IContentDescription.CHARSET))) + description.setProperty(IContentDescription.CHARSET, getCharset(line)); + return VALID; + } + + private String getCharset(String firstLine) { + int encodingPos = firstLine.indexOf(ENCODING); + if (encodingPos == -1) + return null; + char quoteChar = '"'; + int firstQuote = firstLine.indexOf(quoteChar, encodingPos); + if (firstQuote == -1) { + quoteChar = '\''; + firstQuote = firstLine.indexOf(quoteChar, encodingPos); + } + if (firstQuote == -1 || firstLine.length() == firstQuote - 1) + return null; + int secondQuote = firstLine.indexOf(quoteChar, firstQuote + 1); + if (secondQuote == -1) + return null; + return firstLine.substring(firstQuote + 1, secondQuote); + } + + public QualifiedName[] getSupportedOptions() { + return SUPPORTED_OPTIONS; + } +} |