Attribute.java
Upload User: rhdiban
Upload Date: 2013-08-09
Package Size: 15085k
Code Size: 35k
Category:

Windows Develop

Development Platform:

Java

  1. /*
  2.  *    This program is free software; you can redistribute it and/or modify
  3.  *    it under the terms of the GNU General Public License as published by
  4.  *    the Free Software Foundation; either version 2 of the License, or
  5.  *    (at your option) any later version.
  6.  *
  7.  *    This program is distributed in the hope that it will be useful,
  8.  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
  9.  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  10.  *    GNU General Public License for more details.
  11.  *
  12.  *    You should have received a copy of the GNU General Public License
  13.  *    along with this program; if not, write to the Free Software
  14.  *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  15.  */
  16. /*
  17.  *    Attribute.java
  18.  *    Copyright (C) 1999 Eibe Frank
  19.  *
  20.  */
  21. package weka.core;
  22. import java.io.Serializable;
  23. import java.text.ParseException;
  24. import java.text.SimpleDateFormat;
  25. import java.util.Date;
  26. import java.util.Enumeration;
  27. import java.util.Hashtable;
  28. import java.util.Properties;
  29. import java.io.StreamTokenizer;
  30. import java.io.StringReader;
  31. import java.io.IOException;
  32. /** 
  33.  * Class for handling an attribute. Once an attribute has been created,
  34.  * it can't be changed. <p>
  35.  *
  36.  * Three attribute types are supported:
  37.  * <ul>
  38.  *    <li> numeric: <ul>
  39.  *         This type of attribute represents a floating-point number.
  40.  *    </ul>
  41.  *    <li> nominal: <ul>
  42.  *         This type of attribute represents a fixed set of nominal values.
  43.  *    </ul>
  44.  *    <li> string: <ul>
  45.  *         This type of attribute represents a dynamically expanding set of
  46.  *         nominal values. String attributes are not used by the learning
  47.  *         schemes in Weka. They can be used, for example,  to store an 
  48.  *         identifier with each instance in a dataset.
  49.  *    </ul>
  50.  * </ul>
  51.  * Typical usage (code from the main() method of this class): <p>
  52.  *
  53.  * <code>
  54.  * ... <br>
  55.  *
  56.  * // Create numeric attributes "length" and "weight" <br>
  57.  * Attribute length = new Attribute("length"); <br>
  58.  * Attribute weight = new Attribute("weight"); <br><br>
  59.  * 
  60.  * // Create vector to hold nominal values "first", "second", "third" <br>
  61.  * FastVector my_nominal_values = new FastVector(3); <br>
  62.  * my_nominal_values.addElement("first"); <br>
  63.  * my_nominal_values.addElement("second"); <br>
  64.  * my_nominal_values.addElement("third"); <br><br>
  65.  *
  66.  * // Create nominal attribute "position" <br>
  67.  * Attribute position = new Attribute("position", my_nominal_values);<br>
  68.  *
  69.  * ... <br>
  70.  * </code><p>
  71.  *
  72.  * @author Eibe Frank (eibe@cs.waikato.ac.nz)
  73.  * @version $Revision: 1.26 $
  74.  */
  75. public class Attribute implements Copyable, Serializable {
  76.   /** Constant set for numeric attributes. */
  77.   public final static int NUMERIC = 0;
  78.   /** Constant set for nominal attributes. */
  79.   public final static int NOMINAL = 1;
  80.   /** Constant set for attributes with string values. */
  81.   public final static int STRING = 2;
  82.   /** Constant set for attributes with date values. */
  83.   public final static int DATE = 3;
  84.   /** Constant set for symbolic attributes. */
  85.   public final static int ORDERING_SYMBOLIC = 0;
  86.   /** Constant set for ordered attributes. */
  87.   public final static int ORDERING_ORDERED  = 1;
  88.   /** Constant set for modulo-ordered attributes. */
  89.   public final static int ORDERING_MODULO   = 2;
  90.   /** Strings longer than this will be stored compressed. */
  91.   private final static int STRING_COMPRESS_THRESHOLD = 200;
  92.   /** The attribute's name. */
  93.   private String m_Name;
  94.   /** The attribute's type. */
  95.   private int m_Type;
  96.   /** The attribute's values (if nominal or string). */
  97.   private FastVector m_Values;
  98.   /** Mapping of values to indices (if nominal or string). */
  99.   private Hashtable m_Hashtable;
  100.   /** Date format specification for date attributes */
  101.   private SimpleDateFormat m_DateFormat;
  102.   /** The attribute's index. */
  103.   private int m_Index;
  104.   /** The attribute's metadata. */
  105.   private ProtectedProperties m_Metadata;
  106.   /** The attribute's ordering. */
  107.   private int m_Ordering;
  108.   /** Whether the attribute is regular. */
  109.   private boolean m_IsRegular;
  110.   /** Whether the attribute is averagable. */
  111.   private boolean m_IsAveragable;
  112.   /** Whether the attribute has a zeropoint. */
  113.   private boolean m_HasZeropoint;
  114.   /** The attribute's weight. */
  115.   private double m_Weight;
  116.   /** The attribute's lower numeric bound. */
  117.   private double m_LowerBound;
  118.   /** Whether the lower bound is open. */
  119.   private boolean m_LowerBoundIsOpen;
  120.   /** The attribute's upper numeric bound. */
  121.   private double m_UpperBound;
  122.   /** Whether the upper bound is open */
  123.   private boolean m_UpperBoundIsOpen;
  124.   /**
  125.    * Constructor for a numeric attribute.
  126.    *
  127.    * @param attributeName the name for the attribute
  128.    */
  129.   public Attribute(String attributeName) {
  130.     this(attributeName, new ProtectedProperties(new Properties()));
  131.   }
  132.   /**
  133.    * Constructor for a numeric attribute, where metadata is supplied.
  134.    *
  135.    * @param attributeName the name for the attribute
  136.    * @param metadata the attribute's properties
  137.    */
  138.   public Attribute(String attributeName, ProtectedProperties metadata) {
  139.     m_Name = attributeName;
  140.     m_Index = -1;
  141.     m_Values = null;
  142.     m_Hashtable = null;
  143.     m_Type = NUMERIC;
  144.     setMetadata(metadata);
  145.   }
  146.   /**
  147.    * Constructor for a date attribute.
  148.    *
  149.    * @param attributeName the name for the attribute
  150.    * @param dateFormat a string suitable for use with
  151.    * SimpleDateFormatter for parsing dates.
  152.    */
  153.   public Attribute(String attributeName, String dateFormat) {
  154.     this(attributeName, dateFormat,
  155.  new ProtectedProperties(new Properties()));
  156.   }
  157.   /**
  158.    * Constructor for a date attribute, where metadata is supplied.
  159.    *
  160.    * @param attributeName the name for the attribute
  161.    * @param dateFormat a string suitable for use with
  162.    * SimpleDateFormatter for parsing dates.
  163.    * @param metadata the attribute's properties
  164.    */
  165.   public Attribute(String attributeName, String dateFormat,
  166.    ProtectedProperties metadata) {
  167.     m_Name = attributeName;
  168.     m_Index = -1;
  169.     m_Values = null;
  170.     m_Hashtable = null;
  171.     m_Type = DATE;
  172.     if (dateFormat != null) {
  173.       m_DateFormat = new SimpleDateFormat(dateFormat);
  174.     } else {
  175.       m_DateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
  176.     }
  177.     m_DateFormat.setLenient(false);
  178.     setMetadata(metadata);
  179.   }
  180.   /**
  181.    * Constructor for nominal attributes and string attributes.
  182.    * If a null vector of attribute values is passed to the method,
  183.    * the attribute is assumed to be a string.
  184.    *
  185.    * @param attributeName the name for the attribute
  186.    * @param attributeValues a vector of strings denoting the 
  187.    * attribute values. Null if the attribute is a string attribute.
  188.    */
  189.   public Attribute(String attributeName, 
  190.    FastVector attributeValues) {
  191.     this(attributeName, attributeValues,
  192.  new ProtectedProperties(new Properties()));
  193.   }
  194.   /**
  195.    * Constructor for nominal attributes and string attributes, where
  196.    * metadata is supplied. If a null vector of attribute values is passed
  197.    * to the method, the attribute is assumed to be a string.
  198.    *
  199.    * @param attributeName the name for the attribute
  200.    * @param attributeValues a vector of strings denoting the 
  201.    * attribute values. Null if the attribute is a string attribute.
  202.    * @param metadata the attribute's properties
  203.    */
  204.   public Attribute(String attributeName, 
  205.    FastVector attributeValues,
  206.    ProtectedProperties metadata) {
  207.     m_Name = attributeName;
  208.     m_Index = -1;
  209.     if (attributeValues == null) {
  210.       m_Values = new FastVector();
  211.       m_Hashtable = new Hashtable();
  212.       m_Type = STRING;
  213.     } else {
  214.       m_Values = new FastVector(attributeValues.size());
  215.       m_Hashtable = new Hashtable(attributeValues.size());
  216.       for (int i = 0; i < attributeValues.size(); i++) {
  217. Object store = attributeValues.elementAt(i);
  218. if (((String)store).length() > STRING_COMPRESS_THRESHOLD) {
  219.   try {
  220.     store = new SerializedObject(attributeValues.elementAt(i), true);
  221.   } catch (Exception ex) {
  222.     System.err.println("Couldn't compress nominal attribute value -"
  223.        + " storing uncompressed.");
  224.   }
  225. }
  226. m_Values.addElement(store);
  227. m_Hashtable.put(store, new Integer(i));
  228.       }
  229.       m_Type = NOMINAL;
  230.     }
  231.     setMetadata(metadata);
  232.   }
  233.   /**
  234.    * Produces a shallow copy of this attribute.
  235.    *
  236.    * @return a copy of this attribute with the same index
  237.    */
  238.   public Object copy() {
  239.     Attribute copy = new Attribute(m_Name);
  240.     copy.m_Index = m_Index;
  241.     copy.m_Type = m_Type;
  242.     copy.m_Values = m_Values;
  243.     copy.m_Hashtable = m_Hashtable;
  244.     copy.m_DateFormat = m_DateFormat;
  245.     copy.setMetadata(m_Metadata);
  246.  
  247.     return copy;
  248.   }
  249.   /**
  250.    * Returns an enumeration of all the attribute's values if
  251.    * the attribute is nominal or a string, null otherwise. 
  252.    *
  253.    * @return enumeration of all the attribute's values
  254.    */
  255.   public final Enumeration enumerateValues() {
  256.     if (isNominal() || isString()) {
  257.       final Enumeration ee = m_Values.elements();
  258.       return new Enumeration () {
  259.           public boolean hasMoreElements() {
  260.             return ee.hasMoreElements();
  261.           }
  262.           public Object nextElement() {
  263.             Object oo = ee.nextElement();
  264.             if (oo instanceof SerializedObject) {
  265.               return ((SerializedObject)oo).getObject();
  266.             } else {
  267.               return oo;
  268.             }
  269.           }
  270.         };
  271.     }
  272.     return null;
  273.   }
  274.   /**
  275.    * Tests if given attribute is equal to this attribute.
  276.    *
  277.    * @param other the Object to be compared to this attribute
  278.    * @return true if the given attribute is equal to this attribute
  279.    */
  280.   public final boolean equals(Object other) {
  281.     if ((other == null) || !(other.getClass().equals(this.getClass()))) {
  282.       return false;
  283.     }
  284.     Attribute att = (Attribute) other;
  285.     if (!m_Name.equals(att.m_Name)) {
  286.       return false;
  287.     }
  288.     if (isNominal() && att.isNominal()) {
  289.       if (m_Values.size() != att.m_Values.size()) {
  290.         return false;
  291.       }
  292.       for (int i = 0; i < m_Values.size(); i++) {
  293.         if (!m_Values.elementAt(i).equals(att.m_Values.elementAt(i))) {
  294.           return false;
  295.         }
  296.       }
  297.       return true;
  298.     } else {
  299.       return (type() == att.type());
  300.     }
  301.   }
  302.   /**
  303.    * Returns the index of this attribute.
  304.    *
  305.    * @return the index of this attribute
  306.    */
  307.   public final int index() {
  308.     return m_Index;
  309.   }
  310.   /**
  311.    * Returns the index of a given attribute value. (The index of
  312.    * the first occurence of this value.)
  313.    *
  314.    * @param value the value for which the index is to be returned
  315.    * @return the index of the given attribute value if attribute
  316.    * is nominal or a string, -1 if it is numeric or the value 
  317.    * can't be found
  318.    */
  319.   public final int indexOfValue(String value) {
  320.     if (!isNominal() && !isString())
  321.       return -1;
  322.     Object store = value;
  323.     if (value.length() > STRING_COMPRESS_THRESHOLD) {
  324.       try {
  325.         store = new SerializedObject(value, true);
  326.       } catch (Exception ex) {
  327.         System.err.println("Couldn't compress string attribute value -"
  328.                            + " searching uncompressed.");
  329.       }
  330.     }
  331.     Integer val = (Integer)m_Hashtable.get(store);
  332.     if (val == null) return -1;
  333.     else return val.intValue();
  334.   }
  335.   /**
  336.    * Test if the attribute is nominal.
  337.    *
  338.    * @return true if the attribute is nominal
  339.    */
  340.   public final boolean isNominal() {
  341.     return (m_Type == NOMINAL);
  342.   }
  343.   /**
  344.    * Tests if the attribute is numeric.
  345.    *
  346.    * @return true if the attribute is numeric
  347.    */
  348.   public final boolean isNumeric() {
  349.     return ((m_Type == NUMERIC) || (m_Type == DATE));
  350.   }
  351.   /**
  352.    * Tests if the attribute is a string.
  353.    *
  354.    * @return true if the attribute is a string
  355.    */
  356.   public final boolean isString() {
  357.     return (m_Type == STRING);
  358.   }
  359.   /**
  360.    * Tests if the attribute is a date type.
  361.    *
  362.    * @return true if the attribute is a date type
  363.    */
  364.   public final boolean isDate() {
  365.     return (m_Type == DATE);
  366.   }
  367.   /**
  368.    * Returns the attribute's name.
  369.    *
  370.    * @return the attribute's name as a string
  371.    */
  372.   public final String name() {
  373.     return m_Name;
  374.   }
  375.   
  376.   /**
  377.    * Returns the number of attribute values. Returns 0 for numeric attributes.
  378.    *
  379.    * @return the number of attribute values
  380.    */
  381.   public final int numValues() {
  382.     if (!isNominal() && !isString()) {
  383.       return 0;
  384.     } else {
  385.       return m_Values.size();
  386.     }
  387.   }
  388.   /**
  389.    * Returns a description of this attribute in ARFF format. Quotes
  390.    * strings if they contain whitespace characters, or if they
  391.    * are a question mark.
  392.    *
  393.    * @return a description of this attribute as a string
  394.    */
  395.   public final String toString() {
  396.     
  397.     StringBuffer text = new StringBuffer();
  398.     
  399.     text.append("@attribute " + Utils.quote(m_Name) + " ");
  400.     switch (m_Type) {
  401.     case NOMINAL:
  402.       text.append('{');
  403.       Enumeration enum = enumerateValues();
  404.       while (enum.hasMoreElements()) {
  405. text.append(Utils.quote((String) enum.nextElement()));
  406. if (enum.hasMoreElements())
  407.   text.append(',');
  408.       }
  409.       text.append('}');
  410.       break;
  411.     case NUMERIC:
  412.       text.append("numeric");
  413.       break;
  414.     case STRING:
  415.       text.append("string");
  416.       break;
  417.     case DATE:
  418.       text.append("date ").append(Utils.quote(m_DateFormat.toPattern()));
  419.       break;
  420.     default:
  421.       text.append("UNKNOWN");
  422.       break;
  423.     }
  424.     return text.toString();
  425.   }
  426.   /**
  427.    * Returns the attribute's type as an integer.
  428.    *
  429.    * @return the attribute's type.
  430.    */
  431.   public final int type() {
  432.     return m_Type;
  433.   }
  434.   /**
  435.    * Returns a value of a nominal or string attribute. 
  436.    * Returns an empty string if the attribute is neither
  437.    * nominal nor a string attribute.
  438.    *
  439.    * @param valIndex the value's index
  440.    * @return the attribute's value as a string
  441.    */
  442.   public final String value(int valIndex) {
  443.     
  444.     if (!isNominal() && !isString()) {
  445.       return "";
  446.     } else {
  447.       Object val = m_Values.elementAt(valIndex);
  448.       
  449.       // If we're storing strings compressed, uncompress it.
  450.       if (val instanceof SerializedObject) {
  451.         val = ((SerializedObject)val).getObject();
  452.       }
  453.       return (String) val;
  454.     }
  455.   }
  456.   /**
  457.    * Constructor for a numeric attribute with a particular index.
  458.    *
  459.    * @param attributeName the name for the attribute
  460.    * @param index the attribute's index
  461.    */
  462.   Attribute(String attributeName, int index) {
  463.     this(attributeName);
  464.     m_Index = index;
  465.   }
  466.   /**
  467.    * Constructor for date attributes with a particular index.
  468.    *
  469.    * @param attributeName the name for the attribute
  470.    * @param dateFormat a string suitable for use with
  471.    * SimpleDateFormatter for parsing dates.  Null for a default format
  472.    * string.
  473.    * @param index the attribute's index
  474.    */
  475.   Attribute(String attributeName, String dateFormat, 
  476.     int index) {
  477.     this(attributeName, dateFormat);
  478.     m_Index = index;
  479.   }
  480.   /**
  481.    * Constructor for nominal attributes and string attributes with
  482.    * a particular index.
  483.    * If a null vector of attribute values is passed to the method,
  484.    * the attribute is assumed to be a string.
  485.    *
  486.    * @param attributeName the name for the attribute
  487.    * @param attributeValues a vector of strings denoting the attribute values.
  488.    * Null if the attribute is a string attribute.
  489.    * @param index the attribute's index
  490.    */
  491.   Attribute(String attributeName, FastVector attributeValues, 
  492.     int index) {
  493.     this(attributeName, attributeValues);
  494.     m_Index = index;
  495.   }
  496.   /**
  497.    * Adds a string value to the list of valid strings for attributes
  498.    * of type STRING and returns the index of the string.
  499.    *
  500.    * @param value The string value to add
  501.    * @return the index assigned to the string, or -1 if the attribute is not
  502.    * of type Attribute.STRING 
  503.    */
  504.   public int addStringValue(String value) {
  505.     if (!isString()) {
  506.       return -1;
  507.     }
  508.     Object store = value;
  509.     if (value.length() > STRING_COMPRESS_THRESHOLD) {
  510.       try {
  511.         store = new SerializedObject(value, true);
  512.       } catch (Exception ex) {
  513.         System.err.println("Couldn't compress string attribute value -"
  514.                            + " storing uncompressed.");
  515.       }
  516.     }
  517.     Integer index = (Integer)m_Hashtable.get(store);
  518.     if (index != null) {
  519.       return index.intValue();
  520.     } else {
  521.       int intIndex = m_Values.size();
  522.       m_Values.addElement(store);
  523.       m_Hashtable.put(store, new Integer(intIndex));
  524.       return intIndex;
  525.     }
  526.   }
  527.   /**
  528.    * Adds a string value to the list of valid strings for attributes
  529.    * of type STRING and returns the index of the string. This method is
  530.    * more efficient than addStringValue(String) for long strings.
  531.    *
  532.    * @param src The Attribute containing the string value to add.
  533.    * @param int index the index of the string value in the source attribute.
  534.    * @return the index assigned to the string, or -1 if the attribute is not
  535.    * of type Attribute.STRING 
  536.    */
  537.   public int addStringValue(Attribute src, int index) {
  538.     if (!isString()) {
  539.       return -1;
  540.     }
  541.     Object store = src.m_Values.elementAt(index);
  542.     Integer oldIndex = (Integer)m_Hashtable.get(store);
  543.     if (oldIndex != null) {
  544.       return oldIndex.intValue();
  545.     } else {
  546.       int intIndex = m_Values.size();
  547.       m_Values.addElement(store);
  548.       m_Hashtable.put(store, new Integer(intIndex));
  549.       return intIndex;
  550.     }
  551.   }
  552.   /**
  553.    * Adds an attribute value. Creates a fresh list of attribute
  554.    * values before adding it.
  555.    *
  556.    * @param value the attribute value
  557.    */
  558.   final void addValue(String value) {
  559.     m_Values = (FastVector)m_Values.copy();
  560.     m_Hashtable = (Hashtable)m_Hashtable.clone();
  561.     forceAddValue(value);
  562.   }
  563.   /**
  564.    * Produces a shallow copy of this attribute with a new name.
  565.    *
  566.    * @param newName the name of the new attribute
  567.    * @return a copy of this attribute with the same index
  568.    */
  569.   final Attribute copy(String newName) {
  570.     Attribute copy = new Attribute(newName);
  571.     copy.m_Index = m_Index;
  572.     copy.m_DateFormat = m_DateFormat;
  573.     copy.m_Type = m_Type;
  574.     copy.m_Values = m_Values;
  575.     copy.m_Hashtable = m_Hashtable;
  576.     copy.setMetadata(m_Metadata);
  577.  
  578.     return copy;
  579.   }
  580.   /**
  581.    * Removes a value of a nominal or string attribute. Creates a 
  582.    * fresh list of attribute values before removing it.
  583.    *
  584.    * @param index the value's index
  585.    * @exception IllegalArgumentException if the attribute is not nominal
  586.    */
  587.   final void delete(int index) {
  588.     
  589.     if (!isNominal() && !isString()) 
  590.       throw new IllegalArgumentException("Can only remove value of" +
  591.                                          "nominal or string attribute!");
  592.     else {
  593.       m_Values = (FastVector)m_Values.copy();
  594.       m_Values.removeElementAt(index);
  595.       Hashtable hash = new Hashtable(m_Hashtable.size());
  596.       Enumeration enum = m_Hashtable.keys();
  597.       while (enum.hasMoreElements()) {
  598. Object string = enum.nextElement();
  599. Integer valIndexObject = (Integer)m_Hashtable.get(string);
  600. int valIndex = valIndexObject.intValue();
  601. if (valIndex > index) {
  602.   hash.put(string, new Integer(valIndex - 1));
  603. } else if (valIndex < index) {
  604.   hash.put(string, valIndexObject);
  605. }
  606.       }
  607.       m_Hashtable = hash;
  608.     }
  609.   }
  610.   /**
  611.    * Adds an attribute value.
  612.    *
  613.    * @param value the attribute value
  614.    */
  615.   final void forceAddValue(String value) {
  616.     Object store = value;
  617.     if (value.length() > STRING_COMPRESS_THRESHOLD) {
  618.       try {
  619.         store = new SerializedObject(value, true);
  620.       } catch (Exception ex) {
  621.         System.err.println("Couldn't compress string attribute value -"
  622.                            + " storing uncompressed.");
  623.       }
  624.     }
  625.     m_Values.addElement(store);
  626.     m_Hashtable.put(store, new Integer(m_Values.size() - 1));
  627.   }
  628.   /**
  629.    * Sets the index of this attribute.
  630.    *
  631.    * @param the index of this attribute
  632.    */
  633.   final void setIndex(int index) {
  634.     m_Index = index;
  635.   }
  636.   /**
  637.    * Sets a value of a nominal attribute or string attribute.
  638.    * Creates a fresh list of attribute values before it is set.
  639.    *
  640.    * @param index the value's index
  641.    * @param string the value
  642.    * @exception IllegalArgumentException if the attribute is not nominal or 
  643.    * string.
  644.    */
  645.   final void setValue(int index, String string) {
  646.     
  647.     switch (m_Type) {
  648.     case NOMINAL:
  649.     case STRING:
  650.       m_Values = (FastVector)m_Values.copy();
  651.       m_Hashtable = (Hashtable)m_Hashtable.clone();
  652.       Object store = string;
  653.       if (string.length() > STRING_COMPRESS_THRESHOLD) {
  654.         try {
  655.           store = new SerializedObject(string, true);
  656.         } catch (Exception ex) {
  657.           System.err.println("Couldn't compress string attribute value -"
  658.                              + " storing uncompressed.");
  659.         }
  660.       }
  661.       m_Hashtable.remove(m_Values.elementAt(index));
  662.       m_Values.setElementAt(store, index);
  663.       m_Hashtable.put(store, new Integer(index));
  664.       break;
  665.     default:
  666.       throw new IllegalArgumentException("Can only set values for nominal"
  667.                                          + " or string attributes!");
  668.     }
  669.   }
  670.   public String formatDate(double date) {
  671.     switch (m_Type) {
  672.     case DATE:
  673.       return m_DateFormat.format(new Date((long)date));
  674.     default:
  675.       throw new IllegalArgumentException("Can only format date values for date"
  676.                                          + " attributes!");
  677.     }
  678.   }
  679.   public double parseDate(String string) throws ParseException {
  680.     switch (m_Type) {
  681.     case DATE:
  682.       long time = m_DateFormat.parse(string).getTime();
  683.       // TODO put in a safety check here if we can't store the value in a double.
  684.       return (double)time;
  685.     default:
  686.       throw new IllegalArgumentException("Can only parse date values for date"
  687.                                          + " attributes!");
  688.     }
  689.   }
  690.   /**
  691.    * Returns the properties supplied for this attribute.
  692.    *
  693.    * @return metadata for this attribute
  694.    */  
  695.   public final ProtectedProperties getMetadata() {
  696.     return m_Metadata;
  697.   }
  698.   /**
  699.    * Returns the ordering of the attribute. One of the following:
  700.    * 
  701.    * ORDERING_SYMBOLIC - attribute values should be treated as symbols.
  702.    * ORDERING_ORDERED  - attribute values have a global ordering.
  703.    * ORDERING_MODULO   - attribute values have an ordering which wraps.
  704.    *
  705.    * @return the ordering type of the attribute
  706.    */
  707.   public final int ordering() {
  708.     return m_Ordering;
  709.   }
  710.   /**
  711.    * Returns whether the attribute values are equally spaced.
  712.    *
  713.    * @return whether the attribute is regular or not
  714.    */
  715.   public final boolean isRegular() {
  716.     return m_IsRegular;
  717.   }
  718.   /**
  719.    * Returns whether the attribute can be averaged meaningfully.
  720.    *
  721.    * @return whether the attribute can be averaged or not
  722.    */
  723.   public final boolean isAveragable() {
  724.     return m_IsAveragable;
  725.   }
  726.   /**
  727.    * Returns whether the attribute has a zeropoint and may be
  728.    * added meaningfully.
  729.    *
  730.    * @return whether the attribute has a zeropoint or not
  731.    */
  732.   public final boolean hasZeropoint() {
  733.     return m_HasZeropoint;
  734.   }
  735.   /**
  736.    * Returns the attribute's weight.
  737.    *
  738.    * @return the attribute's weight as a double
  739.    */
  740.   public final double weight() {
  741.     return m_Weight;
  742.   }
  743.   /**
  744.    * Returns the lower bound of a numeric attribute.
  745.    *
  746.    * @return the lower bound of the specified numeric range
  747.    */
  748.   public final double getLowerNumericBound() {
  749.     return m_LowerBound;
  750.   }
  751.   /**
  752.    * Returns whether the lower numeric bound of the attribute is open.
  753.    *
  754.    * @return whether the lower numeric bound is open or not (closed)
  755.    */
  756.   public final boolean lowerNumericBoundIsOpen() {
  757.     return m_LowerBoundIsOpen;
  758.   }
  759.   /**
  760.    * Returns the upper bound of a numeric attribute.
  761.    *
  762.    * @return the upper bound of the specified numeric range
  763.    */
  764.   public final double getUpperNumericBound() {
  765.     return m_UpperBound;
  766.   }
  767.   /**
  768.    * Returns whether the upper numeric bound of the attribute is open.
  769.    *
  770.    * @return whether the upper numeric bound is open or not (closed)
  771.    */
  772.   public final boolean upperNumericBoundIsOpen() {
  773.     return m_UpperBoundIsOpen;
  774.   }
  775.   /**
  776.    * Determines whether a value lies within the bounds of the attribute.
  777.    *
  778.    * @return whether the value is in range
  779.    */
  780.   public final boolean isInRange(double value) {
  781.     // dates and missing values are a special case 
  782.     if (m_Type == DATE || value == Instance.missingValue()) return true;
  783.     if (m_Type != NUMERIC) {
  784.       // do label range check
  785.       int intVal = (int) value;
  786.       if (intVal < 0 || intVal >= m_Hashtable.size()) return false;
  787.     } else {
  788.       // do numeric bounds check
  789.       if (m_LowerBoundIsOpen) {
  790. if (value <= m_LowerBound) return false;
  791.       } else {
  792. if (value < m_LowerBound) return false;
  793.       }
  794.       if (m_UpperBoundIsOpen) {
  795. if (value >= m_UpperBound) return false;
  796.       } else {
  797. if (value > m_UpperBound) return false;
  798.       }
  799.     }
  800.     return true;
  801.   }
  802.   /**
  803.    * Sets the metadata for the attribute. Processes the strings stored in the
  804.    * metadata of the attribute so that the properties can be set up for the
  805.    * easy-access metadata methods. Any strings sought that are omitted will
  806.    * cause default values to be set.
  807.    * 
  808.    * The following properties are recognised:
  809.    * ordering, averageable, zeropoint, regular, weight, and range.
  810.    *
  811.    * All other properties can be queried and handled appropriately by classes
  812.    * calling the getMetadata() method.
  813.    *
  814.    * @param metadata the metadata
  815.    * @exception IllegalArgumentException if the properties are not consistent
  816.    */
  817.   private void setMetadata(ProtectedProperties metadata) {
  818.     
  819.     m_Metadata = metadata;
  820.     if (m_Type == DATE) {
  821.       m_Ordering = ORDERING_ORDERED;
  822.       m_IsRegular = true;
  823.       m_IsAveragable = false;
  824.       m_HasZeropoint = false;
  825.     } else {
  826.       // get ordering
  827.       String orderString = m_Metadata.getProperty("ordering","");
  828.       
  829.       // numeric ordered attributes are averagable and zeropoint by default
  830.       String def;
  831.       if (m_Type == NUMERIC
  832.   && orderString.compareTo("modulo") != 0
  833.   && orderString.compareTo("symbolic") != 0)
  834. def = "true";
  835.       else def = "false";
  836.       
  837.       // determine boolean states
  838.       m_IsAveragable =
  839. (m_Metadata.getProperty("averageable",def).compareTo("true") == 0);
  840.       m_HasZeropoint =
  841. (m_Metadata.getProperty("zeropoint",def).compareTo("true") == 0);
  842.       // averagable or zeropoint implies regular
  843.       if (m_IsAveragable || m_HasZeropoint) def = "true";
  844.       m_IsRegular =
  845. (m_Metadata.getProperty("regular",def).compareTo("true") == 0);
  846.       
  847.       // determine ordering
  848.       if (orderString.compareTo("symbolic") == 0)
  849. m_Ordering = ORDERING_SYMBOLIC;
  850.       else if (orderString.compareTo("ordered") == 0)
  851. m_Ordering = ORDERING_ORDERED;
  852.       else if (orderString.compareTo("modulo") == 0)
  853. m_Ordering = ORDERING_MODULO;
  854.       else {
  855. if (m_Type == NUMERIC || m_IsAveragable || m_HasZeropoint)
  856.   m_Ordering = ORDERING_ORDERED;
  857. else m_Ordering = ORDERING_SYMBOLIC;
  858.       }
  859.     }
  860.     // consistency checks
  861.     if (m_IsAveragable && !m_IsRegular)
  862.       throw new IllegalArgumentException("An averagable attribute must be"
  863.  + " regular");
  864.     if (m_HasZeropoint && !m_IsRegular)
  865.       throw new IllegalArgumentException("A zeropoint attribute must be"
  866.  + " regular");
  867.     if (m_IsRegular && m_Ordering == ORDERING_SYMBOLIC)
  868.       throw new IllegalArgumentException("A symbolic attribute cannot be"
  869.  + " regular");
  870.     if (m_IsAveragable && m_Ordering != ORDERING_ORDERED)
  871.       throw new IllegalArgumentException("An averagable attribute must be"
  872.  + " ordered");
  873.     if (m_HasZeropoint && m_Ordering != ORDERING_ORDERED)
  874.       throw new IllegalArgumentException("A zeropoint attribute must be"
  875.  + " ordered");
  876.     // determine weight
  877.     m_Weight = 1.0;
  878.     String weightString = m_Metadata.getProperty("weight");
  879.     if (weightString != null) {
  880.       try{
  881. m_Weight = Double.valueOf(weightString).doubleValue();
  882.       } catch (NumberFormatException e) {
  883. // Check if value is really a number
  884. throw new IllegalArgumentException("Not a valid attribute weight: '" 
  885.    + weightString + "'");
  886.       }
  887.     }
  888.     // determine numeric range
  889.     if (m_Type == NUMERIC) setNumericRange(m_Metadata.getProperty("range"));
  890.   }
  891.   /**
  892.    * Sets the numeric range based on a string. If the string is null the range
  893.    * will default to [-inf,+inf]. A square brace represents a closed interval, a
  894.    * curved brace represents an open interval, and 'inf' represents infinity.
  895.    * Examples of valid range strings: "[-inf,20)","(-13.5,-5.2)","(5,inf]"
  896.    *
  897.    * @param rangeString the string to parse as the attribute's numeric range
  898.    * @exception IllegalArgumentException if the range is not valid
  899.    */
  900.   private void setNumericRange(String rangeString)
  901.   {
  902.     // set defaults
  903.     m_LowerBound = Double.NEGATIVE_INFINITY;
  904.     m_LowerBoundIsOpen = false;
  905.     m_UpperBound = Double.POSITIVE_INFINITY;
  906.     m_UpperBoundIsOpen = false;
  907.     if (rangeString == null) return;
  908.     // set up a tokenzier to parse the string
  909.     StreamTokenizer tokenizer =
  910.       new StreamTokenizer(new StringReader(rangeString));
  911.     tokenizer.resetSyntax();         
  912.     tokenizer.whitespaceChars(0, ' ');    
  913.     tokenizer.wordChars(' '+1,'u00FF');
  914.     tokenizer.ordinaryChar('[');
  915.     tokenizer.ordinaryChar('(');
  916.     tokenizer.ordinaryChar(',');
  917.     tokenizer.ordinaryChar(']');
  918.     tokenizer.ordinaryChar(')');
  919.     try {
  920.       // get opening brace
  921.       tokenizer.nextToken();
  922.     
  923.       if (tokenizer.ttype == '[') m_LowerBoundIsOpen = false;
  924.       else if (tokenizer.ttype == '(') m_LowerBoundIsOpen = true;
  925.       else throw new IllegalArgumentException("Expected opening brace on range,"
  926.       + " found: "
  927.       + tokenizer.toString());
  928.       // get lower bound
  929.       tokenizer.nextToken();
  930.       if (tokenizer.ttype != tokenizer.TT_WORD)
  931. throw new IllegalArgumentException("Expected lower bound in range,"
  932.    + " found: "
  933.    + tokenizer.toString());
  934.       if (tokenizer.sval.compareToIgnoreCase("-inf") == 0)
  935. m_LowerBound = Double.NEGATIVE_INFINITY;
  936.       else if (tokenizer.sval.compareToIgnoreCase("+inf") == 0)
  937. m_LowerBound = Double.POSITIVE_INFINITY;
  938.       else if (tokenizer.sval.compareToIgnoreCase("inf") == 0)
  939. m_LowerBound = Double.NEGATIVE_INFINITY;
  940.       else try {
  941. m_LowerBound = Double.valueOf(tokenizer.sval).doubleValue();
  942.       } catch (NumberFormatException e) {
  943. throw new IllegalArgumentException("Expected lower bound in range,"
  944.    + " found: '" + tokenizer.sval + "'");
  945.       }
  946.       // get separating comma
  947.       if (tokenizer.nextToken() != ',')
  948. throw new IllegalArgumentException("Expected comma in range,"
  949.    + " found: "
  950.    + tokenizer.toString());
  951.       // get upper bound
  952.       tokenizer.nextToken();
  953.       if (tokenizer.ttype != tokenizer.TT_WORD)
  954. throw new IllegalArgumentException("Expected upper bound in range,"
  955.    + " found: "
  956.    + tokenizer.toString());
  957.       if (tokenizer.sval.compareToIgnoreCase("-inf") == 0)
  958. m_UpperBound = Double.NEGATIVE_INFINITY;
  959.       else if (tokenizer.sval.compareToIgnoreCase("+inf") == 0)
  960. m_UpperBound = Double.POSITIVE_INFINITY;
  961.       else if (tokenizer.sval.compareToIgnoreCase("inf") == 0)
  962. m_UpperBound = Double.POSITIVE_INFINITY;
  963.       else try {
  964. m_UpperBound = Double.valueOf(tokenizer.sval).doubleValue();
  965.       } catch (NumberFormatException e) {
  966. throw new IllegalArgumentException("Expected upper bound in range,"
  967.    + " found: '" + tokenizer.sval + "'");
  968.       }
  969.       // get closing brace
  970.       tokenizer.nextToken();
  971.     
  972.       if (tokenizer.ttype == ']') m_UpperBoundIsOpen = false;
  973.       else if (tokenizer.ttype == ')') m_UpperBoundIsOpen = true;
  974.       else throw new IllegalArgumentException("Expected closing brace on range,"
  975.       + " found: "
  976.       + tokenizer.toString());
  977.       // check for rubbish on end
  978.       if (tokenizer.nextToken() != tokenizer.TT_EOF)
  979. throw new IllegalArgumentException("Expected end of range string,"
  980.    + " found: "
  981.    + tokenizer.toString());
  982.     } catch (IOException e) {
  983.       throw new IllegalArgumentException("IOException reading attribute range"
  984.  + " string: " + e.getMessage());
  985.     }
  986.     if (m_UpperBound < m_LowerBound)
  987.       throw new IllegalArgumentException("Upper bound (" + m_UpperBound
  988.  + ") on numeric range is"
  989.  + " less than lower bound ("
  990.  + m_LowerBound + ")!");
  991.   }
  992.   /**
  993.    * Simple main method for testing this class.
  994.    */
  995.   public static void main(String[] ops) {
  996.     try {
  997.       
  998.       // Create numeric attributes "length" and "weight"
  999.       Attribute length = new Attribute("length");
  1000.       Attribute weight = new Attribute("weight");
  1001.       // Create date attribute "date"
  1002.       Attribute date = new Attribute("date", "yyyy-MM-dd HH:mm:ss");
  1003.       System.out.println(date);
  1004.       double dd = date.parseDate("2001-04-04 14:13:55");
  1005.       System.out.println("Test date = " + dd);
  1006.       System.out.println(date.formatDate(dd));
  1007.       dd = new Date().getTime();
  1008.       System.out.println("Date now = " + dd);
  1009.       System.out.println(date.formatDate(dd));
  1010.       
  1011.       // Create vector to hold nominal values "first", "second", "third" 
  1012.       FastVector my_nominal_values = new FastVector(3); 
  1013.       my_nominal_values.addElement("first"); 
  1014.       my_nominal_values.addElement("second"); 
  1015.       my_nominal_values.addElement("third"); 
  1016.       
  1017.       // Create nominal attribute "position" 
  1018.       Attribute position = new Attribute("position", my_nominal_values);
  1019.       // Print the name of "position"
  1020.       System.out.println("Name of "position": " + position.name());
  1021.       // Print the values of "position"
  1022.       Enumeration attValues = position.enumerateValues();
  1023.       while (attValues.hasMoreElements()) {
  1024. String string = (String)attValues.nextElement();
  1025. System.out.println("Value of "position": " + string);
  1026.       }
  1027.       // Shallow copy attribute "position"
  1028.       Attribute copy = (Attribute) position.copy();
  1029.       // Test if attributes are the same
  1030.       System.out.println("Copy is the same as original: " + copy.equals(position));
  1031.       // Print index of attribute "weight" (should be unset: -1)
  1032.       System.out.println("Index of attribute "weight" (should be -1): " + 
  1033.  weight.index());
  1034.       // Print index of value "first" of attribute "position"
  1035.       System.out.println("Index of value "first" of "position" (should be 0): " +
  1036.  position.indexOfValue("first"));
  1037.       // Tests type of attribute "position"
  1038.       System.out.println(""position" is numeric: " + position.isNumeric());
  1039.       System.out.println(""position" is nominal: " + position.isNominal());
  1040.       System.out.println(""position" is string: " + position.isString());
  1041.       // Prints name of attribute "position"
  1042.       System.out.println("Name of "position": " + position.name());
  1043.     
  1044.       // Prints number of values of attribute "position"
  1045.       System.out.println("Number of values for "position": " + position.numValues());
  1046.       // Prints the values (againg)
  1047.       for (int i = 0; i < position.numValues(); i++) {
  1048. System.out.println("Value " + i + ": " + position.value(i));
  1049.       }
  1050.       // Prints the attribute "position" in ARFF format
  1051.       System.out.println(position);
  1052.       // Checks type of attribute "position" using constants
  1053.       switch (position.type()) {
  1054.       case Attribute.NUMERIC:
  1055. System.out.println(""position" is numeric");
  1056. break;
  1057.       case Attribute.NOMINAL:
  1058. System.out.println(""position" is nominal");
  1059. break;
  1060.       case Attribute.STRING:
  1061. System.out.println(""position" is string");
  1062. break;
  1063.       case Attribute.DATE:
  1064. System.out.println(""position" is date");
  1065. break;
  1066.       default:
  1067. System.out.println(""position" has unknown type");
  1068.       }
  1069.     } catch (Exception e) {
  1070.       e.printStackTrace();
  1071.     }
  1072.   }
  1073. }
  1074.