/*
* This file is part of INDI for Java Client.
*
* INDI for Java Client 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 3 of
* the License, or (at your option) any later version.
*
* INDI for Java Client 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 INDI for Java Client. If not, see
* <http://www.gnu.org/licenses/>.
*/
package laazotea.indi.client;
import java.util.Formatter;
import java.util.Locale;
import laazotea.indi.ClassInstantiator;
import laazotea.indi.INDIException;
import laazotea.indi.INDISexagesimalFormatter;
import org.w3c.dom.Element;
/**
* A class representing a INDI Number Element.
*
* @author S. Alonso (Zerjillo) [zerjioi at ugr.es]
* @version 1.38, July 22, 2014
*/
public class INDINumberElement extends INDIElement {
/**
* The current value of this Number Element.
*/
private double value;
/**
* The current desired value for the Element
*/
private Double desiredValue;
/**
* The number format of this Number Element.
*/
private String numberFormat;
/**
* The minimum value for this Number Element.
*/
private double min;
/**
* The maximum value for this Number Element.
*/
private double max;
/**
* The step for this Number Element.
*/
private double step;
/**
* A formatter used to parse and format the values.
*/
private INDISexagesimalFormatter sFormatter;
/**
* A UI component that can be used in graphical interfaces for this Number
* Element.
*/
private INDIElementListener UIComponent;
/**
* Constructs an instance of
* <code>INDINumberElement</code>. Usually called from a
* <code>INDIProperty</code>.
*
* @param xml A XML Element
* <code><defNumber></code> describing the Number Element.
* @param property The
* <code>INDIProperty</code> to which the Element belongs.
* @throws IllegalArgumentException if the XML Element is not well formed (any
* of the max, min, step or value are not correct numbers, if the format if
* not correct or if the value is not within [min, max]).
*/
protected INDINumberElement(Element xml, INDIProperty property) throws IllegalArgumentException {
super(xml, property);
desiredValue = null;
String valueS = xml.getTextContent().trim();
String minS = xml.getAttribute("min").trim();
String maxS = xml.getAttribute("max").trim();
String stepS = xml.getAttribute("step").trim();
String nf = xml.getAttribute("format").trim();
setNumberFormat(nf);
setMin(minS);
setMax(maxS);
setStep(stepS);
setValue(valueS);
}
/**
* Set the number format for this Number Element.
*
* @param newNumberFormat The new number format.
* @throws IllegalArgumentException if the number format is not correct.
*/
private void setNumberFormat(String newNumberFormat) throws IllegalArgumentException {
newNumberFormat = newNumberFormat.trim();
if (!newNumberFormat.startsWith("%")) {
throw new IllegalArgumentException("Number format not starting with %\n");
}
if ((!newNumberFormat.endsWith("f")) && (!newNumberFormat.endsWith("e")) && (!newNumberFormat.endsWith("E")) && (!newNumberFormat.endsWith("g")) && (!newNumberFormat.endsWith("G")) && (!newNumberFormat.endsWith("m"))) {
throw new IllegalArgumentException("Number format not recognized%\n");
}
if (newNumberFormat.endsWith("m")) {
sFormatter = new INDISexagesimalFormatter(newNumberFormat);
}
if (newNumberFormat.equals("%0.f")) {
newNumberFormat = "%.0f";
}
if (newNumberFormat.equals("%.f")) {
newNumberFormat = "%.0f";
}
this.numberFormat = newNumberFormat;
}
/**
* Gets the maximum for this Number Element.
*
* @return The maximum for this Number Element.
*/
public double getMax() {
return max;
}
/**
* Gets the maximum for this Number Element formated as a String according to
* the number format.
*
* @return The maximum for this Number Element formatted as a String.
*/
public String getMaxAsString() {
return getNumberAsString(getMax());
}
/**
* Gets the minimum for this Number Element.
*
* @return The minimum for this Number Element.
*/
public double getMin() {
return min;
}
/**
* Gets the minimum for this Number Element formated as a String according to
* the number format.
*
* @return The minimum for this Number Element formatted as a String.
*/
public String getMinAsString() {
return getNumberAsString(getMin());
}
/**
* Gets the number format of this Number Element.
*
* @return the number format of this Number Element.
*/
public String getNumberFormat() {
return numberFormat;
}
/**
* Gets the step for this Number Element.
*
* @return The step for this Number Element.
*/
public double getStep() {
return step;
}
/**
* Gets the step for this Number Element formated as a String according to the
* number format.
*
* @return The step for this Number Element formatted as a String.
*/
public String getStepAsString() {
return getNumberAsString(getStep());
}
/**
* Gets the value of this Number Element formated as a String according to the
* number format.
*
* @return The value of this Number Element formatted as a String.
*/
@Override
public String getValueAsString() {
return getNumberAsString((Double) getValue());
}
/**
* Returns a number formatted according to the Number Format of this Number
* Element.
*
* @param number the number to be formatted.
* @return the number formatted according to the Number Format of this Number
* Element.
*/
private String getNumberAsString(double number) {
String aux;
if (numberFormat.endsWith("m")) {
aux = sFormatter.format(number);
} else {
// System.out.println("xx" + getNumberFormat());
Formatter formatter = new Formatter(Locale.US);
aux = formatter.format(getNumberFormat(), number).toString();
}
return aux;
}
@Override
public Double getValue() {
return value;
}
/**
* Sets the current value of this Number Element. It is assummed that the XML
* Element is really describing the new value for this particular Number
* Element. <p> This method will notify the change of the value to the
* listeners.
*
* @param xml A XML Element <oneNumber> describing the Element.
* @throws IllegalArgumentException if the
* <code>xml</code> is not well formed (the value is not a correct number or
* it is not in the [min, max] range).
*/
@Override
protected void setValue(Element xml) throws IllegalArgumentException {
String valueS = xml.getTextContent().trim();
setValue(valueS);
notifyListeners();
}
/**
* Sets the maximum of this Number Element
*
* @param maxS A String with the maximum value of this Number Element
*/
private void setMax(String maxS) {
max = parseNumber(maxS);
}
/**
* Sets the minimum of this Number Element
*
* @param maxS A String with the minimum value of this Number Element
*/
private void setMin(String minS) {
min = parseNumber(minS);
}
/**
* Sets the step of this Number Element
*
* @param maxS A String with the step value of this Number Element
*/
private void setStep(String stepS) {
step = parseNumber(stepS);
}
/**
* Sets the value of this Number Element
*
* @param valueS A String with the new value of this Number Element
* @throws IllegalArgumentException the value is not a correct number or it is
* not in the [min, max] range.
*/
private void setValue(String valueS) throws IllegalArgumentException {
value = parseNumber(valueS);
if ((value < min) || (value > max)) {
//throw new IllegalArgumentException(this.getProperty().getName() + " ; " + getName() + " ; " + "Number (" + valueS + ") not in range [" + min + ", " + max + "]");
}
}
/**
* Parses a number according to the Number Format of this Number Element.
*
* @param number The number to be parsed.
* @return the parsed number @throw IllegalArgumentException if the
* <code>number</code> is not correctly formatted.
*/
private double parseNumber(String number) throws IllegalArgumentException {
double res;
if (numberFormat.endsWith("m")) {
res = sFormatter.parseSexagesimal(number);
} else {
try {
res = Double.parseDouble(number);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Number value not correct");
}
}
return res;
}
@Override
public INDIElementListener getDefaultUIComponent() throws INDIException {
if (UIComponent != null) {
removeINDIElementListener(UIComponent);
}
Object[] arguments = new Object[]{this, getProperty().getPermission()};
String[] possibleUIClassNames = new String[]{"laazotea.indi.client.ui.INDINumberElementPanel", "laazotea.indi.androidui.INDINumberElementView"};
try {
UIComponent = (INDIElementListener) ClassInstantiator.instantiate(possibleUIClassNames, arguments);
} catch (ClassCastException e) {
throw new INDIException("The UI component is not a valid INDIElementListener. Probably a incorrect library in the classpath.");
}
addINDIElementListener(UIComponent);
return UIComponent;
}
/**
* Checks if a desired desiredValue would be correct to be applied to the
* Number Element.
*
* @param desiredValue The desiredValue to be checked (usually a String, but
* can be a Double).
* @return
* <code>true</code> if the
* <code>desiredValue</code> is a valid Double or a correct String according
* to the Number Format.
* <code>false</code> otherwise.
* @throws INDIValueException if
* <code>desiredValue</code> is
* <code>null</code> or if it is not a Double or a correctly formatted
* <code>String</code>.
*/
@Override
public boolean checkCorrectValue(Object desiredValue) throws INDIValueException {
if (desiredValue == null) {
throw new INDIValueException(this, "null value");
}
double d;
if (desiredValue instanceof Double) {
d = ((Double) desiredValue).doubleValue();
} else {
if (desiredValue instanceof String) {
String val;
val = ((String) desiredValue).trim();
try {
d = parseNumber(val);
} catch (IllegalArgumentException e) {
throw new INDIValueException(this, e.getMessage());
}
} else {
throw new INDIValueException(this, "The number value is not correct (not Double nor String)");
}
}
if (d < min) {
throw new INDIValueException(this, "Number less than minimum (" + getMinAsString() + ")");
}
if (d > max) {
throw new INDIValueException(this, "Number greater than maximum (" + getMaxAsString() + ")");
}
return true;
}
@Override
public String getNameAndValueAsString() {
return getName() + " - " + this.getValueAsString();
}
@Override
public Double getDesiredValue() {
return desiredValue;
}
@Override
public void setDesiredValue(Object desiredValue) throws INDIValueException {
if (desiredValue instanceof String) {
setDesiredValueAsString((String) desiredValue);
} else if (desiredValue instanceof Double) {
setDesiredValueAsdouble(((Double) desiredValue).doubleValue());
} else {
throw new INDIValueException(this, "Value for a Number Element must be a String or a Double");
}
}
/**
* Sets the desired value from a String.
*
* @param desiredValue The new desired Value
* @throws IllegalArgumentException if the desired value not in range
*/
private void setDesiredValueAsString(String desiredValue) throws INDIValueException {
double dd = parseNumber(desiredValue);
if ((dd < min) || (dd > max)) {
throw new INDIValueException(this, getName() + " ; " + "Number (" + desiredValue + ") not in range [" + min + ", " + max + "]");
}
this.desiredValue = new Double(dd);
}
/**
* Sets the desired value from a double.
*
* @param desiredValue The new desired Value
* @throws IllegalArgumentException if the desired value not in range
*/
private void setDesiredValueAsdouble(double desiredValue) throws INDIValueException {
double dd = desiredValue;
if ((dd < min) || (dd > max)) {
throw new INDIValueException(this, getName() + " ; " + "Number (" + value + ") not in range [" + min + ", " + max + "]");
}
this.desiredValue = new Double(dd);
}
@Override
public boolean isChanged() {
if (desiredValue != null) {
return true;
} else {
return false;
}
}
/**
* Returns the XML code <oneNumber> representing this Number Element
* with a new desired value. Resets the desired value.
*
* @return the XML code
* <code><oneNumber></code> representing this Number Element with a new
* value.
* @see #setDesiredValue
*/
@Override
protected String getXMLOneElementNewValue() {
String xml = "<oneNumber name=\"" + this.getName() + "\">" + desiredValue.doubleValue() + "</oneNumber>";
desiredValue = null;
return xml;
}
@Override
public String toString() {
return this.getNumberAsString(value).trim();
}
}