001/* 002 * Copyright (c) 2005 Einar Pehrson <einar@pehrson.nu>. 003 * 004 * This file is part of 005 * CleanSheets - a spreadsheet application for the Java platform. 006 * 007 * CleanSheets is free software; you can redistribute it and/or modify 008 * it under the terms of the GNU General Public License as published by 009 * the Free Software Foundation; either version 2 of the License, or 010 * (at your option) any later version. 011 * 012 * CleanSheets is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 015 * GNU General Public License for more details. 016 * 017 * You should have received a copy of the GNU General Public License 018 * along with CleanSheets; if not, write to the Free Software 019 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 020 */ 021package csheets.core; 022 023import java.io.Serializable; 024import java.text.DateFormat; 025import java.text.Format; 026import java.text.NumberFormat; 027import java.text.ParseException; 028import java.text.ParsePosition; 029import java.util.Arrays; 030import java.util.Calendar; 031import java.util.Date; 032import java.util.GregorianCalendar; 033 034/** 035 * A typed value that a cell can contain. 036 * @author Einar Pehrson 037 */ 038public class Value implements Comparable<Value>, Serializable { 039 040 /** The unique version identifier used for serialization */ 041 private static final long serialVersionUID = 7140236908025236588L; 042 043 /** The recognized types of values */ 044 public enum Type { 045 046 /** Denotes a value of undefined type */ 047 UNDEFINED, 048 049 /** Denotes a numeric value, with or without decimals */ 050 NUMERIC, 051 052 /** Denotes a text value, or a type of value derived from text */ 053 TEXT, 054 055 /** Denotes a boolean value, i.e. true or false */ 056 BOOLEAN, 057 058 /** Denotes a date, time or date/time value */ 059 DATE, 060 061 /** Denotes a row vector, column vector or two-dimensional matrix of values */ 062 MATRIX, 063 064 /** Denotes an error, e.g. a type mismatch */ 065 ERROR 066 } 067 068 /** The value */ 069 private Serializable value; 070 071 /** The type of the value */ 072 private Type type = Type.UNDEFINED; 073 074 /** 075 * Creates a null value. 076 */ 077 public Value() {} 078 079 /** 080 * Creates a numeric value. 081 * @param number the number of the value 082 */ 083 public Value(Number number) { 084 this.type = Type.NUMERIC; 085 if ((number instanceof Float || number instanceof Double) 086 && number.doubleValue() == number.longValue()) 087 this.value = number.longValue(); 088 else 089 this.value = number; 090 } 091 092 /** 093 * Creates a text value. 094 * @param text the text of the value 095 */ 096 public Value(String text) { 097 this.type = Type.TEXT; 098 this.value = text; 099 } 100 101 /** 102 * Creates a boolean value. 103 * @param booleanValue the boolean of the value 104 */ 105 public Value(Boolean booleanValue) { 106 this.type = Type.BOOLEAN; 107 this.value = booleanValue; 108 } 109 110 /** 111 * Creates a date value. 112 * @param date the date of the value 113 */ 114 public Value(Date date) { 115 this.type = Type.DATE; 116 this.value = date; 117 } 118 119 /** 120 * Creates a one-dimensional matrix value (vector). 121 * @param matrix the value vector 122 */ 123 public Value(Value[] matrix) { 124 this(new Value[][] {matrix}); 125 } 126 127 /** 128 * Creates a two-dimensional matrix value. 129 * @param matrix the value matrix 130 */ 131 public Value(Value[][] matrix) { 132 this.type = Type.MATRIX; 133 this.value = matrix; 134 } 135 136 /** 137 * Creates an error value. 138 * @param error the error of the value 139 */ 140 public Value(Throwable error) { 141 this.type = Type.ERROR; 142 this.value = error; 143 } 144 145 /** 146 * Returns the value in untyped form. 147 * @return the value 148 */ 149 public final Object toAny() { 150 return value; 151 } 152 153 /** 154 * Returns the type of the value. 155 * @return the type of the value 156 */ 157 public final Type getType() { 158 return type; 159 } 160 161 /** 162 * Returns whether the value is of the given type. 163 * @param type the type of value to check against 164 * @return whether the value is of the given type 165 */ 166 public final boolean isOfType(Type type) { 167 return this.type == type; 168 } 169 170 /** 171 * Returns a numeric representation of the value. 172 * @return a numeric representation of the value 173 * @throws IllegalValueTypeException if the value cannot be converted to this type 174 */ 175 public Number toNumber() throws IllegalValueTypeException { 176 if (type == Type.NUMERIC) 177 return (Number)value; 178 else 179 throw new IllegalValueTypeException(this, Type.NUMERIC); 180 } 181 182 /** 183 * Returns a primitive numeric representation of the value. 184 * @return a primitive numeric representation of the value 185 * @throws IllegalValueTypeException if the value cannot be converted to this type 186 */ 187 public double toDouble() throws IllegalValueTypeException{ 188 return toNumber().doubleValue(); 189 } 190 191 /** 192 * Returns a text representation of the value. 193 * @return a text representation of the value 194 * @throws IllegalValueTypeException if the value cannot be converted to this type 195 */ 196 public String toText() throws IllegalValueTypeException { 197 if (type == Type.TEXT) 198 return (String)value; 199 else 200 throw new IllegalValueTypeException(this, Type.TEXT); 201 } 202 203 /** 204 * Returns a boolean representation of the value. 205 * @return a boolean representation of the value 206 * @throws IllegalValueTypeException if the value cannot be converted to this type 207 */ 208 public Boolean toBoolean() throws IllegalValueTypeException { 209 if (type == Type.BOOLEAN) 210 return (Boolean)value; 211 else 212 throw new IllegalValueTypeException(this, Type.BOOLEAN); 213 } 214 215 /** 216 * Returns a date representation of the value. 217 * @return a date representation of the value 218 * @throws IllegalValueTypeException if the value cannot be converted to this type 219 */ 220 public Date toDate() throws IllegalValueTypeException { 221 if (type == Type.DATE) 222 return (Date)value; 223 else 224 throw new IllegalValueTypeException(this, Type.DATE); 225 } 226 227 /** 228 * Returns a matrix representation of the value. 229 * @return a matrix representation of the value 230 * @throws IllegalValueTypeException if the value cannot be converted to this type 231 */ 232 public Value[][] toMatrix() throws IllegalValueTypeException { 233 if (type == Type.MATRIX) 234 return (Value[][])value; 235 else 236 throw new IllegalValueTypeException(this, Type.MATRIX); 237 } 238 239 /** 240 * Returns an error representation of the value. 241 * @return an error representation of the value 242 * @throws IllegalValueTypeException if the value cannot be converted to this type 243 */ 244 public Throwable toError() throws IllegalValueTypeException { 245 if (type == Type.ERROR) 246 return (Throwable)value; 247 else 248 throw new IllegalValueTypeException(this, Type.ERROR); 249 } 250 251 /** 252 * Compares this value with the given value for order. 253 * @param otherValue the value to compare to 254 * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. 255 */ 256 public int compareTo(Value otherValue) { 257 if (type == otherValue.getType()) 258 try { 259 switch (type) { 260 case NUMERIC: 261 return ((Double)toDouble()).compareTo(otherValue.toDouble()); 262 case TEXT: 263 return toText().compareTo(otherValue.toText()); 264 case BOOLEAN: 265 return toBoolean().compareTo(otherValue.toBoolean()); 266 case DATE: 267 return toDate().compareTo(otherValue.toDate()); 268 case MATRIX: 269 return Arrays.hashCode((Object[])otherValue.toAny()) - Arrays.hashCode((Object[])value); 270 default: 271 return 0; 272 } 273 } catch (IllegalValueTypeException e) { 274 return -1; 275 } 276 else 277 return type.compareTo(otherValue.getType()); 278 } 279 280 /** 281 * Returns whether the other object is an identical value . 282 * @param other the object to check for equality 283 * @return true if the objects are equal 284 */ 285 public boolean equals(Object other) { 286 if (!(other instanceof Value) || other == null) 287 return false; 288 Value otherValue = (Value)other; 289 boolean nulls = value == null && otherValue.value == null; 290 return type == otherValue.type 291 && (nulls || (!nulls && value.equals(otherValue.value))); 292 } 293 294 /** 295 * Returns a string representation of the value. 296 * @return a string representation of the value 297 */ 298 public String toString() { 299 if (value != null) 300 switch (type) { 301 case BOOLEAN: 302 return value.toString().toUpperCase(); 303 case DATE: 304 return DateFormat.getDateTimeInstance( 305 DateFormat.SHORT, DateFormat.SHORT).format((Date)value); 306 case MATRIX: 307 Value[][] matrix = (Value[][])value; 308 String string = "{"; 309 for (int row = 0; row < matrix.length; row++) { 310 for (int column = 0; column < matrix[row].length; column++) { 311 string += matrix[row][column]; 312 if (column + 1 < matrix[row].length) 313 string += ";"; 314 } 315 if (row + 1 < matrix.length) 316 string += ";\n"; 317 } 318 string += "}"; 319 return string; 320 default: 321 return value.toString(); 322 } 323 else 324 return ""; 325 } 326 327 /** 328 * Returns a string representation of the value, using the given date or 329 * number format. 330 * @param format the format to use when converting the value 331 * @return a string representation of the value 332 */ 333 public String toString(Format format) { 334 if (value != null) 335 switch (type) { 336 case NUMERIC: 337 if (format instanceof NumberFormat) 338 return format.format((Number)value); 339 else 340 return value.toString(); 341 case DATE: 342 if (format instanceof DateFormat) 343 return format.format((Date)value); 344 default: 345 return value.toString(); 346 } 347 return ""; 348 } 349 350 /** 351 * Attempts to parse a value from the given string. The value is matched 352 * against the given types in order. If no types are supplied, conversion 353 * will be attempted to boolean, date and numeric values. If no other 354 * type matches, the value will be used as a string. 355 * @param value the value 356 * @param types the types for which parsing should be attempted 357 */ 358 public static Value parseValue(String value, Type... types) { 359 // Uses default types 360 if (types.length == 0) 361 types = new Type[] {Type.BOOLEAN, Type.DATE, Type.NUMERIC}; 362 363 for (int i = 0; i < types.length; i++) 364 switch (types[i]) { 365 case BOOLEAN: 366 try { 367 return parseBooleanValue(value); 368 } catch (ParseException e) {} 369 break; 370 371 case DATE: 372 try { 373 return parseDateValue(value); 374 } catch (ParseException e) {} 375 break; 376 377 case NUMERIC: 378 try { 379 return parseNumericValue(value); 380 } catch (ParseException e) {} 381 break; 382 } 383 384 // Uses the string as the value 385 return new Value(value); 386 } 387 388 /** 389 * Attempts to parse a number from the given string. 390 * @param value the value 391 * @return the numeric value that was found 392 * @throws IllegalValueTypeException if no numeric value was found 393 */ 394 public static Value parseNumericValue(String value) throws ParseException { 395 ParsePosition position = new ParsePosition(0); 396 Number number = NumberFormat.getInstance().parse(value, position); 397 if (position.getIndex() == value.length()) 398 return new Value(number); 399 throw new ParseException(value, position.getErrorIndex()); 400 } 401 402 /** 403 * Attempts to parse a boolean from the given string. 404 * @param value the value 405 * @return the boolean value that was found 406 * @throws IllegalValueTypeException if no boolean value was found 407 */ 408 public static Value parseBooleanValue(String value) throws ParseException { 409 if (value.equalsIgnoreCase("true")) 410 return new Value(true); 411 else if (value.equalsIgnoreCase("false")) 412 return new Value(false); 413 else 414 throw new ParseException(value, 0); 415 } 416 417 /** 418 * Attempts to parse a date, time or date/time from the given string. 419 * @param value the value 420 * @return the date value that was found 421 * @throws IllegalValueTypeException if no date value was found 422 */ 423 public static Value parseDateValue(String value) throws ParseException { 424 ParsePosition position = new ParsePosition(0); 425 426 // Attempts to parse a date or date/time 427 DateFormat[] dateFormats = new DateFormat[] { 428 DateFormat.getDateInstance(DateFormat.SHORT), 429 DateFormat.getDateInstance(DateFormat.MEDIUM), 430 DateFormat.getDateInstance(DateFormat.LONG), 431 DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT), 432 DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT), 433 DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM), 434 DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM) 435 }; 436 for (DateFormat format : dateFormats) { 437 Date date = format.parse(value, position); 438 if (position.getIndex() == value.length()) 439 return new Value(date); 440 else if (position.getIndex() > 0) 441 position.setIndex(0); 442 } 443 444 // Attempts to parse a time in the current day 445 DateFormat[] timeFormats = new DateFormat[] { 446 DateFormat.getTimeInstance(DateFormat.SHORT), 447 DateFormat.getTimeInstance(DateFormat.MEDIUM), 448 DateFormat.getTimeInstance(DateFormat.LONG) 449 }; 450 for (int i = 0; i < timeFormats.length; i++) { 451 Calendar datetime = new GregorianCalendar(); 452 Date date = timeFormats[i].parse(value, position); 453 if (position.getIndex() == value.length()) { 454 datetime.setTime(date); 455 Calendar today = new GregorianCalendar(); 456 datetime.set( 457 today.get(Calendar.YEAR), 458 today.get(Calendar.MONTH), 459 today.get(Calendar.DAY_OF_MONTH) 460 ); 461 return new Value(datetime.getTime()); 462 } else if (position.getIndex() > 0) 463 position.setIndex(0); 464 } 465 throw new ParseException(value, position.getErrorIndex()); 466 } 467}