/*
* $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.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.sql.Date;
import java.util.Calendar;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import com.revolsys.datatype.DataType;
import com.revolsys.datatype.EnumerationDataType;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Point;
import com.revolsys.io.FileUtil;
import com.revolsys.logging.Logs;
import com.revolsys.record.Record;
import com.revolsys.record.io.format.saif.SaifConstants;
import com.revolsys.record.schema.RecordDefinition;
import com.revolsys.util.Property;
public class OsnSerializer {
private static final String ATTRIBUTE_SCOPE = "attribute";
private static final String COLLECTION_SCOPE = "collection";
private static final String DATE = "/Date";
private static final String DOCUMENT_SCOPE = "document";
private static final String SPATIAL_OBJECT = "/SpatialObject";
private final OsnConverterRegistry converters;
private boolean endElement = false;
private File file;
private String indent = "";
private boolean indentEnabled = true;
private short index = 0;
private final String lineSeparator;
private long maxSize = Long.MAX_VALUE;
private OutputStream out;
private final String path;
private final String prefix;
private final LinkedList<Object> scope = new LinkedList<>();
private int size = 0;
public OsnSerializer(final String path, final File file, final long maxSize,
final OsnConverterRegistry converters) throws IOException {
this.path = path;
this.file = file;
this.maxSize = maxSize;
this.converters = converters;
this.prefix = ObjectSetUtil.getObjectSubsetPrefix(file);
openFile();
this.scope.addLast(DOCUMENT_SCOPE);
this.lineSeparator = "\r\n";
}
public void attribute(final String name, final double value, final boolean endLine)
throws IOException {
attribute(name, new BigDecimal(value), endLine);
}
public void attribute(final String name, final Object value, final boolean endLine)
throws IOException {
fieldName(name);
attributeValue(value);
if (endLine || this.indentEnabled) {
endLine();
}
}
public void attributeEnum(final String name, final String value, final boolean endLine)
throws IOException {
fieldName(name);
write(value);
endAttribute();
if (endLine || this.indentEnabled) {
endLine();
}
}
public void attributeValue(final Object value) throws IOException {
serializeValue(value);
endAttribute();
}
public void close() throws IOException {
while (!this.scope.isEmpty()) {
final Object scope = this.scope.getLast();
if (scope == COLLECTION_SCOPE) {
endCollection();
} else if (scope == ATTRIBUTE_SCOPE) {
if (this.indentEnabled) {
endLine();
}
this.scope.removeLast();
} else if (scope != DOCUMENT_SCOPE && (scope instanceof Record || scope instanceof String)) {
endObject();
} else {
if (this.indentEnabled) {
endLine();
}
this.scope.removeLast();
}
}
write('\n');
this.out.close();
}
private void decreaseIndent() {
if (this.indentEnabled) {
this.indent = this.indent.substring(1);
}
}
public void endAttribute() {
this.scope.removeLast();
}
public void endCollection() throws IOException {
this.endElement = true;
decreaseIndent();
serializeIndent();
write('}');
if (this.indentEnabled) {
endLine();
}
endAttribute();
}
public void endLine() throws IOException {
write(this.lineSeparator);
}
public void endObject() throws IOException {
this.endElement = true;
decreaseIndent();
serializeIndent();
write(')');
if (this.indentEnabled) {
endLine();
}
endAttribute();
}
public void fieldName(final String name) throws IOException {
this.endElement = false;
serializeIndent();
write(name + ":");
this.scope.addLast(ATTRIBUTE_SCOPE);
}
private void increaseIndent() {
if (this.indentEnabled) {
this.indent += '\t';
}
}
public boolean isIndentEnabled() {
return this.indentEnabled;
}
private void openFile() throws IOException {
Logs.debug(this, "Creating object subset '" + FileUtil.getFileName(this.file) + "'");
this.out = new BufferedOutputStream(new FileOutputStream(this.file), 4096);
}
private void openNextFile() throws IOException {
this.out.flush();
this.out.close();
this.index++;
final String fileName = ObjectSetUtil.getObjectSubsetName(this.prefix, this.index);
this.file = new File(this.file.getParentFile(), fileName);
this.size = 0;
openFile();
}
private void serialize(final Date date) throws IOException {
startObject(DATE);
if (date.equals(DateConverter.NULL_DATE)) {
attribute("year", new BigDecimal(0), false);
} else {
final GregorianCalendar cal = new GregorianCalendar();
cal.setTime(date);
final int day = cal.get(Calendar.DAY_OF_MONTH);
if (day < 10) {
fieldName("day");
write("0" + day);
endAttribute();
endLine();
} else {
attribute("day", new BigDecimal(day), true);
}
final int month = cal.get(Calendar.MONTH) + 1;
if (month < 10) {
fieldName("month");
write("0" + month);
endAttribute();
endLine();
} else {
attribute("month", new BigDecimal(month), true);
}
final int year = cal.get(Calendar.YEAR);
attribute("year", new BigDecimal(year), true);
}
endObject();
}
private void serialize(final Geometry geometry) throws IOException {
final String type = Property.getSimple(geometry, "osnGeometryType");
OsnConverter converter = this.converters.getConverter(type);
if (converter == null) {
if (geometry instanceof Point) {
if (converter == null) {
converter = this.converters.getConverter(SaifConstants.POINT);
}
} else if (geometry instanceof LineString) {
if (converter == null) {
converter = this.converters.getConverter(SaifConstants.ARC);
}
}
}
converter.write(this, geometry);
}
public void serialize(final List<Object> list) throws IOException {
serializeCollection("List", list);
}
public void serialize(final Record object) throws IOException {
serializeStartObject(object);
serializeAttributes(object);
endObject();
}
public void serialize(final Set<Object> set) throws IOException {
serializeCollection("Set", set);
}
public void serialize(final String string) throws IOException {
write('"');
String escapedString = string.replaceAll("\\\\", "\\\\\\\\");
escapedString = escapedString.replaceAll("(\\\\)?\\x22", "\\\\\"");
write(escapedString);
write('"');
}
public void serializeAttribute(final String name, final Object value) throws IOException {
fieldName(name);
if (value instanceof Geometry && name.equals("position")) {
startObject(SPATIAL_OBJECT);
fieldName("geometry");
attributeValue(value);
endAttribute();
endObject();
} else {
attributeValue(value);
}
}
public void serializeAttributes(final Record object) throws IOException {
final RecordDefinition type = object.getRecordDefinition();
final int attributeCount = type.getFieldCount();
for (int i = 0; i < attributeCount; i++) {
final Object value = object.getValue(i);
if (value != null) {
final String name = type.getFieldName(i);
final DataType dataType = type.getFieldType(i);
if (dataType instanceof EnumerationDataType) {
fieldName(name);
write(value.toString());
endAttribute();
} else {
serializeAttribute(name, value);
}
if (this.indentEnabled) {
endLine();
}
if (!this.endElement) {
if (i < attributeCount - 1) {
endLine();
} else if (type.getName().equals("/Coord3D")) {
for (final Object parent : this.scope) {
if (parent instanceof Record) {
final Record parentObject = (Record)parent;
if (parentObject.getRecordDefinition()
.getName()
.equals(SaifConstants.TEXT_ON_CURVE)) {
endLine();
}
}
}
} else {
endLine();
}
}
}
}
}
private void serializeCollection(final String name, final Collection<Object> collection)
throws IOException {
startCollection(name);
for (final Object value : collection) {
serializeValue(value);
if (this.indentEnabled || !this.endElement) {
endLine();
}
}
endCollection();
}
public void serializeIndent() throws IOException {
if (this.indentEnabled) {
write(this.indent);
}
}
public void serializeRecord(final Record object) throws IOException {
if (this.size >= this.maxSize) {
openNextFile();
this.size = 0;
}
serialize(object);
}
public void serializeStartObject(final Record object) throws IOException {
final RecordDefinition type = object.getRecordDefinition();
final String path = type.getPath();
startObject(path);
}
@SuppressWarnings("unchecked")
public void serializeValue(final Object value) throws IOException {
if (this.scope.getLast() == COLLECTION_SCOPE) {
serializeIndent();
}
if (value == null) {
write("nil");
} else {
if (value instanceof List) {
serialize((List<Object>)value);
} else if (value instanceof Set) {
serialize((Set<Object>)value);
} else if (value instanceof String) {
serialize((String)value);
} else if (value instanceof Record) {
serialize((Record)value);
} else if (value instanceof Date) {
serialize((Date)value);
} else if (value instanceof Geometry) {
final Geometry geometry = (Geometry)value;
serialize(geometry);
} else {
write(value.toString());
}
}
}
public void setIndentEnabled(final boolean indentEnabled) {
this.indentEnabled = indentEnabled;
}
public void startCollection(final String name) throws IOException {
this.endElement = false;
write(name);
write('{');
if (this.indentEnabled) {
endLine();
}
increaseIndent();
this.scope.addLast(COLLECTION_SCOPE);
}
public void startObject(final String path) throws IOException {
this.endElement = false;
final String[] elements = path.replaceAll("^/+", "").split("/");
if (elements.length == 1) {
write(elements[0]);
} else {
final String typeName = elements[1];
final String schema = elements[0];
write(typeName);
write("::");
write(schema);
}
write('(');
if (this.indentEnabled) {
endLine();
}
increaseIndent();
this.scope.addLast(path);
}
@Override
public String toString() {
return this.path.toString();
}
public void write(final byte[] b) throws IOException {
write(b, 0, b.length);
}
public void write(final byte[] b, final int off, final int len) throws IOException {
this.out.write(b, off, len);
this.size += len;
}
public void write(final int b) throws IOException {
this.out.write(b);
this.size += 1;
}
public void write(final String s) throws IOException {
final byte[] bytes = s.getBytes();
write(bytes, 0, bytes.length);
}
}