source: src/main/java/weka/core/Attribute.java @ 14

Last change on this file since 14 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 49.9 KB
Line 
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/*
18 *    Attribute.java
19 *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
20 *
21 */
22
23package weka.core;
24
25import java.io.IOException;
26import java.io.Serializable;
27import java.io.StreamTokenizer;
28import java.io.StringReader;
29import java.text.ParseException;
30import java.text.SimpleDateFormat;
31import java.util.Date;
32import java.util.Enumeration;
33import java.util.Hashtable;
34import java.util.Properties;
35import java.util.List;
36import java.util.ArrayList;
37
38/**
39 * Class for handling an attribute. Once an attribute has been created,
40 * it can't be changed. <p>
41 *
42 * The following attribute types are supported:
43 * <ul>
44 *    <li> numeric: <br/>
45 *         This type of attribute represents a floating-point number.
46 *    </li>
47 *    <li> nominal: <br/>
48 *         This type of attribute represents a fixed set of nominal values.
49 *    </li>
50 *    <li> string: <br/>
51 *         This type of attribute represents a dynamically expanding set of
52 *         nominal values. Usually used in text classification.
53 *    </li>
54 *    <li> date: <br/>
55 *         This type of attribute represents a date, internally represented as
56 *         floating-point number storing the milliseconds since January 1,
57 *         1970, 00:00:00 GMT. The string representation of the date must be
58 *         <a href="http://www.iso.org/iso/en/prods-services/popstds/datesandtime.html" target="_blank">
59 *         ISO-8601</a> compliant, the default is <code>yyyy-MM-dd'T'HH:mm:ss</code>.
60 *    </li>
61 *    <li> relational: <br/>
62 *         This type of attribute can contain other attributes and is, e.g.,
63 *         used for representing Multi-Instance data. (Multi-Instance data
64 *         consists of a nominal attribute containing the bag-id, then a
65 *         relational attribute with all the attributes of the bag, and
66 *         finally the class attribute.)
67 *    </li>
68 * </ul>
69 *
70 * Typical usage (code from the main() method of this class): <p>
71 *
72 * <code>
73 * ... <br>
74 *
75 * // Create numeric attributes "length" and "weight" <br>
76 * Attribute length = new Attribute("length"); <br>
77 * Attribute weight = new Attribute("weight"); <br><br>
78 *
79 * // Create list to hold nominal values "first", "second", "third" <br>
80 * List<String> my_nominal_values = new ArrayList<String>(3); <br>
81 * my_nominal_values.add("first"); <br>
82 * my_nominal_values.add("second"); <br>
83 * my_nominal_values.add("third"); <br><br>
84 *
85 * // Create nominal attribute "position" <br>
86 * Attribute position = new Attribute("position", my_nominal_values);<br>
87 *
88 * ... <br>
89 * </code><p>
90 *
91 * @author Eibe Frank (eibe@cs.waikato.ac.nz)
92 * @version $Revision: 5987 $
93 */
94public class Attribute
95  implements Copyable, Serializable, RevisionHandler {
96
97  /** for serialization */
98  static final long serialVersionUID = -742180568732916383L;
99 
100  /** Constant set for numeric attributes. */
101  public static final int NUMERIC = 0;
102
103  /** Constant set for nominal attributes. */
104  public static final int NOMINAL = 1;
105
106  /** Constant set for attributes with string values. */
107  public static final int STRING = 2;
108
109  /** Constant set for attributes with date values. */
110  public static final int DATE = 3;
111
112  /** Constant set for relation-valued attributes. */
113  public static final int RELATIONAL = 4;
114
115  /** Constant set for symbolic attributes. */
116  public static final int ORDERING_SYMBOLIC = 0;
117
118  /** Constant set for ordered attributes. */
119  public static final int ORDERING_ORDERED  = 1;
120
121  /** Constant set for modulo-ordered attributes. */
122  public static final int ORDERING_MODULO   = 2;
123
124  /** The keyword used to denote the start of an arff attribute declaration */
125  public final static String ARFF_ATTRIBUTE = "@attribute";
126
127  /** A keyword used to denote a numeric attribute */
128  public final static String ARFF_ATTRIBUTE_INTEGER = "integer";
129
130  /** A keyword used to denote a numeric attribute */
131  public final static String ARFF_ATTRIBUTE_REAL = "real";
132
133  /** A keyword used to denote a numeric attribute */
134  public final static String ARFF_ATTRIBUTE_NUMERIC = "numeric";
135
136  /** The keyword used to denote a string attribute */
137  public final static String ARFF_ATTRIBUTE_STRING = "string";
138
139  /** The keyword used to denote a date attribute */
140  public final static String ARFF_ATTRIBUTE_DATE = "date";
141
142  /** The keyword used to denote a relation-valued attribute */
143  public final static String ARFF_ATTRIBUTE_RELATIONAL = "relational";
144
145  /** The keyword used to denote the end of the declaration of a subrelation */
146  public final static String ARFF_END_SUBRELATION = "@end";
147
148  /** Strings longer than this will be stored compressed. */
149  private static final int STRING_COMPRESS_THRESHOLD = 200;
150
151  /** The attribute's name. */
152  private /*@ spec_public non_null @*/ String m_Name;
153
154  /** The attribute's type. */
155  private /*@ spec_public @*/ int m_Type;
156  /*@ invariant m_Type == NUMERIC ||
157                m_Type == DATE ||
158                m_Type == STRING ||
159                m_Type == NOMINAL ||
160                m_Type == RELATIONAL;
161  */
162
163  /** The attribute's values (if nominal or string). */
164  private /*@ spec_public @*/ ArrayList<Object> m_Values;
165
166  /** Mapping of values to indices (if nominal or string). */
167  private Hashtable<Object,Integer> m_Hashtable;
168
169  /** The header information for a relation-valued attribute. */
170  private Instances m_Header;
171
172  /** Date format specification for date attributes */
173  private SimpleDateFormat m_DateFormat;
174
175  /** The attribute's index. */
176  private /*@ spec_public @*/ int m_Index;
177
178  /** The attribute's metadata. */
179  private ProtectedProperties m_Metadata;
180
181  /** The attribute's ordering. */
182  private int m_Ordering;
183
184  /** Whether the attribute is regular. */
185  private boolean m_IsRegular;
186
187  /** Whether the attribute is averagable. */
188  private boolean m_IsAveragable;
189
190  /** Whether the attribute has a zeropoint. */
191  private boolean m_HasZeropoint;
192
193  /** The attribute's weight. */
194  private double m_Weight;
195
196  /** The attribute's lower numeric bound. */
197  private double m_LowerBound;
198
199  /** Whether the lower bound is open. */
200  private boolean m_LowerBoundIsOpen;
201
202  /** The attribute's upper numeric bound. */
203  private double m_UpperBound;
204
205  /** Whether the upper bound is open */
206  private boolean m_UpperBoundIsOpen;
207
208  /**
209   * Constructor for a numeric attribute.
210   *
211   * @param attributeName the name for the attribute
212   */
213  //@ requires attributeName != null;
214  //@ ensures  m_Name == attributeName;
215  public Attribute(String attributeName) {
216
217    this(attributeName, new ProtectedProperties(new Properties()));
218  }
219
220  /**
221   * Constructor for a numeric attribute, where metadata is supplied.
222   *
223   * @param attributeName the name for the attribute
224   * @param metadata the attribute's properties
225   */
226  //@ requires attributeName != null;
227  //@ requires metadata != null;
228  //@ ensures  m_Name == attributeName;
229  public Attribute(String attributeName, ProtectedProperties metadata) {
230
231    m_Name = attributeName;
232    m_Index = -1;
233    m_Values = null;
234    m_Hashtable = null;
235    m_Header = null;
236    m_Type = NUMERIC;
237    setMetadata(metadata);
238  }
239
240  /**
241   * Constructor for a date attribute.
242   *
243   * @param attributeName the name for the attribute
244   * @param dateFormat a string suitable for use with
245   * SimpleDateFormatter for parsing dates.
246   */
247  //@ requires attributeName != null;
248  //@ requires dateFormat != null;
249  //@ ensures  m_Name == attributeName;
250  public Attribute(String attributeName, String dateFormat) {
251
252    this(attributeName, dateFormat,
253         new ProtectedProperties(new Properties()));
254  }
255
256  /**
257   * Constructor for a date attribute, where metadata is supplied.
258   *
259   * @param attributeName the name for the attribute
260   * @param dateFormat a string suitable for use with
261   * SimpleDateFormatter for parsing dates.
262   * @param metadata the attribute's properties
263   */
264  //@ requires attributeName != null;
265  //@ requires dateFormat != null;
266  //@ requires metadata != null;
267  //@ ensures  m_Name == attributeName;
268  public Attribute(String attributeName, String dateFormat,
269                   ProtectedProperties metadata) {
270
271    m_Name = attributeName;
272    m_Index = -1;
273    m_Values = null;
274    m_Hashtable = null;
275    m_Header = null;
276    m_Type = DATE;
277    if (dateFormat != null) {
278      m_DateFormat = new SimpleDateFormat(dateFormat);
279    } else {
280      m_DateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
281    }
282    m_DateFormat.setLenient(false);
283    setMetadata(metadata);
284  }
285
286  /**
287   * Constructor for nominal attributes and string attributes.
288   * If a null vector of attribute values is passed to the method,
289   * the attribute is assumed to be a string.
290   *
291   * @param attributeName the name for the attribute
292   * @param attributeValues a vector of strings denoting the
293   * attribute values. Null if the attribute is a string attribute.
294   */
295  //@ requires attributeName != null;
296  //@ ensures  m_Name == attributeName;
297  public Attribute(String attributeName, 
298                   List<String> attributeValues) {
299
300    this(attributeName, attributeValues,
301         new ProtectedProperties(new Properties()));
302  }
303
304  /**
305   * Constructor for nominal attributes and string attributes, where
306   * metadata is supplied. If a null vector of attribute values is passed
307   * to the method, the attribute is assumed to be a string.
308   *
309   * @param attributeName the name for the attribute
310   * @param attributeValues a vector of strings denoting the
311   * attribute values. Null if the attribute is a string attribute.
312   * @param metadata the attribute's properties
313   */
314  //@ requires attributeName != null;
315  //@ requires metadata != null;
316  /*@ ensures  m_Name == attributeName;
317      ensures  m_Index == -1;
318      ensures  attributeValues == null && m_Type == STRING
319            || attributeValues != null && m_Type == NOMINAL
320                  && m_Values.size() == attributeValues.size();
321      signals (IllegalArgumentException ex)
322                 (* if duplicate strings in attributeValues *);
323  */
324  public Attribute(String attributeName, 
325                   List<String> attributeValues,
326                   ProtectedProperties metadata) {
327
328    m_Name = attributeName;
329    m_Index = -1;
330    if (attributeValues == null) {
331      m_Values = new ArrayList<Object>();
332      m_Hashtable = new Hashtable<Object,Integer>();
333      m_Header = null;
334      m_Type = STRING;
335    } else {
336      m_Values = new ArrayList<Object>(attributeValues.size());
337      m_Hashtable = new Hashtable<Object,Integer>(attributeValues.size());
338      m_Header = null;
339      for (int i = 0; i < attributeValues.size(); i++) {
340        Object store = attributeValues.get(i);
341        if (((String)store).length() > STRING_COMPRESS_THRESHOLD) {
342          try {
343            store = new SerializedObject(attributeValues.get(i), true);
344          } catch (Exception ex) {
345            System.err.println("Couldn't compress nominal attribute value -"
346                               + " storing uncompressed.");
347          }
348        }
349        if (m_Hashtable.containsKey(store)) {
350          throw new IllegalArgumentException("A nominal attribute (" +
351                                             attributeName + ") cannot"
352                                             + " have duplicate labels (" + store + ").");
353        }
354        m_Values.add(store);
355        m_Hashtable.put(store, new Integer(i));
356      }
357      m_Type = NOMINAL;
358    }
359    setMetadata(metadata);
360  }
361
362  /**
363   * Constructor for relation-valued attributes.
364   *
365   * @param attributeName the name for the attribute
366   * @param header an Instances object specifying the header of the relation.
367   */
368  public Attribute(String attributeName, Instances header) {
369
370    this(attributeName, header,
371         new ProtectedProperties(new Properties()));
372  }
373
374  /**
375   * Constructor for relation-valued attributes.
376   *
377   * @param attributeName the name for the attribute
378   * @param header an Instances object specifying the header of the relation.
379   * @param metadata the attribute's properties
380   */
381  public Attribute(String attributeName, 
382                   Instances header,
383                   ProtectedProperties metadata) {
384
385    if (header.numInstances() > 0) {
386      throw new IllegalArgumentException("Header for relation-valued " +
387                                         "attribute should not contain " +
388                                         "any instances");
389    }
390    m_Name = attributeName;
391    m_Index = -1;
392    m_Values = new ArrayList<Object>();
393    m_Hashtable = new Hashtable<Object,Integer>();
394    m_Header = header;
395    m_Type = RELATIONAL;
396    setMetadata(metadata);
397  }
398
399  /**
400   * Produces a shallow copy of this attribute.
401   *
402   * @return a copy of this attribute with the same index
403   */
404  //@ also ensures \result instanceof Attribute;
405  public /*@ pure non_null @*/ Object copy() {
406
407    Attribute copy = new Attribute(m_Name);
408
409    copy.m_Index = m_Index;
410    copy.m_Type = m_Type;
411    copy.m_Values = m_Values;
412    copy.m_Hashtable = m_Hashtable;
413    copy.m_DateFormat = m_DateFormat;
414    copy.m_Header = m_Header;
415    copy.setMetadata(m_Metadata);
416 
417    return copy;
418  }
419
420  /**
421   * Returns an enumeration of all the attribute's values if the
422   * attribute is nominal, string, or relation-valued, null otherwise.
423   *
424   * @return enumeration of all the attribute's values
425   */
426  public final /*@ pure @*/ Enumeration enumerateValues() {
427
428    if (isNominal() || isString()) {
429      final Enumeration ee = new WekaEnumeration(m_Values);
430      return new Enumeration () {
431          public boolean hasMoreElements() {
432            return ee.hasMoreElements();
433          }
434          public Object nextElement() {
435            Object oo = ee.nextElement();
436            if (oo instanceof SerializedObject) {
437              return ((SerializedObject)oo).getObject();
438            } else {
439              return oo;
440            }
441          }
442        };
443    }
444    return null;
445  }
446
447  /**
448   * Tests if given attribute is equal to this attribute.
449   *
450   * @param other the Object to be compared to this attribute
451   * @return true if the given attribute is equal to this attribute
452   */
453  public final /*@ pure @*/ boolean equals(Object other) {
454    return (equalsMsg(other) == null);
455  }
456
457  /**
458   * Tests if given attribute is equal to this attribute. If they're not
459   * the same a message detailing why they differ will be returned, otherwise
460   * null.
461   *
462   * @param other       the Object to be compared to this attribute
463   * @return            null if the given attribute is equal to this attribute
464   */
465  public final String equalsMsg(Object other) {
466    if (other == null)
467      return "Comparing with null object";
468   
469    if (!(other.getClass().equals(this.getClass())))
470      return "Object has wrong class";
471   
472    Attribute att = (Attribute) other;
473    if (!m_Name.equals(att.m_Name))
474      return "Names differ: " + m_Name + " != " + att.m_Name;
475
476    if (isNominal() && att.isNominal()) {
477      if (m_Values.size() != att.m_Values.size())
478        return "Different number of labels: " + m_Values.size() + " != " + att.m_Values.size();
479     
480      for (int i = 0; i < m_Values.size(); i++) {
481        if (!m_Values.get(i).equals(att.m_Values.get(i)))
482          return "Labels differ at position " + (i+1) + ": " + m_Values.get(i) + " != " + att.m_Values.get(i);
483      }
484     
485      return null;
486    } 
487   
488    if (isRelationValued() && att.isRelationValued())
489      return m_Header.equalHeadersMsg(att.m_Header);
490   
491    if ((type() != att.type()))
492      return "Types differ: " + typeToString(this) + " != " + typeToString(att);
493   
494    return null;
495  }
496 
497  /**
498   * Returns a string representation of the attribute type.
499   *
500   * @param att         the attribute to return the type string for
501   * @return            the string representation of the attribute type
502   */
503  public static String typeToString(Attribute att) {
504    return typeToString(att.type());
505  }
506 
507  /**
508   * Returns a string representation of the attribute type.
509   *
510   * @param type        the type of the attribute
511   * @return            the string representation of the attribute type
512   */
513  public static String typeToString(int type) {
514    String      result;
515   
516    switch(type) {
517      case NUMERIC:
518        result = "numeric";
519        break;
520       
521      case NOMINAL:
522        result = "nominal";
523        break;
524       
525      case STRING:
526        result = "string";
527        break;
528       
529      case DATE:
530        result = "date";
531        break;
532       
533      case RELATIONAL:
534        result = "relational";
535        break;
536       
537      default:
538        result = "unknown(" + type + ")";
539    }
540   
541    return result;
542  }
543
544  /**
545   * Returns the index of this attribute.
546   *
547   * @return the index of this attribute
548   */
549  //@ ensures \result == m_Index;
550  public final /*@ pure @*/ int index() {
551
552    return m_Index;
553  }
554
555  /**
556   * Returns the index of a given attribute value. (The index of
557   * the first occurence of this value.)
558   *
559   * @param value the value for which the index is to be returned
560   * @return the index of the given attribute value if attribute
561   * is nominal or a string, -1 if it is not or the value
562   * can't be found
563   */
564  public final int indexOfValue(String value) {
565
566    if (!isNominal() && !isString())
567      return -1;
568    Object store = value;
569    if (value.length() > STRING_COMPRESS_THRESHOLD) {
570      try {
571        store = new SerializedObject(value, true);
572      } catch (Exception ex) {
573        System.err.println("Couldn't compress string attribute value -"
574                           + " searching uncompressed.");
575      }
576    }
577    Integer val = (Integer)m_Hashtable.get(store);
578    if (val == null) return -1;
579    else return val.intValue();
580  }
581
582  /**
583   * Test if the attribute is nominal.
584   *
585   * @return true if the attribute is nominal
586   */
587  //@ ensures \result <==> (m_Type == NOMINAL);
588  public final /*@ pure @*/ boolean isNominal() {
589
590    return (m_Type == NOMINAL);
591  }
592
593  /**
594   * Tests if the attribute is numeric.
595   *
596   * @return true if the attribute is numeric
597   */
598  //@ ensures \result <==> ((m_Type == NUMERIC) || (m_Type == DATE));
599  public final /*@ pure @*/ boolean isNumeric() {
600
601    return ((m_Type == NUMERIC) || (m_Type == DATE));
602  }
603
604  /**
605   * Tests if the attribute is relation valued.
606   *
607   * @return true if the attribute is relation valued
608   */
609  //@ ensures \result <==> (m_Type == RELATIONAL);
610  public final /*@ pure @*/ boolean isRelationValued() {
611
612    return (m_Type == RELATIONAL);
613  }
614
615  /**
616   * Tests if the attribute is a string.
617   *
618   * @return true if the attribute is a string
619   */
620  //@ ensures \result <==> (m_Type == STRING);
621  public final /*@ pure @*/ boolean isString() {
622
623    return (m_Type == STRING);
624  }
625
626  /**
627   * Tests if the attribute is a date type.
628   *
629   * @return true if the attribute is a date type
630   */
631  //@ ensures \result <==> (m_Type == DATE);
632  public final /*@ pure @*/ boolean isDate() {
633
634    return (m_Type == DATE);
635  }
636
637  /**
638   * Returns the attribute's name.
639   *
640   * @return the attribute's name as a string
641   */
642  //@ ensures \result == m_Name;
643  public final /*@ pure @*/ String name() {
644
645    return m_Name;
646  }
647 
648  /**
649   * Returns the number of attribute values. Returns 0 for
650   * attributes that are not either nominal, string, or
651   * relation-valued.
652   *
653   * @return the number of attribute values
654   */
655  public final /*@ pure @*/ int numValues() {
656
657    if (!isNominal() && !isString() && !isRelationValued()) {
658      return 0;
659    } else {
660      return m_Values.size();
661    }
662  }
663
664  /**
665   * Returns a description of this attribute in ARFF format. Quotes
666   * strings if they contain whitespace characters, or if they
667   * are a question mark.
668   *
669   * @return a description of this attribute as a string
670   */
671  public final String toString() {
672   
673    StringBuffer text = new StringBuffer();
674   
675    text.append(ARFF_ATTRIBUTE).append(" ").append(Utils.quote(m_Name)).append(" ");
676    switch (m_Type) {
677    case NOMINAL:
678      text.append('{');
679      Enumeration enu = enumerateValues();
680      while (enu.hasMoreElements()) {
681        text.append(Utils.quote((String) enu.nextElement()));
682        if (enu.hasMoreElements())
683          text.append(',');
684      }
685      text.append('}');
686      break;
687    case NUMERIC:
688      text.append(ARFF_ATTRIBUTE_NUMERIC);
689      break;
690    case STRING:
691      text.append(ARFF_ATTRIBUTE_STRING);
692      break;
693    case DATE:
694      text.append(ARFF_ATTRIBUTE_DATE).append(" ").append(Utils.quote(m_DateFormat.toPattern()));
695      break;
696    case RELATIONAL:
697      text.append(ARFF_ATTRIBUTE_RELATIONAL).append("\n");
698      Enumeration enm = m_Header.enumerateAttributes();
699      while (enm.hasMoreElements()) {
700        text.append(enm.nextElement()).append("\n");
701      }
702      text.append(ARFF_END_SUBRELATION).append(" ").append(Utils.quote(m_Name));
703      break;
704    default:
705      text.append("UNKNOWN");
706      break;
707    }
708    return text.toString();
709  }
710
711  /**
712   * Returns the attribute's type as an integer.
713   *
714   * @return the attribute's type.
715   */
716  //@ ensures \result == m_Type;
717  public final /*@ pure @*/ int type() {
718
719    return m_Type;
720  }
721 
722  /**
723   * Returns the Date format pattern in case this attribute is of type DATE,
724   * otherwise an empty string.
725   *
726   * @return the date format pattern
727   * @see SimpleDateFormat
728   */
729  public final String getDateFormat() {
730    if (isDate())
731      return m_DateFormat.toPattern();
732    else
733      return "";
734  }
735
736  /**
737   * Returns a value of a nominal or string attribute.  Returns an
738   * empty string if the attribute is neither a string nor a nominal
739   * attribute.
740   *
741   * @param valIndex the value's index
742   * @return the attribute's value as a string
743   */
744  public final /*@ non_null pure @*/ String value(int valIndex) {
745   
746    if (!isNominal() && !isString()) {
747      return "";
748    } else {
749      Object val = m_Values.get(valIndex);
750     
751      // If we're storing strings compressed, uncompress it.
752      if (val instanceof SerializedObject) {
753        val = ((SerializedObject)val).getObject();
754      }
755      return (String) val;
756    }
757  }
758
759  /**
760   * Returns the header info for a relation-valued attribute,
761   * null if the attribute is not relation-valued.
762   *
763   * @return the attribute's value as an Instances object
764   */
765  public final /*@ non_null pure @*/ Instances relation() {
766   
767    if (!isRelationValued()) {
768      return null;
769    } else {
770      return m_Header;
771    }
772  }
773
774  /**
775   * Returns a value of a relation-valued attribute. Returns
776   * null if the attribute is not relation-valued.
777   *
778   * @param valIndex the value's index
779   * @return the attribute's value as an Instances object
780   */
781  public final /*@ non_null pure @*/ Instances relation(int valIndex) {
782   
783    if (!isRelationValued()) {
784      return null;
785    } else {
786      return (Instances) m_Values.get(valIndex);
787    }
788  }
789
790  /**
791   * Constructor for a numeric attribute with a particular index.
792   *
793   * @param attributeName the name for the attribute
794   * @param index the attribute's index
795   */
796  //@ requires attributeName != null;
797  //@ requires index >= 0;
798  //@ ensures  m_Name == attributeName;
799  //@ ensures  m_Index == index;
800  public Attribute(String attributeName, int index) {
801
802    this(attributeName);
803    m_Index = index;
804  }
805
806  /**
807   * Constructor for date attributes with a particular index.
808   *
809   * @param attributeName the name for the attribute
810   * @param dateFormat a string suitable for use with
811   * SimpleDateFormatter for parsing dates.  Null for a default format
812   * string.
813   * @param index the attribute's index
814   */
815  //@ requires attributeName != null;
816  //@ requires index >= 0;
817  //@ ensures  m_Name == attributeName;
818  //@ ensures  m_Index == index;
819  public Attribute(String attributeName, String dateFormat, 
820            int index) {
821
822    this(attributeName, dateFormat);
823    m_Index = index;
824  }
825
826  /**
827   * Constructor for nominal attributes and string attributes with
828   * a particular index.
829   * If a null vector of attribute values is passed to the method,
830   * the attribute is assumed to be a string.
831   *
832   * @param attributeName the name for the attribute
833   * @param attributeValues a vector of strings denoting the attribute values.
834   * Null if the attribute is a string attribute.
835   * @param index the attribute's index
836   */
837  //@ requires attributeName != null;
838  //@ requires index >= 0;
839  //@ ensures  m_Name == attributeName;
840  //@ ensures  m_Index == index;
841  public Attribute(String attributeName, List<String> attributeValues, 
842            int index) {
843
844    this(attributeName, attributeValues);
845    m_Index = index;
846  }
847
848  /**
849   * Constructor for a relation-valued attribute with a particular index.
850   *
851   * @param attributeName the name for the attribute
852   * @param header the header information for this attribute
853   * @param index the attribute's index
854   */
855  //@ requires attributeName != null;
856  //@ requires index >= 0;
857  //@ ensures  m_Name == attributeName;
858  //@ ensures  m_Index == index;
859  public Attribute(String attributeName, Instances header,
860            int index) {
861
862    this(attributeName, header);
863    m_Index = index;
864  }
865
866  /**
867   * Adds a string value to the list of valid strings for attributes
868   * of type STRING and returns the index of the string.
869   *
870   * @param value The string value to add
871   * @return the index assigned to the string, or -1 if the attribute is not
872   * of type Attribute.STRING
873   */
874  /*@ requires value != null;
875      ensures  isString() && 0 <= \result && \result < m_Values.size() ||
876             ! isString() && \result == -1;
877  */
878  public int addStringValue(String value) {
879
880    if (!isString()) {
881      return -1;
882    }
883    Object store = value;
884
885    if (value.length() > STRING_COMPRESS_THRESHOLD) {
886      try {
887        store = new SerializedObject(value, true);
888      } catch (Exception ex) {
889        System.err.println("Couldn't compress string attribute value -"
890                           + " storing uncompressed.");
891      }
892    }
893    Integer index = (Integer)m_Hashtable.get(store);
894    if (index != null) {
895      return index.intValue();
896    } else {
897      int intIndex = m_Values.size();
898      m_Values.add(store);
899      m_Hashtable.put(store, new Integer(intIndex));
900      return intIndex;
901    }
902  }
903
904  /**
905   * Adds a string value to the list of valid strings for attributes
906   * of type STRING and returns the index of the string. This method is
907   * more efficient than addStringValue(String) for long strings.
908   *
909   * @param src The Attribute containing the string value to add.
910   * @param index the index of the string value in the source attribute.
911   * @return the index assigned to the string, or -1 if the attribute is not
912   * of type Attribute.STRING
913   */
914  /*@ requires src != null;
915      requires 0 <= index && index < src.m_Values.size();
916      ensures  isString() && 0 <= \result && \result < m_Values.size() ||
917             ! isString() && \result == -1;
918  */
919  public int addStringValue(Attribute src, int index) {
920
921    if (!isString()) {
922      return -1;
923    }
924    Object store = src.m_Values.get(index);
925    Integer oldIndex = (Integer)m_Hashtable.get(store);
926    if (oldIndex != null) {
927      return oldIndex.intValue();
928    } else {
929      int intIndex = m_Values.size();
930      m_Values.add(store);
931      m_Hashtable.put(store, new Integer(intIndex));
932      return intIndex;
933    }
934  }
935
936  /**
937   * Adds a relation to a relation-valued attribute.
938   *
939   * @param value The value to add
940   * @return the index assigned to the value, or -1 if the attribute is not
941   * of type Attribute.RELATIONAL
942   */
943  public int addRelation(Instances value) {
944
945    if (!isRelationValued()) {
946      return -1;
947    }
948    if (!m_Header.equalHeaders(value)) {
949      throw new IllegalArgumentException("Incompatible value for " +
950                                         "relation-valued attribute.\n" + 
951                                         m_Header.equalHeadersMsg(value));
952    }
953    Integer index = (Integer)m_Hashtable.get(value);
954    if (index != null) {
955      return index.intValue();
956    } else {
957      int intIndex = m_Values.size();
958      m_Values.add(value);
959      m_Hashtable.put(value, new Integer(intIndex));
960      return intIndex;
961    }
962  }
963
964  /**
965   * Adds an attribute value. Creates a fresh list of attribute
966   * values before adding it.
967   *
968   * @param value the attribute value
969   */
970  final void addValue(String value) {
971
972    m_Values = Utils.cast(m_Values.clone());
973    m_Hashtable = Utils.cast(m_Hashtable.clone());
974    forceAddValue(value);
975  }
976
977  /**
978   * Produces a shallow copy of this attribute with a new name.
979   *
980   * @param newName the name of the new attribute
981   * @return a copy of this attribute with the same index
982   */
983  //@ requires newName != null;
984  //@ ensures \result.m_Name  == newName;
985  //@ ensures \result.m_Index == m_Index;
986  //@ ensures \result.m_Type  == m_Type;
987  public final /*@ pure non_null @*/ Attribute copy(String newName) {
988
989    Attribute copy = new Attribute(newName);
990
991    copy.m_Index = m_Index;
992    copy.m_DateFormat = m_DateFormat;
993    copy.m_Type = m_Type;
994    copy.m_Values = m_Values;
995    copy.m_Hashtable = m_Hashtable;
996    copy.m_Header = m_Header;
997    copy.setMetadata(m_Metadata);
998 
999    return copy;
1000  }
1001
1002  /**
1003   * Removes a value of a nominal, string, or relation-valued
1004   * attribute. Creates a fresh list of attribute values before
1005   * removing it.
1006   *
1007   * @param index the value's index
1008   * @throws IllegalArgumentException if the attribute is not
1009   * of the correct type
1010   */
1011  //@ requires isNominal() || isString() || isRelationValued();
1012  //@ requires 0 <= index && index < m_Values.size();
1013  final void delete(int index) {
1014   
1015    if (!isNominal() && !isString() && !isRelationValued()) 
1016      throw new IllegalArgumentException("Can only remove value of " +
1017                                         "nominal, string or relation-" +
1018                                         " valued attribute!");
1019    else {
1020      m_Values = Utils.cast(m_Values.clone());
1021      m_Values.remove(index);
1022      if (!isRelationValued()) {
1023        Hashtable<Object,Integer> hash = new Hashtable<Object,Integer>(m_Hashtable.size());
1024        Enumeration enu = m_Hashtable.keys();
1025        while (enu.hasMoreElements()) {
1026          Object string = enu.nextElement();
1027          Integer valIndexObject = (Integer)m_Hashtable.get(string);
1028          int valIndex = valIndexObject.intValue();
1029          if (valIndex > index) {
1030            hash.put(string, new Integer(valIndex - 1));
1031          } else if (valIndex < index) {
1032            hash.put(string, valIndexObject);
1033          }
1034        }
1035        m_Hashtable = hash;
1036      }
1037    }
1038  }
1039
1040  /**
1041   * Adds an attribute value.
1042   *
1043   * @param value the attribute value
1044   */
1045  //@ requires value != null;
1046  //@ ensures  m_Values.size() == \old(m_Values.size()) + 1;
1047  final void forceAddValue(String value) {
1048
1049    Object store = value;
1050    if (value.length() > STRING_COMPRESS_THRESHOLD) {
1051      try {
1052        store = new SerializedObject(value, true);
1053      } catch (Exception ex) {
1054        System.err.println("Couldn't compress string attribute value -"
1055                           + " storing uncompressed.");
1056      }
1057    }
1058    m_Values.add(store);
1059    m_Hashtable.put(store, new Integer(m_Values.size() - 1));
1060  }
1061
1062  /**
1063   * Sets the index of this attribute.
1064   *
1065   * @param index the index of this attribute
1066   */
1067  //@ requires 0 <= index;
1068  //@ assignable m_Index;
1069  //@ ensures m_Index == index;
1070  final void setIndex(int index) {
1071
1072    m_Index = index;
1073  }
1074
1075  /**
1076   * Sets a value of a nominal attribute or string attribute.
1077   * Creates a fresh list of attribute values before it is set.
1078   *
1079   * @param index the value's index
1080   * @param string the value
1081   * @throws IllegalArgumentException if the attribute is not nominal or
1082   * string.
1083   */
1084  //@ requires string != null;
1085  //@ requires isNominal() || isString();
1086  //@ requires 0 <= index && index < m_Values.size();
1087  final void setValue(int index, String string) {
1088   
1089    switch (m_Type) {
1090    case NOMINAL:
1091    case STRING:
1092      m_Values = Utils.cast(m_Values.clone());
1093      m_Hashtable = Utils.cast(m_Hashtable.clone());
1094      Object store = string;
1095      if (string.length() > STRING_COMPRESS_THRESHOLD) {
1096        try {
1097          store = new SerializedObject(string, true);
1098        } catch (Exception ex) {
1099          System.err.println("Couldn't compress string attribute value -"
1100                             + " storing uncompressed.");
1101        }
1102      }
1103      m_Hashtable.remove(m_Values.get(index));
1104      m_Values.set(index, store);
1105      m_Hashtable.put(store, new Integer(index));
1106      break;
1107    default:
1108      throw new IllegalArgumentException("Can only set values for nominal"
1109                                         + " or string attributes!");
1110    }
1111  }
1112
1113  /**
1114   * Sets a value of a relation-valued attribute.
1115   * Creates a fresh list of attribute values before it is set.
1116   *
1117   * @param index the value's index
1118   * @param data the value
1119   * @throws IllegalArgumentException if the attribute is not
1120   * relation-valued.
1121   */
1122  final void setValue(int index, Instances data) {
1123   
1124    if (isRelationValued()) { 
1125      if (!data.equalHeaders(m_Header)) {
1126        throw new IllegalArgumentException("Can't set relational value. " +
1127                                           "Headers not compatible.\n" +
1128                                           data.equalHeadersMsg(m_Header));
1129      }
1130      m_Values = Utils.cast(m_Values.clone());
1131      m_Values.set(index, data);
1132    } else {
1133      throw new IllegalArgumentException("Can only set value for"
1134                                         + " relation-valued attributes!");
1135    }
1136  }
1137
1138  /**
1139   * Returns the given amount of milliseconds formatted according to the
1140   * current Date format.
1141   *
1142   * @param date        the date, represented in milliseconds since
1143   *                    January 1, 1970, 00:00:00 GMT, to return as string
1144   * @return            the formatted date
1145   */
1146  //@ requires isDate();
1147  public /*@pure@*/ String formatDate(double date) {
1148    switch (m_Type) {
1149    case DATE:
1150      return m_DateFormat.format(new Date((long)date));
1151    default:
1152      throw new IllegalArgumentException("Can only format date values for date"
1153                                         + " attributes!");
1154    }
1155  }
1156
1157  /**
1158   * Parses the given String as Date, according to the current format and
1159   * returns the corresponding amount of milliseconds.
1160   *
1161   * @param string the date to parse
1162   * @return the date in milliseconds since January 1, 1970, 00:00:00 GMT
1163   * @throws ParseException if parsing fails
1164   */
1165  //@ requires isDate();
1166  //@ requires string != null;
1167  public double parseDate(String string) throws ParseException {
1168    switch (m_Type) {
1169    case DATE:
1170      long time = m_DateFormat.parse(string).getTime();
1171      // TODO put in a safety check here if we can't store the value in a double.
1172      return (double)time;
1173    default:
1174      throw new IllegalArgumentException("Can only parse date values for date"
1175                                         + " attributes!");
1176    }
1177  }
1178
1179  /**
1180   * Returns the properties supplied for this attribute.
1181   *
1182   * @return metadata for this attribute
1183   */ 
1184  public final /*@ pure @*/ ProtectedProperties getMetadata() {
1185
1186    return m_Metadata;
1187  }
1188
1189  /**
1190   * Returns the ordering of the attribute. One of the following:
1191   *
1192   * ORDERING_SYMBOLIC - attribute values should be treated as symbols.
1193   * ORDERING_ORDERED  - attribute values have a global ordering.
1194   * ORDERING_MODULO   - attribute values have an ordering which wraps.
1195   *
1196   * @return the ordering type of the attribute
1197   */
1198  public final /*@ pure @*/ int ordering() {
1199
1200    return m_Ordering;
1201  }
1202
1203  /**
1204   * Returns whether the attribute values are equally spaced.
1205   *
1206   * @return whether the attribute is regular or not
1207   */
1208  public final /*@ pure @*/ boolean isRegular() {
1209
1210    return m_IsRegular;
1211  }
1212
1213  /**
1214   * Returns whether the attribute can be averaged meaningfully.
1215   *
1216   * @return whether the attribute can be averaged or not
1217   */
1218  public final /*@ pure @*/ boolean isAveragable() {
1219
1220    return m_IsAveragable;
1221  }
1222
1223  /**
1224   * Returns whether the attribute has a zeropoint and may be
1225   * added meaningfully.
1226   *
1227   * @return whether the attribute has a zeropoint or not
1228   */
1229  public final /*@ pure @*/ boolean hasZeropoint() {
1230
1231    return m_HasZeropoint;
1232  }
1233
1234  /**
1235   * Returns the attribute's weight.
1236   *
1237   * @return the attribute's weight as a double
1238   */
1239  public final /*@ pure @*/ double weight() {
1240
1241    return m_Weight;
1242  }
1243
1244  /**
1245   * Sets the new attribute's weight
1246   *
1247   * @param value       the new weight
1248   */
1249  public void setWeight(double value) {
1250    Properties  props;
1251    Enumeration names;
1252    String      name;
1253   
1254    m_Weight = value;
1255
1256    // generate new metadata object
1257    props = new Properties();
1258    names = m_Metadata.propertyNames();
1259    while (names.hasMoreElements()) {
1260      name = (String) names.nextElement();
1261      if (!name.equals("weight"))
1262        props.setProperty(name, m_Metadata.getProperty(name));
1263    }
1264    props.setProperty("weight", "" + m_Weight);
1265    m_Metadata = new ProtectedProperties(props);
1266  }
1267 
1268  /**
1269   * Returns the lower bound of a numeric attribute.
1270   *
1271   * @return the lower bound of the specified numeric range
1272   */
1273  public final /*@ pure @*/ double getLowerNumericBound() {
1274
1275    return m_LowerBound;
1276  }
1277
1278  /**
1279   * Returns whether the lower numeric bound of the attribute is open.
1280   *
1281   * @return whether the lower numeric bound is open or not (closed)
1282   */
1283  public final /*@ pure @*/ boolean lowerNumericBoundIsOpen() {
1284
1285    return m_LowerBoundIsOpen;
1286  }
1287
1288  /**
1289   * Returns the upper bound of a numeric attribute.
1290   *
1291   * @return the upper bound of the specified numeric range
1292   */
1293  public final /*@ pure @*/ double getUpperNumericBound() {
1294
1295    return m_UpperBound;
1296  }
1297
1298  /**
1299   * Returns whether the upper numeric bound of the attribute is open.
1300   *
1301   * @return whether the upper numeric bound is open or not (closed)
1302   */
1303  public final /*@ pure @*/ boolean upperNumericBoundIsOpen() {
1304
1305    return m_UpperBoundIsOpen;
1306  }
1307
1308  /**
1309   * Determines whether a value lies within the bounds of the attribute.
1310   *
1311   * @param value the value to check
1312   * @return whether the value is in range
1313   */
1314  public final /*@ pure @*/ boolean isInRange(double value) {
1315
1316    // dates and missing values are a special case
1317    if (m_Type == DATE || Utils.isMissingValue(value)) return true;
1318    if (m_Type != NUMERIC) {
1319      // do label range check
1320      int intVal = (int) value;
1321      if (intVal < 0 || intVal >= m_Hashtable.size()) return false;
1322    } else {
1323      // do numeric bounds check
1324      if (m_LowerBoundIsOpen) {
1325        if (value <= m_LowerBound) return false;
1326      } else {
1327        if (value < m_LowerBound) return false;
1328      }
1329      if (m_UpperBoundIsOpen) {
1330        if (value >= m_UpperBound) return false;
1331      } else {
1332        if (value > m_UpperBound) return false;
1333      }
1334    }
1335    return true;
1336  }
1337
1338  /**
1339   * Sets the metadata for the attribute. Processes the strings stored in the
1340   * metadata of the attribute so that the properties can be set up for the
1341   * easy-access metadata methods. Any strings sought that are omitted will
1342   * cause default values to be set.
1343   *
1344   * The following properties are recognised:
1345   * ordering, averageable, zeropoint, regular, weight, and range.
1346   *
1347   * All other properties can be queried and handled appropriately by classes
1348   * calling the getMetadata() method.
1349   *
1350   * @param metadata the metadata
1351   * @throws IllegalArgumentException if the properties are not consistent
1352   */
1353  //@ requires metadata != null;
1354  private void setMetadata(ProtectedProperties metadata) {
1355   
1356    m_Metadata = metadata;
1357
1358    if (m_Type == DATE) {
1359      m_Ordering = ORDERING_ORDERED;
1360      m_IsRegular = true;
1361      m_IsAveragable = false;
1362      m_HasZeropoint = false;
1363    } else {
1364
1365      // get ordering
1366      String orderString = m_Metadata.getProperty("ordering","");
1367     
1368      // numeric ordered attributes are averagable and zeropoint by default
1369      String def;
1370      if (m_Type == NUMERIC
1371          && orderString.compareTo("modulo") != 0
1372          && orderString.compareTo("symbolic") != 0)
1373        def = "true";
1374      else def = "false";
1375     
1376      // determine boolean states
1377      m_IsAveragable =
1378        (m_Metadata.getProperty("averageable",def).compareTo("true") == 0);
1379      m_HasZeropoint =
1380        (m_Metadata.getProperty("zeropoint",def).compareTo("true") == 0);
1381      // averagable or zeropoint implies regular
1382      if (m_IsAveragable || m_HasZeropoint) def = "true";
1383      m_IsRegular =
1384        (m_Metadata.getProperty("regular",def).compareTo("true") == 0);
1385     
1386      // determine ordering
1387      if (orderString.compareTo("symbolic") == 0)
1388        m_Ordering = ORDERING_SYMBOLIC;
1389      else if (orderString.compareTo("ordered") == 0)
1390        m_Ordering = ORDERING_ORDERED;
1391      else if (orderString.compareTo("modulo") == 0)
1392        m_Ordering = ORDERING_MODULO;
1393      else {
1394        if (m_Type == NUMERIC || m_IsAveragable || m_HasZeropoint)
1395          m_Ordering = ORDERING_ORDERED;
1396        else m_Ordering = ORDERING_SYMBOLIC;
1397      }
1398    }
1399
1400    // consistency checks
1401    if (m_IsAveragable && !m_IsRegular)
1402      throw new IllegalArgumentException("An averagable attribute must be"
1403                                         + " regular");
1404    if (m_HasZeropoint && !m_IsRegular)
1405      throw new IllegalArgumentException("A zeropoint attribute must be"
1406                                         + " regular");
1407    if (m_IsRegular && m_Ordering == ORDERING_SYMBOLIC)
1408      throw new IllegalArgumentException("A symbolic attribute cannot be"
1409                                         + " regular");
1410    if (m_IsAveragable && m_Ordering != ORDERING_ORDERED)
1411      throw new IllegalArgumentException("An averagable attribute must be"
1412                                         + " ordered");
1413    if (m_HasZeropoint && m_Ordering != ORDERING_ORDERED)
1414      throw new IllegalArgumentException("A zeropoint attribute must be"
1415                                         + " ordered");
1416
1417    // determine weight
1418    m_Weight = 1.0;
1419    String weightString = m_Metadata.getProperty("weight");
1420    if (weightString != null) {
1421      try{
1422        m_Weight = Double.valueOf(weightString).doubleValue();
1423      } catch (NumberFormatException e) {
1424        // Check if value is really a number
1425        throw new IllegalArgumentException("Not a valid attribute weight: '" 
1426                                           + weightString + "'");
1427      }
1428    }
1429
1430    // determine numeric range
1431    if (m_Type == NUMERIC) setNumericRange(m_Metadata.getProperty("range"));
1432  }
1433
1434  /**
1435   * Sets the numeric range based on a string. If the string is null the range
1436   * will default to [-inf,+inf]. A square brace represents a closed interval, a
1437   * curved brace represents an open interval, and 'inf' represents infinity.
1438   * Examples of valid range strings: "[-inf,20)","(-13.5,-5.2)","(5,inf]"
1439   *
1440   * @param rangeString the string to parse as the attribute's numeric range
1441   * @throws IllegalArgumentException if the range is not valid
1442   */
1443  //@ requires rangeString != null;
1444  private void setNumericRange(String rangeString)
1445  {
1446    // set defaults
1447    m_LowerBound = Double.NEGATIVE_INFINITY;
1448    m_LowerBoundIsOpen = false;
1449    m_UpperBound = Double.POSITIVE_INFINITY;
1450    m_UpperBoundIsOpen = false;
1451
1452    if (rangeString == null) return;
1453
1454    // set up a tokenzier to parse the string
1455    StreamTokenizer tokenizer =
1456      new StreamTokenizer(new StringReader(rangeString));
1457    tokenizer.resetSyntax();         
1458    tokenizer.whitespaceChars(0, ' ');   
1459    tokenizer.wordChars(' '+1,'\u00FF');
1460    tokenizer.ordinaryChar('[');
1461    tokenizer.ordinaryChar('(');
1462    tokenizer.ordinaryChar(',');
1463    tokenizer.ordinaryChar(']');
1464    tokenizer.ordinaryChar(')');
1465
1466    try {
1467
1468      // get opening brace
1469      tokenizer.nextToken();
1470   
1471      if (tokenizer.ttype == '[') m_LowerBoundIsOpen = false;
1472      else if (tokenizer.ttype == '(') m_LowerBoundIsOpen = true;
1473      else throw new IllegalArgumentException("Expected opening brace on range,"
1474                                              + " found: "
1475                                              + tokenizer.toString());
1476
1477      // get lower bound
1478      tokenizer.nextToken();
1479      if (tokenizer.ttype != tokenizer.TT_WORD)
1480        throw new IllegalArgumentException("Expected lower bound in range,"
1481                                           + " found: "
1482                                           + tokenizer.toString());
1483      if (tokenizer.sval.compareToIgnoreCase("-inf") == 0)
1484        m_LowerBound = Double.NEGATIVE_INFINITY;
1485      else if (tokenizer.sval.compareToIgnoreCase("+inf") == 0)
1486        m_LowerBound = Double.POSITIVE_INFINITY;
1487      else if (tokenizer.sval.compareToIgnoreCase("inf") == 0)
1488        m_LowerBound = Double.NEGATIVE_INFINITY;
1489      else try {
1490        m_LowerBound = Double.valueOf(tokenizer.sval).doubleValue();
1491      } catch (NumberFormatException e) {
1492        throw new IllegalArgumentException("Expected lower bound in range,"
1493                                           + " found: '" + tokenizer.sval + "'");
1494      }
1495
1496      // get separating comma
1497      if (tokenizer.nextToken() != ',')
1498        throw new IllegalArgumentException("Expected comma in range,"
1499                                           + " found: "
1500                                           + tokenizer.toString());
1501
1502      // get upper bound
1503      tokenizer.nextToken();
1504      if (tokenizer.ttype != tokenizer.TT_WORD)
1505        throw new IllegalArgumentException("Expected upper bound in range,"
1506                                           + " found: "
1507                                           + tokenizer.toString());
1508      if (tokenizer.sval.compareToIgnoreCase("-inf") == 0)
1509        m_UpperBound = Double.NEGATIVE_INFINITY;
1510      else if (tokenizer.sval.compareToIgnoreCase("+inf") == 0)
1511        m_UpperBound = Double.POSITIVE_INFINITY;
1512      else if (tokenizer.sval.compareToIgnoreCase("inf") == 0)
1513        m_UpperBound = Double.POSITIVE_INFINITY;
1514      else try {
1515        m_UpperBound = Double.valueOf(tokenizer.sval).doubleValue();
1516      } catch (NumberFormatException e) {
1517        throw new IllegalArgumentException("Expected upper bound in range,"
1518                                           + " found: '" + tokenizer.sval + "'");
1519      }
1520
1521      // get closing brace
1522      tokenizer.nextToken();
1523   
1524      if (tokenizer.ttype == ']') m_UpperBoundIsOpen = false;
1525      else if (tokenizer.ttype == ')') m_UpperBoundIsOpen = true;
1526      else throw new IllegalArgumentException("Expected closing brace on range,"
1527                                              + " found: "
1528                                              + tokenizer.toString());
1529
1530      // check for rubbish on end
1531      if (tokenizer.nextToken() != tokenizer.TT_EOF)
1532        throw new IllegalArgumentException("Expected end of range string,"
1533                                           + " found: "
1534                                           + tokenizer.toString());
1535
1536    } catch (IOException e) {
1537      throw new IllegalArgumentException("IOException reading attribute range"
1538                                         + " string: " + e.getMessage());
1539    }
1540
1541    if (m_UpperBound < m_LowerBound)
1542      throw new IllegalArgumentException("Upper bound (" + m_UpperBound
1543                                         + ") on numeric range is"
1544                                         + " less than lower bound ("
1545                                         + m_LowerBound + ")!");
1546  }
1547 
1548  /**
1549   * Returns the revision string.
1550   *
1551   * @return            the revision
1552   */
1553  public String getRevision() {
1554    return RevisionUtils.extract("$Revision: 5987 $");
1555  }
1556
1557  /**
1558   * Simple main method for testing this class.
1559   *
1560   * @param ops the commandline options
1561   */
1562  //@ requires ops != null;
1563  //@ requires \nonnullelements(ops);
1564  public static void main(String[] ops) {
1565
1566    try {
1567     
1568      // Create numeric attributes "length" and "weight"
1569      Attribute length = new Attribute("length");
1570      Attribute weight = new Attribute("weight");
1571
1572      // Create date attribute "date"
1573      Attribute date = new Attribute("date", "yyyy-MM-dd HH:mm:ss");
1574
1575      System.out.println(date);
1576      double dd = date.parseDate("2001-04-04 14:13:55");
1577      System.out.println("Test date = " + dd);
1578      System.out.println(date.formatDate(dd));
1579
1580      dd = new Date().getTime();
1581      System.out.println("Date now = " + dd);
1582      System.out.println(date.formatDate(dd));
1583     
1584      // Create vector to hold nominal values "first", "second", "third"
1585      List<String> my_nominal_values = new ArrayList<String>(3); 
1586      my_nominal_values.add("first"); 
1587      my_nominal_values.add("second"); 
1588      my_nominal_values.add("third"); 
1589     
1590      // Create nominal attribute "position"
1591      Attribute position = new Attribute("position", my_nominal_values);
1592
1593      // Print the name of "position"
1594      System.out.println("Name of \"position\": " + position.name());
1595
1596      // Print the values of "position"
1597      Enumeration attValues = position.enumerateValues();
1598      while (attValues.hasMoreElements()) {
1599        String string = (String)attValues.nextElement();
1600        System.out.println("Value of \"position\": " + string);
1601      }
1602
1603      // Shallow copy attribute "position"
1604      Attribute copy = (Attribute) position.copy();
1605
1606      // Test if attributes are the same
1607      System.out.println("Copy is the same as original: " + copy.equals(position));
1608
1609      // Print index of attribute "weight" (should be unset: -1)
1610      System.out.println("Index of attribute \"weight\" (should be -1): " + 
1611                         weight.index());
1612
1613      // Print index of value "first" of attribute "position"
1614      System.out.println("Index of value \"first\" of \"position\" (should be 0): " +
1615                         position.indexOfValue("first"));
1616
1617      // Tests type of attribute "position"
1618      System.out.println("\"position\" is numeric: " + position.isNumeric());
1619      System.out.println("\"position\" is nominal: " + position.isNominal());
1620      System.out.println("\"position\" is string: " + position.isString());
1621
1622      // Prints name of attribute "position"
1623      System.out.println("Name of \"position\": " + position.name());
1624   
1625      // Prints number of values of attribute "position"
1626      System.out.println("Number of values for \"position\": " + position.numValues());
1627
1628      // Prints the values (againg)
1629      for (int i = 0; i < position.numValues(); i++) {
1630        System.out.println("Value " + i + ": " + position.value(i));
1631      }
1632
1633      // Prints the attribute "position" in ARFF format
1634      System.out.println(position);
1635
1636      // Checks type of attribute "position" using constants
1637      switch (position.type()) {
1638      case Attribute.NUMERIC:
1639        System.out.println("\"position\" is numeric");
1640        break;
1641      case Attribute.NOMINAL:
1642        System.out.println("\"position\" is nominal");
1643        break;
1644      case Attribute.STRING:
1645        System.out.println("\"position\" is string");
1646        break;
1647      case Attribute.DATE:
1648        System.out.println("\"position\" is date");
1649        break;
1650      case Attribute.RELATIONAL:
1651        System.out.println("\"position\" is relation-valued");
1652        break;
1653      default:
1654        System.out.println("\"position\" has unknown type");
1655      }
1656
1657      ArrayList<Attribute> atts = new ArrayList<Attribute>(1);
1658      atts.add(position);
1659      Instances relation = new Instances("Test", atts, 0);
1660      Attribute relationValuedAtt = new Attribute("test", relation);
1661      System.out.println(relationValuedAtt);
1662    } catch (Exception e) {
1663      e.printStackTrace();
1664    }
1665  }
1666}
1667 
Note: See TracBrowser for help on using the repository browser.