/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2011, Geomatys
*
* 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.geotoolkit.process.mapfile;
import java.awt.Color;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import org.geotoolkit.factory.FactoryFinder;
import org.geotoolkit.nio.IOUtilities;
import org.geotoolkit.style.DefaultStyleFactory;
import org.geotoolkit.style.MutableStyleFactory;
import org.apache.sis.util.ObjectConverters;
import org.apache.sis.util.CharSequences;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.PropertyName;
import static org.geotoolkit.process.mapfile.MapfileTypes.*;
import org.opengis.feature.AttributeType;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
import org.opengis.feature.PropertyNotFoundException;
import org.opengis.feature.PropertyType;
/**
* Read the given mapfile and return a feature which type is MapFileTypes.MAP.
*
* @author Johann Sorel (Geomatys)
*/
public class MapfileReader {
private static final MutableStyleFactory SF = new DefaultStyleFactory();
private static final FilterFactory FF = FactoryFinder.getFilterFactory(null);
private Object in = null;
public MapfileReader() {
}
/**
* @param in : source, can be a File, String, URL or URI
*/
public void setInput(final Object in){
this.in = in;
}
/**
* Read the given file and return a feature which type is MapFileTypes.MAP.
* @return Feature
* @throws IOException
*/
public Feature read() throws IOException{
InputStream stream = IOUtilities.open(in);
LineNumberReader reader = null;
try{
reader = new LineNumberReader(new InputStreamReader(stream));
return (Feature) readElement(null,reader,null);
}finally{
if(reader != null){
reader.close();
}
}
}
private static Feature readElement(final Feature parent, final LineNumberReader reader, String line) throws IOException{
if(line ==null){
line = reader.readLine().trim();
}
if(line == null){
return null;
}
final String typeName;
String value;
final int spaceIndex = line.indexOf(' ');
if(spaceIndex < 0){
//value is on next lines
typeName = line;
value = null;
}else{
//value is on the line
typeName = line.substring(0, spaceIndex);
value = line.substring(spaceIndex);
}
if(typeName.equalsIgnoreCase("INCLUDE")){
//TODO open the related file and append all string to it
}
final FeatureType ft = getType(typeName);
if(value == null && ft != null){
//it's a feature type
final Feature f = ft.newInstance();
//read until element END
line = reader.readLine().trim();
while(!line.equalsIgnoreCase("END")){
final Feature prop = readElement(f, reader,line);
if(prop != null){
final String name = prop.getType().getName().toString();
final Object oldValues = f.getPropertyValue(name);
if (oldValues instanceof Collection) {
final List<Feature> values = new ArrayList<>((Collection)oldValues);
values.add(prop);
f.setPropertyValue(name, values);
} else {
f.setPropertyValue(name, prop);
}
}
line = reader.readLine().trim();
}
return f;
}else{
//read the full value if needed
if(value == null){
//read all until next END element
line = reader.readLine().trim();
while(!line.equalsIgnoreCase("END")){
value += line;
line = reader.readLine().trim();
}
}
//it's a single property from parent type
final AttributeType desc = getDescriptorIgnoreCase(parent.getType(), typeName);
if(desc!=null){
parent.setPropertyValue(desc.getName().toString(), convertType(value, desc));
}
return null;
}
}
/**
* Handle mapfile formating :
* - Boolean [on/off] [true/false]
* - Color [r] [g] [b]
* - Point [x] [y]
*/
private static Object convertType(String value, final AttributeType desc) throws IOException{
value = value.trim();
if(value.endsWith("END")){
value = value.substring(0,value.length()-4);
}
if(value.startsWith("\"") || value.startsWith("'")){
value = value.substring(1, value.length()-1);
}
final Class clazz = desc.getValueClass();
if(clazz == Boolean.class){
return (value.equalsIgnoreCase("on") || value.equalsIgnoreCase("true"));
}else if(clazz == Color.class){
if(value.startsWith("#")){
//normal conversion
return ObjectConverters.convert(value, clazz);
}
final String[] colors = value.split(" ");
return new Color(Integer.valueOf(colors[0]), Integer.valueOf(colors[1]), Integer.valueOf(colors[2]));
}else if(Point2D.class.isAssignableFrom(clazz)){
final String[] parts = value.split(" ");
return new Point2D.Double(Double.parseDouble(parts[0]), Double.parseDouble(parts[1]));
}else if(PropertyName.class.isAssignableFrom(clazz)){
//return it as a property name
return FF.property(value);
}else if(Expression.class.isAssignableFrom(clazz)){
if(value.startsWith("[")){
// like : [ATTRIBUTE] => PropertyName
value = value.substring(1, value.length()-1);
return FF.property(value);
}
if(value.startsWith("#")){
// like : #CD5F29 => Literal
return FF.literal(value);
}
final int nbspace = CharSequences.count(value, " ");
if(nbspace == 2){
// color 255 255 255 => Literal
final String[] colors = value.split(" ");
final Integer r = Integer.valueOf(colors[0]);
final Integer g = Integer.valueOf(colors[1]);
final Integer b = Integer.valueOf(colors[2]);
//possible -1 -1 -1 : we translate to white
final Color c;
if(r < 0 || g < 0 || b < 0){
c = Color.WHITE;
}else{
c = new Color(r,g,b);
}
return SF.literal(c);
}
//parse it as a number
try{
final double d = Double.valueOf(value);
return FF.literal(d);
}catch(final NumberFormatException ex){
}
//return it as a string literal
return FF.literal(value);
}else if(clazz == float[].class){
final String[] parts = value.split(" ");
final float[] dashes = new float[parts.length];
for(int i=0;i<parts.length;i++){
dashes[i] = Float.valueOf(parts[i].trim());
}
return dashes;
}
return ObjectConverters.convert(value, clazz);
}
private static AttributeType getDescriptorIgnoreCase(final FeatureType parent, final String name){
try {
final PropertyType pt = parent.getProperty(name);
if(pt instanceof AttributeType) return (AttributeType) pt;
} catch(PropertyNotFoundException ex) {
//do nothing
}
return null;
}
}