blob: 30d617380adda969bac11cf64e2af7cff33aca45 [file] [log] [blame]
package org.robolectric.res;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class StaxDocumentLoader {
private static final FsFile.Filter ENDS_WITH_XML = new FsFile.Filter() {
@Override public boolean accept(@NotNull FsFile fsFile) {
return fsFile.getName().endsWith(".xml");
}
};
private static final NodeHandler NO_OP_HANDLER = new NodeHandler();
private final String packageName;
private final ResourcePath resourcePath;
private final StaxLoader[] staxLoaders;
private final NodeHandler topLevelNodeHandler;
private final XMLInputFactory factory;
public StaxDocumentLoader(String packageName, ResourcePath resourcePath, StaxLoader... staxLoaders) {
this.packageName = packageName;
this.resourcePath = resourcePath;
this.staxLoaders = staxLoaders;
topLevelNodeHandler = new NodeHandler();
for (StaxLoader staxLoader : staxLoaders) {
staxLoader.addTo(topLevelNodeHandler);
}
factory = XMLInputFactory.newFactory();
}
public void load(String folderBaseName) {
FsFile resourceBase = resourcePath.getResourceBase();
FsFile[] files = resourceBase.listFiles(new StartsWithFilter(folderBaseName));
if (files == null) {
throw new RuntimeException(resourceBase.join(folderBaseName) + " is not a directory");
}
for (FsFile dir : files) {
loadFile(dir);
}
}
private void loadFile(FsFile dir) {
if (!dir.exists()) {
throw new RuntimeException("no such directory " + dir);
}
for (FsFile file : dir.listFiles(ENDS_WITH_XML)) {
loadResourceXmlFile(new XmlContext(packageName, file));
}
}
protected void loadResourceXmlFile(XmlContext xmlContext) {
FsFile xmlFile = xmlContext.getXmlFile();
XMLStreamReader xmlStreamReader = null;
try {
xmlStreamReader = factory.createXMLStreamReader(xmlFile.getInputStream());
doParse(xmlStreamReader, xmlContext);
} catch (Exception e) {
throw new RuntimeException("error parsing " + xmlFile, e);
}
if (xmlStreamReader != null) {
try {
xmlStreamReader.close();
} catch (XMLStreamException e) {
throw new RuntimeException(e);
}
}
}
protected void doParse(XMLStreamReader reader, XmlContext xmlContext) throws XMLStreamException {
NodeHandler nodeHandler = this.topLevelNodeHandler;
Deque<NodeHandler> nodeHandlerStack = new ArrayDeque<>();
while (reader.hasNext()) {
int event = reader.next();
switch (event) {
case XMLStreamConstants.START_DOCUMENT:
break;
case XMLStreamConstants.START_ELEMENT:
nodeHandlerStack.push(nodeHandler);
NodeHandler elementHandler = nodeHandler.findMatchFor(reader);
nodeHandler = elementHandler == null ? NO_OP_HANDLER : elementHandler;
nodeHandler.onStart(reader, xmlContext);
break;
case XMLStreamConstants.CDATA:
case XMLStreamConstants.CHARACTERS:
nodeHandler.onCharacters(reader, xmlContext);
break;
case XMLStreamConstants.END_ELEMENT:
nodeHandler.onEnd(reader, xmlContext);
nodeHandler = nodeHandlerStack.pop();
break;
case XMLStreamConstants.ATTRIBUTE:
}
}
}
static class NodeHandler {
private final String elementName;
private final Map<String, String> attrs;
private final Map<String, List<NodeHandler>> subHandlers = new HashMap<>();
private NodeListener listener;
NodeHandler(String elementName, Map<String, String> attrs) {
this.elementName = "*".equals(elementName) ? null : elementName;
this.attrs = attrs == null ? Collections.<String, String>emptyMap() : attrs;
}
NodeHandler() {
this.elementName = null;
this.attrs = Collections.emptyMap();
}
NodeHandler findMatchFor(XMLStreamReader xml) {
String tagName = xml.getLocalName();
List<NodeHandler> nodeHandlers = subHandlers.get(tagName);
if (nodeHandlers == null) {
nodeHandlers = subHandlers.get("*");
}
if (nodeHandlers != null) {
for (NodeHandler subHandler : nodeHandlers) {
if (subHandler.matches(xml)) {
return subHandler;
}
}
}
return null;
}
NodeHandler findMatchFor(String elementName, Map<String, String> attrs) {
List<NodeHandler> nodeHandlers = null;
if (elementName != null) {
nodeHandlers = subHandlers.get(elementName);
}
if (nodeHandlers == null) {
nodeHandlers = subHandlers.get("*");
}
if (nodeHandlers != null) {
for (NodeHandler subHandler : nodeHandlers) {
if (subHandler.attrs.equals(attrs)) {
return subHandler;
}
}
}
NodeHandler nodeHandler = new NodeHandler(elementName, attrs);
String elementNameOrStar = elementName == null ? "*" : elementName;
nodeHandlers = subHandlers.get(elementName);
if (nodeHandlers == null) {
nodeHandlers = new ArrayList<>();
subHandlers.put(elementNameOrStar, nodeHandlers);
}
nodeHandlers.add(nodeHandler);
return nodeHandler;
}
private boolean matches(XMLStreamReader xml) {
if (elementName != null && !elementName.equals(xml.getLocalName())) {
return false;
}
int attributeCount = xml.getAttributeCount();
for (int i = 0; i < attributeCount; i++) {
String attrName = xml.getAttributeLocalName(i);
String expectedAttrValue = attrs.get(attrName);
if (expectedAttrValue != null && !expectedAttrValue.equals(xml.getAttributeValue(i))) {
return false;
}
}
return true;
}
public NodeHandler addHandler(String elementName, Map<String, String> attrs) {
return findMatchFor(elementName, attrs);
}
void addListener(NodeListener nodeListener) {
if (listener != null) {
throw new RuntimeException("already have a listener");
}
listener = nodeListener;
}
public void onStart(XMLStreamReader xml, XmlContext xmlContext) throws XMLStreamException {
if (listener != null) listener.onStart(xml, xmlContext);
}
public void onCharacters(XMLStreamReader xml, XmlContext xmlContext) throws XMLStreamException {
if (listener != null) listener.onCharacters(xml, xmlContext);
}
public void onEnd(XMLStreamReader xml, XmlContext xmlContext) throws XMLStreamException {
if (listener != null) listener.onEnd(xml, xmlContext);
}
@Override
public String toString() {
return "/" + elementName + "[@" + attrs + "]";
}
}
interface NodeListener {
void onStart(XMLStreamReader xml, XmlContext xmlContext) throws XMLStreamException;
void onCharacters(XMLStreamReader xml, XmlContext xmlContext) throws XMLStreamException;
void onEnd(XMLStreamReader xml, XmlContext xmlContext) throws XMLStreamException;
}
}