/*
* $URL$
* $Author$
* $Date$
* $Revision$
* Copyright 2004-2005 Revolution Systems Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.revolsys.record.io.format.saif.util;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.Iterator;
import java.util.Stack;
import java.util.zip.ZipFile;
import com.revolsys.gis.parser.ParseException;
public class OsnIterator implements Iterator<Object> {
public static final Object BOOLEAN_VALUE = new Integer(9);
public static final Object END_DOCUMENT = new Integer(1);
public static final Object END_LIST = new Integer(12);
public static final Object END_OBJECT = new Integer(3);
public static final Object END_RELATION = new Integer(16);
public static final Object END_SET = new Integer(14);
public static final Object ENUM_TAG = new Integer(8);
private static final Object IN_ATTRIBUTE = "attribute";
private static final Object IN_DOCUMENT = "document";
private static final Object IN_LIST = "list";
private static final Object IN_OBJECT = "object";
private static final Object IN_RELATION = "relation";
private static final Object IN_SET = "set";
private static final boolean[] IS_DIGIT_CHARACTER = new boolean[256];
private static final boolean[] IS_LOWER_CASE_CHARACTER = new boolean[256];
private static final boolean[] IS_NAME_CHARACTER = new boolean[256];
private static final boolean[] IS_NUMBER_CHARACTER = new boolean[256];
private static final boolean[] IS_UPPER_CASE_CHARACTER = new boolean[256];
private static final boolean[] IS_WHITESPACE_CHARACTER = new boolean[256];
public static final Object NULL_VALUE = new Integer(10);
public static final Object NUMERIC_VALUE = new Integer(5);
public static final Object START_ATTRIBUTE = new Integer(4);
public static final Object START_DEFINITION = new Integer(2);
public static final Object START_DOCUMENT = new Integer(0);
public static final Object START_LIST = new Integer(11);
public static final Object START_RELATION = new Integer(15);
public static final Object START_SET = new Integer(13);
public static final Object TEXT_VALUE = new Integer(7);
public static final Object UNKNOWN = new Integer(-1);
static {
for (int c = 'a'; c <= 'z'; c++) {
IS_LOWER_CASE_CHARACTER[c] = true;
IS_NAME_CHARACTER[c] = true;
}
for (int c = 'A'; c <= 'Z'; c++) {
IS_UPPER_CASE_CHARACTER[c] = true;
IS_NAME_CHARACTER[c] = true;
}
for (int c = '0'; c <= '9'; c++) {
IS_DIGIT_CHARACTER[c] = true;
IS_NAME_CHARACTER[c] = true;
IS_NUMBER_CHARACTER[c] = true;
}
IS_NUMBER_CHARACTER['+'] = true;
IS_NUMBER_CHARACTER['-'] = true;
IS_NUMBER_CHARACTER['.'] = true;
IS_NAME_CHARACTER['_'] = true;
IS_WHITESPACE_CHARACTER[0] = true;
IS_WHITESPACE_CHARACTER[' '] = true;
IS_WHITESPACE_CHARACTER['\t'] = true;
IS_WHITESPACE_CHARACTER['\n'] = true;
IS_WHITESPACE_CHARACTER['\r'] = true;
IS_WHITESPACE_CHARACTER['/'] = true;
}
private final byte[] buffer = new byte[4096];
private int bufferIndex = 0;
private int bufferLength = 0;
private int columnNumber = 1;
// private StringBuilder buffer = new StringBuilder();
private int currentCharacter;
private int currentColumnNumber = 0;
private int currentLineNumber = 1;
private Object eventType = START_DOCUMENT;
private final String fileName;
private final InputStream in;
private int lineNumber = 0;
private final Stack<Object> scopeStack = new Stack<>();
private Object value;
public OsnIterator(final File directory, final String fileName) throws IOException {
this(fileName, new ObjectSetInputStream(directory, fileName));
}
public OsnIterator(final String fileName, final InputStream in) throws IOException {
this.in = new BufferedInputStream(in);
this.fileName = fileName;
this.scopeStack.push(IN_DOCUMENT);
}
public OsnIterator(final ZipFile zipFile, final String fileName) throws IOException {
this(fileName, new ObjectSetInputStream(zipFile, fileName));
}
private void checkStartCollection(final String name) throws IOException {
skipWhitespace();
if (!isNextCharacter('{')) {
throw new IllegalStateException("Expecting a '{' to start a " + name);
}
}
private Object checkStartObject() throws IOException {
skipWhitespace();
if (isNextCharacter('(')) {
this.scopeStack.push(IN_OBJECT);
return START_DEFINITION;
} else {
return UNKNOWN;
}
}
public void close() throws IOException {
this.in.close();
}
private String findClassName() throws IOException {
final String className = findUpperName(true);
// If the class name is fullowed by '::' get and return the schema name
if (this.currentCharacter == ':' && getNextCharacter() == ':') {
getNextCharacter();
final String schemaName = findUpperName(false);
return (className + "::" + schemaName).intern();
} else {
return className;
}
}
private Object findEndCollection() throws IOException {
if (isNextCharacter('}')) {
final Object scope = this.scopeStack.pop();
if (scope == IN_LIST) {
return END_LIST;
} else if (scope == IN_SET) {
return END_SET;
} else if (scope == IN_RELATION) {
return END_RELATION;
} else {
return UNKNOWN;
}
} else {
return UNKNOWN;
}
}
private Object findEndObject() throws IOException {
if (isNextCharacter(')')) {
this.scopeStack.pop();
return END_OBJECT;
} else {
return UNKNOWN;
}
}
private Object findExpression() throws IOException {
Object eventType = UNKNOWN;
final int c = this.currentCharacter;
if (IS_NUMBER_CHARACTER[c]) {
eventType = processDigitString();
} else if (c == '"') {
eventType = processTextString();
} else if (IS_LOWER_CASE_CHARACTER[c]) {
final String name = findLowerName(true);
if (name.equals("true")) {
this.value = Boolean.TRUE;
eventType = BOOLEAN_VALUE;
} else if (name.equals("false")) {
this.value = Boolean.FALSE;
eventType = BOOLEAN_VALUE;
} else if (name.equals("nil")) {
this.value = null;
eventType = NULL_VALUE;
} else {
this.value = name;
eventType = ENUM_TAG;
}
} else if (IS_UPPER_CASE_CHARACTER[c]) {
final String name = findClassName();
if (name.equals("List")) {
checkStartCollection(name);
eventType = START_LIST;
this.scopeStack.push(IN_LIST);
} else if (name.equals("Set")) {
checkStartCollection(name);
eventType = START_SET;
this.scopeStack.push(IN_SET);
} else if (name.equals("Relation")) {
checkStartCollection(name);
eventType = START_RELATION;
this.scopeStack.push(IN_RELATION);
} else {
this.value = name;
eventType = checkStartObject();
if (eventType == UNKNOWN) {
throwParseError("Expecting a '('");
}
}
}
return eventType;
}
private Object findFieldName() throws IOException {
this.value = findLowerName(true);
if (this.value == null) {
return UNKNOWN;
} else {
skipWhitespace();
if (isNextCharacter(':')) {
this.scopeStack.push(IN_ATTRIBUTE);
return START_ATTRIBUTE;
} else {
return UNKNOWN;
}
}
}
private String findLowerName(final boolean tokenStart) throws IOException {
if (IS_LOWER_CASE_CHARACTER[this.currentCharacter]) {
return findName(tokenStart);
} else {
return null;
}
}
private String findName(final boolean tokenStart) throws IOException {
if (tokenStart) {
this.lineNumber = this.currentLineNumber;
this.columnNumber = this.currentColumnNumber;
}
final StringBuilder name = new StringBuilder();
int c = this.currentCharacter;
while (c != -1 && IS_NAME_CHARACTER[c]) {
name.append((char)c);
c = getNextCharacter();
}
return name.toString().intern();
}
private Object findStartObject() throws IOException {
this.value = findClassName();
if (this.value == null) {
return UNKNOWN;
} else {
return checkStartObject();
}
}
private String findUpperName(final boolean tokenStart) throws IOException {
if (IS_UPPER_CASE_CHARACTER[this.currentCharacter]) {
return findName(tokenStart);
} else {
return null;
}
}
public Boolean getBooleanValue() {
if (this.value == null) {
return null;
} else if (this.value instanceof Boolean) {
return (Boolean)this.value;
} else {
return Boolean.valueOf(this.value.toString());
}
}
public double getDoubleValue() {
return ((BigDecimal)this.value).doubleValue();
}
public Object getEventType() {
return this.eventType;
}
public float getFloatValue() {
return ((BigDecimal)this.value).floatValue();
}
public int getIntegerValue() {
return ((BigDecimal)this.value).intValue();
}
private int getNextCharacter() {
if (this.bufferIndex == this.bufferLength) {
try {
this.bufferLength = this.in.read(this.buffer);
} catch (final IOException e) {
return -1;
}
if (this.bufferLength == -1) {
return -1;
} else {
this.bufferIndex = 0;
}
}
this.currentCharacter = this.buffer[this.bufferIndex];
this.bufferIndex++;
// currentCharacter = in.read();
// line.append((char)currentCharacter);
this.currentColumnNumber++;
return this.currentCharacter;
}
public String getPathValue() {
final String name = getStringValue();
if (name != null) {
return PathCache.getName(name);
} else {
return null;
}
}
public String getStringValue() {
if (this.value != null) {
return this.value.toString();
}
return null;
}
public Object getValue() {
return this.value;
}
@Override
public boolean hasNext() {
return true;
}
private boolean isNextCharacter(final int c) throws IOException {
if (this.currentCharacter == c) {
getNextCharacter();
return true;
} else {
return false;
}
}
@Override
public Object next() {
try {
if (skipWhitespace() == -1) {
return END_DOCUMENT;
}
this.eventType = UNKNOWN;
this.value = null;
final Object scope = this.scopeStack.peek();
if (scope == IN_DOCUMENT) {
processDocument();
} else if (scope == IN_OBJECT) {
processObject();
} else if (scope == IN_ATTRIBUTE) {
processAttribute();
} else if (scope == IN_LIST) {
processList();
} else if (scope == IN_SET) {
this.eventType = findExpression();
processSet();
} else if (scope == IN_RELATION) {
processRelation();
}
return this.eventType;
} catch (final IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public Boolean nextBooleanAttribute(final String name) {
final String fieldName = nextFieldName();
if (fieldName == null || !fieldName.equals(name)) {
throwParseError("Expecting attribute " + name);
}
return nextBooleanValue();
}
public Boolean nextBooleanValue() {
if (this.eventType != OsnIterator.BOOLEAN_VALUE) {
if (this.eventType == END_OBJECT) {
return null;
} else if (next() != OsnIterator.BOOLEAN_VALUE) {
throwParseError("Excepecting an boolean value");
}
}
return getBooleanValue();
}
public double nextDoubleAttribute(final String name) {
final String fieldName = nextFieldName();
if (fieldName == null || !fieldName.equals(name)) {
throwParseError("Expecting attribute " + name);
}
return nextDoubleValue();
}
public double nextDoubleValue() {
if (this.eventType != OsnIterator.NUMERIC_VALUE) {
if (this.eventType == END_OBJECT) {
return 0;
} else if (next() != OsnIterator.NUMERIC_VALUE) {
throwParseError("Excepecting an numeric value");
}
}
return getDoubleValue();
}
public void nextEndObject() {
if (next() != OsnIterator.END_OBJECT) {
throwParseError("Expecting End Object");
}
}
public String nextFieldName() {
Object currentEventType = getEventType();
if (currentEventType != OsnIterator.START_ATTRIBUTE) {
currentEventType = next();
if (currentEventType == OsnIterator.END_OBJECT) {
return null;
} else if (currentEventType != OsnIterator.START_ATTRIBUTE) {
throwParseError("Excepecting an attribute name");
}
}
return getStringValue();
}
public int nextIntValue() {
if (this.eventType != OsnIterator.NUMERIC_VALUE) {
if (this.eventType == END_OBJECT) {
return 0;
} else if (next() != OsnIterator.NUMERIC_VALUE) {
throwParseError("Excepecting an numeric value");
}
}
return getIntegerValue();
}
public String nextObjectName() {
Object currentEventType = getEventType();
if (currentEventType != OsnIterator.START_DEFINITION) {
if (currentEventType == END_OBJECT) {
return null;
} else {
currentEventType = next();
if (currentEventType == OsnIterator.END_OBJECT) {
return null;
} else if (currentEventType != OsnIterator.START_DEFINITION) {
throwParseError("Excepecting an attribute name");
}
}
}
return getPathValue();
}
public String nextStringAttribute(final String name) {
final String fieldName = nextFieldName();
if (fieldName == null) {
return null;
} else if (!fieldName.equals(name)) {
throwParseError("Expecting attribute " + name);
}
return nextStringValue();
}
public String nextStringValue() {
if (this.eventType != OsnIterator.TEXT_VALUE && this.eventType != OsnIterator.ENUM_TAG) {
if (this.eventType == END_OBJECT) {
return null;
} else if (next() != OsnIterator.TEXT_VALUE && this.eventType != OsnIterator.ENUM_TAG) {
throwParseError("Excepecting an text value");
}
}
return getStringValue();
}
public Object nextValue() {
if (this.eventType != OsnIterator.BOOLEAN_VALUE && this.eventType != OsnIterator.NUMERIC_VALUE
&& this.eventType != OsnIterator.TEXT_VALUE && this.eventType != OsnIterator.ENUM_TAG) {
if (this.eventType == END_OBJECT) {
return null;
} else if (next() != OsnIterator.TEXT_VALUE && this.eventType != OsnIterator.NUMERIC_VALUE
&& this.eventType != OsnIterator.BOOLEAN_VALUE && this.eventType != OsnIterator.ENUM_TAG) {
throwParseError("Excepecting a value");
}
}
return getValue();
}
private void processAttribute() throws IOException {
this.scopeStack.pop();
this.eventType = findExpression();
if (this.eventType == UNKNOWN) {
throwParseError("Expecting an expression");
}
}
private Object processDigitString() throws IOException {
final StringBuilder number = new StringBuilder();
int c = this.currentCharacter;
while (IS_NUMBER_CHARACTER[(char)c]) {
number.append((char)c);
c = getNextCharacter();
}
if (number.length() > 0) {
setNextToken(new BigDecimal(number.toString()));
this.eventType = NUMERIC_VALUE;
}
return this.eventType;
}
private void processDocument() throws IOException {
this.eventType = findStartObject();
if (this.eventType == UNKNOWN) {
throwParseError("Expecting start of an object definition");
}
}
private void processList() throws IOException {
this.eventType = findExpression();
if (this.eventType == UNKNOWN) {
this.eventType = findEndCollection();
if (this.eventType == UNKNOWN) {
throwParseError("Expecting an expression or end of a list");
}
}
}
private void processObject() throws IOException {
skipWhitespace();
this.eventType = findFieldName();
if (this.eventType == UNKNOWN) {
skipWhitespace();
this.eventType = findEndObject();
if (this.eventType == UNKNOWN) {
throwParseError("Expecting start of an attribute definition or end of object definition");
}
}
}
private void processRelation() throws IOException {
this.eventType = findStartObject();
if (this.eventType == UNKNOWN) {
this.eventType = findEndCollection();
if (this.eventType == UNKNOWN) {
throwParseError("Expecting an expression or end of a relation");
}
}
}
private void processSet() throws IOException {
if (this.eventType == UNKNOWN) {
this.eventType = findEndCollection();
if (this.eventType == UNKNOWN) {
throwParseError("Expecting an expression or end of a set");
}
}
}
private Object processTextString() throws IOException {
this.lineNumber = this.currentLineNumber;
this.columnNumber = this.currentColumnNumber;
final StringBuilder text = new StringBuilder();
char c = (char)getNextCharacter();
while (c != '"') {
if (c == '\\') {
text.append((char)getNextCharacter());
} else {
text.append(c);
}
c = (char)getNextCharacter();
}
if (text.length() > 0 && text.charAt(text.length() - 1) == '\n') {
text.deleteCharAt(text.length() - 1);
}
final String string = text.toString();
setNextToken(string);
this.eventType = TEXT_VALUE;
getNextCharacter();
return this.eventType;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private void setNextToken(final Object token) {
this.value = token;
}
public int skipWhitespace() {
int c = this.currentCharacter;
while (c != -1 && IS_WHITESPACE_CHARACTER[c]) {
if (c == '\n') {
// line.setLength(0);
this.currentLineNumber++;
this.currentColumnNumber = 1;
c = getNextCharacter();
} else if (c == '/') {
c = getNextCharacter();
if (c == '/') {
do {
c = getNextCharacter();
} while (c != -1 && c != '\n');
} else {
return this.currentCharacter;
}
} else {
c = getNextCharacter();
}
}
return c;
}
public void throwParseError(final String message) {
final int startIndex = Math.max(this.bufferIndex - 40, 0);
final int endIndex = Math.min(80, this.bufferLength - 1 - startIndex);
throw new ParseException(toString(), message + " got '" + (char)this.currentCharacter
+ "' context=" + new String(this.buffer, startIndex, endIndex));
}
@Override
public String toString() {
return this.fileName + "[" + this.lineNumber + "," + this.columnNumber + "]";
}
}