/*
* JETERS – Java Extensible Text Replacement System
* Copyright (C) 2006–2008 Tobias Knerr
*
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
*/
package net.sf.jeters.configuration.io;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.List;
import net.sf.jeters.configuration.Configuration;
import nu.xom.*;
// TODO: all sysouts to user interface (Exceptions?)
/**
* class that reads from and writes to XML configuration files
*
* @author Tobias Knerr
*/
public class XMLConfigurationWriter implements ConfigurationStorage {
/**
* reads the configuration of a component from the corresponding xml file
*
* @param configuredClass class to read configuration for; != null
* @return configuration for component
* or null if the file does not exist
*/
public Configuration read(Class<?> configuredClass){
if (configuredClass == null) {
throw new NullPointerException("componentClass must not be null");
}
String componentName = configuredClass.getSimpleName();
File configFile = getConfigFile(componentName);
if (!configFile.exists()) {
return null;
} else{
Configuration configuration = new Configuration(configuredClass);
try {
//get a xml-document object from the file
Builder builder = new Builder();
Document configurationDocument = builder.build(configFile);
//get configuration set from the xml document
Element xmlRootElement = configurationDocument.getRootElement();
if( xmlRootElement == null ){
System.out.println("error: file \"" + configFile + "\" is not a valid configuration file: <configuration>-tags missing");
}
else{
Elements xmlConfigurationElements = xmlRootElement.getChildElements("conf");
for( int xmlConfigurationElementIndex = 0; xmlConfigurationElementIndex < xmlConfigurationElements.size(); ++ xmlConfigurationElementIndex ){
Element xmlConfigurationElement = xmlConfigurationElements.get(xmlConfigurationElementIndex);
String name = null;
String type = null;
boolean list = false;
Attribute attributeName = xmlConfigurationElement.getAttribute("name");
if( attributeName != null ){
name = attributeName.getValue();
}
else{
System.out.println("warning: configuration data partially invalid: no name attribute at \"conf\" child element " + String.valueOf(xmlConfigurationElementIndex) );
}
Attribute attributeType = xmlConfigurationElement.getAttribute("type");
if( attributeType != null ){
type = attributeType.getValue();
}
else{
System.out.println("warning: configuration data partially invalid: no type attribute at \"conf\" child element " + String.valueOf(xmlConfigurationElementIndex) );
}
Attribute attributeList = xmlConfigurationElement.getAttribute("list");
if( attributeList != null ){
list = stringToBoolean(attributeList.getValue());
}
if (name != null && type != null) {
Field field = null;
for (Field f : configuration.getConfigurableFields()) {
if (f.getName().equals(name)) {
field = f;
break;
}
}
if (field != null) {
if (!list) {
String data = null;
if( xmlConfigurationElement.getChildCount() > 0 ){
Node dataText = xmlConfigurationElement.getChild(0);
if( dataText != null ){
data = ((Text)dataText).getValue();
}
}
if (data == null) {
data = "";
}
if (type.toLowerCase().equals("integer")) {
configuration.setValue(field, Integer.parseInt(data));
} else if (type.toLowerCase().equals("float")) {
configuration.setValue(field, Float.parseFloat(data));
} else if (type.toLowerCase().equals("string")) {
configuration.setValue(field, data);
} else if (type.toLowerCase().equals("boolean")) {
configuration.setValue(field, stringToBoolean(data));
}
} else { // list
List<String> dataList = new LinkedList<String>();
Elements childs = xmlConfigurationElement.getChildElements("entry");
if (childs.size() > 0) {
for (int i = 0; i < childs.size(); i++) {
dataList.add(childs.get(i).getValue());
}
} else {
//note: getChild also returns the element's text
Text text = (Text)xmlConfigurationElement.getChild(0);
if (text != null) {
dataList.add(text.getValue());
}
}
if (type.toLowerCase().equals("integer")) {
List<Integer> ints = new LinkedList<Integer>();
for (String data : dataList) {
ints.add(Integer.parseInt(data));
}
configuration.setValue(field,
ints.toArray(new Integer[ints.size()]));
} else if (type.toLowerCase().equals("float")) {
List<Float> floats = new LinkedList<Float>();
for (String data : dataList) {
floats.add(Float.parseFloat(data));
}
configuration.setValue(field,
floats.toArray(new Float[floats.size()]));
} else if (type.toLowerCase().equals("string")) {
configuration.setValue(field,
dataList.toArray(new String[dataList.size()]));
} else if (type.toLowerCase().equals("boolean")) {
List<Boolean> bools = new LinkedList<Boolean>();
for (String data : dataList) {
bools.add(stringToBoolean(data));
}
configuration.setValue(field,
bools.toArray(new Boolean[bools.size()]));
}
}
}
}
}
}
} catch (ParsingException e) {
System.out.println("warning: a ParsingException occurred" +
" reading from file \"" + configFile + "\":" +
e.toString() + "\"");
} catch (IOException e) {
System.out.println("warning: a IOException occurred reading" +
" from file \"" + configFile + "\":"
+ e.toString() + "\"");
}
return configuration;
}
}
/**
* writes a configuration of a component to the corresponding
* xml config file (if it exists)
*
* @param configuration configuration to be transformed to xml, not null
*/
public void write(Configuration configuration){
if (configuration == null) {
throw new NullPointerException("configuration must not be null");
}
String componentName = configuration.getConfiguredClass().getSimpleName();
createConfigDirIfNecessary();
if (configuration != null && configuration.getConfigurableFields().size() > 0) {
//if there is a file named .JETERS, stop writing
if(!getConfigDir().isDirectory()){
System.err.println("error saving configuration:" +
"can't create directory \"" + getConfigDir() +
"\": file with that name exists");
//TODO: log
} else {
File configFile = getConfigFile(componentName);
/* create a xml tree for the elements */
Element xmlConfigurationElement = new Element("configuration");
for (Field field : configuration.getConfigurableFields()){
if( field != null ){
Object value = configuration.getValue(field);
if (value != null) {
//create an XML element for the entry
Element newElement = createConfElementForField(field, value);
if (newElement != null) {
//append the new element to the xmlConfigurationElement
xmlConfigurationElement.appendChild(newElement);
}
}
}
}
Document configurationDocument = new Document(xmlConfigurationElement);
/* write the xml document to a file */
try{
FileOutputStream ostream = new FileOutputStream(configFile);
Serializer configurationSerializer = new Serializer(ostream,"UTF-8");
configurationSerializer.setIndent(4);
configurationSerializer.write(configurationDocument);
ostream.close();
} catch (IOException e) {
System.err.println("error saving configuration: problem" +
" writing to file \"" + configFile +
"\": " + e.getMessage());
//TODO: log
}
}
}
}
/**
* creates a xml element from a conf field
*
* @param entry configuration field to create an element for, != null
* @param entry value of the field, != null
* @return xml element representing the configuration entry
* or null if the entry cannot be represented
* (usually because the value's data type is unsupported)
*/
private Element createConfElementForField(Field field, Object value) {
assert field != null && value != null;
Element xmlConfElement = new Element("conf");
/* get values for the attributes and the child element */
xmlConfElement.addAttribute(new Attribute("name", field.getName()));
String type;
boolean list = false;
if (int.class.isAssignableFrom(field.getType())) {
type = "integer";
xmlConfElement.appendChild(
String.valueOf((Integer)value));
} else if (float.class.isAssignableFrom(field.getType())) {
type = "float";
xmlConfElement.appendChild(String.valueOf((Float)value));
} else if (String.class.isAssignableFrom(field.getType())) {
type = "string";
xmlConfElement.appendChild(((String)value));
} else if (boolean.class.isAssignableFrom(field.getType())) {
type = "boolean";
xmlConfElement.appendChild(Boolean.toString((Boolean)value));
} else if (int[].class.isAssignableFrom(field.getType())) {
type = "integer";
list = true;
for (Integer val : (Integer[])value) {
Element child = new Element("entry");
child.appendChild(val.toString());
xmlConfElement.appendChild(child);
}
} else if (float[].class.isAssignableFrom(field.getType())) {
type = "float";
list = true;
for (Float val : (Float[])value) {
Element child = new Element("entry");
child.appendChild(val.toString());
xmlConfElement.appendChild(child);
}
} else if (String[].class.isAssignableFrom(field.getType())) {
type = "string";
list = true;
for (String val : (String[])value) {
Element child = new Element("entry");
child.appendChild(val);
xmlConfElement.appendChild(child);
}
} else if (boolean[].class.isAssignableFrom(field.getType())) {
type = "boolean";
list = true;
for (Boolean val : (Boolean[])value) {
Element child = new Element("entry");
child.appendChild(val.toString());
xmlConfElement.appendChild(child);
}
}
else {
return null;
}
/* add attributes and child to the "xmlConfElement" */
xmlConfElement.addAttribute(new Attribute("type", type));
if (list) {
xmlConfElement.addAttribute(new Attribute("list", "true"));
}
return xmlConfElement;
}
/**
* provides the path of the directory for JETERS' configuration files
*
* @return JETERS' config file directory
*/
private static File getConfigDir() {
String path =
System.getProperty("user.home") + File.separator + ".JETERS";
return new File(path);
}
/**
* provides the path of the directory for JETERS' configuration files
*
* @param componentName name of the component to get the config file for
* @return config file
*/
private static File getConfigFile(String componentName) {
String filename = componentName + ".xml";
return new File(getConfigDir(), filename);
}
/**
* creates the directory for JETERS' configuration files if it does not
* already exist (and no file with its name)
*/
private static void createConfigDirIfNecessary() {
File configDir = getConfigDir();
if( ! configDir.exists() ){
configDir.mkdir();
//read and execute access: only for owner
configDir.setReadable(false, false);
configDir.setReadable(true, true);
configDir.setExecutable(false, false);
configDir.setExecutable(true, true);
}
}
private static final boolean stringToBoolean(String s) {
return s.equalsIgnoreCase("true") || s.equals("1") || s.equals("yes");
}
}