/* * Copyright 2011, 2012 Odysseus Software GmbH * * Licensed 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 * * http://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.synapse.commons.staxon.core.json.util; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import javax.xml.XMLConstants; import javax.xml.stream.XMLEventWriter; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import javax.xml.stream.events.ProcessingInstruction; import org.apache.synapse.commons.staxon.core.event.SimpleXMLEventFactory; import org.apache.synapse.commons.staxon.core.json.JsonXMLStreamConstants; /** * Package-private helper used by {@link XMLMultipleStreamWriter} and {@link XMLMultipleEventWriter} * to handle path matching and insert <code><xml-multiple></code> processing instruction events. */ class XMLMultipleProcessingInstructionHandler { private static final ProcessingInstruction MULTIPLE_PI = new SimpleXMLEventFactory().createProcessingInstruction(JsonXMLStreamConstants.MULTIPLE_PI_TARGET, null); /** * Processing instruction writer */ private static abstract class ProcessingInstructionWriter { abstract void add(ProcessingInstruction pi) throws XMLStreamException; } private final StringBuilder path = new StringBuilder(); private final Set<String> absoluteMultiplePaths = new HashSet<String>(); private final List<String> relativeMultiplePaths = new ArrayList<String>(); private final String[] names = new String[64]; private final boolean matchRoot; private final boolean matchPrefixes; private final Pattern pathPattern; private final ProcessingInstructionWriter writer; private String previousSiblingName = null; private int depth = 0; XMLMultipleProcessingInstructionHandler(final XMLStreamWriter writer, boolean matchRoot, boolean matchPrefixes) { this(new ProcessingInstructionWriter() { @Override void add(ProcessingInstruction pi) throws XMLStreamException { if (pi.getData() == null) { writer.writeProcessingInstruction(pi.getTarget()); } else { writer.writeProcessingInstruction(pi.getTarget(), pi.getData()); } } }, matchRoot, matchPrefixes); } XMLMultipleProcessingInstructionHandler(final XMLEventWriter writer, boolean matchRoot, boolean matchPrefixes) { this(new ProcessingInstructionWriter() { @Override void add(ProcessingInstruction pi) throws XMLStreamException { writer.add(pi); } }, matchRoot, matchPrefixes); } private XMLMultipleProcessingInstructionHandler(ProcessingInstructionWriter writer, boolean matchRoot, boolean matchPrefixes) { this.matchRoot = matchRoot; this.matchPrefixes = matchPrefixes; this.writer = writer; /* * determine path pattern */ String identifier = "\\w(-?\\w)*"; String name = matchPrefixes ? "(" + identifier + ":)?" + identifier : identifier; pathPattern = Pattern.compile("/?" + name + "(/" + name + ")*"); } private boolean matches() { if (absoluteMultiplePaths.contains(path.toString())) { return true; } if (!relativeMultiplePaths.isEmpty()) { for (String suffix : relativeMultiplePaths) { if (path.toString().endsWith(suffix) && path.charAt(path.length() - suffix.length() - 1) == '/') { return true; } } } return false; } private void push(String name) throws XMLStreamException { if (matchRoot || depth > 0) { path.append('/').append(name); } if (!name.equals(previousSiblingName) && matches()) { writer.add(MULTIPLE_PI); } names[depth] = name; previousSiblingName = null; depth++; } private void pop() { depth--; previousSiblingName = names[depth]; names[depth] = null; if (matchRoot || depth > 0) { path.setLength(path.length() - previousSiblingName.length() - 1); } } /** * Add path to trigger <code><?xml-multiple?></code> PI. * The path may start with <code>'/'</code> and contain element * names, separated by <code>'/'</code>, e.g * <code>"/foo/bar"</code>, <code>"foo/bar"</code> or <code>"bar"</code>. * If <code>matchPrefixes</code> isv set to <code>true</code>, element * names may be prefixed, e.g. <code>"p:foo/bar"</code>. * * @param path */ void addMultiplePath(String path) throws XMLStreamException { if (!pathPattern.matcher(path).matches()) { throw new XMLStreamException("multiple path does not match " + pathPattern.pattern()); } if (path.charAt(0) == '/') { absoluteMultiplePaths.add(path); } else { relativeMultiplePaths.add(path); } } void preStartElement(String prefix, String localPart) throws XMLStreamException { if (matchPrefixes) { push(XMLConstants.DEFAULT_NS_PREFIX.equals(prefix) ? localPart : prefix + ':' + localPart); } else { push(localPart); } } void postStartElement() throws XMLStreamException { // do nothing } void preEndElement() throws XMLStreamException { // do nothing } void postEndElement() throws XMLStreamException { pop(); } void preEmptyElement(String prefix, String localPart) throws XMLStreamException { preStartElement(prefix, localPart); } void postEmptyElement() throws XMLStreamException { postStartElement(); preEndElement(); postEndElement(); } }