blob: 31582581016982bac317e104c7358678b19db64b [file] [log] [blame]
/**
* Copyright (c) 2002-2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* IBM - Initial API and implementation
*/
package org.eclipse.xsd.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.WeakHashMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
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.ext.LexicalHandler;
import org.xml.sax.helpers.DefaultHandler;
import org.eclipse.xsd.XSDDiagnostic;
import org.eclipse.xsd.XSDDiagnosticSeverity;
import org.eclipse.xsd.XSDFactory;
import org.eclipse.xsd.XSDPlugin;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.impl.XSDSchemaImpl;
/**
* The <b>SAX Parser</b> for the model.
* This handles the conversion of SAX parse exceptions to {@link XSDDiagnostic}s.
* It also computes line column information,
* which is stored in a map of each node to its user data.
* This class is not intended for reuse outside of the model implementation and is subject to change.
*/
public class XSDParser extends DefaultHandler implements LexicalHandler
{
protected static final Map<Node, Map<Object, Object>> userDataMap = Collections.synchronizedMap(new WeakHashMap<Node, Map<Object, Object>>());
private static class Flusher
{
@Override
protected void finalize() throws Throwable
{
userDataMap.size();
new Flusher();
}
}
static
{
new Flusher();
}
private static final class LocationMap extends AbstractMap<Object,Object>
{
protected static final int UNSET = Integer.MAX_VALUE;
protected Map<Object, Object> delegateMap;
private int startLine = UNSET;
private int startColumn = UNSET;
private int endLine = UNSET;
private int endColumn = UNSET;
@Override
public Set<java.util.Map.Entry<Object, Object>> entrySet()
{
return getDelegateMap().entrySet();
}
protected Map<Object, Object> getDelegateMap()
{
if (delegateMap == null)
{
delegateMap = new HashMap<Object, Object>();
if (startLine != UNSET)
{
delegateMap.put("startLine", startLine);
}
if (startColumn != UNSET)
{
delegateMap.put("startColumn", startColumn);
}
if (endLine != UNSET)
{
delegateMap.put("endLine", endLine);
}
if (endColumn != UNSET)
{
delegateMap.put("endColumn", endColumn);
}
}
return delegateMap;
}
protected int objectToInt(Object value)
{
return value == null ? UNSET : (Integer)value;
}
protected Object intToObject(int value)
{
return value == UNSET ? null : value;
}
@Override
public Object put(Object key, Object value)
{
Object result;
if (delegateMap == null)
{
if ("startLine".equals(key))
{
result = intToObject(startLine);
startLine = objectToInt(value);
}
else if ("endLine".equals(key))
{
result = intToObject(endLine);
endLine = objectToInt(value);
}
else if ("startColumn".equals(key))
{
result = intToObject(startColumn);
startColumn = objectToInt(value);
}
else if ("endColumn".equals(key))
{
result = intToObject(endColumn);
endColumn = objectToInt(value);
}
else
{
result = getDelegateMap().put(key, value);
}
}
else
{
result = getDelegateMap().put(key, value);
}
return result;
}
public int getStartLine()
{
return delegateMap == null ? startLine : objectToInt(delegateMap.get("startLine"));
}
public void setStartLine(int startLine)
{
if (delegateMap == null)
{
this.startLine = startLine;
}
else
{
delegateMap.put("startLine", startLine);
}
}
public int getStartColumn()
{
return delegateMap == null ? startColumn : objectToInt(delegateMap.get("startColumn"));
}
public void setStartColumn(int startColumn)
{
if (delegateMap == null)
{
this.startColumn = startColumn;
}
else
{
delegateMap.put("startColumn", startColumn);
}
}
public int getEndLine()
{
return delegateMap == null ? endLine : objectToInt(delegateMap.get("endLine"));
}
public void setEndLine(int endLine)
{
if (delegateMap == null)
{
this.endLine = endLine;
}
else
{
delegateMap.put("endLine", endLine);
}
}
public int getEndColumn()
{
return delegateMap == null ? endColumn : objectToInt(delegateMap.get("endColumn"));
}
public void setEndColumn(int endColumn)
{
if (delegateMap == null)
{
this.endColumn = endColumn;
}
else
{
delegateMap.put("endColumn", endColumn);
}
}
}
/**
* Returns the user data associated with the node.
* If the node has no user data, a new empty map is created.
* @param node the node to query.
* @return the user data associated with the node.
*/
public static Map<Object, Object> getUserData(Node node)
{
return getLocationMap(node);
}
private static LocationMap getLocationMap(Node node)
{
LocationMap result = (LocationMap)userDataMap.get(node);
if (result == null)
{
result = new LocationMap();
userDataMap.put(node, result);
}
return result;
}
/**
* Returns the line at which the given node starts.
* @param node the node to query.
* @return the line at which the given node starts.
*/
public static int getStartLine(Node node)
{
int result = getLocationMap(node).getStartLine();
return result == LocationMap.UNSET ? 1 : result;
}
/**
* Sets the line at which the given node starts.
* @param node the node to set.
* @param value the line at which the given node starts.
*/
public static void setStartLine(Node node, int value)
{
getLocationMap(node).setStartLine(value);
}
/**
* Returns the column at which the given node starts.
* @param node the node to query.
* @return the column at which the given node starts.
*/
public static int getStartColumn(Node node)
{
int result = getLocationMap(node).getStartColumn();
return result == LocationMap.UNSET ? 1 : result;
}
/**
* Sets the column at which the given node starts.
* @param node the node to set.
* @param value the column at which the given node starts.
*/
public static void setStartColumn(Node node, int value)
{
getLocationMap(node).setStartColumn(value);
}
/**
* Returns the line at which the given node ends.
* @param node the node to query.
* @return the line at which the given node ends.
*/
public static int getEndLine(Node node)
{
int result = getLocationMap(node).getEndLine();
return result == LocationMap.UNSET ? 1 : result;
}
/**
* Sets the line at which the given node ends.
* @param node the node to set.
* @param value the line at which the given node ends.
*/
public static void setEndLine(Node node, int value)
{
getLocationMap(node).setEndLine(value);
}
/**
* Returns the column at which the given node ends.
* @param node the node to query.
* @return the column at which the given node ends.
*/
public static int getEndColumn(Node node)
{
int result = getLocationMap(node).getEndColumn();
return result == LocationMap.UNSET ? 1 : result;
}
/**
* Sets the column at which the given node ends.
* @param node the node to set.
* @param value the column at which the given node ends.
*/
public static void setEndColumn(Node node, int value)
{
getLocationMap(node).setEndColumn(value);
}
protected XSDFactory xsdFactory = XSDFactory.eINSTANCE;
protected XSDSchema xsdSchema;
protected List<XSDDiagnostic> xsdDiagnostics = new ArrayList<XSDDiagnostic>();
protected SAXParser saxParser;
protected JAXPPool jaxpPool;
protected Document document;
protected Stack<Element> stack = new Stack<Element>();
protected Element element;
protected Locator locator;
protected int line;
protected int column;
protected String encoding;
protected StringBuilder cdata;
protected CDATASection cdataSection;
/**
* @deprecated since 2.2
*
*/
@Deprecated
public XSDParser()
{
try
{
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setNamespaceAware(true);
saxParserFactory.setValidating(false);
saxParserFactory.setFeature("http://xml.org/sax/features/validation", false);
saxParserFactory.setFeature("http://xml.org/sax/features/namespaces", true);
saxParserFactory.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
saxParser = saxParserFactory.newSAXParser();
saxParser.setProperty("http://xml.org/sax/properties/lexical-handler", this);
}
catch (SAXException exception)
{
fatalError(exception);
}
catch (ParserConfigurationException exception)
{
fatalError(exception);
}
}
public XSDParser(Map<?, ?> options)
{
JAXPConfiguration config = null;
if (options != null)
{
jaxpPool = (JAXPPool)options.get(XSDResourceImpl.XSD_JAXP_POOL);
config = (JAXPConfiguration)options.get(XSDResourceImpl.XSD_JAXP_CONFIG);
}
try
{
if (jaxpPool == null)
{
if (config != null)
{
saxParser = config.createSAXParser(this);
}
else
{
saxParser = new DefaultJAXPConfiguration().createSAXParser(this);
}
}
else
{
saxParser = jaxpPool.getSAXParser(this);
}
}
catch (SAXException exception)
{
fatalError(exception);
}
catch (ParserConfigurationException exception)
{
fatalError(exception);
}
}
public void parse(String uri)
{
try
{
saxParser.parse(new InputSource(uri), this);
}
catch (IOException exception)
{
fatalError(exception);
}
catch (SAXException exception)
{
if (xsdDiagnostics.isEmpty())
{
fatalError(exception);
}
}
finally
{
if (jaxpPool != null)
{
jaxpPool.releaseSAXParser(saxParser);
}
}
}
public void parseString(String input)
{
parse(new InputSource(new StringReader(input)));
}
public void parse(InputSource inputSource)
{
try
{
saxParser.parse(inputSource, this);
}
catch (IOException exception)
{
fatalError(exception);
}
catch (SAXException exception)
{
if (xsdDiagnostics.isEmpty())
{
fatalError(exception);
}
}
finally
{
if (jaxpPool != null)
{
jaxpPool.releaseSAXParser(saxParser);
}
}
}
public void parse(InputStream inputStream)
{
try
{
saxParser.parse(new InputSource(inputStream), this);
}
catch (IOException exception)
{
fatalError(exception);
}
catch (SAXException exception)
{
if (xsdDiagnostics.isEmpty())
{
fatalError(exception);
}
}
finally
{
if (jaxpPool != null)
{
jaxpPool.releaseSAXParser(saxParser);
}
}
}
public String getEncoding()
{
if (locator != null)
{
try
{
Method getEncodingMethod = locator.getClass().getMethod("getEncoding", new Class[]{});
if (getEncodingMethod != null)
{
encoding = (String)getEncodingMethod.invoke(locator);
}
}
catch (Exception e)
{
// If we can't find it, there's nothing we can do...
}
}
return encoding;
}
public Document getDocument()
{
return document;
}
public void setSchema(XSDSchema xsdSchema)
{
if (document != null)
{
this.xsdSchema = xsdSchema;
Element element = document.getDocumentElement();
if (element != null)
{
xsdSchema.setElement(element);
}
xsdSchema.clearDiagnostics();
XSDResourceImpl.assignDiagnostics(xsdSchema, xsdDiagnostics);
XSDResourceImpl resource = (XSDResourceImpl)xsdSchema.eResource();
if (resource != null && getEncoding() != null)
{
resource.getDefaultSaveOptions().put(XSDResourceImpl.XSD_ENCODING, getEncoding());
}
}
}
public XSDSchema getSchema()
{
if (xsdSchema == null && document != null)
{
Element element = document.getDocumentElement();
xsdSchema = element == null ? xsdFactory.createXSDSchema() : XSDSchemaImpl.createSchema(element);
XSDResourceImpl.assignDiagnostics(xsdSchema, xsdDiagnostics);
}
return xsdSchema;
}
public Collection<XSDDiagnostic> getDiagnostics()
{
return xsdDiagnostics;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
{
Element newElement = document.createElementNS(uri, qName);
for (int i = 0, length = attributes.getLength(); i < length; ++i)
{
String attributeURI = attributes.getURI(i);
String attributeQName = attributes.getQName(i);
String attributeValue = attributes.getValue(i);
if (attributeQName.equals("xmlns") || attributeQName.startsWith("xmlns:"))
{
attributeURI = XSDConstants.XMLNS_URI_2000;
}
else if ("".equals(attributeURI))
{
attributeURI = null;
}
newElement.setAttributeNS(attributeURI, attributeQName, attributeValue);
}
if (stack.isEmpty())
{
document.appendChild(newElement);
}
else
{
element.appendChild(newElement);
}
stack.push(element);
element = newElement;
LocationMap extendedAttributes = getLocationMap(element);
extendedAttributes.setStartLine(line);
extendedAttributes.setStartColumn(column);
saveLocation();
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException
{
saveLocation();
LocationMap extendedAttributes = getLocationMap(element);
extendedAttributes.setEndLine(line);
extendedAttributes.setEndColumn(column);
element = stack.pop();
}
@Override
public void setDocumentLocator(Locator locator)
{
this.locator = locator;
super.setDocumentLocator(locator);
}
@Override
public void startDocument()
{
saveLocation();
document = createDocument();
xsdSchema = null;
stack.clear();
getEncoding();
}
public static Document createDocument()
{
try
{
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
documentBuilderFactory.setValidating(false);
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
return documentBuilder.newDocument();
}
catch (ParserConfigurationException exception)
{
XSDPlugin.INSTANCE.log(exception);
return null;
}
}
@Override
public void endDocument()
{
element = null;
saveLocation();
this.locator = null;
}
@Override
public void characters(char [] characters, int start, int length) throws SAXException
{
if (cdata != null)
{
cdata.append(characters, start, length);
}
else
{
int lineFeed = 0;
int carriageReturn = 0;
for (int i = 0; i < length; ++i)
{
char character = characters[start + i];
if (character == '\n')
{
++lineFeed;
column = 1;
}
else if (character == '\r')
{
++carriageReturn;
column = 1;
}
else if (character == ' ' && character == '\t')
{
++column;
}
else
{
// Put the leading whitespace in its own text node.
if (i != 0)
{
Text textNode = document.createTextNode(new String(characters, start, i));
element.appendChild(textNode);
}
// Create a next node that starts with the non-whitespace.
//
line += Math.max(lineFeed, carriageReturn);
Text textNode = document.createTextNode(new String(characters, start + i, length - i));
element.appendChild(textNode);
LocationMap extendedAttributes = getLocationMap(textNode);
extendedAttributes.setStartLine(line);
extendedAttributes.setStartColumn(column);
saveLocation();
extendedAttributes.setEndLine(line);
extendedAttributes.setEndColumn(column);
return;
}
}
Text textNode = document.createTextNode(new String(characters, start, length));
element.appendChild(textNode);
saveLocation();
}
}
public void comment(char [] characters, int start, int length) throws SAXException
{
Comment commentNode = document.createComment(new String(characters, start, length));
if (element == null)
{
document.appendChild(commentNode);
}
else
{
element.appendChild(commentNode);
}
saveLocation();
}
public void startCDATA()
{
cdata = new StringBuilder();
cdataSection = document.createCDATASection("");
LocationMap extendedAttributes = getLocationMap(cdataSection);
extendedAttributes.setStartLine(line);
extendedAttributes.setStartColumn(column);
}
public void endCDATA()
{
cdataSection.setData(cdata.toString());
element.appendChild(cdataSection);
LocationMap extendedAttributes = getLocationMap(cdataSection);
cdata = null;
saveLocation();
extendedAttributes.setEndLine(line);
extendedAttributes.setEndColumn(column);
}
public void startDTD(String name, String publicId, String systemId)
{
// Ignore
}
public void endDTD()
{
// Ignore
}
public void startEntity(String name)
{
// Ignore
}
public void endEntity(String name)
{
// Ignore
}
@Override
public void processingInstruction(String target, String data)
{
Node processingInstruction = document.createProcessingInstruction(target, data);
if (stack.isEmpty())
{
document.appendChild(processingInstruction);
}
else
{
element.appendChild(processingInstruction);
}
}
protected void fatalError(IOException exception)
{
XSDDiagnostic xsdDiagnostic = xsdFactory.createXSDDiagnostic();
xsdDiagnostic.setSeverity(XSDDiagnosticSeverity.FATAL_LITERAL);
xsdDiagnostic.setMessage(XSDPlugin.INSTANCE.getString("_UI_IOError_message", new Object [] { exception.getLocalizedMessage() }));
xsdDiagnostics.add(xsdDiagnostic);
}
protected void fatalError(ParserConfigurationException exception)
{
XSDDiagnostic xsdDiagnostic = xsdFactory.createXSDDiagnostic();
xsdDiagnostic.setSeverity(XSDDiagnosticSeverity.FATAL_LITERAL);
xsdDiagnostic.setMessage(XSDPlugin.INSTANCE.getString("_UI_ParserError_message", new Object [] { exception.getLocalizedMessage() }));
xsdDiagnostics.add(xsdDiagnostic);
}
public void fatalError(SAXException exception)
{
XSDDiagnostic xsdDiagnostic = xsdFactory.createXSDDiagnostic();
xsdDiagnostic.setSeverity(XSDDiagnosticSeverity.FATAL_LITERAL);
xsdDiagnostic.setMessage(XSDPlugin.INSTANCE.getString("_UI_ParserError_message", new Object [] { exception.getMessage() }));
xsdDiagnostics.add(xsdDiagnostic);
}
@Override
public void fatalError(SAXParseException exception)
{
XSDDiagnostic xsdDiagnostic = xsdFactory.createXSDDiagnostic();
xsdDiagnostic.setSeverity(XSDDiagnosticSeverity.FATAL_LITERAL);
xsdDiagnostic.setMessage(XSDPlugin.INSTANCE.getString("_UI_ParserError_message", new Object [] { exception.getMessage() }));
xsdDiagnostic.setLine(exception.getLineNumber());
xsdDiagnostic.setColumn(exception.getColumnNumber());
xsdDiagnostics.add(xsdDiagnostic);
}
@Override
public void error(SAXParseException exception)
{
XSDDiagnostic xsdDiagnostic = xsdFactory.createXSDDiagnostic();
xsdDiagnostic.setSeverity(XSDDiagnosticSeverity.ERROR_LITERAL);
xsdDiagnostic.setMessage("DOM:" + exception.getMessage());
xsdDiagnostic.setLine(exception.getLineNumber());
xsdDiagnostic.setColumn(exception.getColumnNumber());
xsdDiagnostics.add(xsdDiagnostic);
}
@Override
public void warning(SAXParseException exception)
{
XSDDiagnostic xsdDiagnostic = xsdFactory.createXSDDiagnostic();
xsdDiagnostic.setSeverity(XSDDiagnosticSeverity.WARNING_LITERAL);
xsdDiagnostic.setMessage("DOM:" + exception.getMessage());
xsdDiagnostic.setLine(exception.getLineNumber());
xsdDiagnostic.setColumn(exception.getColumnNumber());
xsdDiagnostics.add(xsdDiagnostic);
}
protected void saveLocation()
{
if (locator != null)
{
line = locator.getLineNumber();
column = locator.getColumnNumber();
// The crimson parser seems to give poor coodinates and is 0-based for line count.
//
if (column == -1)
{
column = 1;
}
}
}
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException
{
InputSource inputSource;
if ("-//W3C//DTD XMLSCHEMA 200102//EN".equalsIgnoreCase(publicId))
{
inputSource = new InputSource( XSDPlugin.INSTANCE.getBaseURL() + "cache/www.w3.org/2001/XMLSchema.dtd");
inputSource.setPublicId(publicId);
}
else if (systemId != null && systemId.startsWith("file://bundleentry:"))
{
inputSource = new InputSource(systemId.substring(7));
inputSource.setPublicId(publicId);
}
else
{
try
{
inputSource = super.resolveEntity(publicId, systemId);
}
catch (IOException exception)
{
throw new SAXException(exception);
}
}
return inputSource;
}
}