/**
* StAXModelBuilder.java
*
* @author Charles Groves
*/
package edu.sc.seis.sod.validator.model;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import edu.sc.seis.fissuresUtil.xml.XMLUtil;
import edu.sc.seis.sod.SodUtil;
import edu.sc.seis.sod.Start;
import edu.sc.seis.sod.validator.ModelWalker;
import edu.sc.seis.sod.validator.model.datatype.AnyText;
import edu.sc.seis.sod.validator.model.datatype.BooleanDatatype;
import edu.sc.seis.sod.validator.model.datatype.DoubleDatatype;
import edu.sc.seis.sod.validator.model.datatype.FloatDatatype;
import edu.sc.seis.sod.validator.model.datatype.IntegerDatatype;
import edu.sc.seis.sod.validator.model.datatype.NonnegativeIntegerDatatype;
import edu.sc.seis.sod.validator.model.datatype.Token;
public class StAXModelBuilder implements XMLStreamConstants {
public StAXModelBuilder(String relaxLoc) throws XMLStreamException,
IOException {
if(parsedGrammars.containsKey(relaxLoc)) {
definedGrammar = parsedGrammars.get(relaxLoc);
} else {
if(relaxLoc.endsWith("anyXML.rng")) {
definedGrammar = new Grammar(relaxLoc);
NamedElement anyXML = new NamedElement(1, 1, "anyXML");
anyXML.setChild(new Empty(anyXML));
Definition start = new Definition("", definedGrammar);
start.set(anyXML);
Annotation ann = new Annotation();
ann.setSummary("Any well-formed XML");
ann.setDescription("This ingredient describes any well formed XML document. You must replace it with a root element, and then inside of it, any well formed XML will do.");
definedGrammar.add(start);
} else {
ClassLoader cl = getClass().getClassLoader();
InputStream relaxSource = Start.createInputStream(cl, relaxLoc);
reader = XMLUtil.getStaxInputFactory().createXMLStreamReader(relaxSource);
definedGrammar = new Grammar(relaxLoc);
reader.next();// SKIP SPACE
reader.next();// GET TO GRAMMAR START TAG
try {
handleGrammar();
} catch(XMLStreamException e) {
System.out.println("ERROR ON " + relaxLoc);
e.printStackTrace();
System.exit(0);
}
reader.close();
}
parsedGrammars.put(relaxLoc, definedGrammar);
if(waiters.size() > 0) {
Iterator<Map.Entry<String, String>> it = waiters.entrySet().iterator();
while(it.hasNext()) {
Map.Entry<String, String> entry = it.next();
definedGrammar.add(entry.getKey(),
definedGrammar.getDef(entry.getValue()));
}
}
}
}
private void handleGrammar() throws XMLStreamException {
while(reader.hasNext()) {
switch(reader.next()){
case START_ELEMENT:
String tag = reader.getLocalName();
if(tag.equals("start")) {
Definition root = handleStart();
definedGrammar.add(root);
} else if (tag.equals("define")) {
Definition def = handleDef();
if(def != null) {
definedGrammar.add(def);
}
} else if(tag.equals("include")) {
handleInclude();
} else {
System.out.println("I DON'T THINK THIS SHOULD BE HERE");
whatIs();
}
break;
default:
break;
}
}
}
public void whatIs() {
switch(reader.getEventType()){
case START_ELEMENT:
System.out.println("START " + reader.getLocalName());
break;
case END_ELEMENT:
System.out.println("END " + reader.getLocalName());
break;
case COMMENT:
System.out.println("COMMENT " + reader.getText());
break;
default:
System.out.println("DUNNO");
break;
}
}
private void handleInclude() {
// TODO handle inclusion overrides
definedGrammar.include(getGrammar(getAbsPath()));
}
private Definition handleStart() throws XMLStreamException {
Definition def = new Definition("", definedGrammar);
nextTag();
FormProvider result = handleAll();
def.set(result);
return def;
}
private Definition handleDef() throws XMLStreamException {
int combo = Definition.UNDEFINED;
String name = "";
for(int i = 0; i < reader.getAttributeCount(); i++) {
if(reader.getAttributeLocalName(i).equals("combine")) {
String value = reader.getAttributeValue(i);
if(value.equals("choice")) {
combo = Definition.CHOICE;
} else if(value.equals("interleave")) {
combo = Definition.INTERLEAVE;
}
} else if(reader.getAttributeLocalName(i).equals("name")) {
name = reader.getAttributeValue(i);
}
}
Definition def = new Definition(name, definedGrammar, combo);
nextTag();
FormProvider result = handleAll();
def.set(result);
if(result instanceof Ref) {
Ref ref = (Ref)result;
def = ref.getDef();
waiters.put(name, ref.getName());
}
return def;
}
private void nextTag() throws XMLStreamException {
if(reader.hasNext()) {
int ev = reader.next();
while(reader.hasNext() && ev != START_ELEMENT && ev != END_ELEMENT) {
ev = reader.next();
}
}
}
private FormProvider handleAll() throws XMLStreamException {
List<Object> kids = new ArrayList<Object>();
while(reader.getEventType() == START_ELEMENT) {
String tag = reader.getLocalName();
if(isCardinality(tag)) {
kids.add(handleCardinality());
} else if(tag.equals("element")) {
kids.add(handleElement());
} else if(tag.equals("attribute")) {
kids.add(handleAttr());
} else if(isMulitgen(tag)) {
kids.add(handleMultigen());
} else if(tag.equals("ref")) {
kids.add(handleRef());
} else if(tag.equals("externalRef")) {
kids.add(handleExtRef());
} else if(tag.equals("anyName")) {
// ignore...
nextTag();
nextTag();
} else if(isData(tag)) {
kids.add(handleData());
} else {
throw new RuntimeException("Unknown tag! " + tag + " " + definedGrammar);
}
}
if(kids.size() == 0) {
return null;
}// Hopefully an attribute called
// handleAll
if(kids.size() == 1) {
return (FormProvider)kids.get(0);
}
Group g = new Group(1, 1);
Iterator<Object> it = kids.iterator();
while(it.hasNext()) {
g.add((FormProvider)it.next());
}
return g;
}
private boolean isAnn(String tag) {
return tag.equals("annotation");
}
private Annotation handleAnn() throws XMLStreamException {
Annotation note = new Annotation();
if(isAnn(reader.getLocalName())) {
while(reader.next() != END_ELEMENT
|| !reader.getLocalName().equals("annotation")) {
if(reader.getEventType() == START_ELEMENT) {
if(reader.getLocalName().equals("summary")) {
reader.next();
note.setSummary(reader.getText());
} else if(reader.getLocalName().equals("description")) {
note.setDescription(extractSubstructure());
} else if(reader.getLocalName().equals("deprecated")) {
note.setDeprecation(extractSubstructure());
} else if(reader.getLocalName().equals("include")) {
reader.next();
note.setInclude(true);
} else if(reader.getLocalName().equals("example")) {
String example = extractSubstructure();
example = example.replaceAll("\\t", " ");
int j = 0;
for(int i = 0; i < example.length(); i++) {
if(example.charAt(i) == '\n') {
j = 0;
} else if(example.charAt(i) != ' ') {
break;
} else {
j++;
}
}
example = example.trim();
note.setExample(example.replaceAll("[ \\t]{" + j + "}",
""));
} else if(reader.getLocalName().equals("velocity")) {
reader.next();
note.setVelocity(reader.getText());
} else if(!reader.getLocalName().equals("documentation")) {
System.out.println("Unrecognized tag "
+ reader.getLocalName() + " in annotation in "
+ definedGrammar);
}
}
}
reader.nextTag();
}
return note;
}
public String extractSubstructure() throws XMLStreamException {
String stopTagName = reader.getLocalName();
StringBuffer buf = new StringBuffer();
reader.next();
int prevEventType = -1;
while(reader.getEventType() != END_ELEMENT
|| !reader.getLocalName().equals(stopTagName)) {
int curEventType = reader.getEventType();
// this if-else block takes care of empty tags
if(prevEventType == START_ELEMENT && curEventType == END_ELEMENT) {
buf.setCharAt(buf.length() - 1, ' ');
buf.append("/>");
} else {
buf.append(XMLUtil.readEvent(reader));
}
reader.next();
prevEventType = curEventType;
}
return buf.toString();
}
/**
* Method handleCardinality assumes that the reader has been advanced to a
* START_ELEMENT with a local name of one of the cardinality elements:
* zeroOrMore, optional or oneOrMore It returns a FormProvider representing
* the internals of that cardinality and advances the reader to the next tag
* past the END_ELEMENT of the cardinality The parent on the returned
* FormProvider is not set, so this must be handled by the object calling
* this.
*/
private FormProvider handleCardinality() throws XMLStreamException {
// get cardinality based on the tag name
int min = 1;
int max = 1;
String tag = reader.getLocalName();
if(tag.equals("zeroOrMore") || tag.equals("optional")) {
min = 0;
}
if(tag.equals("zeroOrMore") || tag.equals("oneOrMore")) {
max = Integer.MAX_VALUE;
}
// make sub structure
reader.nextTag();
if(isAnn(reader.getLocalName())) {
Annotation note = handleAnn();
throw new RuntimeException("Annotation with summary "
+ note.getSummary()
+ " not allowed directly in cardinality tag");
}
FormProvider result = handleAll();
// set cardinality on substructure
if(min == 0) {
result.setMin(min);
}
if(max == Integer.MAX_VALUE) {
result.setMax(max);
}
// advance past the end of cardinality end element and return
reader.nextTag();
return result;
}
private boolean isCardinality(String tag) {
return tag.equals("oneOrMore") || tag.equals("zeroOrMore")
|| tag.equals("optional");
}
/**
* Method handleElement assumes that the reader has been advanced to a
* START_ELEMENT with a local name of element It returns a NamedElement
* representing that element and its children and advances the reader to the
* next tag past the END_ELEMENT of the element handle started The parent on
* the returned FormProvider is not set, so this must be handled by the
* object calling this.
*/
private FormProvider handleElement() throws XMLStreamException {
if (reader.getAttributeCount() == 0) {
// no attributes, so no name specified in attribute
// we only do this (hopefully) for cases where any xml is valid
nextTag();
AnyXMLElement out = new AnyXMLElement(1, 1);
out.setNamespace(ModelWalker.getNamespaceFromAncestors(out));
out.setAnnotation(handleAnn());
out.setChild(handleAll());
nextTag();
return out;
}
String name = reader.getAttributeValue(0);
String ns = reader.getAttributeValue(null, "ns");
nextTag();
NamedElement result = new NamedElement(1, 1, name);
if(ns == null) {
ns = ModelWalker.getNamespaceFromAncestors(result);
}
result.setNamespace(ns);
result.setAnnotation(handleAnn());
try {
result.setChild(handleAll());
} catch (RuntimeException e) {
throw new RuntimeException("in "+name, e);
}
nextTag();
return result;
}
private Object handleAttr() throws XMLStreamException {
String name;
if (reader.getAttributeCount() == 0) {
name = "anyAttribute";
} else {
name = reader.getAttributeValue(0);
}
String ns = reader.getAttributeValue(null, "ns");
nextTag();
Attribute result = new Attribute(1, 1, name);
if(ns == null) {
ns = ModelWalker.getNamespaceFromAncestors(result);
} // inherit ns from parents if there is no specified ns
result.setNamespace(ns);
result.setAnnotation(handleAnn());
FormProvider child = handleAll();
if(child == null) {
child = new Text();
} // An empty attribute has text as a value
result.setChild(child);
nextTag();
return result;
}
private FormProvider handleMultigen() throws XMLStreamException {
String tag = reader.getLocalName();
AbstractMultigenitorForm parent;
if(tag.equals("choice")) {
parent = new Choice(1, 1);
} else if(tag.equals("group")) {
parent = new Group(1, 1);
} else if(tag.equals("list")) {
parent = new DataList(1, 1);
} else {
parent = new Interleave(1, 1);
}
nextTag();
Annotation followingNote = handleAnn();
FormProvider child = handleAll();
nextTag();
if(child instanceof AbstractMultigenitorForm) {
// If the child is an AbstractMultigenitorForm, there were multiple
// child FormProviders, so suck all of them out of the child and put
// them in this one
FormProvider[] myKids = ((AbstractMultigenitorForm)child).getFormProviders();
for(int i = 0; i < myKids.length; i++) {
parent.add(myKids[i].copyWithNewParent(parent));
}
parent.setAnnotation(followingNote);
return parent;
}
return child;
}
private boolean isMulitgen(String tag) {
return tag.equals("choice") || tag.equals("group")
|| tag.equals("interleave") || tag.equals("list");
}
/**
* Method handleElement assumes that the reader has been advanced to a
* START_ELEMENT with a local name of ref It returns a Ref object with the
* name contained in the name value of the ref element and belonging to the
* current Grammar. It advances the reader to the next tag past the
* END_ELEMENT tag of the Ref. The parent is not set, so this must be
* handled by the caller.
*/
private Ref handleRef() throws XMLStreamException {
String name = reader.getAttributeValue(0);
nextTag();
Ref ref = new Ref(definedGrammar, name);
ref.setAnnotation(handleAnn());
nextTag();
return ref;
}
private Ref handleExtRef() throws XMLStreamException {
String refGramLoc = getAbsPath();
nextTag();
Ref ref = new Ref(getGrammar(refGramLoc));
ref.setAnnotation(handleAnn());
nextTag();
return ref;
}
private Form handleData() throws XMLStreamException {
String tag = reader.getLocalName();
Form result = null;
if(tag.equals("notAllowed")) {
result = new NotAllowed();
} else if(tag.equals("empty")) {
result = new Empty();
} else if(tag.equals("text")) {
result = new Text();
} else if(tag.equals("value") || tag.equals("data")) {
ModelDatatype type = handleType();
if(tag.equals("data")) {
result = new Data(1, 1, type);
} else {
reader.next();
result = new Value(1, 1, reader.getText(), type);
}
} else if(tag.equals("list")) {
FormProvider listInternals = handleAll();
if(listInternals instanceof AbstractMultigenitorForm) {}
}
nextTag();
result.setAnnotation(handleAnn());
while(reader.getEventType() == START_ELEMENT
&& reader.getLocalName().equals("param")) {
handleParam((Data)result);
}
nextTag();
return result;
}
private void handleParam(Data result) throws XMLStreamException {
// TODO handle params
while(reader.getEventType() != END_ELEMENT) {
reader.next();
}
reader.nextTag();
}
private boolean isData(String tag) {
return tag.equals("empty") || tag.equals("data") || tag.equals("value")
|| tag.equals("text") || tag.equals("notAllowed");
}
private ModelDatatype handleType() {
if(reader.getAttributeCount() > 0) {
String type = reader.getAttributeValue(0);
if(type.equals("float")) {
return new FloatDatatype();
} else if(type.equals("string")) {
return new AnyText();
} else if(type.equals("double")) {
return new DoubleDatatype();
} else if(type.equals("integer")) {
return new IntegerDatatype();
} else if(type.equals("nonNegativeInteger")) {
return new NonnegativeIntegerDatatype();
} else if(type.equals("boolean")) {
return new BooleanDatatype();
}
return new Token();
}
return null;
}
private String getAbsPath() {
String href = reader.getAttributeValue(0);
String curLoc = definedGrammar.getLoc();
try {
return SodUtil.getAbsolutePath(curLoc, href);
} catch(IOException e) {
throw new RuntimeException(e);
}
}
public Form getRoot() {
return definedGrammar.getRoot();
}
Grammar getGrammar(String loc) {
if(!parsedGrammars.containsKey(loc)) {
try {
new StAXModelBuilder(loc);
} catch(Exception e) {
e.printStackTrace();
}
}
return parsedGrammars.get(loc);
}
public static Collection<Definition> getAllDefinitions() {
Set<Definition> defs = new HashSet<Definition>();
Iterator<Grammar> it = parsedGrammars.values().iterator();
while(it.hasNext()) {
Grammar cur = it.next();
defs.addAll(cur.getDefs());
}
return defs;
}
private Map<String, String> waiters = new HashMap<String, String>();
private XMLStreamReader reader;
private Grammar definedGrammar;
private static Map<String, Grammar> parsedGrammars = new HashMap<String, Grammar>();
}