001: /**
002: * Objective Database Abstraction Layer (ODAL)
003: * Copyright (c) 2004, The ODAL Development Group
004: * All rights reserved.
005: * For definition of the ODAL Development Group please refer to LICENCE.txt file
006: *
007: * Distributable under LGPL license.
008: * See terms of license at gnu.org.
009: */package com.completex.objective.components.sdl.reader.impl;
010:
011: import com.completex.objective.components.sdl.reader.SdlReader;
012:
013: import java.io.EOFException;
014: import java.io.IOException;
015: import java.io.Reader;
016: import java.math.BigDecimal;
017: import java.sql.Timestamp;
018: import java.util.*;
019:
020: /**
021: * @author Andrew Suprun
022: */
023: public class SdlReaderImpl implements SdlReader {
024: public Object read(Reader source) throws IOException {
025: Context ctx = new Context(source);
026: return read(ctx);
027: }
028:
029: private Object read(Context ctx) throws IOException {
030: ctx.skipWhitespace();
031: // System.out.println("transformRead: ctx.ch = " + (int)ctx.ch);
032: switch (ctx.ch) {
033: case '{':
034: return readMap(ctx);
035: case '[':
036: return readList(ctx);
037: case '"':
038: return readString(ctx);
039: case '@':
040: return readTimestamp(ctx);
041: default:
042: return readNumberOrSymbol(ctx);
043: }
044: }
045:
046: private Map readMap(Context ctx) throws IOException {
047: // System.out.println("readMap: enter");
048: ctx.next();
049: ctx.skipWhitespace();
050:
051: Map result = new LinkedHashMap();
052: while (ctx.ch != '}') {
053: readMapEntity(ctx, result);
054: ctx.skipWhitespace();
055: }
056:
057: try {
058: ctx.next();
059: } catch (EOFException e) {
060: // OK
061: }
062: // System.out.println("readMap: result = " + result);
063: return result;
064: }
065:
066: private void readMapEntity(Context ctx, Map map) throws IOException {
067: Object key = read(ctx);
068: // System.out.println("readMapEntity: key = " + key);
069:
070: if (map.containsKey(key)) {
071: throw new RuntimeException("Error: (" + ctx.line + ", "
072: + ctx.position + ") duplicate key: " + key);
073: }
074:
075: ctx.skipWhitespace();
076: if (ctx.ch != '=') {
077: throw new IOException("Error: (" + ctx.line + ", "
078: + ctx.position + ") Expected '='. Encountered "
079: + ctx.ch + ".");
080: }
081:
082: ctx.next();
083: Object value = read(ctx);
084: // System.out.println("readMapEntity: value = " + value);
085: map.put(key, value);
086: }
087:
088: private List readList(Context ctx) throws IOException {
089: // System.out.println("readList: enter");
090: ctx.next();
091: ctx.skipWhitespace();
092: List result = new ArrayList();
093: while (ctx.ch != ']') {
094: Object value = read(ctx);
095: // System.out.println("readList: value = " + value);
096: result.add(value);
097: ctx.skipWhitespace();
098: }
099: try {
100: ctx.next();
101: } catch (EOFException e) {
102: // OK
103: }
104: // System.out.println("readList: result = " + result);
105: return result;
106: }
107:
108: private String readString(Context ctx) throws IOException {
109: StringBuffer buffer = new StringBuffer();
110: while (true) {
111: ctx.next();
112: if (ctx.ch == '\\') {
113: buffer.append(ctx.next());
114: } else if (ctx.ch == '"') {
115: ctx.next();
116: return buffer.toString();
117: } else {
118: buffer.append(ctx.ch);
119: }
120: }
121: }
122:
123: private Timestamp readTimestamp(Context ctx) throws IOException {
124: int year = getTwoDigits(ctx) * 100 + getTwoDigits(ctx);
125: skip(ctx, '-');
126: int month = getTwoDigits(ctx) - 1;
127: skip(ctx, '-');
128: int date = getTwoDigits(ctx);
129:
130: if (ctx.next() == 'T') {
131: int hours = getTwoDigits(ctx);
132: skip(ctx, ':');
133: int minutes = getTwoDigits(ctx);
134: skip(ctx, ':');
135: int seconds = getTwoDigits(ctx);
136: ctx.next();
137: return new Timestamp(new GregorianCalendar(year, month,
138: date, hours, minutes, seconds).getTime().getTime());
139: } else {
140: return new Timestamp(new GregorianCalendar(year, month,
141: date).getTime().getTime());
142: }
143: }
144:
145: private int getTwoDigits(Context ctx) throws IOException {
146: char first = ctx.next();
147: char second = ctx.next();
148: int result = (first - '0') * 10 + (second - '0');
149: return result;
150: }
151:
152: private void skip(Context ctx, char ch) throws IOException {
153: if (ctx.next() != ch) {
154: throw new IOException("Error: (" + ctx.line + ", "
155: + ctx.position + ") Expected '" + ch
156: + "'. Encountered " + ctx.ch + ".");
157: }
158: }
159:
160: private Object readNumberOrSymbol(Context ctx) throws IOException {
161: if ((ctx.ch >= 'a' && ctx.ch <= 'z')
162: || (ctx.ch >= 'A' && ctx.ch <= 'Z') || ctx.ch == '_'
163: || ctx.ch == '$' || ctx.ch == '/') {
164: return readSymbol(ctx);
165: } else if ((ctx.ch >= '0' && ctx.ch <= '9') || ctx.ch == '+'
166: || ctx.ch == '-' || ctx.ch == '.') {
167: return readNumber(ctx);
168: } else {
169: if (ctx.ch == (char) -1) {
170: return null;
171: }
172: throw new IOException("Error: (" + ctx.line + ", "
173: + ctx.position + ") No value can start with "
174: + ctx.ch);
175: }
176: }
177:
178: private Object readSymbol(Context ctx) throws IOException {
179: // System.out.println("readSymbol: ctx.ch = " + ctx.ch);
180: StringBuffer buffer = new StringBuffer();
181: buffer.append(ctx.ch);
182: while (isSymbolChar(ctx.next())) {
183: buffer.append(ctx.ch);
184: }
185:
186: String result = buffer.toString();
187: // System.out.println("readSymbol: result = " + result);
188: if (result.equals("NULL")) {
189: return null;
190: } else if (result.equals("TRUE")) {
191: return Boolean.valueOf(true);
192: } else if (result.equals("FALSE")) {
193: return Boolean.valueOf(false);
194: } else {
195: return result;
196: }
197: }
198:
199: private Number readNumber(Context ctx) throws IOException {
200: StringBuffer buffer = new StringBuffer(ctx.ch);
201: while (ctx.ch == '+' || ctx.ch == '-' || ctx.ch == '.'
202: || ctx.ch == 'e' || ctx.ch == 'E'
203: || (ctx.ch >= '0' && ctx.ch <= '9')) {
204: buffer.append(ctx.ch);
205: ctx.next();
206: }
207: return new BigDecimal(buffer.toString());
208: }
209:
210: private boolean isSymbolChar(char c) {
211: return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')
212: || (c >= 'A' && c <= 'Z') || c == '-' || c == '_'
213: || c == ':' || c == '.' || c == '$' || c == '/';
214: }
215:
216: private class Context {
217: Reader source;
218: char ch;
219: int line = 1;
220: int position = 0;
221: public static final char COMMENT_CHAR = '#';
222: public static final char NL = '\n';
223:
224: public Context(Reader source) throws IOException {
225: this .source = source;
226: next();
227: }
228:
229: char next() throws IOException {
230: ch = (char) source.read();
231: switch (ch) {
232: case NL:
233: line++;
234: position = 1;
235: break;
236: case (char) -1:
237: throw new EOFException("Error: (" + line + ", "
238: + position + ") Unexpected end of file.");
239: default:
240: position++;
241: }
242: // System.out.println(this + "(" + line + ", " + position + ") ch = " + ch);
243: return ch;
244: }
245:
246: private void skipWhitespace() throws IOException {
247: while (true) {
248: if (Character.isWhitespace(ch)) {
249: next();
250: } else if (ch == COMMENT_CHAR) {
251: while (ch != NL) {
252: next();
253: }
254: } else {
255: break;
256: }
257: }
258: }
259: }
260: }
|