/**
* Copyright (C) 2008-2010 Daniel Senff
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package de.danielsenff.imageflow.models.delegates;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.zip.DataFormatException;
import javax.imageio.ImageIO;
import org.jdom.Element;
import org.jdom.JDOMException;
import de.danielsenff.imageflow.controller.DelegatesController;
import de.danielsenff.imageflow.models.datatype.DataType;
import de.danielsenff.imageflow.models.datatype.DataTypeFactory;
import de.danielsenff.imageflow.models.datatype.ImageDataType;
import de.danielsenff.imageflow.models.parameter.BooleanParameter;
import de.danielsenff.imageflow.models.parameter.ChoiceParameter;
import de.danielsenff.imageflow.models.unit.NodeIcon;
import de.danielsenff.imageflow.models.unit.UnitElement;
import de.danielsenff.imageflow.models.unit.UnitFactory;
import de.danielsenff.imageflow.models.unit.UnitModelComponent.Size;
import de.danielsenff.imageflow.utils.Tools;
/**
* UnitDescription stores the single pieces a UnitElement is made of from an
* XML-file. The {@link UnitFactory} can construct an {@link UnitElement}-Instance based
* on this description.
* @author Daniel Senff
*
*/
public class UnitDescription implements NodeDescription {
public URL unitURL;
public String unitName;
public String helpString;
public String pathToIcon;
public String colorString;
public String componentSizeString;
public Size componentSize;
public Color color;
public String imageJSyntax;
public int argbDefault = (0xFF<<24)|(128<<16)|(128<<8)|128;
public int numParas;
public Para[] para;
public int numInputs;
public Input[] input;
public int numOutputs;
public Output[] output;
public boolean isDisplayUnit = false;
public boolean isDisplaySilentUnit = false;
public BufferedImage icon = null;
public URL iconURL;
public UnitDescription(URL url) {
this.unitURL = url;
}
/**
* Read Unit Properties from XML-Element
* @param root
* @throws IOException
* @throws JDOMException
* @throws DataFormatException
*/
public void readXML() throws JDOMException, IOException, DataFormatException {
final Element root = Tools.getXMLRoot(this.unitURL);
readXML(root);
}
/**
* Read Unit Properties from XML URL
* @param root
* @throws DataFormatException
*/
public void readXML(final Element root) throws DataFormatException {
// TODO save XML in model
// read general info about this unit
Element elementGeneral = root.getChild("General");
unitName = elementGeneral.getChild("UnitName").getValue();
pathToIcon = elementGeneral.getChild("PathToIcon").getValue();
helpString = elementGeneral.getChild("HelpString").getValue();
colorString = elementGeneral.getChild("Color").getValue();
if(elementGeneral.getChild("DoDisplay") != null)
isDisplayUnit = elementGeneral.getChild("DoDisplay").getValue().equalsIgnoreCase("true") ? true : false;
if(elementGeneral.getChild("DoDisplaySilent") != null)
isDisplaySilentUnit = elementGeneral.getChild("DoDisplaySilent").getValue().equalsIgnoreCase("true") ? true : false;
try {
color = Color.decode(colorString);
if (color == null)
color = new Color(argbDefault);
} catch (NumberFormatException e) {
System.out.println("Wrong color string ");
}
if(elementGeneral.getChild("IconSize") != null) {
componentSizeString = elementGeneral.getChild("IconSize").getValue();
componentSize = NodeIcon.getSizeFromString(componentSizeString);
}
imageJSyntax = elementGeneral.getChild("ImageJSyntax").getValue() + "\n";
try {
// get icon
iconURL = getIconURL(this.unitURL, pathToIcon);
icon = ImageIO.read(iconURL.openStream());
} catch (Exception e) {
// no exception handling is needed here, since it is often
// the case that icons are missing. Most of the units are
// not even intended to have icons.
}
// parameters
Element parametersElement = root.getChild("Parameters");
if (parametersElement != null) {
parseParameter(parametersElement);
}
// Inputs
Element inputsElement = root.getChild("Inputs");
if (inputsElement != null) {
parseInputs(inputsElement);
}
// Outputs
Element outputsElement = root.getChild("Outputs");
if (outputsElement != null) {
parseOutputs(outputsElement);
}
}
/**
* @param parametersElement
* @throws DataFormatException
*/
private void parseParameter(Element parametersElement) throws DataFormatException {
List<Element> parametersList = parametersElement.getChildren();
Iterator<Element> parametersIterator = parametersList.iterator();
numParas = parametersList.size();
para = new Para[numParas + 1];
// loop for all Parameter
int num = 1;
while (parametersIterator.hasNext()) {
Element actualParameterElement = (Element) parametersIterator.next();
processParameters(num, actualParameterElement);
num++;
}
}
/**
* process a single input
* @param inputsElement
*/
private void parseInputs(Element inputsElement) {
List<Element> inputsList = inputsElement.getChildren();
Iterator<Element> inputsIterator = inputsList.iterator();
numInputs = inputsList.size();
input = new Input[numInputs+1];
// loop over all Inputs
int num = 1;
while (inputsIterator.hasNext()) {
Element inputElement = (Element) inputsIterator.next();
Input actInput = input[num] = new Input();
actInput.name = inputElement.getChild("Name").getValue();
if(inputElement.getChild("Required") != null)
actInput.required = inputElement.getChild("Required").getValue().equalsIgnoreCase("true") ? true : false;
actInput.shortName = inputElement.getChild("ShortName").getValue();
if(inputElement.getChild("Required") != null)
actInput.required = inputElement.getChild("Required").getValue().equalsIgnoreCase("true") ? true : false;
// legacy: in case no type is given, assume DataTypeFactory.Image
if(inputElement.getChild("DataType") != null) {
actInput.dataType = DataTypeFactory.createDataType(inputElement.getChild("DataType").getValue());
} else
actInput.dataType = DataTypeFactory.createDataType("Image");
actInput.needToCopyInput = inputElement.getChild("NeedToCopyInput").getValue().equalsIgnoreCase("true") ? true : false;
if(actInput.dataType instanceof ImageDataType) {
int imageType = Integer.valueOf(inputElement.getChild("ImageType").getValue());
actInput.imageType = imageType;
((ImageDataType)actInput.dataType).setImageBitDepth(imageType);
}
num++;
}
}
/**
* @param outputsElement
*/
private void parseOutputs(Element outputsElement) {
List<Element> outputsList = outputsElement.getChildren();
Iterator<Element> outputIterator = outputsList.iterator();
numOutputs = outputsList.size();
output = new Output[outputsList.size()+1];
// loop over all Inputs
int num = 1;
while (outputIterator.hasNext()) {
Element outputElement = (Element) outputIterator.next();
Output actOutput = output[num] = new Output();
actOutput.name = outputElement.getChild("Name").getValue();
actOutput.shortName = outputElement.getChild("ShortName").getValue();
// legacy: in case no type is given, assume DataTypeFactory.Image
if(outputElement.getChild("DataType") != null) {
actOutput.dataType = DataTypeFactory.createDataType(outputElement.getChild("DataType").getValue());
} else {
actOutput.dataType = DataTypeFactory.createDataType("Image");
}
if(actOutput.dataType instanceof ImageDataType) {
int imageType = Integer.valueOf(outputElement.getChild("ImageType").getValue());
actOutput.imageType = imageType;
((ImageDataType)actOutput.dataType).setImageBitDepth(imageType);
}
actOutput.doDisplay = outputElement.getChild("DoDisplay").getValue().equalsIgnoreCase("true")? true : false;
isDisplayUnit = actOutput.doDisplay;
if (outputElement.getChild("DoDisplaySilent") != null) {
actOutput.doDisplaySilent = outputElement.getChild("DoDisplaySilent").getValue().equalsIgnoreCase("true")? true : false;
isDisplaySilentUnit = actOutput.doDisplaySilent;
}
num++;
}
}
/**
* Read parameter definition
* @param num
* @param actualParameterElement
* @throws DataFormatException
*/
private void processParameters(int num, Element actualParameterElement)
throws DataFormatException {
Para actPara = para[num] = new Para();
actPara.options = new HashMap<String, Object>();
actPara.name = actualParameterElement.getChild("Name").getValue();
if(actualParameterElement.getChild("HelpString") != null)
actPara.helpString = actualParameterElement.getChild("HelpString").getValue();
else
actPara.helpString = actualParameterElement.getChild("Name").getValue();
Element dataTypeElement = actualParameterElement.getChild("DataType");
String dataTypeString = actPara.dataTypeString = dataTypeElement.getValue();
Element valueElement = actualParameterElement.getChild("Value");
String valueString = valueElement.getValue();
Element readOnlyElement = actualParameterElement.getChild("ReadOnly");
if (readOnlyElement != null)
actPara.readOnly = readOnlyElement.getValue().equalsIgnoreCase("true")? true : false;;
Element hiddenElement = actualParameterElement.getChild("Hidden");
if (hiddenElement != null)
actPara.hidden = hiddenElement.getValue().equalsIgnoreCase("true")? true : false;;
if (dataTypeString.toLowerCase().equals("double"))
processDoubleDataType(actPara, dataTypeElement, valueElement, valueString);
else if (dataTypeString.equalsIgnoreCase("file")) {
actPara.value = valueString;
if (hasAttribute(dataTypeElement, "as", "openfilechooser")
|| hasAttribute(dataTypeElement, "as", "savefilechooser") ) {
actPara.options.put("as", dataTypeElement.getAttribute("as").getValue());
} else {
actPara.options.put("as", "textfield");
}
} else if (dataTypeString.equalsIgnoreCase("string"))
actPara.value = valueString;
else if (dataTypeString.equalsIgnoreCase("text")) {
actPara.value = valueString;
} else if (dataTypeString.equalsIgnoreCase("integer")) {
processIntegerDataType(actPara, dataTypeElement, valueElement, valueString);
} else if (dataTypeString.equalsIgnoreCase("stringarray")) {
int choiceNumber = Integer.valueOf(actualParameterElement.getChild("ChoiceNumber").getValue());
String[] strings = valueString.split(ChoiceParameter.DELIMITER);
ArrayList<String> choicesList;
choicesList = new ArrayList<String>(strings.length);
for (int i = 0; i < strings.length; i++) {
choicesList.add(strings[i]);
}
actPara.value = choicesList;
//actPara.choiceIndex = Integer.valueOf(choiceNumber);
actPara.options.put("choiceIndex", choiceNumber);
if (hasAttribute(dataTypeElement, "as", "radio") ){
actPara.options.put("as", "radio");
}
}
else if (dataTypeString.equalsIgnoreCase("boolean")) {
actPara.value = Boolean.valueOf(valueString);
actPara.trueString = actualParameterElement.getChild("TrueString").getValue();
} else
throw new DataFormatException("invalid datatype");
}
private void processDoubleDataType(final Para actPara,
final Element dataTypeElement,
final Element valueElement,
final String valueString) {
actPara.value = Double.valueOf(valueString);
if (hasAttribute(dataTypeElement, "as", "slider")
&& (valueElement.getAttribute("min") != null
&& valueElement.getAttribute("max") != null) ) {
actPara.options.put("as", "slider");
actPara.options.put("min", Double.valueOf(valueElement.getAttribute("min").getValue()));
actPara.options.put("max", Double.valueOf(valueElement.getAttribute("max").getValue()));
} else {
actPara.options.put("as", "textfield");
}
}
private void processIntegerDataType(final Para actPara,
final Element dataTypeElement,
final Element valueElement,
final String valueString) {
actPara.value = Integer.valueOf(valueString);
if (hasAttribute(dataTypeElement, "as", "slider")
&& (valueElement.getAttribute("min") != null
&& valueElement.getAttribute("max") != null) ) {
actPara.options.put("as", "slider");
actPara.options.put("min", Integer.valueOf(valueElement.getAttribute("min").getValue()));
actPara.options.put("max", Integer.valueOf(valueElement.getAttribute("max").getValue()));
} else {
actPara.options.put("as", "textfield");
}
}
private boolean hasAttribute(Element dataTypeElement, String attributeName, String value) {
return dataTypeElement.getAttribute(attributeName) != null
&& dataTypeElement.getAttribute(attributeName).getValue().equalsIgnoreCase(value);
}
/**
* Creates a new URL based on a given context URL.
* If a path is given, the new URL is dependent of the protocol
* of the context URL. If not, the context URL with an extension
* replacement from "xml" to "png" is returned.
*/
private URL getIconURL(URL context, String relativeIconPath) throws MalformedURLException {
String path;
if(relativeIconPath != null && relativeIconPath.length() > 0) {
String iconFolder = DelegatesController.getUnitIconFolderName();
if (context.getProtocol().equals("jar"))
path = "/" + iconFolder + "/" + relativeIconPath;
else
path = System.getProperty("user.dir") + File.separator
+ iconFolder + File.separator + relativeIconPath;
} else {
// search for unitname.png in same directory as xml
path = context.getPath().replace(".xml", ".png");
}
return new URL(context, path);
}
public boolean hasHelpString() {
return (helpString != null);
}
public String getHelpString() {
return helpString;
}
public String getUnitName() {
return unitName;
}
public String getXMLName() {
return this.unitURL.getFile();
}
public boolean getIsDisplayUnit() {
return isDisplayUnit;
}
public boolean hasInputs() {
return this.input != null && this.input.length > 0;
}
public boolean hasOutputs() {
return this.output != null && this.output.length > 0;
}
public boolean hasParameters() {
return this.para != null && this.para.length > 0;
}
/**
* Returns the previously read in icon
* or null if no icon was found
* @return
*/
public BufferedImage getUnitIcon() {
return icon;
}
public class Para {
public String name;
public String dataTypeString;
/*
* for storing special options,
* in the long run the help class could be replaced by this, we'll see - ds
*/
public HashMap<String, Object> options;
/**
* can be
* ArrayList
* Integer
* Double
* String
* Boolean
*/
public Object value;
/**
* String used when value true for {@link BooleanParameter}
*/
public String trueString;
/**
* String used as description for the Parameter.
*/
public String helpString;
public boolean readOnly;
public boolean hidden;
}
public class Input {
public String name;
public String shortName;
public DataType dataType;
public boolean required = true;
public int imageType;
public boolean needToCopyInput;
}
public class Output {
public String name;
public String shortName;
public DataType dataType;
public int imageType;
public boolean doDisplay;
public boolean doDisplaySilent;
}
public Color getColor() {
return this.color;
}
}