/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2003-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.data;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.data.collection.CollectionDataStore;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.AttributeTypeBuilder;
import org.geotools.feature.DefaultFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureCollections;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.FeatureTypes;
import org.geotools.feature.IllegalAttributeException;
import org.geotools.feature.NameImpl;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.feature.type.AttributeDescriptorImpl;
import org.geotools.feature.type.AttributeTypeImpl;
import org.geotools.feature.type.GeometryDescriptorImpl;
import org.geotools.feature.type.GeometryTypeImpl;
import org.geotools.filter.FilterAttributeExtractor;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.referencing.CRS;
import org.geotools.resources.Utilities;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.util.Converters;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureVisitor;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.GeometryType;
import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Expression;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
/**
* Utility functions for use when implementing working with data classes.
* <p>
* TODO: Move FeatureType manipulation to feature package
* </p>
* @author Jody Garnett, Refractions Research
* @source $URL$
*/
public class DataUtilities {
static Map<String,Class> typeMap = new HashMap<String,Class>();
static Map<Class,String> typeEncode = new HashMap<Class,String>();
static FilterFactory ff = CommonFactoryFinder.getFilterFactory( null );
static {
typeEncode.put( String.class, "String");
typeMap.put("String", String.class);
typeMap.put("string", String.class);
typeMap.put("\"\"", String.class);
typeEncode.put( Integer.class, "Integer");
typeMap.put("Integer", Integer.class);
typeMap.put("int", Integer.class);
typeMap.put("0", Integer.class);
typeEncode.put( Double.class, "Double");
typeMap.put("Double", Double.class);
typeMap.put("double", Double.class);
typeMap.put("0.0", Double.class);
typeEncode.put( Float.class, "Float");
typeMap.put("Float", Float.class);
typeMap.put("float", Float.class);
typeMap.put("0.0f", Float.class);
typeEncode.put( Boolean.class, "Boolean");
typeMap.put("Boolean", Boolean.class);
typeMap.put("true",Boolean.class);
typeMap.put("false",Boolean.class);
typeEncode.put( Geometry.class, "Geometry");
typeMap.put("Geometry", Geometry.class);
typeEncode.put( Point.class, "Point");
typeMap.put("Point", Point.class);
typeEncode.put( LineString.class, "LineString");
typeMap.put("LineString", LineString.class);
typeEncode.put( Polygon.class, "Polygon");
typeMap.put("Polygon", Polygon.class);
typeEncode.put( MultiPoint.class, "MultiPoint");
typeMap.put("MultiPoint", MultiPoint.class);
typeEncode.put( MultiLineString.class, "MultiLineString");
typeMap.put("MultiLineString", MultiLineString.class);
typeEncode.put( MultiPolygon.class, "MultiPolygon");
typeMap.put("MultiPolygon", MultiPolygon.class);
typeEncode.put( GeometryCollection.class, "GeometryCollection");
typeMap.put("GeometryCollection", GeometryCollection.class);
typeEncode.put( Date.class, "Date");
typeMap.put("Date",Date.class);
}
/**
* DOCUMENT ME!
*
* @param featureType DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public static String[] attributeNames(SimpleFeatureType featureType) {
String[] names = new String[featureType.getAttributeCount()];
final int count = featureType.getAttributeCount();
for (int i = 0; i < count; i++) {
names[i] = featureType.getDescriptor(i).getLocalName();
}
return names;
}
/**
* A replacement for File.toURI().toURL().
* <p>
* The handling of file.toURL() is broken; the handling of file.toURI().toURL() is known
* to be broken on a few platforms like mac. We have the urlToFile( URL ) method that
* is able to untangle both these problems and we use it in the geotools library.
* <p>
* However occasionally we need to pick up a file and hand it to a third party library
* like EMF; this method performs a couple of sanity checks which we can use to prepare
* a good URL reference to a file in these situtations.
*
* @param file
* @return URL
*/
public static URL fileToURL(File file) {
try {
URL url = file.toURI().toURL();
String string = url.toExternalForm();
if( string.contains("+")){
// this represents an invalid URL created using either
// file.toURL(); or
// file.toURI().toURL() on a specific version of Java 5 on Mac
string = string.replace("+","%2B");
}
if( string.contains(" ")){
// this represents an invalid URL created using either
// file.toURL(); or
// file.toURI().toURL() on a specific version of Java 5 on Mac
string = string.replace(" ","%20");
}
return new URL( string );
} catch (MalformedURLException e) {
return null;
}
}
/**
* Takes a URL and converts it to a File. The attempts to deal with
* Windows UNC format specific problems, specifically files located
* on network shares and different drives.
*
* If the URL.getAuthority() returns null or is empty, then only the
* url's path property is used to construct the file. Otherwise, the
* authority is prefixed before the path.
*
* It is assumed that url.getProtocol returns "file".
*
* Authority is the drive or network share the file is located on.
* Such as "C:", "E:", "\\fooServer"
*
* @param url a URL object that uses protocol "file"
* @return a File that corresponds to the URL's location
*/
public static File urlToFile(URL url) {
if( !"file".equals(url.getProtocol())){
return null; // not a File URL
}
String string = url.toExternalForm();
if( string.contains("+")){
// this represents an invalid URL created using either
// file.toURL(); or
// file.toURI().toURL() on a specific version of Java 5 on Mac
string = string.replace("+","%2B");
}
try {
string = URLDecoder.decode(string, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Could not decode the URL to UTF-8 format", e);
}
String path3;
String simplePrefix = "file:/";
String standardPrefix = "file://";
String os = System.getProperty("os.name");
if (os.toUpperCase().contains("WINDOWS") && string.startsWith(standardPrefix)) {
// win32: host/share reference
path3 = string.substring(standardPrefix.length()-2);
}
else if( string.startsWith(standardPrefix) ){
path3 = string.substring( standardPrefix.length() );
} else if( string.startsWith(simplePrefix)){
path3 = string.substring( simplePrefix.length()-1 );
} else {
String auth = url.getAuthority();
String path2 = url.getPath().replace("%20", " ");
if (auth != null && !auth.equals("")) {
path3 = "//" + auth + path2;
} else {
path3 = path2;
}
}
return new File(path3);
}
/**
* Traverses the filter and returns any encoutered property names.
* <p>
* The feautre type is supplied as contexts used to lookup expressions in cases where the
* attributeName does not match the actual name of the type.
* </p>
*/
public static String[] attributeNames( Filter filter, final SimpleFeatureType featureType ) {
if (filter == null) {
return new String[0];
}
FilterAttributeExtractor attExtractor = new FilterAttributeExtractor(featureType);
filter.accept(attExtractor, null);
String[] attributeNames = attExtractor.getAttributeNames();
return attributeNames;
}
/**
* Traverses the filter and returns any encoutered property names.
* @deprecated use {@link #attributeNames(Filter, FeatureType)}/
*/
public static String[] attributeNames(Filter filter) {
return attributeNames( filter, null );
}
/**
* Traverses the expression and returns any encoutered property names.
* <p>
* The feautre type is supplied as contexts used to lookup expressions in cases where the
* attributeName does not match the actual name of the type.
* </p>
*/
public static String[] attributeNames(Expression expression, final SimpleFeatureType featureType ) {
if (expression == null) {
return new String[0];
}
FilterAttributeExtractor attExtractor = new FilterAttributeExtractor(featureType);
expression.accept(attExtractor, null);
String[] attributeNames = attExtractor.getAttributeNames();
return attributeNames;
}
/**
* Traverses the expression and returns any encoutered property names.
* @deprecated use {@link #attributeNames(Expression, FeatureType)}/
*/
public static String[] attributeNames(Expression expression) {
return attributeNames( expression, null );
}
/**
* Compare operation for FeatureType.
*
* <p>
* Results in:
* </p>
*
* <ul>
* <li>
* 1: if typeA is a sub type/reorder/renamespace of typeB
* </li>
* <li>
* 0: if typeA and typeB are the same type
* </li>
* <li>
* -1: if typeA is not subtype of typeB
* </li>
* </ul>
*
* <p>
* Comparison is based on AttributeTypes, an IOException is thrown if the
* AttributeTypes are not compatiable.
* </p>
*
* <p>
* Namespace is not considered in this opperations. You may still need to
* reType to get the correct namesapce, or reorder.
* </p>
*
* @param typeA FeatureType beind compared
* @param typeB FeatureType being compared against
*
*/
public static int compare(SimpleFeatureType typeA, SimpleFeatureType typeB) {
if (typeA == typeB) {
return 0;
}
if (typeA == null) {
return -1;
}
if (typeB == null) {
return -1;
}
int countA = typeA.getAttributeCount();
int countB = typeB.getAttributeCount();
if (countA > countB) {
return -1;
}
// may still be the same featureType
// (Perhaps they differ on namespace?)
AttributeDescriptor a;
// may still be the same featureType
// (Perhaps they differ on namespace?)
int match = 0;
for (int i = 0; i < countA; i++) {
a = typeA.getDescriptor(i);
if (isMatch(a, typeB.getDescriptor(i))) {
match++;
} else if (isMatch(a, typeB.getDescriptor(a.getLocalName()))) {
// match was found in a different position
} else {
// cannot find any match for Attribute in typeA
return -1;
}
}
if ((countA == countB) && (match == countA)) {
// all attributes in typeA agreed with typeB
// (same order and type)
// if (typeA.getNamespace() == null) {
// if(typeB.getNamespace() == null) {
// return 0;
// } else {
// return 1;
// }
// } else if(typeA.getNamespace().equals(typeB.getNamespace())) {
// return 0;
// } else {
// return 1;
// }
return 0;
}
return 1;
}
/**
* DOCUMENT ME!
*
* @param a DOCUMENT ME!
* @param b DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public static boolean isMatch(AttributeDescriptor a, AttributeDescriptor b) {
if (a == b) {
return true;
}
if (b == null) {
return false;
}
if (a == null) {
return false;
}
if (a.equals(b)) {
return true;
}
if (a.getLocalName().equals(b.getLocalName())
&& a.getClass().equals(b.getClass())) {
return true;
}
return false;
}
/**
* Creates duplicate of feature adjusted to the provided featureType.
*
* @param featureType FeatureType requested
* @param feature Origional Feature from DataStore
*
* @return An instance of featureType based on feature
*
* @throws IllegalAttributeException If opperation could not be performed
*/
public static SimpleFeature reType(SimpleFeatureType featureType, SimpleFeature feature)
throws IllegalAttributeException {
SimpleFeatureType origional = feature.getFeatureType();
if (featureType.equals(origional)) {
return SimpleFeatureBuilder.copy(feature);
}
String id = feature.getID();
int numAtts = featureType.getAttributeCount();
Object[] attributes = new Object[numAtts];
String xpath;
for (int i = 0; i < numAtts; i++) {
AttributeDescriptor curAttType = featureType.getDescriptor(i);
xpath = curAttType.getLocalName();
attributes[i] = duplicate(feature.getAttribute(xpath));
}
return SimpleFeatureBuilder.build(featureType, attributes, id);
}
public static Object duplicate( Object src ) {
//JD: this method really needs to be replaced with somethign better
if (src == null) {
return null;
}
//
// The following are things I expect
// Features will contain.
//
if (src instanceof String || src instanceof Integer
|| src instanceof Double || src instanceof Float
|| src instanceof Byte || src instanceof Boolean
|| src instanceof Short || src instanceof Long
|| src instanceof Character || src instanceof Number) {
return src;
}
if (src instanceof Date) {
return new Date( ((Date)src).getTime() );
}
if (src instanceof URL || src instanceof URI ) {
return src; //immutable
}
if (src instanceof Object[]) {
Object[] array = (Object[]) src;
Object[] copy = new Object[array.length];
for (int i = 0; i < array.length; i++) {
copy[i] = duplicate(array[i]);
}
return copy;
}
if (src instanceof Geometry) {
Geometry geometry = (Geometry) src;
return geometry.clone();
}
if (src instanceof SimpleFeature) {
SimpleFeature feature = (SimpleFeature) src;
return SimpleFeatureBuilder.copy(feature);
}
//
// We are now into diminishing returns
// I don't expect Features to contain these often
// (eveything is still nice and recursive)
//
Class<? extends Object> type = src.getClass();
if (type.isArray() && type.getComponentType().isPrimitive()) {
int length = Array.getLength(src);
Object copy = Array.newInstance(type.getComponentType(), length);
System.arraycopy(src, 0, copy, 0, length);
return copy;
}
if (type.isArray()) {
int length = Array.getLength(src);
Object copy = Array.newInstance(type.getComponentType(), length);
for (int i = 0; i < length; i++) {
Array.set(copy, i, duplicate(Array.get(src, i)));
}
return copy;
}
if (src instanceof List) {
List list = (List) src;
List<Object> copy = new ArrayList<Object>(list.size());
for (Iterator i = list.iterator(); i.hasNext();) {
copy.add(duplicate(i.next()));
}
return Collections.unmodifiableList(copy);
}
if (src instanceof Map) {
Map map = (Map) src;
Map copy = new HashMap(map.size());
for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry) i.next();
copy.put(entry.getKey(), duplicate(entry.getValue()));
}
return Collections.unmodifiableMap(copy);
}
if( src instanceof GridCoverage ){
return src; // inmutable
}
//
// I have lost hope and am returning the orgional reference
// Please extend this to support additional classes.
//
// And good luck getting Cloneable to work
throw new IllegalAttributeException("Do not know how to deep copy "
+ type.getName());
}
/**
* Constructs an empty feature to use as a Template for new content.
*
* <p>
* We may move this functionality to FeatureType.create( null )?
* </p>
*
* @param featureType Type of feature we wish to create
*
* @return A new Feature of type featureType
*
* @throws IllegalAttributeException if we could not create featureType
* instance with acceptable default values
*/
public static SimpleFeature template(SimpleFeatureType featureType)
throws IllegalAttributeException {
return SimpleFeatureBuilder.build(featureType, defaultValues(featureType), null);
}
/**
* DOCUMENT ME!
*
* @param featureType DOCUMENT ME!
* @param featureID DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws IllegalAttributeException DOCUMENT ME!
*/
public static SimpleFeature template(SimpleFeatureType featureType, String featureID)
throws IllegalAttributeException {
return SimpleFeatureBuilder.build(featureType, defaultValues(featureType), featureID);
}
/**
* DOCUMENT ME!
*
* @param featureType DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws IllegalAttributeException DOCUMENT ME!
*/
public static Object[] defaultValues(SimpleFeatureType featureType)
throws IllegalAttributeException {
return defaultValues(featureType, null);
}
/**
* DOCUMENT ME!
*
* @param featureType DOCUMENT ME!
* @param atts DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws IllegalAttributeException DOCUMENT ME!
*/
public static SimpleFeature template(SimpleFeatureType featureType, Object[] atts)
throws IllegalAttributeException {
return SimpleFeatureBuilder.build(featureType,defaultValues(featureType, atts),null);
}
/**
* DOCUMENT ME!
*
* @param featureType DOCUMENT ME!
* @param featureID DOCUMENT ME!
* @param atts DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws IllegalAttributeException DOCUMENT ME!
*/
public static SimpleFeature template(SimpleFeatureType featureType, String featureID,
Object[] atts) throws IllegalAttributeException {
return SimpleFeatureBuilder.build(featureType, defaultValues(featureType, atts), featureID);
}
/**
* DOCUMENT ME!
*
* @param featureType DOCUMENT ME!
* @param values DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws IllegalAttributeException DOCUMENT ME!
* @throws ArrayIndexOutOfBoundsException DOCUMENT ME!
*/
public static Object[] defaultValues(SimpleFeatureType featureType,
Object[] values) throws IllegalAttributeException {
if (values == null) {
values = new Object[featureType.getAttributeCount()];
} else if (values.length != featureType.getAttributeCount()) {
throw new ArrayIndexOutOfBoundsException("values");
}
for (int i = 0; i < featureType.getAttributeCount(); i++) {
values[i] = defaultValue(featureType.getDescriptor(i));
}
return values;
}
/**
* Provides a defautlValue for attributeType.
*
* <p>
* Will return null if attributeType isNillable(), or attempt to use
* Reflection, or attributeType.parse( null )
* </p>
*
* @param attributeType
*
* @return null for nillable attributeType, attempt at reflection
*
* @throws IllegalAttributeException If value cannot be constructed for
* attribtueType
*/
public static Object defaultValue(AttributeDescriptor attributeType)
throws IllegalAttributeException {
Object value = attributeType.getDefaultValue();
if (value == null && !attributeType.isNillable()) {
return null; // sometimes there is no valid default value :-(
// throw new IllegalAttributeException("Got null default value for non-null type.");
}
return value;
}
/**
* Returns a non-null default value for the class that is passed in. This is a helper class an can't create a
* default class for any type but it does support:
* <ul>
* <li>String</li>
* <li>Object - will return empty string</li>
* <li>Number</li>
* <li>Character</li>
* <li>JTS Geometries</li>
* </ul>
*
*
* @param type
* @return
*/
public static Object defaultValue(Class type){
if( type==String.class || type==Object.class){
return "";
}
if( type==Integer.class ){
return new Integer(0);
}
if( type==Double.class ){
return new Double(0);
}
if( type==Long.class ){
return new Long(0);
}
if( type==Short.class ){
return new Short((short)0);
}
if( type==Float.class ){
return new Float(0.0f);
}
if( type==BigDecimal.class){
return BigDecimal.valueOf(0);
}
if( type==BigInteger.class){
return BigInteger.valueOf(0);
}
if( type==Character.class ){
return new Character(' ');
}
if( type==Boolean.class){
return Boolean.FALSE;
}
if( type==Timestamp.class)
return new Timestamp(System.currentTimeMillis());
if( type==java.sql.Date.class)
return new java.sql.Date(System.currentTimeMillis());
if( type==java.sql.Time.class)
return new java.sql.Time(System.currentTimeMillis());
if( type==java.util.Date.class)
return new java.util.Date();
GeometryFactory fac=new GeometryFactory();
Coordinate coordinate = new Coordinate(0, 0);
Point point = fac.createPoint(coordinate);
if( type==Point.class ){
return point;
}
if( type==MultiPoint.class ){
return fac.createMultiPoint(new Point[]{point});
}
if( type==LineString.class ){
return fac.createLineString(new Coordinate[]{coordinate,coordinate,coordinate,coordinate});
}
LinearRing linearRing = fac.createLinearRing(new Coordinate[]{coordinate,coordinate,coordinate,coordinate});
if( type==LinearRing.class ){
return linearRing;
}
if( type==MultiLineString.class ){
return fac.createMultiLineString(new LineString[]{linearRing});
}
Polygon polygon = fac.createPolygon(linearRing, new LinearRing[0]);
if( type==Polygon.class ){
return polygon;
}
if( type==MultiPolygon.class ){
return fac.createMultiPolygon(new Polygon[]{polygon});
}
throw new IllegalArgumentException(type+" is not supported by this method");
}
/**
* Creates a FeatureReader<SimpleFeatureType, SimpleFeature> for testing.
*
* @param features Array of features
*
* @return FeatureReader<SimpleFeatureType, SimpleFeature> spaning provided feature array
*
* @throws IOException If provided features Are null or empty
* @throws NoSuchElementException DOCUMENT ME!
*/
public static FeatureReader<SimpleFeatureType, SimpleFeature> reader(final SimpleFeature[] features)
throws IOException {
if ((features == null) || (features.length == 0)) {
throw new IOException("Provided features where empty");
}
return new FeatureReader<SimpleFeatureType, SimpleFeature>() {
SimpleFeature[] array = features;
int offset = -1;
public SimpleFeatureType getFeatureType() {
return features[0].getFeatureType();
}
public SimpleFeature next(){
if (!hasNext()) {
throw new NoSuchElementException("No more features");
}
return array[++offset];
}
public boolean hasNext(){
return (array != null) && (offset < (array.length - 1));
}
public void close(){
array = null;
offset = -1;
}
};
}
/**
* DOCUMENT ME!
*
* @param featureArray DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws IOException DOCUMENT ME!
* @throws RuntimeException DOCUMENT ME!
*/
public static FeatureSource<SimpleFeatureType, SimpleFeature> source(final SimpleFeature[] featureArray) {
final SimpleFeatureType featureType;
if ((featureArray == null) || (featureArray.length == 0)) {
featureType = FeatureTypes.EMPTY;
} else {
featureType = featureArray[0].getFeatureType();
}
DataStore arrayStore = new AbstractDataStore() {
public String[] getTypeNames() {
return new String[] { featureType.getTypeName() };
}
public SimpleFeatureType getSchema(String typeName)
throws IOException {
if ((typeName != null)
&& typeName.equals(featureType.getTypeName())) {
return featureType;
}
throw new IOException(typeName + " not available");
}
protected FeatureReader<SimpleFeatureType, SimpleFeature> getFeatureReader(String typeName)
throws IOException {
return reader(featureArray);
}
};
try {
return arrayStore.getFeatureSource(arrayStore.getTypeNames()[0]);
} catch (IOException e) {
throw new RuntimeException(
"Something is wrong with the geotools code, "
+ "this exception should not happen", e);
}
}
/**
* Wrap up the provided FeatureCollection as a feature soruce; allowing queries to be performed etc...
*
* @param collection FeatureCollection
*
* @return FeatureSource
*
* @throws NullPointerException If the collection is null
* @throws RuntimeException
*/
public static FeatureSource<SimpleFeatureType, SimpleFeature> source(final FeatureCollection<SimpleFeatureType, SimpleFeature> collection) {
if (collection == null) {
throw new NullPointerException();
}
DataStore store = new CollectionDataStore(collection);
try {
return store.getFeatureSource(store.getTypeNames()[0]);
} catch (IOException e) {
throw new RuntimeException(
"FeatureCollection is not consistent, unable to access contents", e);
}
}
public static FeatureCollection<SimpleFeatureType, SimpleFeature> results(SimpleFeature[] featureArray){
return results(collection(featureArray));
}
/**
* Returns collection if non empty.
*
* @param collection
*
* @return provided collection
*
* @throws IOException Raised if collection was empty
*/
public static FeatureCollection<SimpleFeatureType, SimpleFeature> results(final FeatureCollection<SimpleFeatureType, SimpleFeature> collection){
if (collection.size() == 0) {
//throw new IOException("Provided collection was empty");
}
return collection;
}
/**
* Adapt a collection to a reader for use with FeatureStore.setFeatures( reader ).
*
* @param collection Collection of SimpleFeature
*
* @return FeatureRedaer over the provided contents
* @throws IOException IOException if there is any problem reading the content.
*/
public static FeatureReader<SimpleFeatureType, SimpleFeature> reader(Collection<SimpleFeature> collection)
throws IOException {
return reader(collection.toArray(
new SimpleFeature[collection.size()]));
}
/**
* Adapt a collection to a reader for use with FeatureStore.setFeatures( reader ).
*
* @param collection Collection of SimpleFeature
*
* @return FeatureRedaer over the provided contents
* @throws IOException IOException if there is any problem reading the content.
*/
public static FeatureReader<SimpleFeatureType, SimpleFeature> reader(
FeatureCollection<SimpleFeatureType,SimpleFeature> collection) throws IOException {
return reader(collection
.toArray(new SimpleFeature[collection.size()]));
}
/**
* Copies the provided features into a FeatureCollection.
* <p>
* Often used when gathering features for FeatureStore:<pre><code>
* featureStore.addFeatures( DataUtilities.collection(array));
* </code></pre>
*
* @param features Array of features
* @return FeatureCollection
*/
public static FeatureCollection<SimpleFeatureType, SimpleFeature> collection(SimpleFeature[] features) {
FeatureCollection<SimpleFeatureType, SimpleFeature> collection = FeatureCollections.newCollection();
final int length = features.length;
for (int i = 0; i < length; i++) {
collection.add(features[i]);
}
return collection;
}
/**
* Copies the provided features into a FeatureCollection.
* <p>
* Often used when gathering a FeatureCollection<SimpleFeatureType, SimpleFeature> into memory.
*
* @param FeatureCollection<SimpleFeatureType, SimpleFeature> the features to add to a new feature collection.
* @return FeatureCollection
*/
public static DefaultFeatureCollection collection( FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection ){
return new DefaultFeatureCollection( featureCollection );
}
/**
* Copies the provided fetaures into a List.
*
* @param featureCollection
* @return List of features copied into memory
*/
public static List<SimpleFeature> list( FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection ){
final ArrayList<SimpleFeature> list = new ArrayList<SimpleFeature>();
try {
featureCollection.accepts( new FeatureVisitor(){
public void visit(Feature feature) {
list.add( (SimpleFeature) feature );
}
}, null );
}
catch( IOException ignore ){
}
return list;
}
/**
* Copies the feature ids from each and every feature into a set.
* <p>
* This method can be slurp an in memory record of the contents of a
* @param featureCollection
* @return
*/
public static Set<String> fidSet( FeatureCollection<?,?> featureCollection ){
final HashSet<String> fids = new HashSet<String>();
try {
featureCollection.accepts( new FeatureVisitor(){
public void visit(Feature feature) {
fids.add( feature.getIdentifier().getID() );
}
}, null );
}
catch( IOException ignore ){
}
return fids;
}
/**
* Copies the provided features into a FeatureCollection.
* <p>
* Often used when gathering a FeatureCollection<SimpleFeatureType, SimpleFeature> into memory.
*
* @param list features to add to a new FeatureCollection
* @return FeatureCollection
*/
public static FeatureCollection<SimpleFeatureType, SimpleFeature> collection( List<SimpleFeature> list ) {
FeatureCollection<SimpleFeatureType, SimpleFeature> collection = FeatureCollections.newCollection();
for ( SimpleFeature feature : list ){
collection.add( feature );
}
return collection;
}
/**
* Copies the provided features into a FeatureCollection.
* <p>
* Often used when gathering features for FeatureStore:<pre><code>
* featureStore.addFeatures( DataUtilities.collection(feature));
* </code></pre>
*
* @param feature a feature to add to a new collection
* @return FeatureCollection
*/
public static FeatureCollection<SimpleFeatureType, SimpleFeature> collection( SimpleFeature feature ){
FeatureCollection<SimpleFeatureType, SimpleFeature> collection = FeatureCollections.newCollection();
collection.add(feature);
return collection;
}
/**
* Copies the provided reader into a FeatureCollection, reader will be closed.
* <p>
* Often used when gathering features for FeatureStore:<pre><code>
* featureStore.addFeatures( DataUtilities.collection(reader));
* </code></pre>
*
* @return FeatureCollection
*/
public static FeatureCollection<SimpleFeatureType, SimpleFeature> collection(FeatureReader <SimpleFeatureType, SimpleFeature> reader) throws IOException {
FeatureCollection<SimpleFeatureType, SimpleFeature> collection = FeatureCollections.newCollection();
try {
while( reader.hasNext() ) {
try {
collection.add( reader.next() );
} catch (NoSuchElementException e) {
throw (IOException) new IOException("EOF").initCause( e );
} catch (IllegalAttributeException e) {
throw (IOException) new IOException().initCause( e );
}
}
}
finally {
reader.close();
}
return collection;
}
/**
* Copies the provided reader into a FeatureCollection, reader will be closed.
* <p>
* Often used when gathering features for FeatureStore:<pre><code>
* featureStore.addFeatures( DataUtilities.collection(reader));
* </code></pre>
*
* @return FeatureCollection
*/
public static FeatureCollection<SimpleFeatureType, SimpleFeature> collection(FeatureIterator<SimpleFeature> reader) throws IOException {
FeatureCollection<SimpleFeatureType, SimpleFeature> collection = FeatureCollections.newCollection();
try {
while( reader.hasNext() ) {
try {
collection.add( reader.next() );
} catch (NoSuchElementException e) {
throw (IOException) new IOException("EOF").initCause( e );
}
}
}
finally {
reader.close();
}
return collection;
}
/**
* DOCUMENT ME!
*
* @param att DOCUMENT ME!
* @param otherAtt DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public static boolean attributesEqual(Object att, Object otherAtt) {
if (att == null) {
if (otherAtt != null) {
return false;
}
} else {
if (!att.equals(otherAtt)) {
if (att instanceof Geometry && otherAtt instanceof Geometry) {
// we need to special case Geometry
// as JTS is broken
// Geometry.equals( Object ) and Geometry.equals( Geometry )
// are different
// (We should fold this knowledge into AttributeType...)
//
if (!((Geometry) att).equals((Geometry) otherAtt)) {
return false;
}
} else {
return false;
}
}
}
return true;
}
/**
* Create a derived FeatureType
*
* <p></p>
*
* @param featureType
* @param properties - if null, every property of the feature type in input will be used
* @param override
*
*
* @throws SchemaException
*/
public static SimpleFeatureType createSubType(SimpleFeatureType featureType,
String[] properties, CoordinateReferenceSystem override)
throws SchemaException {
URI namespaceURI = null;
if ( featureType.getName().getNamespaceURI() != null ) {
try {
namespaceURI = new URI( featureType.getName().getNamespaceURI() );
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
return createSubType( featureType, properties, override, featureType.getTypeName(), namespaceURI );
}
public static SimpleFeatureType createSubType(SimpleFeatureType featureType,
String[] properties, CoordinateReferenceSystem override, String typeName, URI namespace )
throws SchemaException {
if ((properties == null) && (override == null)) {
return featureType;
}
if(properties == null) {
properties = new String[featureType.getAttributeCount()];
for (int i = 0; i < properties.length; i++) {
properties[i] = featureType.getDescriptor(i).getLocalName();
}
}
String namespaceURI = namespace != null ? namespace.toString() : null;
boolean same = featureType.getAttributeCount() == properties.length &&
featureType.getTypeName().equals( typeName ) &&
Utilities.equals(featureType.getName().getNamespaceURI(), namespaceURI );
for (int i = 0; (i < featureType.getAttributeCount()) && same; i++) {
AttributeDescriptor type = featureType.getDescriptor(i);
same = type.getLocalName().equals(properties[i])
&& (((override != null)
&& type instanceof GeometryDescriptor)
? assertEquals(override, ((GeometryDescriptor) type).getCoordinateReferenceSystem())
: true);
}
if (same) {
return featureType;
}
AttributeDescriptor[] types = new AttributeDescriptor[properties.length];
for (int i = 0; i < properties.length; i++) {
types[i] = featureType.getDescriptor(properties[i]);
if ((override != null) && types[i] instanceof GeometryDescriptor) {
AttributeTypeBuilder ab = new AttributeTypeBuilder();
ab.init( types[i] );
ab.setCRS(override);
types[i] = ab.buildDescriptor(types[i].getLocalName(), ab.buildGeometryType());
}
}
if( typeName == null ) typeName = featureType.getTypeName();
if( namespace == null && featureType.getName().getNamespaceURI() != null )
try {
namespace = new URI(featureType.getName().getNamespaceURI());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
tb.setName( typeName );
tb.setNamespaceURI( namespace );
tb.addAll(types);
return tb.buildFeatureType();
}
private static boolean assertEquals(Object o1, Object o2){
return o1 == null && o2 == null? true :
(o1 != null? o1.equals(o2) : false);
}
/**
* DOCUMENT ME!
*
* @param featureType DOCUMENT ME!
* @param properties DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws SchemaException DOCUMENT ME!
*/
public static SimpleFeatureType createSubType(SimpleFeatureType featureType,
String[] properties) throws SchemaException {
if (properties == null) {
return featureType;
}
boolean same = featureType.getAttributeCount() == properties.length;
for (int i = 0; (i < featureType.getAttributeCount()) && same; i++) {
same = featureType.getDescriptor(i).getLocalName().equals(properties[i]);
}
if (same) {
return featureType;
}
SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
tb.setName( featureType.getName() );
for (int i = 0; i < properties.length; i++) {
tb.add( featureType.getDescriptor(properties[i]) );
}
return tb.buildFeatureType();
}
/**
* Utility method for FeatureType construction.
* <p>
* Will parse a String of the form: <i>"name:Type,name2:Type2,..."</i>
* </p>
*
* <p>
* Where <i>Type</i> is defined by createAttribute.
* </p>
*
* <p>
* You may indicate the default Geometry with an astrix: "*geom:Geometry". You
* may also indicate the srid (used to look up a EPSG code).
* </p>
*
* <p>
* Examples:
* <ul>
* <li><code>name:"",age:0,geom:Geometry,centroid:Point,url:java.io.URL"</code>
* <li><code>id:String,polygonProperty:Polygon:srid=32615</code>
* </ul>
* </p>
*
* @param identification identification of FeatureType:
* (<i>namesapce</i>).<i>typeName</i>
* @param typeSpec Specification for FeatureType
*
*
* @throws SchemaException
*/
public static SimpleFeatureType createType(String identification, String typeSpec)
throws SchemaException {
int split = identification.lastIndexOf('.');
String namespace = (split == -1) ? null
: identification.substring(0, split);
String typeName = (split == -1) ? identification
: identification.substring(split + 1);
return createType(namespace, typeName, typeSpec);
}
/**
* Utility method for FeatureType construction.
* <p>
* Will parse a String of the form: <i>"name:Type,name2:Type2,..."</i>
* </p>
*
* <p>
* Where <i>Type</i> is defined by createAttribute.
* </p>
*
* <p>
* You may indicate the default Geometry with an astrix: "*geom:Geometry". You
* may also indicate the srid (used to look up a EPSG code).
* </p>
*
* <p>
* Examples:
* <ul>
* <li><code>name:"",age:0,geom:Geometry,centroid:Point,url:java.io.URL"</code>
* <li><code>id:String,polygonProperty:Polygon:srid=32615</code>
* </ul>
* </p>
*
* @param identification identification of FeatureType:
* (<i>namesapce</i>).<i>typeName</i>
* @param typeSpec Specification for FeatureType
*
*
* @throws SchemaException
*/
public static SimpleFeatureType createType(String namespace, String typeName, String typeSpec)
throws SchemaException {
SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
tb.setName( typeName );
tb.setNamespaceURI(namespace);
String[] types = typeSpec.split(",");
AttributeDescriptor attributeType;
for (int i = 0; i < types.length; i++) {
boolean defaultGeometry = types[i].startsWith("*");
if (types[i].startsWith("*")) {
types[i] = types[i].substring(1);
}
attributeType = createAttribute(types[i]);
tb.add(attributeType);
if ( defaultGeometry ) {
tb.setDefaultGeometry(attributeType.getLocalName());
}
}
return tb.buildFeatureType();
}
/**
* DOCUMENT ME!
*
* @param type DOCUMENT ME!
* @param fid DOCUMENT ME!
* @param text DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws IllegalAttributeException DOCUMENT ME!
*/
public static SimpleFeature parse(SimpleFeatureType type, String fid, String[] text)
throws IllegalAttributeException {
Object[] attributes = new Object[text.length];
for (int i = 0; i < text.length; i++) {
AttributeType attType = type.getDescriptor(i).getType();
attributes[i] = Converters.convert(text[i], attType.getBinding());
}
return SimpleFeatureBuilder.build(type, attributes, fid);
}
/**
* A "quick" String representation of a FeatureType.
* <p>
* This string representation may be used with createType( name, spec ).
* </p>
* @param featureType FeatureType to represent
*
* @return The string "specification" for the featureType
*/
public static String spec(FeatureType featureType) {
Collection<PropertyDescriptor> types = featureType.getDescriptors();
StringBuffer buf = new StringBuffer();
for( PropertyDescriptor type : types ){
buf.append(type.getName().getLocalPart());
buf.append(":");
buf.append(typeMap(type.getType().getBinding()));
if(type instanceof GeometryDescriptor) {
GeometryDescriptor gd = (GeometryDescriptor) type;
if(gd.getCoordinateReferenceSystem() != null && gd.getCoordinateReferenceSystem().getIdentifiers() != null) {
for (Iterator<ReferenceIdentifier> it = gd.getCoordinateReferenceSystem().getIdentifiers().iterator(); it.hasNext();) {
ReferenceIdentifier id = it.next();
if ((id.getAuthority() != null)
&& id.getAuthority().getTitle().equals(Citations.EPSG.getTitle())) {
buf.append(":srid=" + id.getCode());
break;
}
}
}
}
buf.append(",");
}
buf.delete(buf.length()-1,buf.length()); // remove last ","
return buf.toString();
}
static Class type(String typeName) throws ClassNotFoundException {
if (typeMap.containsKey(typeName)) {
return typeMap.get(typeName);
}
return Class.forName(typeName);
}
static String typeMap(Class type) {
if( typeEncode.containsKey(type)){
return typeEncode.get( type );
}
/*
SortedSet<String> choose = new TreeSet<String>();
for (Iterator i = typeMap.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Entry) i.next();
if (entry.getValue().equals(type)) {
choose.add( (String) entry.getKey() );
}
}
if( !choose.isEmpty() ){
return choose.last();
}
*/
return type.getName();
}
/**
* Takes two {@link Query}objects and produce a new one by mixing the
* restrictions of both of them.
*
* <p>
* The policy to mix the queries components is the following:
*
* <ul>
* <li>
* typeName: type names MUST match (not checked if some or both queries
* equals to <code>Query.ALL</code>)
* </li>
* <li>
* handle: you must provide one since no sensible choice can be done
* between the handles of both queries
* </li>
* <li>
* maxFeatures: the lower of the two maxFeatures values will be used (most
* restrictive)
* </li>
* <li>
* attributeNames: the attributes of both queries will be joined in a
* single set of attributes. IMPORTANT: only <b><i>explicitly</i></b>
* requested attributes will be joint, so, if the method
* <code>retrieveAllProperties()</code> of some of the queries returns
* <code>true</code> it does not means that all the properties will be
* joined. You must create the query with the names of the properties you
* want to load.
* </li>
* <li>
* filter: the filtets of both queries are or'ed
* </li>
* <li>
* <b>any other query property is ignored</b> and no guarantees are made of
* their return values, so client code shall explicitly care of hints, startIndex, etc.,
* if needed.
* </li>
* </ul>
* </p>
*
* @param firstQuery Query against this DataStore
* @param secondQuery DOCUMENT ME!
* @param handle DOCUMENT ME!
*
* @return Query restricted to the limits of definitionQuery
*
* @throws NullPointerException if some of the queries is null
* @throws IllegalArgumentException if the type names of both queries do
* not match
*/
public static Query mixQueries(Query firstQuery, Query secondQuery,
String handle) {
if ((firstQuery == null) || (secondQuery == null)) {
throw new NullPointerException("got a null query argument");
}
if (firstQuery.equals(Query.ALL)) {
return secondQuery;
} else if (secondQuery.equals(Query.ALL)) {
return firstQuery;
}
if ((firstQuery.getTypeName() != null)
&& (secondQuery.getTypeName() != null)) {
if (!firstQuery.getTypeName().equals(secondQuery.getTypeName())) {
String msg = "Type names do not match: "
+ firstQuery.getTypeName() + " != "
+ secondQuery.getTypeName();
throw new IllegalArgumentException(msg);
}
}
// mix versions, if possible
String version;
if(firstQuery.getVersion() != null) {
if(secondQuery.getVersion() != null && !secondQuery.getVersion().equals(firstQuery.getVersion()))
throw new IllegalArgumentException("First and second query refer different versions");
version = firstQuery.getVersion();
} else {
version = secondQuery.getVersion();
}
//none of the queries equals Query.ALL, mix them
//use the more restrictive max features field
int maxFeatures = Math.min(firstQuery.getMaxFeatures(),
secondQuery.getMaxFeatures());
//join attributes names
String[] propNames = joinAttributes(firstQuery.getPropertyNames(),
secondQuery.getPropertyNames());
//join filters
Filter filter = firstQuery.getFilter();
Filter filter2 = secondQuery.getFilter();
if ((filter == null) || filter.equals(Filter.INCLUDE)) {
filter = filter2;
} else if ((filter2 != null) && !filter2.equals(Filter.INCLUDE)) {
filter = ff.and( filter, filter2);
}
Integer start=0;
if( firstQuery.getStartIndex() != null ){
start = firstQuery.getStartIndex();
}
if( secondQuery.getStartIndex() != null ){
start += secondQuery.getStartIndex();
}
//build the mixed query
String typeName = firstQuery.getTypeName() != null?
firstQuery.getTypeName() : secondQuery.getTypeName();
DefaultQuery mixed = new DefaultQuery(typeName, filter, maxFeatures, propNames, handle);
mixed.setVersion(version);
if(start != 0){
mixed.setStartIndex(start);
}
return mixed;
}
/**
* Creates a set of attribute names from the two input lists of names,
* maintaining the order of the first list and appending the non repeated
* names of the second.
* <p>
* In the case where both lists are <code>null</code>, <code>null</code>
* is returned.
* </p>
*
* @param atts1 the first list of attribute names, who's order will be
* maintained
* @param atts2 the second list of attribute names, from wich the non
* repeated names will be appended to the resulting list
*
* @return Set of attribute names from <code>atts1</code> and
* <code>atts2</code>
*/
private static String[] joinAttributes(String[] atts1, String[] atts2) {
String[] propNames = null;
if ( atts1 == null && atts2 == null ) {
return null;
}
List<String> atts = new LinkedList<String>();
if (atts1 != null) {
atts.addAll(Arrays.asList(atts1));
}
if (atts2 != null) {
for (int i = 0; i < atts2.length; i++) {
if (!atts.contains(atts2[i])) {
atts.add(atts2[i]);
}
}
}
propNames = new String[atts.size()];
atts.toArray(propNames);
return propNames;
}
/**
* Returns AttributeType based on String specification (based on UML).
*
* <p>
* Will parse a String of the form: <i>"name:Type:hint"</i>
* </p>
*
* <p>
* Where <i>Type</i> is:
* </p>
*
* <ul>
* <li>
* 0,Interger,int: represents Interger
* </li>
* <li>
* 0.0, Double, double: represents Double
* </li>
* <li>
* "",String,string: represents String
* </li>
* <li>
* Geometry: represents Geometry
* </li>
* <li>
* <i>full.class.path</i>: represents java type
* </li>
* </ul>
*
* <p>
* Where <i>hint</i> is "hint1;hint2;...;hintN", in which "hintN" is one
* of:
* <ul>
* <li><code>nillable</code></li>
* <li><code>srid=<#></code></li>
* </ul>
* </p>
*
* @param typeSpec
*
*
* @throws SchemaException If typeSpect could not be interpreted
*/
static AttributeDescriptor createAttribute(String typeSpec)
throws SchemaException {
int split = typeSpec.indexOf(":");
String name;
String type;
String hint = null;
if (split == -1) {
name = typeSpec;
type = "String";
} else {
name = typeSpec.substring(0, split);
int split2 = typeSpec.indexOf(":", split + 1);
if (split2 == -1) {
type = typeSpec.substring(split + 1);
} else {
type = typeSpec.substring(split + 1, split2);
hint = typeSpec.substring(split2 + 1);
}
}
try {
boolean nillable = true;
CoordinateReferenceSystem crs = null;
if ( hint != null ) {
StringTokenizer st = new StringTokenizer( hint, ";" );
while ( st.hasMoreTokens() ) {
String h = st.nextToken();
h = h.trim();
//nillable?
//JD: i am pretty sure this hint is useless since the
// default is to make attributes nillable
if ( h.equals( "nillable" )) {
nillable = true;
}
//spatial reference identieger?
if ( h.startsWith("srid=" )) {
String srid = h.split("=")[1];
Integer.parseInt( srid );
try {
crs = CRS.decode( "EPSG:" + srid );
}
catch( Exception e ) {
String msg = "Error decoding srs: " + srid;
throw new SchemaException( msg, e );
}
}
}
}
Class clazz = type(type);
if(Geometry.class.isAssignableFrom(clazz)) {
GeometryType at = new GeometryTypeImpl(new NameImpl( name ), clazz , crs, false, false, Collections.EMPTY_LIST, null, null );
return new GeometryDescriptorImpl( at, new NameImpl(name), 0,1, nillable, null );
} else {
AttributeType at = new AttributeTypeImpl( new NameImpl( name ), clazz , false, false, Collections.EMPTY_LIST, null, null );
return new AttributeDescriptorImpl( at, new NameImpl(name), 0,1, nillable, null );
}
} catch (ClassNotFoundException e) {
throw new SchemaException("Could not type " + name + " as:" + type, e);
}
}
/**
* Manually calculates the bounds of a feature collection.
*
* @param collection
* @return
*/
public static Envelope bounds(
FeatureCollection<? extends FeatureType, ? extends Feature> collection) {
FeatureIterator<? extends Feature> i = collection.features();
try {
ReferencedEnvelope bounds = new ReferencedEnvelope(collection.getSchema()
.getCoordinateReferenceSystem());
if (!i.hasNext()) {
bounds.setToNull();
return bounds;
}
bounds.init(((SimpleFeature) i.next()).getBounds());
return bounds;
} finally {
i.close();
}
}
/**
* Changes the ending (e.g. ".sld") of a {@link URL}
*
* @param url
* {@link URL} like <code>file:/sds/a.bmp</code> or
* <code>http://www.some.org/foo/bar.shp</code>
* @param postfix
* New file extension for the {@link URL} without <code>.</code>
*
* @return A new {@link URL} with new extension.
*
* @throws {@link MalformedURLException} if the new {@link URL} can not be
* created.
*/
public static URL changeUrlExt(final URL url, final String postfix)
throws IllegalArgumentException {
String a = url.toExternalForm();
final int lastDotPos = a.lastIndexOf('.');
if (lastDotPos >= 0)
a = a.substring(0, lastDotPos);
a = a + "." + postfix;
try {
return new URL(a);
} catch (final MalformedURLException e) {
throw new IllegalArgumentException("can't create a new URL for "+ url + " with new extension " + postfix, e);
}
}
/**
* The function is supposed to be equivalent to {@link File}.getParent().
* The {@link URL} is converted to a String, truncated to the last / and
* then recreated as a new URL.
*
* @throws {@link MalformedURLException} if the parent {@link URL} can not
* be created.
*/
public static URL getParentUrl(final URL url) throws MalformedURLException {
String a = url.toExternalForm();
final int lastDotPos = a.lastIndexOf('/');
if (lastDotPos >= 0)
a = a.substring(0, lastDotPos);
/**
* The parent of jar:file:some!/bar.file is jar:file:some!/, not jar:file:some!
*/
if (a.endsWith("!")) a+="/";
return new URL(a);
}
/**
* Extends an {@link URL}.
*
* @param base
* Has to be a {@link URL} pointing to a directory. If it doesn't
* end with a <code>/</code> it will be added automatically.
* @param extension
* The part that will be added to the {@link URL}
*
* @throws MalformedURLException
* if the new {@link URL} can not be created.
*/
public static URL extendURL(URL base, String extension)
throws MalformedURLException {
if(base==null)
throw new NullPointerException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1,"base"));
if(extension==null)
throw new NullPointerException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1,"extension"));
String a = base.toExternalForm();
if (!a.endsWith("/"))
a += "/";
a += extension;
return new URL(a);
}
/**
* Checks that a {@link File} is a real file, exists and is readable.
*
* @param file the {@link File} instance to check. Must not be null.
* @param logger an optional {@link Logger} (can be null) where to log detailed
* info about the file properties (path/readable/hidden/writable)
*
* @return {@code true} in case the file is a real file, exists and is readable;
* {@code false} otherwise.
*/
public static boolean checkFileReadable(final File file, final Logger logger) {
if (logger != null && logger.isLoggable(Level.FINE)) {
final StringBuilder builder = new StringBuilder("Checking file:")
.append(file.getAbsolutePath()).append("\n")
.append("canRead:").append(file.canRead()).append("\n")
.append("isHidden:").append(file.isHidden()).append("\n")
.append("isFile").append(file.isFile()).append("\n")
.append("canWrite").append(file.canWrite()).append("\n");
logger.fine(builder.toString());
}
if (!file.exists() || !file.canRead() || !file.isFile())
return false;
return true;
}
/**
* Checks that the provided directory path refers to an existing/readable directory.
* Finally, return it as a normalized directory path (removing double and single dot path steps
* if any) followed by the separator char if missing ({@code '/'} On UNIX systems; {@code '\\}
* on Microsoft Windows systems.
*
* @param directoryPath the input directory path. Must not be null.
* @return the re-formatted directory path.
* @throws IllegalArgumentException in case the specified path doesn't rely on a
* existing/readable directory.
*/
public static File checkDirectory(File file)
throws IllegalArgumentException {
String directoryPath = file.getPath();
File inDir = file;
if(!inDir.isDirectory()){
throw new IllegalArgumentException("Not a directory: "+directoryPath );
}
if (!inDir.canRead()) {
throw new IllegalArgumentException("Not a writable directory: "+ directoryPath );
}
try {
directoryPath = inDir.getCanonicalPath();
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
/*
directoryPath = FilenameUtils.normalize(directoryPath);
if (!directoryPath.endsWith(File.separator)){
directoryPath = directoryPath + File.separator;
}
*/
// test to see if things are still good
inDir = new File(directoryPath);
if(!inDir.isDirectory()){
throw new IllegalArgumentException("Not a directory: "+directoryPath );
}
if (!inDir.canRead()) {
throw new IllegalArgumentException("Not a writable directory: "+ directoryPath );
}
return new File(directoryPath);
}
/**
* Returns a {@link IOFileFilter} obtained by excluding from the first input filter argument,
* the additional filter arguments.
*
* @param inputFilter the initial filter from which to exclude other ones.
* @param filters additional filters to be excluded
*
* @return the updated {@link IOFileFilter}
*/
public static FilenameFilter excludeFilters(final FilenameFilter inputFilter,
final FilenameFilter... filters) {
return new FilenameFilter() {
public boolean accept(File dir, String name) {
if( inputFilter.accept(dir, name)){
for( FilenameFilter exclude : filters ){
if( exclude.accept(dir, name)){
return false;
}
}
return true;
}
return false;
}
};
}
/**
* Returns a {@link IOFileFilter} obtained by adding to the first input filter argument,
* the additional filter arguments.
*
* @param inputFilter the initial filter to which to add other ones.
* @param filters additional filters to be included in the main filter.
*
* @return the updated {@link IOFileFilter}
*/
public static FilenameFilter includeFilters(final FilenameFilter inputFilter,
final FilenameFilter... filters) {
return new FilenameFilter() {
public boolean accept(File dir, String name) {
if( inputFilter.accept(dir, name)){
return true;
}
for( FilenameFilter include : filters ){
if( include.accept(dir, name)){
return true;
}
}
return false;
}
};
}
}