package avm;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.io.File;
import java.io.IOException;
import java.util.*;
import javax.swing.JComponent;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import avm.WidgetAdapter;
public class AVMEngine {
private static final String XML_ROOT_ELEMENT_TAG = "avm";
private JComponent parent;
private static Map<String, WidgetAdapter> adapterForWidgetType = new HashMap<String, WidgetAdapter>();
private static Map<Class, String> widgetTypeForComponentType = new HashMap<Class, String>();
public static Map<String, WidgetAdapter> getAdapterForWidgetType() {
return adapterForWidgetType;
}
public static Map<Class, String> getWidgetTypeForComponentType() {
return widgetTypeForComponentType;
}
public AVMEngine(JComponent parent) {
this.parent = parent;
try {
Class.forName("avm.ButtonAdapter");
Class.forName("avm.TextboxAdapter");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// ...
}
/**
* Construct the widgets according to the specification in a file in the AWM XML format.
*
* @param fileName the file path of the AWM XML file
*/
public void load(String fileName) {
// Set up the XML parser and parse the AVM XML file
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder;
Document document = null;
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
document = documentBuilder.parse(fileName);
} catch (ParserConfigurationException e) {
e.printStackTrace();
return;
} catch (SAXException e) {
e.printStackTrace();
return;
} catch (IOException e) {
e.printStackTrace();
return;
}
//TODO check for duplicated property names.
//TODO Set a property to the default value if it is not found in the AVM XML.
//TODO Handle the case that when an unrecognized property is read.
Element element = document.getDocumentElement();
NodeList widgetNodeList = element.getChildNodes();
for (int i = 0; i < widgetNodeList.getLength(); i++) {
Node node = widgetNodeList.item(i);
//System.out.println("node:" + node + "---" + "Node type:" + node.getNodeType()); //debug
if (node.getNodeType() == Node.ELEMENT_NODE) {
//It is a widget node. Process its properties.
String widgetTypeName = node.getNodeName();
WidgetAdapter adapter = adapterForWidgetType.get(widgetTypeName);
String widgetName = null;
JComponent widget = adapter.New();
Set<String> readProperties = new HashSet<String>();
NodeList propertyNodeList = node.getChildNodes();
for (int j = 0; j < propertyNodeList.getLength(); j++) {
Node propertyNode = propertyNodeList.item(j);
if (propertyNode.getNodeType() == Node.ELEMENT_NODE) {
String propertyName = propertyNode.getNodeName();
if (readProperties.contains(propertyName)) {
// TODO Output an error message or throw an exception?
System.err.println("The property '" + propertyName + "' is duplicated.");
} else {
if (propertyName.equals("name")) {
widgetName = propertyNode.getTextContent();
widget.setName(widgetName);
} else {
String str = propertyNode.getTextContent();
String type = adapter.getPropertyType(propertyName);
try {
adapter.setProperty(widget, propertyName,
convertToObject(type, str));
} catch (Exception e) {
System.err.println("invalid conversion: " + str + " as " + type);
}
}
}
readProperties.add(propertyName);
}
}
// Set a property to the default value if it is not found in the AVM XML.
for (String property : adapter.getAllPropertyNames()) {
if (!readProperties.contains(property)) {
adapter.resetPropertyToDefaultValue(widget, property);
}
}
parent.add(widget);
}
}
}
public void save(String fileName) {
//initializeWidgetTypeLookup();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = null;
Document doc = null;
Element root = null;
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
doc = documentBuilder.newDocument();
root = doc.createElement(XML_ROOT_ELEMENT_TAG);
doc.appendChild(root);
} catch (ParserConfigurationException e) {
e.printStackTrace();
return;
}
//Read widgets one by one and read the properties of each widget.
//Construct the widget node and its properties nodes with values and add
//them to the DOM tree.
//TODO Does each widget must have a name? Handle the case if the name is not found.
for (Component component : parent.getComponents()) {
JComponent widget = (JComponent)component;
String widgetTypeName = widgetTypeForComponentType.get(widget.getClass());
//Add the element node for this widget
Element widgetElement = doc.createElement(widgetTypeName);
root.appendChild(widgetElement);
WidgetAdapter adapter = adapterForWidgetType.get(widgetTypeName);
Element nameElement = doc.createElement("name");
widgetElement.appendChild(nameElement);
Text nameText = doc.createTextNode(widget.getName());
nameElement.appendChild(nameText);
//Read the properties of this widget.
//Record the property if it does not have a default property value.
//If it matches the default value of the property then it does not record it in the AVM XML file.
for (String propertyName : adapter.getAllPropertyNames()) {
if (!adapter.hasPropertyDefaultValue(widget, propertyName)) {
Object propertyValue = adapter.getProperty(widget, propertyName);
String type = adapter.getPropertyType(propertyName);
Element propertyElement = doc.createElement(propertyName);
widgetElement.appendChild(propertyElement);
if (propertyValue != null) {
Text valueText = null;
try {
valueText = doc.createTextNode(
convertToString(type, propertyValue));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
propertyElement.appendChild(valueText);
}
}
}
}
// Write the DOM tree to the output XML file
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer;
try {
transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(doc);
File outputFile = new File(fileName);
StreamResult result = new StreamResult(outputFile);
//StreamResult result = new StreamResult(System.out); //debug
transformer.transform(source, result);
} catch (TransformerConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TransformerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Object convertToObject(String type, String str) throws Exception {
Object obj = null;
if (type.equals("String")) {
obj = str;
} else if (type.equals("int")) {
obj = Integer.parseInt(str);
} else if (type.equals("boolean")) {
if (str.equalsIgnoreCase("true")) {
obj = true;
} else if (str.equalsIgnoreCase("false")) {
obj = false;
} else {
throw new Exception();
}
} else if (type.equals("Color")) {
obj = Color.decode(str);
} else if (type.equals("Font")) {
// TODO Define a representation of a font that is independent of
// Java implementation
obj = Font.decode(str);
} else {
throw new Exception();
}
return obj;
}
String convertToString(String type, Object obj) throws Exception {
String str = null;
if (type.equals("String")) {
str = obj.toString();
} else if (type.equals("int")) {
str = obj.toString();
} else if (type.equals("boolean")) {
str = obj.toString();
} else if (type.equals("Color")) {
int rgb = ((Color)obj).getRGB();
str = "#" + Integer.toHexString(rgb).substring(2);
} else if (type.equals("Font")) {
// TODO Define a representation of a font that is independent of
// Java implementation
str = obj.toString();
} else {
throw new Exception();
}
return str;
}
}