/**
* ShowCaseStandalone
* Copyright (C) 2012 Kellerkindt <copyright at kellerkindt.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kellerkindt.scs.storage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.lang.Validate;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import com.kellerkindt.scs.internals.Storage;
import com.kellerkindt.scs.utilities.Utilities;
public class SAXStorageParser implements ContentHandler{
private static final String elementAttrType = "type";
private static final String elementAttrTypeString = "string";
private static final String elementAttrTypeDouble = "double";
private static final String elementAttrTypeInteger = "integer";
private static final String elementAttrTypeBoolean = "boolean";
private static final String elementAttrTypeStorage = "storage";
private static final String elementAttrTypeList = "list";
private static final String elementAttrTypeEntry = "entry";
private static final String elementAttrStrgVersion = "version";
private static final String elementAttrStrgUUID = "uuid";
private static final String defaultEncoding = "UTF-8";
private XMLOutputFactory factory;
private Transformer transformer;
private String encoding = defaultEncoding;
private List<HashMap<String, String>> elementAttributes = new ArrayList<HashMap<String, String>>();
private List<StringBuilder> elementContents = new ArrayList<StringBuilder>();
private List<Storage> elementStorages = new ArrayList<Storage>();
private HashMap<Integer, List<?>> elementLists = new HashMap<Integer, List<?>>();
private int currentIsNoStorage = 0; // == 0 if it is a storage
public SAXStorageParser () throws TransformerConfigurationException, TransformerFactoryConfigurationError {
this.factory = XMLOutputFactory.newFactory();
this.transformer = SAXTransformerFactory.newInstance().newTransformer();
}
/**
* Sets a OuputProperty for the Transformer
* @param key
* @param value
*/
public void setTransformerOutputProperty (String key, String value) {
if (OutputKeys.ENCODING.equals(key))
encoding = value;
transformer.setOutputProperty(key, value);
}
/**
* Saves a Storage to the given destination
* @param storage Storage to save
* @param destination Destination where the Storage should be saved to
* @throws IOException
* @throws XMLStreamException
* @throws TransformerException
*/
public void saveStorage (Storage storage, String name, File destination) throws IOException, XMLStreamException, TransformerException {
// prepare streams for writer
ByteArrayOutputStream baos = new ByteArrayOutputStream ();
FileOutputStream fos = new FileOutputStream (destination);
// create writer
XMLStreamWriter writer = factory.createXMLStreamWriter(baos, encoding);
// write document
writer.writeStartDocument();
saveStorage(storage, name, writer);
writer.writeEndDocument();
// flush and close writer
writer.flush();
writer.close();
// prepare for transformation
ByteArrayInputStream bais = new ByteArrayInputStream (baos.toByteArray());
Source source = new StreamSource(bais);
StreamResult result = new StreamResult(fos);
// transform
transformer.transform(source, result);
// not closed yet?
if (fos != null) {
// flush and close file
fos.flush();
fos.close();
}
}
/**
* @param object
* @param name
* @param writer
* @throws XMLStreamException
*/
private void writeListEntry(Object object, XMLStreamWriter writer) throws XMLStreamException {
String name = null;
if (object instanceof String) {
name = elementAttrTypeString;
} else if (object instanceof Double) {
name = elementAttrTypeDouble;
} else if (object instanceof Integer) {
name = elementAttrTypeInteger;
} else if (object instanceof Boolean) {
name = elementAttrTypeBoolean;
}
// } else if (object instanceof Storage) {
// name = elementAttrTypeStorage;
// }
Validate.notNull(name, "Couldn't find type of "+object);
writer.writeStartElement(name);
writer.writeAttribute(elementAttrType, elementAttrTypeEntry);
// if (object instanceof Storage) {
// // save the storage
// saveStorage((Storage)object, elementAttrTypeEntry, writer);
//
// } else {
// // save it "normally"
writer.writeCharacters(""+object);
// }
writer.writeEndElement();
}
/**
* Saves the storage to the given writer
* @param storage Storage to save
* @param name Name of the storage
* @param writer Writer to write to
* @throws XMLStreamException
*/
private void saveStorage(Storage storage, String name, XMLStreamWriter writer) throws XMLStreamException {
// start of this storage
writer.writeStartElement(name);
writer.writeAttribute(elementAttrStrgUUID, storage.getUUIDAsString());
writer.writeAttribute(elementAttrStrgVersion, ""+storage.getVersion());
writer.writeAttribute(elementAttrType, elementAttrTypeStorage);
// Strings
for (String key : storage.getStringKeys()) {
writer.writeStartElement(key);
writer.writeAttribute(elementAttrType, elementAttrTypeString);
writer.writeCharacters(storage.getString(key));
writer.writeEndElement();
}
// Doubles
for (String key : storage.getDoubleKeys()) {
writer.writeStartElement(key);
writer.writeAttribute(elementAttrType, elementAttrTypeDouble);
writer.writeCharacters(""+storage.getDouble(key));
writer.writeEndElement();
}
// Integers
for (String key : storage.getIntegerKeys()) {
writer.writeStartElement(key);
writer.writeAttribute(elementAttrType, elementAttrTypeInteger);
writer.writeCharacters(""+storage.getInteger(key));
writer.writeEndElement();
}
// Booleans
for (String key : storage.getBooleanKeys()) {
writer.writeStartElement(key);
writer.writeAttribute(elementAttrType, elementAttrTypeBoolean);
writer.writeCharacters(""+storage.getBoolean(key));
writer.writeEndElement();
}
// Storages
for (String key : storage.getStorageKeys())
saveStorage(storage.getStorage(key), key, writer);
// lists
for (String key : storage.getListKeys()) {
List<?> list = storage.getList(key);
if (list == null) {
continue;
}
writer.writeStartElement(key);
writer.writeAttribute(elementAttrType, elementAttrTypeList);
System.out.println ("list: "+list);
for (Object object : list) {
System.out.println ("object: " + object);
writeListEntry(object, writer);
}
writer.writeEndElement();
}
// end of this storage
writer.writeEndElement();
writer.flush();
}
/**
* Loads a Storage from the given File
* @param source File to load from
* @return Loaded Storage or null
* @throws SAXException
* @throws IOException
*/
public Storage loadStorage (File source) throws SAXException, IOException {
elementAttributes.clear();
elementContents.clear();
elementStorages.clear();
XMLReader reader = XMLReaderFactory.createXMLReader();
// FileReader fReader = new FileReader (source);
FileInputStream fis = new FileInputStream(source);
InputSource iSource = new InputSource (fis);
// iSource.setEncoding(encoding);
reader.setContentHandler(this);
reader.parse(iSource);
// fReader.close();
fis.close();
if (elementStorages.size() > 0)
return elementStorages.get(0); // return the last
else
return null;
}
// For testing
// public static void main (String args[]) throws Exception {
// SAXStorageParser parser = new SAXStorageParser();
//
// parser.setTransformerOutputProperty(OutputKeys.ENCODING, "UTF-8");
// parser.setTransformerOutputProperty(OutputKeys.INDENT, "yes");
// parser.setTransformerOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
//
//// Storage storage = new Storage(1, "usid");
//// storage.setString("string", "nein");
//// storage.setDouble("double", 2.1);
//// storage.setInteger("int", 1);
//// storage.setBoolean("bool", true);
////
//// Storage pony = new Storage(2, "ponyid");
//// pony.setBoolean("tut", true);
//// storage.setStorage("my-little-pony", pony);
//
//
//
// File file = new File("C:/temp/test.xml");
// Storage storage = parser.loadStorage(file);
//
// System.out.println("storage="+storage);
//
//
// parser.saveStorage(storage, "lolol-storage", new File("C:/temp/test_2.xml"));
// System.out.println("done");
// }
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
int index = this.elementAttributes.size() - 1;
StringBuilder builder = this.elementContents.get(index);
if (currentIsNoStorage > 0)
builder.append(ch, start, length);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
// Load the attributes
HashMap<String, String> attributes = new HashMap<String, String>();
for (int i = 0; i < atts.getLength(); i++)
attributes.put(atts.getLocalName(i), atts.getValue(i));
this.elementAttributes.add(attributes);
this.elementContents.add(new StringBuilder());
this.currentIsNoStorage++;
// only if this is a storage
String uuid = atts.getValue(elementAttrStrgUUID);
String version = atts.getValue(elementAttrStrgVersion);
// String type = atts.getValue(elementAttrType); // don't care, if there is a usid and a version, it has to be a storage
// usid == null? -> create random (was at some point after SAX was introduced)
if (uuid == null)
uuid = Utilities.getRandomSha1();
// first element has to be a storage + there were missing version attributes in old versions :(
if (version == null && elementStorages.size() == 0)
version = "0";
// check if this is a storage
if (uuid == null || version == null)
return;
try {
int ver = Integer.parseInt(version);
UUID id = ver >= 6 ? UUID.fromString(uuid) : UUID.randomUUID();
// Add the storage
Storage storage = new Storage(Integer.parseInt(version), id);
// set for storage
this.currentIsNoStorage--;
// set storage
if (elementStorages.size() > 0)
elementStorages.get(elementStorages.size()-1).setStorage(localName, storage);
// add storage
elementStorages.add(storage);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
try {
// check for missing values
if (elementAttributes.size() <= 0 || elementContents.size() <= 0 || elementStorages.size() <= 0) {
System.out.println( "There is a missing value in a xml entry: " + ( elementAttributes.size() <= 0 ) + " " + ( elementContents.size() <= 0 ) + "" + ( elementStorages.size() <= 0 ) );
return;
}
HashMap<String, String> atts = this.elementAttributes.get(elementAttributes.size()-1);
String content = this.elementContents .get(elementContents.size()-1).toString();
String name = localName;
String type = atts.get(elementAttrType);
Storage storage = elementStorages.get(elementStorages.size()-1);
// remove last ones - because it ends here - has to be BEFORE the if!!!
this.elementAttributes .remove(elementAttributes .size()-1);
this.elementContents .remove(elementContents .size()-1);
// check for missing attributes
if (atts == null || content == null || content.length() == 0 || name == null | type == null || storage == null)
return;
// has to be AFTER the if !!!!
this.currentIsNoStorage--;
// Storage
if (elementAttrTypeStorage.equalsIgnoreCase(type) || type == null) {
if (elementStorages.size() > 1) // > 1 because of the main storage...
elementStorages.remove(storage); // remove the last storage
}
// entry
else if (elementAttrTypeEntry.equals(type)) {
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>)elementLists.get(currentIsNoStorage);
if (list == null) {
list = new ArrayList<Object>();
elementLists.put(currentIsNoStorage, list);
}
if (elementAttrTypeString.equalsIgnoreCase(name)) {
list.add(content);
} else if (elementAttrTypeDouble.equalsIgnoreCase(name)) {
list.add(Double.parseDouble(content));
} else if (elementAttrTypeInteger.equalsIgnoreCase(name)) {
list.add(Integer.parseInt(content));
} else if (elementAttrTypeBoolean.equalsIgnoreCase(name)) {
list.add(Boolean.parseBoolean(content));
}
}
// String
else if (elementAttrTypeString.equalsIgnoreCase(type))
storage.setString(name, content);
// Double
else if (elementAttrTypeDouble.equalsIgnoreCase(type))
storage.setDouble(name, Double.parseDouble(content));
// Integer
else if (elementAttrTypeInteger.equalsIgnoreCase(type))
storage.setInteger(name, Integer.parseInt(content));
// Boolean
else if (elementAttrTypeBoolean.equalsIgnoreCase(type))
storage.setBoolean(name, Boolean.parseBoolean(content));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void endDocument() throws SAXException { }
@Override
public void endPrefixMapping(String arg0) throws SAXException { }
@Override
public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException { }
@Override
public void processingInstruction(String arg0, String arg1) throws SAXException { }
@Override
public void setDocumentLocator(Locator arg0) { }
@Override
public void skippedEntity(String arg0) throws SAXException { }
@Override
public void startDocument() throws SAXException { }
@Override
public void startPrefixMapping(String arg0, String arg1) throws SAXException {}
}