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: */
021: package csheets.core;
022:
023: import java.io.Serializable;
024: import java.text.DateFormat;
025: import java.text.Format;
026: import java.text.NumberFormat;
027: import java.text.ParseException;
028: import java.text.ParsePosition;
029: import java.util.Arrays;
030: import java.util.Calendar;
031: import java.util.Date;
032: import java.util.GregorianCalendar;
033:
034: /**
035: * A typed value that a cell can contain.
036: * @author Einar Pehrson
037: */
038: public 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: /**
081: * Creates a numeric value.
082: * @param number the number of the value
083: */
084: public Value(Number number) {
085: this .type = Type.NUMERIC;
086: if ((number instanceof Float || number instanceof Double)
087: && number.doubleValue() == number.longValue())
088: this .value = number.longValue();
089: else
090: this .value = number;
091: }
092:
093: /**
094: * Creates a text value.
095: * @param text the text of the value
096: */
097: public Value(String text) {
098: this .type = Type.TEXT;
099: this .value = text;
100: }
101:
102: /**
103: * Creates a boolean value.
104: * @param booleanValue the boolean of the value
105: */
106: public Value(Boolean booleanValue) {
107: this .type = Type.BOOLEAN;
108: this .value = booleanValue;
109: }
110:
111: /**
112: * Creates a date value.
113: * @param date the date of the value
114: */
115: public Value(Date date) {
116: this .type = Type.DATE;
117: this .value = date;
118: }
119:
120: /**
121: * Creates a one-dimensional matrix value (vector).
122: * @param matrix the value vector
123: */
124: public Value(Value[] matrix) {
125: this (new Value[][] { matrix });
126: }
127:
128: /**
129: * Creates a two-dimensional matrix value.
130: * @param matrix the value matrix
131: */
132: public Value(Value[][] matrix) {
133: this .type = Type.MATRIX;
134: this .value = matrix;
135: }
136:
137: /**
138: * Creates an error value.
139: * @param error the error of the value
140: */
141: public Value(Throwable error) {
142: this .type = Type.ERROR;
143: this .value = error;
144: }
145:
146: /**
147: * Returns the value in untyped form.
148: * @return the value
149: */
150: public final Object toAny() {
151: return value;
152: }
153:
154: /**
155: * Returns the type of the value.
156: * @return the type of the value
157: */
158: public final Type getType() {
159: return type;
160: }
161:
162: /**
163: * Returns whether the value is of the given type.
164: * @param type the type of value to check against
165: * @return whether the value is of the given type
166: */
167: public final boolean isOfType(Type type) {
168: return this .type == type;
169: }
170:
171: /**
172: * Returns a numeric representation of the value.
173: * @return a numeric representation of the value
174: * @throws IllegalValueTypeException if the value cannot be converted to this type
175: */
176: public Number toNumber() throws IllegalValueTypeException {
177: if (type == Type.NUMERIC)
178: return (Number) value;
179: else
180: throw new IllegalValueTypeException(this , Type.NUMERIC);
181: }
182:
183: /**
184: * Returns a primitive numeric representation of the value.
185: * @return a primitive numeric representation of the value
186: * @throws IllegalValueTypeException if the value cannot be converted to this type
187: */
188: public double toDouble() throws IllegalValueTypeException {
189: return toNumber().doubleValue();
190: }
191:
192: /**
193: * Returns a text representation of the value.
194: * @return a text representation of the value
195: * @throws IllegalValueTypeException if the value cannot be converted to this type
196: */
197: public String toText() throws IllegalValueTypeException {
198: if (type == Type.TEXT)
199: return (String) value;
200: else
201: throw new IllegalValueTypeException(this , Type.TEXT);
202: }
203:
204: /**
205: * Returns a boolean representation of the value.
206: * @return a boolean representation of the value
207: * @throws IllegalValueTypeException if the value cannot be converted to this type
208: */
209: public Boolean toBoolean() throws IllegalValueTypeException {
210: if (type == Type.BOOLEAN)
211: return (Boolean) value;
212: else
213: throw new IllegalValueTypeException(this , Type.BOOLEAN);
214: }
215:
216: /**
217: * Returns a date representation of the value.
218: * @return a date representation of the value
219: * @throws IllegalValueTypeException if the value cannot be converted to this type
220: */
221: public Date toDate() throws IllegalValueTypeException {
222: if (type == Type.DATE)
223: return (Date) value;
224: else
225: throw new IllegalValueTypeException(this , Type.DATE);
226: }
227:
228: /**
229: * Returns a matrix representation of the value.
230: * @return a matrix representation of the value
231: * @throws IllegalValueTypeException if the value cannot be converted to this type
232: */
233: public Value[][] toMatrix() throws IllegalValueTypeException {
234: if (type == Type.MATRIX)
235: return (Value[][]) value;
236: else
237: throw new IllegalValueTypeException(this , Type.MATRIX);
238: }
239:
240: /**
241: * Returns an error representation of the value.
242: * @return an error representation of the value
243: * @throws IllegalValueTypeException if the value cannot be converted to this type
244: */
245: public Throwable toError() throws IllegalValueTypeException {
246: if (type == Type.ERROR)
247: return (Throwable) value;
248: else
249: throw new IllegalValueTypeException(this , Type.ERROR);
250: }
251:
252: /**
253: * Compares this value with the given value for order.
254: * @param otherValue the value to compare to
255: * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
256: */
257: public int compareTo(Value otherValue) {
258: if (type == otherValue.getType())
259: try {
260: switch (type) {
261: case NUMERIC:
262: return ((Double) toDouble()).compareTo(otherValue
263: .toDouble());
264: case TEXT:
265: return toText().compareTo(otherValue.toText());
266: case BOOLEAN:
267: return toBoolean()
268: .compareTo(otherValue.toBoolean());
269: case DATE:
270: return toDate().compareTo(otherValue.toDate());
271: case MATRIX:
272: return Arrays.hashCode((Object[]) otherValue
273: .toAny())
274: - Arrays.hashCode((Object[]) value);
275: default:
276: return 0;
277: }
278: } catch (IllegalValueTypeException e) {
279: return -1;
280: }
281: else
282: return type.compareTo(otherValue.getType());
283: }
284:
285: /**
286: * Returns whether the other object is an identical value .
287: * @param other the object to check for equality
288: * @return true if the objects are equal
289: */
290: public boolean equals(Object other) {
291: if (!(other instanceof Value) || other == null)
292: return false;
293: Value otherValue = (Value) other;
294: boolean nulls = value == null && otherValue.value == null;
295: return type == otherValue.type
296: && (nulls || (!nulls && value.equals(otherValue.value)));
297: }
298:
299: /**
300: * Returns a string representation of the value.
301: * @return a string representation of the value
302: */
303: public String toString() {
304: if (value != null)
305: switch (type) {
306: case BOOLEAN:
307: return value.toString().toUpperCase();
308: case DATE:
309: return DateFormat.getDateTimeInstance(DateFormat.SHORT,
310: DateFormat.SHORT).format((Date) value);
311: case MATRIX:
312: Value[][] matrix = (Value[][]) value;
313: String string = "{";
314: for (int row = 0; row < matrix.length; row++) {
315: for (int column = 0; column < matrix[row].length; column++) {
316: string += matrix[row][column];
317: if (column + 1 < matrix[row].length)
318: string += ";";
319: }
320: if (row + 1 < matrix.length)
321: string += ";\n";
322: }
323: string += "}";
324: return string;
325: default:
326: return value.toString();
327: }
328: else
329: return "";
330: }
331:
332: /**
333: * Returns a string representation of the value, using the given date or
334: * number format.
335: * @param format the format to use when converting the value
336: * @return a string representation of the value
337: */
338: public String toString(Format format) {
339: if (value != null)
340: switch (type) {
341: case NUMERIC:
342: if (format instanceof NumberFormat)
343: return format.format((Number) value);
344: else
345: return value.toString();
346: case DATE:
347: if (format instanceof DateFormat)
348: return format.format((Date) value);
349: default:
350: return value.toString();
351: }
352: return "";
353: }
354:
355: /**
356: * Attempts to parse a value from the given string. The value is matched
357: * against the given types in order. If no types are supplied, conversion
358: * will be attempted to boolean, date and numeric values. If no other
359: * type matches, the value will be used as a string.
360: * @param value the value
361: * @param types the types for which parsing should be attempted
362: */
363: public static Value parseValue(String value, Type... types) {
364: // Uses default types
365: if (types.length == 0)
366: types = new Type[] { Type.BOOLEAN, Type.DATE, Type.NUMERIC };
367:
368: for (int i = 0; i < types.length; i++)
369: switch (types[i]) {
370: case BOOLEAN:
371: try {
372: return parseBooleanValue(value);
373: } catch (ParseException e) {
374: }
375: break;
376:
377: case DATE:
378: try {
379: return parseDateValue(value);
380: } catch (ParseException e) {
381: }
382: break;
383:
384: case NUMERIC:
385: try {
386: return parseNumericValue(value);
387: } catch (ParseException e) {
388: }
389: break;
390: }
391:
392: // Uses the string as the value
393: return new Value(value);
394: }
395:
396: /**
397: * Attempts to parse a number from the given string.
398: * @param value the value
399: * @return the numeric value that was found
400: * @throws IllegalValueTypeException if no numeric value was found
401: */
402: public static Value parseNumericValue(String value)
403: throws ParseException {
404: ParsePosition position = new ParsePosition(0);
405: Number number = NumberFormat.getInstance().parse(value,
406: position);
407: if (position.getIndex() == value.length())
408: return new Value(number);
409: throw new ParseException(value, position.getErrorIndex());
410: }
411:
412: /**
413: * Attempts to parse a boolean from the given string.
414: * @param value the value
415: * @return the boolean value that was found
416: * @throws IllegalValueTypeException if no boolean value was found
417: */
418: public static Value parseBooleanValue(String value)
419: throws ParseException {
420: if (value.equalsIgnoreCase("true"))
421: return new Value(true);
422: else if (value.equalsIgnoreCase("false"))
423: return new Value(false);
424: else
425: throw new ParseException(value, 0);
426: }
427:
428: /**
429: * Attempts to parse a date, time or date/time from the given string.
430: * @param value the value
431: * @return the date value that was found
432: * @throws IllegalValueTypeException if no date value was found
433: */
434: public static Value parseDateValue(String value)
435: throws ParseException {
436: ParsePosition position = new ParsePosition(0);
437:
438: // Attempts to parse a date or date/time
439: DateFormat[] dateFormats = new DateFormat[] {
440: DateFormat.getDateInstance(DateFormat.SHORT),
441: DateFormat.getDateInstance(DateFormat.MEDIUM),
442: DateFormat.getDateInstance(DateFormat.LONG),
443: DateFormat.getDateTimeInstance(DateFormat.SHORT,
444: DateFormat.SHORT),
445: DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
446: DateFormat.SHORT),
447: DateFormat.getDateTimeInstance(DateFormat.SHORT,
448: DateFormat.MEDIUM),
449: DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
450: DateFormat.MEDIUM) };
451: for (DateFormat format : dateFormats) {
452: Date date = format.parse(value, position);
453: if (position.getIndex() == value.length())
454: return new Value(date);
455: else if (position.getIndex() > 0)
456: position.setIndex(0);
457: }
458:
459: // Attempts to parse a time in the current day
460: DateFormat[] timeFormats = new DateFormat[] {
461: DateFormat.getTimeInstance(DateFormat.SHORT),
462: DateFormat.getTimeInstance(DateFormat.MEDIUM),
463: DateFormat.getTimeInstance(DateFormat.LONG) };
464: for (int i = 0; i < timeFormats.length; i++) {
465: Calendar datetime = new GregorianCalendar();
466: Date date = timeFormats[i].parse(value, position);
467: if (position.getIndex() == value.length()) {
468: datetime.setTime(date);
469: Calendar today = new GregorianCalendar();
470: datetime.set(today.get(Calendar.YEAR), today
471: .get(Calendar.MONTH), today
472: .get(Calendar.DAY_OF_MONTH));
473: return new Value(datetime.getTime());
474: } else if (position.getIndex() > 0)
475: position.setIndex(0);
476: }
477: throw new ParseException(value, position.getErrorIndex());
478: }
479: }
|