package eu.choreos.vv.common;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import eu.choreos.vv.clientgenerator.Item;
import eu.choreos.vv.exceptions.ParserException;
/**
* This class supports the Item (complex types) building
*
* @author Felipe Besson
*
*/
public class ItemBuilder {
private static SAXParserFactory parserFactory = SAXParserFactory.newInstance();
private SAXParser parser;
private StringBuilder outputBuilder = new StringBuilder();
private final String REGEXP = "<!--.* or more repetitions:-->\\s*(<(\\w*:*\\w+)>.*<\\/\\2>)";
private class RequestParserHandler extends DefaultHandler {
private Integer currentLevel = 0;
private Item currentItem;
private Item item;
private boolean foundRoot = false;
private class TreeItem {
public Integer level = 0;
public HashMap<String, List<Item>> itens = new HashMap<String, List<Item>>();
public TreeItem(Integer level){
this.level = level;
}
}
private Stack<TreeItem> itemStack = new Stack<TreeItem>();
public RequestParserHandler(Item item){
super();
this.item = item;
}
/**
* @param ch - The characters.
* @param start - The start position in the character array.
* @param length - The number of characters to use from the character array.
*/
@Override
public void characters(char[] ch, int start, int lenght)
throws SAXException {
String trimmed = new String(ch, start, lenght).trim();
if (!trimmed.isEmpty()){
if(trimmed.equals("?")){
if(currentItem == null || currentItem.getContent() == null){
throw new SAXException("Expected content for \"" + currentItem.getName() + "\", but found none.");
} else {
outputBuilder.append(currentItem.getContent());
}
} else {
outputBuilder.append(trimmed);
}
}
}
/**
* Adds the children of the <code>currentItem</code> to the <code>itemStack</code>
* as a <code>TreeItem</code>
*/
private void addChildrenToStack(){
// inserts all children in the stack as a "bag"
TreeItem bag = new TreeItem(currentLevel + 1);
for(Item child : currentItem.getChildren()){
List<Item> children = bag.itens.get(child.getName());
if(children == null){
children = new LinkedList<Item>();
bag.itens.put(child.getName(), children);
}
children.add(child);
}
if(!bag.itens.isEmpty()){
itemStack.push(bag);
}
}
/**
* @param uri - The Namespace URI.
* @param localName - The local name (without prefix).
* @param qName - The qualified name (with prefix).
*/
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
outputBuilder.append("</" + qName + ">");
currentLevel--;
}
/**
* @param uri - The Namespace URI.
* @param localName - The local name (without prefix).
* @param qName - The qualified name (with prefix).
* @param attributes - The attributes attached to the element.
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
String name = getNameWithoutNamespace(qName);
if(itemStack.empty() && name.equals(item.getName())){
// root found for the first time
foundRoot = true;
currentItem = item;
addChildrenToStack();
} else if(!itemStack.empty() && itemStack.peek().itens.containsKey(name)){
if(currentLevel == itemStack.peek().level){
// Remove from the stack bag and update current item
List<Item> itens = itemStack.peek().itens.get(name);
currentItem = itens.get(0);
itens.remove(0);
// if List is empty, remove key from hash
if(itens.isEmpty()){
itemStack.peek().itens.remove(name);
}
// Remove bag from stack if it's empty
if(itemStack.peek().itens.isEmpty()){
itemStack.pop();
}
addChildrenToStack();
} else {
throw new SAXException("Found item with wrong hierarchy level. Found \""
+ currentItem.getName() + "\" on level [" + itemStack.peek().level
+ "], expected \"" + name + "\" on level [" + currentLevel + "]");
}
} else if(!itemStack.empty() || foundRoot){
throw new SAXException("Expected \"" + name + "\" on level [" + currentLevel
+ "] but found none.");
}
outputBuilder.append("<" + qName);
int numAttributes = attributes.getLength();
for(int i = 0; i < numAttributes; i++){
String attrName = attributes.getQName(i);
String value = attributes.getValue(i);
outputBuilder.append(" " + attrName + "=\"" + value + "\"");
}
outputBuilder.append(">");
currentLevel++;
}
private String getNameWithoutNamespace(String qName) {
String[] names = qName.split(":");
return names[names.length - 1];
}
}
public String buildItem(String baseXml, Item root) throws ParserException {
if(root == null){
// FIXME: What if there should be parameters?
return baseXml;
}
try {
baseXml = addListEntriesToXml(baseXml, root);
InputStream is = new ByteArrayInputStream(baseXml.getBytes("UTF-8"));
parser = parserFactory.newSAXParser();
parser.parse(is, new RequestParserHandler(root));
}
catch (Exception e) {
throw new ParserException(e);
}
return outputBuilder.toString();
}
private String addListEntriesToXml(String baseXml, Item root) {
String result = baseXml;
Pattern LIST_ENTRY = Pattern.compile(REGEXP);
Matcher m = LIST_ENTRY.matcher(baseXml);
while (m.find()) {
String tagName = m.group(2);
String formattedTagName = new RequestParserHandler(null).getNameWithoutNamespace(m.group(2));
int listSize = root.getListSizeFromItem(formattedTagName);
String repeatedEntry = m.group(1).replace(tagName, tagName);
String listOfTagName = multiplyString(repeatedEntry, listSize);
result = result.replaceFirst(REGEXP, listOfTagName);
}
return result;
}
private String multiplyString(String group, int listSize) {
String resultString = "";
for (int i = 0; i < listSize; i++)
resultString += group;
return resultString;
}
}