/*
* 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();
}
}