Package jimm.datavision

Source Code of jimm.datavision.Parameter

package jimm.datavision;
import jimm.util.XMLWriter;
import jimm.util.I18N;
import java.util.Observable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Date;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.text.ParsePosition;

/**
* A parameter is a piece of data the value of which is determined by
* asking the user each time a report runs. Default values are only used
* when asking the user for values, not when generating values in
* <code>getValue</code>.
* <p>
* I started out with subclasses for each type of parameter. The problem is,
* the user gets to pick what kind of data the parameter holds and that
* type can be changed any time after the parameter gets created. Therefore,
* we hold objects and change our output based on the type of the data.
*
* @author Jim Menard, <a href="mailto:jimm@io.com">jimm@io.com</a>
*/
public class Parameter
    extends Observable
    implements Identity, Nameable, Writeable, Draggable, Cloneable
{

public static final int TYPE_BOOLEAN = 0;
public static final int TYPE_STRING = 1;
public static final int TYPE_NUMERIC = 2;
public static final int TYPE_DATE = 3;

public static final int ARITY_ONE = 0;
public static final int ARITY_RANGE = 1;
public static final int ARITY_LIST_SINGLE = 2;
public static final int ARITY_LIST_MULTIPLE = 3;

protected static SimpleDateFormat formatter =
    new SimpleDateFormat("yyyy-MM-dd");
protected static ParsePosition parsePosition = new ParsePosition(0);

protected Long id;
protected Report report;
protected String name;
protected String question;
protected int type;
protected int arity;
protected ArrayList defaultValues;
protected ArrayList values;

/**
* Constructor. Creates a string parameter with no name or question
* string.
*
* @param id the unique identifier for the new parameter; if
* <code>null</code>, generate a new id
* @param report the report in which this parameter resides
*/
public Parameter(Long id, Report report)
{
    this(id, report, "string", "", "", "single");
}

/**
* Constructor.
* <p>
* If <i>id</i> is <code>null</code>, generates a new id number. This number
* is one higher than any previously-seen id number. This does <em>not</em>
* guarantee that no later parameter will be created manually with the same
* id number.
*
* @param id the unique identifier for the new parameter; if
* <code>null</code>, generate a new id
* @param report the report in which this parameter resides
* @param typeName one of "string", "numeric", or "date"; found in report XML
* @param name the name of this parameter
* @param question the question to ask when getting the parameter's value
* from the user
* @param arityString arity (single, range, list) as a string
*/
public Parameter(Long id, Report report, String typeName, String name,
        String question, String arityString)
{
    this.report = report;

    // Convert type name to type number.
    if (typeName == null || typeName.length() == 0) {
  String str = I18N.get("Parameter.param_cap") + " " + id + ": "
      + I18N.get("Parameter.missing_type");
  throw new IllegalArgumentException(str);
    }

    typeName = typeName.toLowerCase().trim();
    if ("boolean".equals(typeName)) type = TYPE_BOOLEAN;
    else if ("string".equals(typeName)) type = TYPE_STRING;
    else if ("numeric".equals(typeName)) type = TYPE_NUMERIC;
    else if ("date".equals(typeName)) type = TYPE_DATE;
    else {
  String str = I18N.get("Parameter.param_cap") + " " + id + ": "
      + I18N.get("Parameter.illegal_type");
  throw new IllegalArgumentException(str);
    }

    this.name = name;
    this.question = question;

    // Convert arity string ("range", "list", or "single").
    if (arityString == null || arityString.length() == 0) {
  String str = I18N.get("Parameter.param_cap") + id + ": "
      + I18N.get("Parameter.missing_arity");
  throw new IllegalArgumentException(str);
    }
    arityString = arityString.toLowerCase().trim();
    if ("single".equals(arityString)) arity = ARITY_ONE;
    else if ("range".equals(arityString)) arity = ARITY_RANGE;
    else if ("list-single".equals(arityString)) arity = ARITY_LIST_SINGLE;
    else if ("list-multiple".equals(arityString)) arity = ARITY_LIST_MULTIPLE;
    else {
  String str = I18N.get("Parameter.param_cap") + id + ": "
      + I18N.get("Parameter.illegal_arity");
  throw new IllegalArgumentException(str);
    }

    initialize(id);
}

/**
* Constructor.
* <p>
* If <i>id</i> is <code>null</code>, generates a new id number. This number
* is one higher than any previously-seen id number. This does <em>not</em>
* guarantee that no later parameter will be created manually with the same
* id number.
*
* @param id the unique identifier for the new parameter; if
* <code>null</code>, generate a new id
* @param report the report in which this parameter resides
* @param type one of
* <code>TYPE_BOOLEAN</code>, <code>TYPE_STRING</code>,
* <code>TYPE_NUMERIC</code>, or <code>TYPE_DATE</code>
* @param name the name of this parameter
* @param question the name of this parameter
* @param arity one of <code>ARITY_ONE</code>, <code>ARITY_RANGE</code>,
* <code>ARITY_LIST_SINGLE</code>, or <code>ARITY_LIST_MULTIPLE</code>
*/
public Parameter(Long id, Report report, int type, String name,
        String question, int arity)
{
    this.report = report;
    this.type = type;
    this.name = name;
    this.question = question;
    this.arity = arity;

    initialize(id);
}

private void initialize(Long id) {
    if (id == null)
  id = report.generateNewParameterId();
    this.id = id;

    // Check for legal combinations of type and arity
    switch (type) {
    case TYPE_BOOLEAN:
  if (arity != ARITY_ONE) {
      String str = I18N.get("Parameter.param_cap") + id + ": "
    + I18N.get("Parameter.yesno_single");
      throw new IllegalArgumentException(str);
  }
  break;
    case TYPE_DATE:
  if (arity != ARITY_ONE && arity != ARITY_RANGE) {
      String str = I18N.get("Parameter.param_cap") + id + ": "
    + I18N.get("Parameter.date_arity_err");
      throw new IllegalArgumentException(str);
  }
  break;
    }

    defaultValues = new ArrayList();
    values = new ArrayList();
}

public Object clone() {
    Parameter p = new Parameter(null, report, type, name, question, arity);
    for (Iterator iter = defaultValues.iterator(); iter.hasNext(); ) {
  Object obj = iter.next();
  if (obj instanceof Boolean)
      p.defaultValues.add(obj);
  else if (obj instanceof String)
      p.defaultValues.add(new String((String)obj));
  else if (obj instanceof Number)
      p.defaultValues.add(obj);
  else if (obj instanceof Date)
      p.defaultValues.add(((Date)obj).clone());
    }
    return p;
}

public Object getId() { return id; }

/**
* Returns the name for this parameter.
*
* @return the name
*/
public String getName() { return name; }

/**
* Sets the name.
*
* @param newName the new name
*/
public void setName(String newName) {
    if (name != newName && (name == null || !name.equals(newName))) {
  name = newName;
  setChanged();
  notifyObservers();
    }
}

/**
* Returns the question for this parameter.
*
* @return the question
*/
public String getQuestion() { return question; }

/**
* Sets the question.
*
* @param newQuestion the new question
*/
public void setQuestion(String newQuestion) {
    if (question != newQuestion
  && (question == null || !question.equals(newQuestion)))
    {
  question = newQuestion;
  setChanged();
  notifyObservers();
    }
}

/**
* Returns the type of this field. Will be one of
* <code>TYPE_BOOLEAN</code>, <code>TYPE_STRING</code>,
* <code>TYPE_NUMERIC</code>, or <code>TYPE_DATE</code>.
*
* @return the type number
*/
public int getType() { return type; }

/**
* Sets the parameter type. Must be one of
* <code>TYPE_BOOLEAN</code>, <code>TYPE_STRING</code>,
* <code>TYPE_NUMERIC</code>, or <code>TYPE_DATE</code>. If the new type
* is different than the old, we also make sure the arity is appropriate
* (for example, no boolean lists) and clear the value and default value
* lists.
*
* @param newType the new type; must be one of <code>TYPE_BOOLEAN</code>,
* <code>TYPE_STRING</code>, <code>TYPE_NUMERIC</code>, or
* <code>TYPE_DATE</code>
*/
public void setType(int newType) {
    if (type != newType) {
  type = newType;

  defaultValues.clear();
  values.clear();

  if (type == TYPE_BOOLEAN) {
      if (arity != ARITY_ONE)
    arity = ARITY_ONE;
  }
  else if (type == TYPE_DATE) {
      if (arity == ARITY_LIST_SINGLE || arity == ARITY_LIST_MULTIPLE)
    arity = ARITY_ONE;
  }

  setChanged();
  notifyObservers();
    }
}

/**
* Returns the arity of this field. Will be one of <code>ARITY_ONE</code>,
* <code>ARITY_RANGE</code>, <code>ARITY_LIST_SINGLE</code>, or
* <code>ARITY_LIST_MULTIPLE</code>.
*
* @return the arity number
*/
public int getArity() { return arity; }

/**
* Returns <code>true</code> if the specified combination of type and arity
* are legal.
*
* @param aType one of <code>TYPE_BOOLEAN</code>, <code>TYPE_STRING</code>,
* <code>TYPE_NUMERIC</code>, or <code>TYPE_DATE</code>
* @param anArity one of <code>ARITY_ONE</code>, <code>ARITY_RANGE</code>,
* <code>ARITY_LIST_SINGLE</code>, or <code>ARITY_LIST_MULTIPLE</code>
* @return <code>true</code> if the specified combination of type and arity
* are legal
*/
public boolean isLegal(int aType, int anArity) {
    switch (aType) {
    case TYPE_BOOLEAN:
  return anArity == ARITY_ONE;
    case TYPE_DATE:
  return anArity != ARITY_LIST_SINGLE && anArity != ARITY_LIST_MULTIPLE;
    case TYPE_STRING:
    case TYPE_NUMERIC:
    default:
  return true;
    }
}

/**
* Sets the parameter arity. Must be one of <code>ARITY_ONE</code>,
* <code>ARITY_RANGE</code>, <code>ARITY_LIST_SINGLE</code>, or
* <code>ARITY_LIST_MULTIPLE</code>. We disallow illegal arity values.
* For example, if our type is boolean we disallow a list arity.
*
* @param newArity one of <code>ARITY_ONE</code>, <code>ARITY_RANGE</code>,
* <code>ARITY_LIST_SINGLE</code>, or <code>ARITY_LIST_MULTIPLE</code>
*/
public void setArity(int newArity) {
    if (arity != newArity) {

  if (type == TYPE_BOOLEAN) {
      if (newArity != ARITY_ONE) {
    String str = I18N.get("Parameter.param_cap") + id + ": "
        + I18N.get("Parameter.yesno_single");
    throw new IllegalArgumentException(str);
      }
  }
  else if (type == TYPE_DATE) {
      if (newArity == ARITY_LIST_SINGLE
    || newArity == ARITY_LIST_MULTIPLE)
      {
    String str = I18N.get("Parameter.param_cap") + id + ": "
        + I18N.get("Parameter.date_arity_err");
    throw new IllegalArgumentException(str);
      }
  }

  arity = newArity;

  defaultValues.clear();
  values.clear();

  setChanged();
  notifyObservers();
    }
}

/**
* Returns an iterator over the default values for this parameter.
*
* @return an interator
*/
public Iterator defaultValues() { return defaultValues.iterator(); }

/**
* Returns the i'th defaultValue for this parameter. If none has been
* assigned, create and return -- but do not store -- a reasonable default.
* The default is obtained by calling {@link #getDefaultForType}.
*
* @param i the index
* @return the defaultValue
*/
public Object getDefaultValue(int i) {
    Object val;
    if (i < 0 || i >= defaultValues.size()
  || (val = defaultValues.get(i)) == null)
    {
  return getDefaultForType(type);
    }
    else
  return val;
}

/**
* Returns the default value for a specific parameter type. This is not
* the same as the i'th default value; it is called when you have a
* parameter that has no value or default value, or when you have one
* with a different type and you want to switch types.
*
* @param type one of <code>TYPE_BOOLEAN</code>, <code>TYPE_STRING</code>,
* <code>TYPE_NUMERIC</code>, or <code>TYPE_DATE</code>
* @return a new object appropriate for the type
*/
public Object getDefaultForType(int type) {
  switch (type) {
  case TYPE_BOOLEAN: return Boolean.valueOf(false);
  case TYPE_STRING: return "";
  case TYPE_NUMERIC: return new Integer(0);
  case TYPE_DATE: return new Date();
  default:
      String str = I18N.get("Paramter.illegal_type_value");
      throw new IllegalArgumentException(str + " " + type);
  }
}

/**
* Erases all default values.
*/
public void removeDefaultValues() {
    if (defaultValues.size() > 0) {
  defaultValues.clear();
  setChanged();
  notifyObservers();
    }
}

/**
* Adds a default value to the list.
*
* @param newDefaultValue a new default value
*/
public void addDefaultValue(Object newDefaultValue) {
    // Make sure newDefaultValue is of proper type for the values we hold
    newDefaultValue = convertType(newDefaultValue);

    defaultValues.add(newDefaultValue);
    setChanged();
    notifyObservers();
}

/**
* Sets the i'th defaultValue. If <var>i</var> is out of range,
* the list of default values grows to fit.
*
* @param i the index
* @param newDefaultValue a value
*/
public void setDefaultValue(int i, Object newDefaultValue) {
    // Make sure newDefaultValue is of proper type for the values we hold
    newDefaultValue = convertType(newDefaultValue);

    Object defaultValue = null;
    if (i < defaultValues.size())
  defaultValue = getDefaultValue(i);
    if (defaultValue != newDefaultValue
  && (defaultValue == null || !defaultValue.equals(newDefaultValue)))
    {
  defaultValues.add(i, newDefaultValue);
  setChanged();
  notifyObservers();
    }
}

/**
* Returns an iterator over the values for this parameter.
*
* @return an interator
*/
public Iterator values() { return values.iterator(); }

/**
* Returns the parameter value(s) the user has previously specified. If
* the parameter has one value, return that value or possibly null. Else,
* return a copy of our list of values.
*
* @return values (see description)
*/
public Object getValue() {
    switch (arity) {
    case ARITY_ONE:
    case ARITY_LIST_SINGLE:
  return getValue(0);
    case ARITY_RANGE:
  ArrayList list = new ArrayList();
  list.add(getValue(0));
  list.add(getValue(1));
  return list;
    case ARITY_LIST_MULTIPLE:
  return values.clone();
    }
    return null;    // Will never happen
}

/**
* Returns the current value or, if that is <code>null</code>, the default
* value. If the index is out of range, return <code>null</code>.
*
* @param i the index
* @return the current or default value.
*/
public Object getValue(int i) {
    Object val = null;
    if (i < values.size())
  val = values.get(i);
    if (val == null) {
  if (i < defaultValues.size())
      val = defaultValues.get(i);
    }
    return val;
}

/**
* Adds a value to the list.
*
* @param newValue a new value
*/
public void addValue(Object newValue) {
    values.add(convertType(newValue));
    setChanged();
    notifyObservers();
}

/**
* Erases all values.
*/
public void removeValues() {
    if (values.size() > 0) {
  values.clear();
  setChanged();
  notifyObservers();
    }
}

/**
* Sets the <var>i</var>'th value. If <var>i</var> is out of range,
* the list of values grows to fit.
*
* param i the index
* @param newValue the new value
*/
public void setValue(int i, Object newValue) {
    // Make sure newValue is of proper type for the values we hold
    values.add(i, convertType(newValue));

    setChanged();
    notifyObservers();
}

/**
* Converts the specified object to the proper type for this parameter.
* Whenever we add or set a value or default value, we convert it to the
* proper type (string, date, etc.)
* <p>
* If our type is boolean and the incoming object is:
* <ul>
* <li>A string, return
* a <code>true Boolean</code> if the value matches "true", "t", "yes",
* or "y" (ignoring case).
* <li>A number, return a <code>true Boolean</code> if the value is non-zero.
* <li>Anything else, return a <code>true Boolean</code> (any better
* suggestions?)
*
* @param val any old object
* @return some object of the proper type
*/
protected Object convertType(Object val) {
    if (val == null)
  return null;

    switch (type) {
    case TYPE_BOOLEAN:    // Return value as a boolean
  if (val instanceof Boolean)
      return val;
  else if (val instanceof String) {
      val = ((String)val).toLowerCase().trim();
      if ("true".equals(val) || "t".equals(val)
    || "yes".equals(val) || "y".equals(val))
    return Boolean.valueOf(true);
      else
    return Boolean.valueOf(false);
  }
  else if (val instanceof Number) {
      return Boolean.valueOf(((Number)val).doubleValue() == 0);
  }
  else {
      return Boolean.valueOf(true); // What to do here?
  }
    case TYPE_STRING:    // Return value as a string
  return val.toString();
    case TYPE_NUMERIC:    // Return value as a number
  if (val instanceof Number)
      return val;
  else {      // Convert val to string, then to number
      String str = val.toString();
      if (str.length() == 0)
    return new Integer(0);
      else if (str.indexOf(".") == -1)
    return new Integer(str);
      else
    return new Double(str);
  }
    case TYPE_DATE:    // Return value as a date
  if (val instanceof Date)
      return val;
  else {      // Convert val to string, then to date
      String str = val.toString();
      if (str.length() == 0)
    return new Date();
      else {
    parsePosition.setIndex(0);
    return formatter.parse(str, parsePosition);
      }
  }
    default:      // Should never happen
  return null;
    }
}

/**
* Returns the string used as the "type" attribute when writing this
* parameter as XML.
*
* @return the "type" attribute string
*/
protected String typeString() {
    switch (type) {
    case TYPE_BOOLEAN: return "boolean";
    case TYPE_STRING: return "string";
    case TYPE_NUMERIC: return "numeric";
    case TYPE_DATE: return "date";
    default: return "unknown"// Should never happen
    }
}

public String dragString() {
    return "parameter:" + getId();
}

public String designLabel() {
    return "{?" + getName() + "}";
}

public String formulaString() {
    return "{?" + getId() + "}";
}

/**
* Writes this parameter as an XML tag.
*
* @param out a writer that knows how to write XML
*/
public void writeXML(XMLWriter out) {
    String arityString = null;
    switch (arity) {
    case ARITY_ONE: arityString = "single"; break;
    case ARITY_RANGE: arityString = "range"; break;
    case ARITY_LIST_SINGLE: arityString = "list-single"; break;
    case ARITY_LIST_MULTIPLE: arityString = "list-multiple"; break;
    }

    out.startElement("parameter");
    out.attr("id", id);
    out.attr("type", typeString());
    out.attr("name", name);
    out.attr("question", question);
    out.attr("arity", arityString);

    for (Iterator iter = defaultValues.iterator(); iter.hasNext(); )
  out.textElement("default", iter.next().toString());

    out.endElement();
}

public String toString() {
    StringWriter sw = new StringWriter();
    writeXML(new XMLWriter(sw));
    return sw.toString();
}

}
TOP

Related Classes of jimm.datavision.Parameter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.