/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.controls;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
/**
* This defines methods for storing data in an xml property element.
*
* @author Douglas Brown
* @version 1.0
*/
public class XMLPropertyElement implements XMLProperty {
// static fields
@SuppressWarnings("javadoc")
public static boolean defaultWriteNullFinalArrayElements = true;
// instance fields
protected XMLProperty parent;
protected String name;
protected String type;
protected String className;
protected List<Object> content = new ArrayList<Object>();
protected boolean writeNullFinalElement;
/**
* Constructs an empty property element.
*
* @param mother the parent
*/
public XMLPropertyElement(XMLProperty mother) {
parent = mother;
}
/**
* Constructs a property element with the specified value.
*
* @param mother the parent
* @param propertyName the name
* @param propertyType the type
* @param value the value
*/
public XMLPropertyElement(XMLProperty mother, String propertyName, String propertyType, Object value) {
this(mother, propertyName, propertyType, value, defaultWriteNullFinalArrayElements);
}
/**
* Constructs a property element with the specified value.
*
* @param mother the parent
* @param propertyName the name
* @param propertyType the type
* @param value the value
* @param writeNullFinalArrayElement true to write a final null array element (if needed)
*/
public XMLPropertyElement(XMLProperty mother, String propertyName, String propertyType, Object value,
boolean writeNullFinalArrayElement) {
this(mother);
name = propertyName;
type = propertyType;
writeNullFinalElement = writeNullFinalArrayElement;
if(type.equals("string")) { //$NON-NLS-1$
if(XML.requiresCDATA((String) value)) {
content.add(XML.CDATA_PRE+value+XML.CDATA_POST);
} else {
content.add(value.toString());
}
} else if("intdoubleboolean".indexOf(type)!=-1) { //$NON-NLS-1$
content.add(value.toString());
} else if(type.equals("object")) { //$NON-NLS-1$
if (value==null) {
content.add("null"); //$NON-NLS-1$
}
else {
className = value.getClass().getName();
XMLControl control = new XMLControlElement(this);
control.saveObject(value);
content.add(control);
}
} else if(type.equals("collection")) { //$NON-NLS-1$
className = value.getClass().getName();
Iterator<?> it = ((Collection<?>) value).iterator();
while(it.hasNext()) {
Object next = it.next();
String type = XML.getDataType(next);
if(type==null) {
continue;
}
content.add(new XMLPropertyElement(this, "item", type, next, writeNullFinalElement)); //$NON-NLS-1$
}
} else if(type.equals("array")) { //$NON-NLS-1$
className = value.getClass().getName();
// determine if base component type is primitive and count array elements
Class<?> baseType = value.getClass().getComponentType();
Object array = value;
int count = Array.getLength(array);
while((count>0)&&(baseType.getComponentType()!=null)) {
baseType = baseType.getComponentType();
array = Array.get(array, 0);
if(array==null) {
break;
}
count = count*Array.getLength(array);
}
boolean primitive = "intdoubleboolean".indexOf(baseType.getName())!=-1; //$NON-NLS-1$
if(primitive&&(count>XMLControlElement.compactArraySize)) {
// write array as string if base type is primitive
String s = getArrayString(value);
content.add(new XMLPropertyElement(this, "array", "string", s, writeNullFinalElement)); //$NON-NLS-1$ //$NON-NLS-2$
} else {
int length = Array.getLength(value);
int last = writeNullFinalElement? length-1: length;
for(int j = 0; j<length; j++) {
Object next = Array.get(value, j);
String type = XML.getDataType(next);
if(type==null) {
if (j<last) continue;
type = "object"; //$NON-NLS-1$
}
content.add(new XMLPropertyElement(this, "["+j+"]", type, next, writeNullFinalElement)); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
}
/**
* Gets the property name.
*
* @return a name
*/
public String getPropertyName() {
return name;
}
/**
* Gets the property type.
*
* @return the type
*/
public String getPropertyType() {
return type;
}
/**
* Gets the property class.
*
* @return the class
*/
public Class<?> getPropertyClass() {
if(type.equals("int")) { //$NON-NLS-1$
return Integer.TYPE;
} else if(type.equals("double")) { //$NON-NLS-1$
return Double.TYPE;
} else if(type.equals("boolean")) { //$NON-NLS-1$
return Boolean.TYPE;
} else if(type.equals("string")) { //$NON-NLS-1$
return String.class;
}
try {
return Class.forName(className);
} catch(Exception ex) {
return null;
}
}
/**
* Gets the immediate parent property.
*
* @return the type
*/
public XMLProperty getParentProperty() {
return parent;
}
/**
* Gets the level of this property relative to root.
*
* @return the non-negative integer level
*/
public int getLevel() {
return parent.getLevel()+1;
}
/**
* Gets the xml content for this property. Content items may be strings,
* XMLControls or XMLProperties.
*
* @return a list of content items
*/
public List<Object> getPropertyContent() {
return content;
}
/**
* Gets the named XMLControl child of this property. May return null.
*
* @param name the property name
* @return the XMLControl
*/
public XMLControl getChildControl(String name) {
XMLControl[] children = getChildControls();
for(int i = 0; i<children.length; i++) {
if(children[i].getPropertyName().equals(name)) {
return children[i];
}
}
return null;
}
/**
* Gets the XMLControl children of this property. The returned array has
* length for type "object" = 1, "collection" and "array" = 0+, other
* types = 0.
*
* @return an XMLControl array
*/
public XMLControl[] getChildControls() {
if(type.equals("object") && !getPropertyContent().isEmpty()) { //$NON-NLS-1$
XMLControl child = (XMLControl) getPropertyContent().get(0);
return new XMLControl[] {child};
} else if("arraycollection".indexOf(type)!=-1) { //$NON-NLS-1$
ArrayList<XMLControl> list = new ArrayList<XMLControl>();
Iterator<Object> it = getPropertyContent().iterator();
while(it.hasNext()) {
XMLProperty prop = (XMLProperty) it.next();
if(prop.getPropertyType().equals("object") && !prop.getPropertyContent().isEmpty()) { //$NON-NLS-1$
list.add((XMLControl) prop.getPropertyContent().get(0));
}
}
return list.toArray(new XMLControl[0]);
}
return new XMLControl[0];
}
/**
* Sets the value of this property if property type is primitive or string.
* This does nothing for other property types.
*
* @param stringValue the string value of a primitive or string property
*/
public void setValue(String stringValue) {
boolean valid = true;
try {
if(type.equals("int")) { //$NON-NLS-1$
Integer.parseInt(stringValue);
} else if(type.equals("double")) { //$NON-NLS-1$
Double.parseDouble(stringValue);
} else if(type.equals("boolean")) { //$NON-NLS-1$
stringValue = stringValue.equals("true") ? "true" : "false"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
} else if("objectarraycollection".indexOf(type)!=-1) { //$NON-NLS-1$
valid = false;
} else if(type.equals("string")&&XML.requiresCDATA(stringValue)) { //$NON-NLS-1$
stringValue = XML.CDATA_PRE+stringValue+XML.CDATA_POST;
}
} catch(NumberFormatException ex) {
valid = false;
}
if(valid) {
content.clear();
content.add(stringValue);
}
}
/**
* Returns the xml string representation of this property.
*
* @return the xml string
*/
public String toString() {
// write the opening tag with attributes
StringBuffer xml = new StringBuffer(XML.NEW_LINE+indent(getLevel())+"<property name=\""+name+"\" type=\""+type+"\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if("arraycollection".indexOf(type)!=-1) { //$NON-NLS-1$
xml.append(" class=\""+className+"\""); //$NON-NLS-1$ //$NON-NLS-2$
}
// write the content
List<Object> content = getPropertyContent();
// special case: null object
if (content.isEmpty() && "object".equals(type)) { //$NON-NLS-1$
content.add("null"); //$NON-NLS-1$
}
// if no content, write closing tag and return
if(content.isEmpty()) {
xml.append("/>"); //$NON-NLS-1$
return xml.toString();
}
// else write content
xml.append(">"); //$NON-NLS-1$
boolean hasChildren = false;
Iterator<Object> it = content.iterator();
while(it.hasNext()) {
Object next = it.next();
hasChildren = hasChildren||(next instanceof XMLProperty);
xml.append(next);
}
// write the closing tag
if(hasChildren) {
xml.append(XML.NEW_LINE+indent(getLevel()));
}
xml.append("</property>"); //$NON-NLS-1$
return xml.toString();
}
/**
* Returns a space for indentation.
*
* @param level the indent level
* @return the space
*/
protected String indent(int level) {
String space = ""; //$NON-NLS-1$
for(int i = 0; i<XML.INDENT*level; i++) {
space += " "; //$NON-NLS-1$
}
return space;
}
/**
* Returns a string representation of a primitive array.
*
* @param array the array
* @return the array string
*/
protected String getArrayString(Object array) {
StringBuffer sb = new StringBuffer("{"); //$NON-NLS-1$
int length = Array.getLength(array);
for(int j = 0; j<length; j++) {
// add separator except for first element
if(j>0) {
sb.append(','); // s += ",";
}
Object element = Array.get(array, j);
if((element!=null)&&element.getClass().isArray()) {
sb.append(getArrayString(element));
} else {
sb.append(element);
}
}
sb.append('}');
return sb.toString();
}
}
/*
* Open Source Physics software is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be released
* under the GNU GPL license.
*
* This software 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; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/