/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2007-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.caching.util;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.opengis.feature.IllegalAttributeException;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
/** Simple marshaller that can write features to an ObjectOutputStream.
* Feature is not Serializable, but this is based on the idea that most attributes object are Serializable
* (JTS geometries are Serializable), and that attributes which are not simple, are either a collection we can iterate through, or another Feature.
* Serialization is then achieved recursively.
* Unmarshalling implies to know the FeatureType of the marshalled feature.
*
* Storage format : Header,
* Attributes
*
* Header := int : FeatureType hashCode,
* String : FeatureType name,
* String : Feature ID,
* int : number of attributes
* Attributes := [Attribute]
* Attribute := int : multiplicity, or O if simple, or -1 if FeatureAttribute,
* Object|Feature|[Attribute] : attribute value
*
* This implementation does not have the ambition of being robust.
*
* @task test with other FeatureType than DefaultFeatureType
* @task add method marshall(Feature, ByteArrayOutputStream) and unmarshall(ByteArrayOutputStream), or create sub class.
*
* @author Christophe Rousson, SoC 2007, CRG-ULAVAL
*
*
*
* @source $URL$
*/
public class SimpleFeatureMarshaller {
/**
* marker to indicate an attribute is a feature in the serialized form
*/
public static final int FEATURE = -1;
public static final int SIMPLEATTRIBUTE = 0;
private HashMap<String, SimpleFeatureType> types;
private HashMap<String, SimpleFeatureBuilder> builders;
/** Default constructor.
*/
public SimpleFeatureMarshaller() {
types = new HashMap<String, SimpleFeatureType>();
builders = new HashMap<String, SimpleFeatureBuilder>();
}
/**
* Registers a type with the feature marshaller
*
* @param type
*/
public void registerType(SimpleFeatureType type) {
if (!types.containsKey(type.getName().getURI())){
types.put(type.getName().getURI(), type);
}
}
/**
* Looks in the type cache for a particular feature type
* @param typeName
* @return
*/
private SimpleFeatureType typeLookUp(String typeName) {
return types.get(typeName);
}
/** Marshall a feature into a stream.
* The type of that feature is not marshalled,
* type name is marshalled.
*
* @param f the Feature to marshall
* @param s the stream to write to
* @throws IOException
*/
public void marshall(SimpleFeature f, ObjectOutput s)
throws IOException {
SimpleFeatureType type = (SimpleFeatureType) f.getType();
registerType(type);
s.writeObject(type.getName().getURI());
s.writeObject(f.getID());
int natt = f.getAttributes().size();
s.writeInt(natt);
for (Iterator<Object> it = f.getAttributes().iterator(); it.hasNext();) {
Object att = it.next();
marshallSimpleAttribute(att, s);
}
}
/** Marshall an attribute into a stream.
*
* @task test object is instance of Serializable
*
* @param o an attribute value which is Serializable, or a feature, or a collection
* @param s the stream to write to
* @throws IOException
*/
protected void marshallSimpleAttribute(Object o, ObjectOutput s)
throws IOException {
if (o instanceof Collection) {
throw new IllegalArgumentException(
"Got instance of SimpleFeature with complex attributes.");
} else if (o instanceof SimpleFeature) {
s.writeInt(FEATURE);
marshall((SimpleFeature) o, s);
} else {
s.writeInt(SIMPLEATTRIBUTE);
s.writeObject(o);
}
}
/** Inverse operation of marshall : read a feature from a stream.
*
* @param s the stream to read from
* @return the unmarshalled feature
* @throws IOException
* @throws ClassNotFoundException
* @throws IllegalAttributeException
*/
public SimpleFeature unmarshall(ObjectInput s)
throws IOException, ClassNotFoundException, IllegalAttributeException {
String typeName = (String) s.readObject();
SimpleFeatureType type = typeLookUp(typeName);
if (type != null) {
SimpleFeature f = unmarshall(s, type);
if (f == null) {
System.err.println("Returning null feature");
}
return f;
} else {
throw new IllegalStateException(typeName + " is not a registered type.");
}
}
/** Inverse operation of marshall : read a feature from a stream.
*
* @param s the stream to read from
* @param the type of the feature to unmarshall
* @return the unmarshalled feature
* @throws IOException
* @throws ClassNotFoundException
* @throws IllegalAttributeException
*/
protected SimpleFeature unmarshall(ObjectInput s, SimpleFeatureType type)
throws IOException, ClassNotFoundException, IllegalAttributeException {
String fid = (String) s.readObject();
int natt = s.readInt();
SimpleFeatureBuilder builder = lookupBuilder(type);
if (!(natt == type.getAttributeCount())) {
throw new IOException("Schema error");
}
for (int i = 0; i < natt; i++) {
builder.add(unmarshallSimpleAttribute(s));
}
//return builder.feature(fid);
return builder.buildFeature(fid);
}
/*
* Looks up a feature builder from the builder cache; if not found then
* it will create a new one and add it to the cache.
*/
private SimpleFeatureBuilder lookupBuilder(SimpleFeatureType type){
SimpleFeatureBuilder builder = builders.get(type.getName().getURI());
if (builder == null){
builder = new SimpleFeatureBuilder(type);
builders.put(type.getName().getURI(), builder);
}
return builder;
}
/** Read attribute values from a stream.
*
* @param s the stream to read from
* @return a list of attribute values, possibly a singleton, if attribute's multiplicity is 1
* @throws IOException
* @throws ClassNotFoundException
* @throws IllegalAttributeException
*/
protected Object unmarshallSimpleAttribute(ObjectInput s)
throws IOException, ClassNotFoundException, IllegalAttributeException {
int m = s.readInt();
Object att = null;
if (m == SIMPLEATTRIBUTE) {
att = s.readObject();
} else if (m == FEATURE) {
SimpleFeature f = unmarshall(s);
att = f;
} else { // this should never happen
throw new IllegalAttributeException(null, null, "Found complex attribute which is not legal for SimpleFeature.");
}
return att;
}
}