| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 |
| * |
| * https://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.apache.ivy.osgi.util; |
| |
| import java.util.HashMap; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| import org.apache.ivy.util.Message; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.DTDHandler; |
| import org.xml.sax.ErrorHandler; |
| import org.xml.sax.Locator; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| public class DelegatingHandler extends DefaultHandler implements DTDHandler, ContentHandler, |
| ErrorHandler { |
| |
| private DelegatingHandler delegate = null; |
| |
| DelegatingHandler parent; |
| |
| private final Map<String, DelegatingHandler> saxHandlerMapping = new HashMap<>(); |
| |
| private final Map<String, ChildElementHandler<?>> childHandlerMapping = new HashMap<>(); |
| |
| private final String tagName; |
| |
| private boolean started = false; |
| |
| private boolean skip = false; |
| |
| private boolean skipOnError = false; |
| |
| private StringBuilder charBuffer = new StringBuilder(); |
| |
| private boolean bufferingChar = false; |
| |
| private Locator locator; |
| |
| public DelegatingHandler(String name) { |
| this.tagName = name; |
| charBuffer.setLength(0); |
| } |
| |
| protected <DH extends DelegatingHandler> void addChild(DH saxHandler, |
| ChildElementHandler<DH> elementHandler) { |
| saxHandlerMapping.put(saxHandler.getName(), saxHandler); |
| childHandlerMapping.put(saxHandler.getName(), elementHandler); |
| saxHandler.parent = this; |
| } |
| |
| public String getName() { |
| return tagName; |
| } |
| |
| public DelegatingHandler getParent() { |
| return parent; |
| } |
| |
| public void setBufferingChar(boolean bufferingChar) { |
| this.bufferingChar = bufferingChar; |
| } |
| |
| public void setSkipOnError(boolean skipOnError) { |
| this.skipOnError = skipOnError; |
| } |
| |
| public boolean isBufferingChar() { |
| return bufferingChar; |
| } |
| |
| public String getBufferedChars() { |
| return charBuffer.toString(); |
| } |
| |
| @Override |
| public void setDocumentLocator(Locator locator) { |
| this.locator = locator; |
| for (DelegatingHandler subHandler : saxHandlerMapping.values()) { |
| subHandler.setDocumentLocator(locator); |
| } |
| } |
| |
| public Locator getLocator() { |
| return locator; |
| } |
| |
| /** |
| * Return an sort of identifier of the current element being parsed. It will only be used for |
| * logging purpose. |
| * |
| * @return an empty string by default |
| */ |
| protected String getCurrentElementIdentifier() { |
| return ""; |
| } |
| |
| public void skip() { |
| skip = true; |
| for (DelegatingHandler subHandler : saxHandlerMapping.values()) { |
| subHandler.stopDelegating(); |
| } |
| } |
| |
| protected void stopDelegating() { |
| parent.delegate = null; |
| skip = false; |
| started = false; |
| for (DelegatingHandler subHandler : saxHandlerMapping.values()) { |
| subHandler.stopDelegating(); |
| } |
| } |
| |
| private interface SkipOnErrorCallback { |
| void call() throws SAXException; |
| } |
| |
| private void skipOnError(SkipOnErrorCallback callback) throws SAXException { |
| try { |
| callback.call(); |
| } catch (SAXException e) { |
| if (skipOnError) { |
| skip(); |
| log(Message.MSG_ERR, e.getMessage(), e); |
| } else { |
| throw e; |
| } |
| } |
| } |
| |
| @Override |
| public final void startDocument() throws SAXException { |
| if (skip) { |
| return; |
| } |
| if (delegate != null) { |
| delegate.startDocument(); |
| } else { |
| doStartDocument(); |
| } |
| } |
| |
| /** |
| * By default do nothing. |
| * |
| * @throws SAXException API told me so |
| */ |
| protected void doStartDocument() throws SAXException { |
| } |
| |
| @Override |
| public final void endDocument() throws SAXException { |
| if (skip) { |
| return; |
| } |
| if (delegate != null) { |
| delegate.endDocument(); |
| } else { |
| doEndDocument(); |
| } |
| } |
| |
| /** |
| * By default do nothing. |
| * |
| * @throws SAXException API told me so |
| */ |
| protected void doEndDocument() throws SAXException { |
| } |
| |
| @Override |
| public final void startElement(final String uri, final String localName, final String n, |
| final Attributes atts) throws SAXException { |
| // reset the char buffer |
| charBuffer.setLength(0); |
| if (delegate != null) { |
| // we are already delegating, let's continue |
| skipOnError(new SkipOnErrorCallback() { |
| public void call() throws SAXException { |
| delegate.startElement(uri, localName, n, atts); |
| } |
| }); |
| } else { |
| if (!started) { // first time called ? |
| // just for the root, check the expected element name |
| // not need to check the delegated as the mapping is already taking care of it |
| if (parent == null && !localName.equals(tagName)) { |
| // we are at the root and the saxed element doesn't match |
| throw new SAXException("The root element of the parsed document '" + localName |
| + "' didn't matched the expected one: '" + tagName + "'"); |
| } |
| skipOnError(new SkipOnErrorCallback() { |
| public void call() throws SAXException { |
| handleAttributes(atts); |
| } |
| }); |
| started = true; |
| } else { |
| if (skip) { |
| // we con't care anymore about that part of the xml tree |
| return; |
| } |
| // time now to delegate for a new element |
| delegate = saxHandlerMapping.get(localName); |
| if (delegate != null) { |
| skipOnError(new SkipOnErrorCallback() { |
| public void call() throws SAXException { |
| delegate.startElement(uri, localName, n, atts); |
| } |
| }); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Called when the expected node is achieved; nothing to do by default. |
| * |
| * @param atts |
| * the xml attributes attached to the expected node |
| * @exception SAXException |
| * in case the parsing should be completely stopped |
| */ |
| protected void handleAttributes(Attributes atts) throws SAXException { |
| } |
| |
| /** |
| * By default do nothing. |
| * |
| * @param uri String |
| * @param localName String |
| * @param name String |
| * @param atts Attributes |
| * @throws SAXException API told me so |
| */ |
| protected void doStartElement(String uri, String localName, String name, Attributes atts) |
| throws SAXException { |
| } |
| |
| @Override |
| public final void endElement(final String uri, final String localName, final String n) |
| throws SAXException { |
| if (delegate != null) { |
| final DelegatingHandler savedDelegate = delegate; |
| // we are already delegating, let's continue |
| skipOnError(new SkipOnErrorCallback() { |
| public void call() throws SAXException { |
| delegate.endElement(uri, localName, n); |
| } |
| }); |
| if (delegate == null) { |
| // we just stopped delegating, it means that the child has ended |
| final ChildElementHandler<?> childHandler = childHandlerMapping.get(localName); |
| if (childHandler != null) { |
| skipOnError(new SkipOnErrorCallback() { |
| public void call() throws SAXException { |
| childHandler._childHandled(savedDelegate); |
| } |
| }); |
| } |
| } |
| } else { |
| if (!skip) { |
| doEndElement(uri, localName, n); |
| } |
| if (parent != null && tagName.equals(localName)) { |
| // the current element is closed, let's tell the parent to stop delegating |
| stopDelegating(); |
| } |
| } |
| } |
| |
| /** |
| * By default do nothing. |
| * |
| * @param uri String |
| * @param localName String |
| * @param name String |
| * @throws SAXException API told me so |
| */ |
| protected void doEndElement(String uri, String localName, String name) throws SAXException { |
| } |
| |
| public abstract static class ChildElementHandler<DH extends DelegatingHandler> { |
| |
| /** |
| * @param child DH |
| * @throws SAXParseException on failure |
| * @deprecated because of renaming due spell check. |
| */ |
| @Deprecated |
| public void childHanlded(DH child) throws SAXParseException { |
| childHandled(child); |
| } |
| |
| public abstract void childHandled(DH child) throws SAXParseException; |
| |
| // because we know what we're doing |
| @SuppressWarnings("unchecked") |
| private void _childHandled(DelegatingHandler delegate) throws SAXParseException { |
| childHandled((DH) delegate); |
| } |
| |
| } |
| |
| @Override |
| public final void characters(char[] ch, int start, int length) throws SAXException { |
| if (skip) { |
| return; |
| } |
| if (delegate != null) { |
| delegate.characters(ch, start, length); |
| } else { |
| doCharacters(ch, start, length); |
| } |
| } |
| |
| /** |
| * @param ch char[] |
| * @param start int |
| * @param length int |
| * @throws SAXException if something goes wrong |
| */ |
| protected void doCharacters(char[] ch, int start, int length) throws SAXException { |
| if (bufferingChar) { |
| charBuffer.append(ch, start, length); |
| } |
| } |
| |
| @Override |
| public final void startPrefixMapping(String prefix, String uri) throws SAXException { |
| if (skip) { |
| return; |
| } |
| if (delegate != null) { |
| delegate.startPrefixMapping(prefix, uri); |
| } else { |
| doStartPrefixMapping(prefix, uri); |
| } |
| } |
| |
| /** |
| * By default do nothing. |
| * |
| * @param prefix String |
| * @param uri String |
| * @throws SAXException API told me so |
| */ |
| protected void doStartPrefixMapping(String prefix, String uri) throws SAXException { |
| } |
| |
| @Override |
| public final void endPrefixMapping(String prefix) throws SAXException { |
| if (skip) { |
| return; |
| } |
| if (delegate != null) { |
| delegate.endPrefixMapping(prefix); |
| } else { |
| doEndPrefixMapping(prefix); |
| } |
| } |
| |
| /** |
| * By default do nothing. |
| * |
| * @param prefix String |
| * @throws SAXException API told me so |
| */ |
| protected void doEndPrefixMapping(String prefix) throws SAXException { |
| } |
| |
| @Override |
| public final void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { |
| if (skip) { |
| return; |
| } |
| if (delegate != null) { |
| delegate.ignorableWhitespace(ch, start, length); |
| } else { |
| doIgnorableWhitespace(ch, start, length); |
| } |
| } |
| |
| /** |
| * By default do nothing |
| * |
| * @param ch char[] |
| * @param start int |
| * @param length int |
| * @throws SAXException API told me so |
| */ |
| protected void doIgnorableWhitespace(char[] ch, int start, int length) throws SAXException { |
| } |
| |
| @Override |
| public final void notationDecl(String name, String publicId, String systemId) |
| throws SAXException { |
| if (skip) { |
| return; |
| } |
| if (delegate != null) { |
| delegate.notationDecl(name, publicId, systemId); |
| } else { |
| doNotationDecl(name, publicId, systemId); |
| } |
| } |
| |
| /** |
| * By default do nothing. |
| * |
| * @param name String |
| * @param publicId String |
| * @param systemId String |
| * @throws SAXException API told me so |
| */ |
| protected void doNotationDecl(String name, String publicId, String systemId) |
| throws SAXException { |
| } |
| |
| @Override |
| public final void processingInstruction(String target, String data) throws SAXException { |
| if (skip) { |
| return; |
| } |
| if (delegate != null) { |
| delegate.processingInstruction(target, data); |
| } else { |
| doProcessingInstruction(target, data); |
| } |
| } |
| |
| /** |
| * By default do nothing |
| * |
| * @param target String |
| * @param data String |
| * @throws SAXException API told me so |
| */ |
| protected void doProcessingInstruction(String target, String data) throws SAXException { |
| } |
| |
| @Override |
| public final void skippedEntity(String name) throws SAXException { |
| if (skip) { |
| return; |
| } |
| if (delegate != null) { |
| delegate.skippedEntity(name); |
| } else { |
| doSkippedEntity(name); |
| } |
| } |
| |
| /** |
| * By default do nothing. |
| * |
| * @param name String |
| * @throws SAXException API told me so |
| */ |
| protected void doSkippedEntity(String name) throws SAXException { |
| } |
| |
| @Override |
| public final void unparsedEntityDecl(String name, String publicId, String systemId, |
| String notationName) throws SAXException { |
| if (skip) { |
| return; |
| } |
| if (delegate != null) { |
| delegate.unparsedEntityDecl(name, publicId, systemId, notationName); |
| } else { |
| doUnparsedEntityDecl(name, publicId, systemId, notationName); |
| } |
| } |
| |
| /** |
| * By default do nothing. |
| * |
| * @param name String |
| * @param publicId String |
| * @param systemId String |
| * @param notationName String |
| * @throws SAXException API told me so |
| */ |
| protected void doUnparsedEntityDecl(String name, String publicId, String systemId, |
| String notationName) throws SAXException { |
| } |
| |
| // ERROR HANDLING |
| |
| @Override |
| public final void warning(SAXParseException exception) throws SAXException { |
| if (skip) { |
| return; |
| } |
| if (delegate != null) { |
| delegate.warning(exception); |
| } else { |
| doWarning(exception); |
| } |
| } |
| |
| /** |
| * By default do nothing. |
| * |
| * @param exception SAXParseException |
| * @throws SAXException API told me so |
| */ |
| protected void doWarning(SAXParseException exception) throws SAXException { |
| } |
| |
| @Override |
| public final void error(SAXParseException exception) throws SAXException { |
| if (skip) { |
| return; |
| } |
| if (delegate != null) { |
| delegate.error(exception); |
| } else { |
| doError(exception); |
| } |
| } |
| |
| /** |
| * By default do nothing. |
| * |
| * @param exception SAXParseException |
| * @throws SAXException API told me so |
| */ |
| protected void doError(SAXParseException exception) throws SAXException { |
| } |
| |
| @Override |
| public final void fatalError(SAXParseException exception) throws SAXException { |
| if (skip) { |
| return; |
| } |
| if (delegate != null) { |
| delegate.fatalError(exception); |
| } else { |
| doFatalError(exception); |
| } |
| } |
| |
| /** |
| * By default do nothing. |
| * |
| * @param exception SAXParseException |
| * @throws SAXException API told me so |
| */ |
| protected void doFatalError(SAXParseException exception) throws SAXException { |
| } |
| |
| // ////////////////////// |
| // Functions related to error handling |
| // ////////////////////// |
| |
| protected void log(int logLevel, String message, Throwable t) { |
| Message.debug(t); |
| log(logLevel, message); |
| } |
| |
| protected void log(int logLevel, String message) { |
| Message.log(logLevel, getLocation(getLocator()) + message); |
| } |
| |
| protected static String getLocation(Locator locator) { |
| if (locator == null) { |
| return ""; |
| } |
| return "[line " + locator.getLineNumber() + " col. " + locator.getColumnNumber() + "] "; |
| } |
| |
| @SuppressWarnings("unused") |
| private void skipOnError(DelegatingHandler currentHandler, |
| Class<? extends DelegatingHandler> handlerClassToSkip, String message) { |
| DelegatingHandler handlerToSkip = currentHandler; |
| while (!(handlerClassToSkip.isAssignableFrom(handlerToSkip.getClass()))) { |
| handlerToSkip = handlerToSkip.getParent(); |
| } |
| log(Message.MSG_ERR, message + ". The '" + handlerToSkip.getName() + "' element " |
| + getCurrentElementIdentifier() + " is then ignored."); |
| handlerToSkip.skip(); |
| } |
| |
| // ////////////////////// |
| // Helpers to parse the attributes |
| // ////////////////////// |
| |
| protected String getRequiredAttribute(Attributes atts, String name) throws SAXParseException { |
| String value = atts.getValue(name); |
| if (value == null) { |
| throw new SAXParseException("Required attribute '" + name + "' not found", getLocator()); |
| } |
| return value; |
| } |
| |
| protected String getOptionalAttribute(Attributes atts, String name, String defaultValue) { |
| String value = atts.getValue(name); |
| if (value == null) { |
| return defaultValue; |
| } |
| return value; |
| } |
| |
| protected int getRequiredIntAttribute(Attributes atts, String name, Integer logLevel) |
| throws SAXParseException { |
| return parseInt(name, getRequiredAttribute(atts, name)); |
| } |
| |
| protected Integer getOptionalIntAttribute(Attributes atts, String name, Integer defaultValue) |
| throws SAXParseException { |
| String value = atts.getValue(name); |
| if (value == null) { |
| return defaultValue; |
| } |
| return parseInt(name, value); |
| } |
| |
| private int parseInt(String name, String value) throws SAXParseException { |
| try { |
| return Integer.parseInt(value); |
| } catch (NumberFormatException e) { |
| throw new SAXParseException("Attribute '" + name |
| + "' is expected to be an integer but was '" + value + "' (" + e.getMessage() |
| + ")", getLocator()); |
| } |
| } |
| |
| protected long getRequiredLongAttribute(Attributes atts, String name) throws SAXParseException { |
| return parseLong(name, getRequiredAttribute(atts, name)); |
| } |
| |
| protected Long getOptionalLongAttribute(Attributes atts, String name, Long defaultValue) |
| throws SAXParseException { |
| String value = atts.getValue(name); |
| if (value == null) { |
| return defaultValue; |
| } |
| return parseLong(name, value); |
| } |
| |
| private long parseLong(String name, String value) throws SAXParseException { |
| try { |
| return Long.parseLong(value); |
| } catch (NumberFormatException e) { |
| throw new SAXParseException("Attribute '" + name |
| + "' is expected to be an long but was '" + value + "' (" + e.getMessage() |
| + ")", getLocator()); |
| } |
| } |
| |
| protected boolean getRequiredBooleanAttribute(Attributes atts, String name) |
| throws SAXParseException { |
| return parseBoolean(name, getRequiredAttribute(atts, name)); |
| } |
| |
| protected Boolean getOptionalBooleanAttribute(Attributes atts, String name, Boolean defaultValue) |
| throws SAXParseException { |
| String value = atts.getValue(name); |
| if (value == null) { |
| return defaultValue; |
| } |
| return parseBoolean(name, value); |
| } |
| |
| static final String TRUE = Boolean.TRUE.toString().toLowerCase(Locale.US); |
| |
| static final String FALSE = Boolean.FALSE.toString().toLowerCase(Locale.US); |
| |
| private boolean parseBoolean(String name, String value) throws SAXParseException { |
| String lowerValue = value.toLowerCase(Locale.US); |
| if (lowerValue.equals(TRUE)) { |
| return true; |
| } |
| if (lowerValue.equals(FALSE)) { |
| return false; |
| } |
| throw new SAXParseException("Attribute '" + name |
| + "' is expected to be a boolean but was '" + value + "'", getLocator()); |
| } |
| } |