/* * 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; } }; } }