/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2003-2016, 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.Closeable; import java.io.File; import java.io.FileFilter; 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.Comparator; 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.UUID; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.data.DataAccessFactory.Param; import org.geotools.data.collection.CollectionFeatureSource; import org.geotools.data.collection.ListFeatureCollection; import org.geotools.data.collection.SpatialIndexFeatureCollection; import org.geotools.data.collection.SpatialIndexFeatureSource; import org.geotools.data.collection.TreeSetFeatureCollection; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.data.simple.SimpleFeatureLocking; import org.geotools.data.simple.SimpleFeatureReader; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.data.simple.SimpleFeatureStore; import org.geotools.factory.CommonFactoryFinder; import org.geotools.factory.Hints; import org.geotools.feature.AttributeImpl; 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.GeometryAttributeImpl; import org.geotools.feature.NameImpl; import org.geotools.feature.SchemaException; import org.geotools.feature.collection.BridgeIterator; 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.filter.visitor.PropertyNameResolvingVisitor; import org.geotools.filter.visitor.SimplifyingFilterVisitor; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.geometry.jts.WKTReader2; import org.geotools.metadata.iso.citation.Citations; import org.geotools.referencing.CRS; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; import org.geotools.styling.UserLayer; import org.geotools.util.Converters; import org.geotools.util.NullProgressListener; import org.geotools.util.Utilities; import org.opengis.coverage.grid.GridCoverage; import org.opengis.feature.Feature; import org.opengis.feature.FeatureFactory; import org.opengis.feature.FeatureVisitor; import org.opengis.feature.IllegalAttributeException; import org.opengis.feature.Property; 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.Name; import org.opengis.feature.type.PropertyDescriptor; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory2; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.sort.SortBy; import org.opengis.filter.sort.SortOrder; import org.opengis.geometry.BoundingBox; import org.opengis.metadata.citation.Citation; import org.opengis.referencing.FactoryException; import org.opengis.referencing.ReferenceIdentifier; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.util.ProgressListener; 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; import org.geotools.data.view.DefaultView; /** * Utility functions for use with GeoTools with data classes. * <p> * These methods fall into several categories: * <p> * Conversion between common data structures. * <ul> * <li>{@link #collection} methods: creating/converting a {@link SimpleFeatureCollection} from a range of input.</li> * <li>{@link #simple} methods: adapting from generic Feature use to SimpleFeature. Used to convert to SimpleFeature, * SimpleFeatureCollection,SimpleFeatureSource</li> * <li>{@link #list} to quickly copy features into a memory based list</li> * <li>{@link #reader} methods to convert to FeatureReader</li> * <li>{@link #expression} setup a FeatureSource wrapper around the provided data</li> * </ul> * </p> * <p> * SimpleFeatureType and SimpleFeature encoding/decoding from String as used by the PropertyDataStore tutorials. * <ul> * <li>{@link #createType} methods: to create a SimpleFeatureType from a one line text string</li> * <li>{@link #encodeType}: text string representation of a SimpleFeaturerType</li> * <li>{@link #createFeature}: create a SimpleFeature from a one line text String</li> * <li>{@link #encodeFeature}: encode a feature as a single line text string</li> * </ul> * </p> * <p> * Working with SimpleFeatureType (this class is immutable so we always have to make a modified copy): * <ul> * <li>{@link #createSubType(SimpleFeatureType, String[])} methods return a modified copy of an origional feature type. Used to cut down an exsiting * feature type to reflect only the attributes requested when using {@link SimpleFeatureSource#getFeatures(Filter)}.</li> * <li>{@link #compare} and {@link #isMatch(AttributeDescriptor, AttributeDescriptor)} are used to check for types compatible with * {@link #createSubType} used to verify that feature values can be copied across</li> * </ul> * </p> * <p> * Manipulating individual features and data values: * <ul> * <li>{@link #reType} generates a cut down version of an original feature in the same manners as {@link #createSubType}</li> * <li>{@link #template} and {@link #defaultValue} methods which uses {@link AttributeDescriptor#getDefaultValue()} when creating new empty features</li> * <li>{@link #duplicate(Object)} used for deep copy of feature data</li> * <li> * </ul> * </p> * And a grab bag of helpful utility methods for those implementing a DataStore: * <ul> * <li>{@link #urlToFile(URL)} and {@link #fileToURL(File)} and {@link #getParentUrl(URL)} used to work with files across platforms</li> * <li>{@link #includeFilters} and {@link #excludeFilters} work as a compound {@link FileFilter} making {@link File#listFiles} easier to use</li> * <li>{@link #propertyNames}, {@link #fidSet}, {@link #attributeNames} methods are used to double check a provided query and ensure * it can be correctly handed.</li> * </li>{@link #sortComparator}, {@link #resolvePropertyNames} and {@link #mixQueries} are used to prep a {@link Query} prior to use</li> * </ul> * * @author Jody Garnett, Refractions Research * * * @source $URL$ http://svn.osgeo.org/geotools/trunk/modules/library/main/src/main/java/org/geotools/ data/DataUtilities.java $ */ public class DataUtilities { /** Typemap used by {@link #createType(String, String)} methods */ static Map<String, Class> typeMap = new HashMap<String, Class>(); /** Reverse type map used by {@link #encodeType(FeatureType)} */ static Map<Class, String> typeEncode = new HashMap<Class, String>(); static FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(); static final boolean IS_WINDOWS_OS; 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(UUID.class, "UUID"); typeMap.put("UUID", UUID.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); // check if we are running on windows String os = System.getProperty("os.name"); IS_WINDOWS_OS = os.toUpperCase().contains("WINDOWS"); } /** * Retrieve the attributeNames defined by the featureType * * @param featureType * @return array of simple attribute names */ 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( url.getQuery() != null){ string = string.substring(0, string.indexOf("?")); } 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://"; if (IS_WINDOWS_OS && string.startsWith(standardPrefix)) { // win32: host/share reference. Keep the host slashes. path3 = string.substring(standardPrefix.length() - 2); File f = new File(path3); if (!f.exists()) { // Make path relative to be backwards compatible. path3 = path3.substring(2, path3.length()); } } 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 encountered property names. * <p> * The feature 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 encountered property names. * <p> * The feature 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 Set<PropertyName> propertyNames(Filter filter, final SimpleFeatureType featureType) { if (filter == null) { return Collections.emptySet(); } FilterAttributeExtractor attExtractor = new FilterAttributeExtractor(featureType); filter.accept(attExtractor, null); Set<PropertyName> propertyNames = attExtractor.getPropertyNameSet(); return propertyNames; } /** * Traverses the filter and returns any encountered property names. */ public static String[] attributeNames(Filter filter) { return attributeNames(filter, null); } /** * Traverses the filter and returns any encountered property names. */ public static Set<PropertyName> propertyNames(Filter filter) { return propertyNames(filter, null); } /** * Traverses the expression and returns any encountered property names. * <p> * The feature 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 encountered property names. * <p> * The feature 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 Set<PropertyName> propertyNames(Expression expression, final SimpleFeatureType featureType) { if (expression == null) { return Collections.emptySet(); } FilterAttributeExtractor attExtractor = new FilterAttributeExtractor(featureType); expression.accept(attExtractor, null); Set<PropertyName> propertyNames = attExtractor.getPropertyNameSet(); return propertyNames; } /** * Traverses the expression and returns any encountered property names. */ public static String[] attributeNames(Expression expression) { return attributeNames(expression, null); } /** * Traverses the expression and returns any encountered property names. */ public static Set<PropertyName> propertyNames(Expression expression) { return propertyNames(expression, null); } /** * Compare attribute coverage between two feature types (allowing the identification of subTypes). * <p> * The comparison results in a number with the following meaning: * </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 {@link AttributeDescriptor} - the {@link #isMatch(AttributeDescriptor, AttributeDescriptor)} * method is used to quickly confirm that the local name and java binding are compatible. * </p> * * <p> * Namespace is not considered in this opperations. You may still need to reType to get the * correct namesapce, or reorder. * </p> * <p> * Please note this method will not result in a stable sort if used in a {@link Comparator} * as -1 is used to indicate incompatiblity (rather than simply "before"). * * @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; 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) return 0; } return 1; } /** * Quickly check if two descriptors are at all compatible. * <p> * This method checks the descriptors name and class binding to see if the values have any * chance of being compatible. * * @param a * descriptor to compare * @param b * descriptor to compare * * @return true to the descriptors name and binding class match */ 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. * <p> * Please note this implementation provides "deep copy" using {@link #duplicate(Object)} to copy * each attribute. * * @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); } /** * Retypes the feature to match the provided featureType. * <p> * The duplicate parameter indicates how the new feature is to be formed: * <ul> * <li>dupliate is true: A "deep copy" is made of each attribute resulting in a safe * "copy"Adjusts the attribute order to match the provided featureType.</li> * <li>duplicate is false: the attributes are simply reordered and are actually the same * instances as those in the origional feature</li> * </ul> * In the future this method may simply return a "wrapper" when duplicate is false. * <p> * * @param featureType * @param feature * @param duplicate * True to perform {@link #duplicate(Object)} on each attribute * @return * @throws IllegalAttributeException */ public static SimpleFeature reType(SimpleFeatureType featureType, SimpleFeature feature, boolean duplicate) throws IllegalAttributeException { if (duplicate) { return reType(featureType, feature); } FeatureType origional = feature.getFeatureType(); if (featureType.equals(origional)) { return 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); attributes[i] = feature.getAttribute(curAttType.getLocalName()); } return SimpleFeatureBuilder.build(featureType, attributes, id); } /** * Performs a deep copy of the provided object. * <p> * A number of tricks are used to make this as fast as possible: * <ul> * <li>Simple or Immutable types are copied as is (String, Integer, Float, URL, etc..)</li> * <li>JTS Geometry objects are cloned</li> * <li>Arrays and the Collection classes are duplicated element by element</li> * </ul> * This function is used recusively for (in order to handle complext features) no attempt * is made to detect cycles at this time so your milage may vary. * * @param src * Source object * @return copy of source object */ 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(null, "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 */ public static SimpleFeature template(SimpleFeatureType featureType) throws IllegalAttributeException { return SimpleFeatureBuilder.build(featureType, defaultValues(featureType), null); } /** * Use the provided featureType to create an empty feature. * <p> * The {@link #defaultValues(SimpleFeatureType)} method is used to generate * the intial values (making use of {@link AttributeDescriptor#getDefaultValue()} as required. * * @param featureType * @param featureID * @return Craeted feature */ public static SimpleFeature template(SimpleFeatureType featureType, String featureID) { return SimpleFeatureBuilder.build(featureType, defaultValues(featureType), featureID); } /** * Produce a set of default values for the provided FeatureType * * @param featureType * @return Array of values, that are good starting point for data entry */ public static Object[] defaultValues(SimpleFeatureType featureType) { return defaultValues(featureType, null); } /** * Create a new feature from the provided values, using appropriate default values for any nulls * provided. * * @param featureType * @param providedValues * @return newly created feature * @throws ArrayIndexOutOfBoundsException If the number of provided values does not match the featureType */ public static SimpleFeature template(SimpleFeatureType featureType, Object[] providedValues) { return SimpleFeatureBuilder.build(featureType, defaultValues(featureType, providedValues), null); } /** * Create a new feature from the provided values, using appropriate default values for any nulls * provided. * * @param featureType * @param featureID * @param providedValues provided attributes * * @return newly created feature * * @throws ArrayIndexOutOfBoundsException If the number of provided values does not match the featureType */ public static SimpleFeature template(SimpleFeatureType featureType, String featureID, Object[] providedValues) { return SimpleFeatureBuilder.build(featureType, defaultValues(featureType, providedValues), featureID); } /** * Create default values matching the provided feature type. * * @param featureType * @param values * * @return set of default values * @throws ArrayIndexOutOfBoundsException If the number of provided values does not match the featureType */ public static Object[] defaultValues(SimpleFeatureType featureType, Object[] values) { 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++) { AttributeDescriptor descriptor = featureType.getDescriptor(i); values[i] = descriptor.getDefaultValue(); } 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 * @deprecated Please {@link AttributeDescriptor#getDefaultValue()} */ 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 :-( } 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>Integer</li> * <li>Double</li> * <li>Long</li> * <li>Short</li> * <li>Float</li> * <li>BigDecimal</li> * <li>BigInteger</li> * <li>Character</li> * <li>Boolean</li> * <li>UUID</li> * <li>Timestamp</li> * <li>java.sql.Date</li> * <li>java.sql.Time</li> * <li>java.util.Date</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 == UUID.class) { return UUID.fromString("00000000-0000-0000-0000-000000000000"); } if (type == Timestamp.class) return new Timestamp(0); if (type == java.sql.Date.class) return new java.sql.Date(0); if (type == java.sql.Time.class) return new java.sql.Time(0); if (type == java.util.Date.class) return new java.util.Date(0); 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 }); } LineString lineString = fac.createLineString(new Coordinate[] { new Coordinate(0, 0), new Coordinate(0, 1) }); if (type == LineString.class) { return lineString; } LinearRing linearRing = fac.createLinearRing(new Coordinate[] { new Coordinate(0, 0), new Coordinate(0, 1), new Coordinate(1, 1), new Coordinate(1, 0), new Coordinate(0, 0)}); if (type == LinearRing.class) { return linearRing; } if (type == MultiLineString.class) { return fac.createMultiLineString(new LineString[] { lineString }); } 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; } }; } /** * Wrap up an array of features as a FeatureSource. * * @param featureArray * Array of features * @return FeatureSource * * @throws IOException * @throws RuntimeException */ public static SimpleFeatureSource source(final SimpleFeature[] featureArray) { final SimpleFeatureType featureType; if ((featureArray == null) || (featureArray.length == 0)) { featureType = FeatureTypes.EMPTY; } else { featureType = featureArray[0].getFeatureType(); } ListFeatureCollection collection = new ListFeatureCollection( featureType, featureArray ); for( SimpleFeature feature : collection ){ if( feature.getFeatureType() != featureType ){ String typeName = featureType.getTypeName(); throw new IllegalStateException("Array inconsistent, expected each feature of type "+typeName); } } return source( collection ); } /** * Wraps up the provided feature collection in as a SimpleFeatureSource. * <p> * This is usually done for use by the renderer; allowing it to query the feature collection as required. * * @param collection Feature collection providing content * @return FeatureSource used to wrap the content * * @throws NullPointerException if any of the features are null * @throws IllegalArgumentException If the provided collection is inconsistent (perhaps containing mixed feature types) */ public static SimpleFeatureSource source( final FeatureCollection<SimpleFeatureType, SimpleFeature> collection) { if (collection == null) { throw new NullPointerException("No content provided"); } if (collection instanceof ListFeatureCollection) { ListFeatureCollection list = (ListFeatureCollection) collection; CollectionFeatureSource source = new CollectionFeatureSource(list); return source; } if (collection instanceof SpatialIndexFeatureCollection) { SpatialIndexFeatureCollection indexed = (SpatialIndexFeatureCollection) collection; SpatialIndexFeatureSource source = new SpatialIndexFeatureSource(indexed); return source; } if (collection instanceof TreeSetFeatureCollection) { TreeSetFeatureCollection tree = (TreeSetFeatureCollection) collection; CollectionFeatureSource source = new CollectionFeatureSource(tree); return source; } SimpleFeatureCollection simpleFeatureCollection = simple( collection ); CollectionFeatureSource source = new CollectionFeatureSource(simpleFeatureCollection); return source; } /** * Return a 'view' of the given {@code DataStore} constrained by a {@code Query}. * * @param store * the data store * @param query * the query * * @return the constrained view * * @throws IOException * if the data store cannot be accessed * @throws SchemaException * if the query is incompatible with the store's contents */ public static SimpleFeatureSource createView(final DataStore store, final Query query) throws IOException, SchemaException { return createView(store.getFeatureSource(query.getTypeName()), query); } /** * Return a 'view' of the given {@code FeatureSource} constrained by a {@code Query}. * * @param source * feature source * @param query * the query * * @return the constrained view * * @throws IOException * if the data store cannot be accessed * @throws SchemaException * if the query is incompatible with the store's contents */ public static SimpleFeatureSource createView(final SimpleFeatureSource source, final Query query) throws IOException, SchemaException { return new DefaultView(source, query); } /** * Adapt a feature collection as a read-only DataStore. * <p> * See {@link UserLayer} for example use. * @param features feature collection to adap * @return read-only DataStore */ public static DataStore dataStore( final SimpleFeatureCollection features ){ SimpleFeatureSource source = source( features ); return dataStore( source ); } /** * Adapt a single FeatureSource as a read-only DataStore. * <p> * See {@link UserLayer} for example use. * @param source Feature source to adapt * @return read-only DataStore */ public static DataStore dataStore( SimpleFeatureSource source ){ return new DataStoreAdaptor(source); } /** * 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 SimpleFeatureCollection collection(SimpleFeature[] features) { // JG: There may be some performance to be gained by using ListFeatureCollection here DefaultFeatureCollection collection = new DefaultFeatureCollection(null,null); 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 SimpleFeatureCollection into memory. * * @param SimpleFeatureCollection * the features to add to a new feature collection. * @return FeatureCollection */ public static DefaultFeatureCollection collection( FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection) { return new DefaultFeatureCollection(featureCollection); } /** * Checks if the provided iterator implements {@link Closeable}. * <p> * Any problems are logged at {@link Level#FINE}. */ public static void close(Iterator<?> iterator) { if (iterator != null && iterator instanceof Closeable) { try { ((Closeable) iterator).close(); } catch (IOException e) { String name = iterator.getClass().getPackage().toString(); Logger log = Logger.getLogger(name); log.log(Level.FINE, e.getMessage(), e); } } } /** * Obtain the first feature from the collection as an exemplar. * * @param featureCollection * @return first feature from the featureCollection */ public static <F extends Feature> F first( FeatureCollection<?,F> featureCollection ){ if (featureCollection == null) { return null; } FeatureIterator<F> iter = featureCollection.features(); try { while (iter.hasNext()) { F feature = iter.next(); if (feature != null) { return feature; } } return null; // not found! } finally { iter.close(); } } // // Conversion (or casting) from general feature model to simple feature model // /** * A safe cast to SimpleFeatureCollection; that will introduce a wrapper if it has to. * <p> * Please keep the use of this class to a minimum; if you are expecting a * FeatureCollection<SimpleFeatureType,SimpleFeature> please make use of SimpleFeatureCollection * if you possibly can. * <p> * So when are you stuck using this class?: * <ul> * <li>Offering backwards compatible constructors for legacy code prior to 2.7</li> * <li>implementing FeatureStore.addFeatures(...) since we cannot type narrow parameters</li> * </ul> * * @param featureCollection * will be returned as a SimpleFeatureCollection and wrapped only if needed * @return SimpleFeatureCollection * @since 2.7 */ public static SimpleFeatureCollection simple( FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection) { if (featureCollection instanceof SimpleFeatureCollection) { return (SimpleFeatureCollection) featureCollection; } if (featureCollection.getSchema() instanceof SimpleFeatureType) { return new SimpleFeatureCollectionBridge(featureCollection); } throw new IllegalArgumentException( "The provided feature collection contains complex features, cannot be bridged to a simple one"); } public static SimpleFeatureReader simple(FeatureReader<SimpleFeatureType, SimpleFeature> reader) { if(reader instanceof SimpleFeatureReader) { return (SimpleFeatureReader) reader; } else { return new SimpleFeatureReaderBrige(reader); } } /** * A safe cast to SimpleFeatureSource; that will introduce a wrapper if it has to. * <p> * Please keep the use of this class to a minimum; if you are expecting a * FeatureSource<SimpleFeatureType,SimpleFeature> please make use of SimpleFeatureSource if you * possibly can. * * @since 2.7 */ public static SimpleFeatureSource simple(FeatureSource source) { if (source instanceof FeatureLocking) { return simple((FeatureLocking) source); } else if (source instanceof FeatureStore) { return simple((FeatureStore) source); } if (source instanceof SimpleFeatureSource) { return (SimpleFeatureSource) source; } if (source.getSchema() instanceof SimpleFeatureType) { return new SimpleFeatureSourceBridge(source); } throw new IllegalArgumentException( "The provided feature source contains complex features, cannot be bridged to a simple one"); } /** * A safe cast to SimpleFeatureStore; that will introduce a wrapper if it has to. * <p> * Please keep the use of this class to a minimum; if you are expecting a * FeatureStore<SimpleFeatureType,SimpleFeature> please make use of SimpleFeatureStore if you * possibly can. * * @since 2.7 */ public static SimpleFeatureStore simple(FeatureStore store) { if (store instanceof FeatureLocking) { return simple((FeatureLocking) store); } if (store instanceof SimpleFeatureStore) { return (SimpleFeatureStore) store; } if (store.getSchema() instanceof SimpleFeatureType) { return new SimpleFeatureStoreBridge(store); } throw new IllegalArgumentException( "The provided feature store contains complex features, cannot be bridged to a simple one"); } /** * Go through FeatureType description and convert to a SimpleFeatureType. Also ignores * AbstractFeatureType contributions such as name etc... * * @param featureType * FeatureType being converted * @return SimpleFeatureType created by stripping any complicated content from the provided * featureType * @throws DataSourceException */ public static SimpleFeatureType simple(final FeatureType featureType) throws DataSourceException { if (featureType == null) { return null; } // go through the attributes and strip out complicated contents // List<PropertyDescriptor> attributes; Collection<PropertyDescriptor> descriptors = featureType.getDescriptors(); attributes = new ArrayList<PropertyDescriptor>(descriptors); List<String> simpleProperties = new ArrayList<String>(); List<AttributeDescriptor> simpleAttributes = new ArrayList<AttributeDescriptor>(); // HACK HACK!! the parser sets no namespace to the properties so we're // doing a hardcode property name black list final List<String> ignoreList = Arrays.asList(new String[] { "location", "metaDataProperty", "description", "name", "boundedBy" }); for (Iterator<PropertyDescriptor> it = attributes.iterator(); it.hasNext();) { PropertyDescriptor property = it.next(); if (!(property instanceof AttributeDescriptor)) { continue; } AttributeDescriptor descriptor = (AttributeDescriptor) property; Name name = descriptor.getName(); if (ignoreList.contains(name.getLocalPart())) { it.remove(); } } // / HACK END for (PropertyDescriptor descriptor : attributes) { Class<?> binding = descriptor.getType().getBinding(); int maxOccurs = descriptor.getMaxOccurs(); Name name = descriptor.getName(); if (Object.class.equals(binding)) { continue; // skip complex } if (maxOccurs > 1) { continue; // skip multi value } if ("http://www.opengis.net/gml".equals(name.getNamespaceURI())) { continue; // skip AbstractFeature stuff } if (descriptor instanceof AttributeDescriptor) { AttributeDescriptor attribute = (AttributeDescriptor) descriptor; simpleAttributes.add(attribute); simpleProperties.add(attribute.getLocalName()); } } String[] properties = simpleProperties.toArray(new String[simpleProperties.size()]); SimpleFeatureType simpleFeatureType; try { if (featureType instanceof SimpleFeature) { simpleFeatureType = DataUtilities.createSubType((SimpleFeatureType) featureType, properties); } else { SimpleFeatureTypeBuilder build = new SimpleFeatureTypeBuilder(); build.setName(featureType.getName()); build.setAttributes(simpleAttributes); build.setDefaultGeometry(featureType.getGeometryDescriptor().getLocalName()); simpleFeatureType = build.buildFeatureType(); } } catch (SchemaException e) { throw new DataSourceException(e); } return simpleFeatureType; } /** * A safe cast to SimpleFeatureLocking; that will introduce a wrapper if it has to. * <p> * Please keep the use of this class to a minimum; if you are expecting a * FeatureLocking<SimpleFeatureType,SimpleFeature> please make use of SimpleFeatureLocking if * you possibly can. * * @since 2.7 */ public static SimpleFeatureLocking simple(FeatureLocking locking) { if (locking instanceof SimpleFeatureLocking) { return (SimpleFeatureLocking) locking; } if (locking.getSchema() instanceof SimpleFeatureType) { return new SimpleFeatureLockingBridge(locking); } throw new IllegalArgumentException( "The provided feature store contains complex features, cannot be bridged to a simple one"); } // // FeatureCollection Utility Methods // /** * Copies the provided features into a List. * * @param featureCollection * @return List of features copied into memory */ public static <F extends Feature> List<F> list(FeatureCollection<?, F> featureCollection) { final ArrayList<F> list = new ArrayList<F>(); FeatureIterator<F> iter = featureCollection.features(); try { while( iter.hasNext() ){ F feature = iter.next(); list.add( feature ); } } finally { iter.close(); } return list; } /** * Copies the provided fetaures into a List. * * @param featureCollection * @param maxFeatures Maximum number of features to load * @return List of features copied into memory */ public static <F extends Feature> List<F> list(FeatureCollection<?, F> featureCollection, int maxFeatures) { final ArrayList<F> list = new ArrayList<F>(); FeatureIterator<F> iter = featureCollection.features(); try { for( int count = 0; iter.hasNext() && count < maxFeatures; count++){ F feature = iter.next(); list.add( feature ); } } finally { iter.close(); } return list; } /** * Iteator wrapped around the provided FeatureIterator, implementing {@link Closeable}. * * @see #close(Iterator) * @param featureIterator * @return Iterator wrapped around provided FeatureIterator, implements Closeable */ public static <F extends Feature> Iterator<F> iterator( FeatureIterator<F> featureIterator ){ return new BridgeIterator<F>( featureIterator ); } /** * 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; } // // Conversion to java.util.Collection // /** * Used to quickly cast to a java.util.Collection. * * @param featureCollection * @return Collection */ @SuppressWarnings("unchecked") public static <F extends Feature> Collection<F> collectionCast( FeatureCollection<?, F> featureCollection) { if (featureCollection instanceof Collection<?>) { return (Collection<F>) featureCollection; } else { throw new IllegalArgumentException( "Require access to SimpleFeatureCollection implementing Collecion.add"); } } // // Conversion to FeatureCollection // /** * Copies the provided features into a FeatureCollection. * <p> * Often used when gathering a SimpleFeatureCollection into memory. * * @param list * features to add to a new FeatureCollection * @return FeatureCollection */ public static SimpleFeatureCollection collection(List<SimpleFeature> list) { DefaultFeatureCollection collection = new DefaultFeatureCollection( null, null); 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 SimpleFeatureCollection collection(SimpleFeature feature) { DefaultFeatureCollection collection = new DefaultFeatureCollection( null, null); 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 SimpleFeatureCollection collection( FeatureReader<SimpleFeatureType, SimpleFeature> reader) throws IOException { DefaultFeatureCollection collection = new DefaultFeatureCollection(null,null); 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 SimpleFeatureCollection collection(SimpleFeatureIterator reader) throws IOException { DefaultFeatureCollection collection = new DefaultFeatureCollection( null,null); try { while (reader.hasNext()) { try { collection.add(reader.next()); } catch (NoSuchElementException e) { throw (IOException) new IOException("EOF").initCause(e); } } } finally { reader.close(); } return collection; } // // Attribute Value Utility Methods // /** * Used to compare if two values are equal. * <p> * This method is here to work around the fact that JTS Geometry requires a specific method to * be called rather than object.equals. * * This method uses: * * <ul> * <li>Object.equals( Object )</li> * <li>Geometry.equals( Geometry ) - similar to {@link Geometry#equalsExact(Geometry)}</li> * </ul> * * @param att * Attribute value * @param otherAtt * Other value * * @return True if the values are equal */ public static boolean attributesEqual(Object att, Object otherAtt) { if (att == null) { if (otherAtt != null) { return false; } } else { // Note: for JTS Geometry objects this is equivalent // to equalsExact( Geometry ) if (!att.equals(otherAtt)) { return false; } } return true; } // // TypeConversion methods used by FeatureReaders // /** * Create a derived FeatureType * * @param featureType Original feature type to derive from. * @param properties If null, every property of the featureType in input will be used * @param override Intended CoordinateReferenceSystem, if null original will be used * * @return derived FeatureType * @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); } /** * Create a derived FeatureType * * @param featureType Original feature type to derive from. * @param properties If null, every property of the featureType in input will be used * @param override Intended CoordinateReferenceSystem, if null original will be used * @param typeName Type name override * @param namespace Namespace override * @return derived FeatureType * @throws SchemaException */ 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.setCRS(null); // not interested in warnings from this simple method tb.addAll(types); setDefaultGeometry(tb, properties, featureType); return tb.buildFeatureType(); } private static boolean assertEquals(Object o1, Object o2) { return o1 == null && o2 == null ? true : (o1 != null ? o1.equals(o2) : false); } /** * Create a type limited to the named properties provided. * * @param featureType * @param properties * * @return type limited to the named properties provided * * @throws SchemaException */ 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()); tb.setCRS(null); // not interested in warnings from this simple method for (int i = 0; i < properties.length; i++) { // let's get the attribute descriptor corresponding to the current property AttributeDescriptor attributeDescriptor = featureType.getDescriptor(properties[i]); if (attributeDescriptor != null) { // if the property doesn't map to an attribute descriptor we ignore it // an attribute descriptor may be omitted for security proposes for example tb.add(attributeDescriptor); } } setDefaultGeometry(tb, properties, featureType); return tb.buildFeatureType(); } private static void setDefaultGeometry(SimpleFeatureTypeBuilder typeBuilder, String[] properties, SimpleFeatureType featureType) { GeometryDescriptor geometryDescriptor = featureType.getGeometryDescriptor(); if (geometryDescriptor != null) { String propertyName = geometryDescriptor.getLocalName(); if (Arrays.asList(properties).contains(propertyName)) { typeBuilder.setDefaultGeometry(propertyName); } } } // // Decoding (ie Parsing) support for PropertyAttributeReader and tutorials // /** * 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 {@link createAttribute}: * <ul> * <li>Each attribute descriptor is defined using the form: <i>"name:Type:hint"</i> </li> * <li>Where <i>Type</i> is: * <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, additional common geometry types supported including * Point,LineString,Polygon,MultiLineString,MultiPolygon,MultiPoint,GeometryCollection</ul> * <li>UUID</li> * <li>Date</li> * <li><i>full.class.path</i>: represents java type</li> * </ul> * </li> * <li>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> * </li> * <li>You may indicate the default Geometry with an astrix: "*geom:Geometry".</li> * <li>You may also indicate the srid (used to look up a EPSG code): "*point:Point:3226</li> * </p> * * <p> * Examples: * <ul> * <li><code>name:"",age:0,geom:Geometry,centroid:Point,url:java.io.URL"</code></li> * <li><code>id:String,polygonProperty:Polygon:srid=32615</code></li> * <li><code>identifier:UUID,location:Point,*area:MultiPolygon,created:Date</code></li> * <li><code>uuid:UUID,name:String,description:String,time:java.sql.Timestamp</code></li> * </ul> * </p> * * @param typeName * identification of FeatureType: (<i>namesapce</i>).<i>typeName</i> * @param typeSpec * Specification of FeatureType attributes "name:Type,name2:Type2,..." * * * @throws SchemaException */ public static SimpleFeatureType createType(String typeName, String typeSpec) throws SchemaException { int split = typeName.lastIndexOf('.'); String namespace = (split == -1) ? null : typeName.substring(0, split); String name = (split == -1) ? typeName : typeName.substring(split + 1); return createType(namespace, name, typeSpec); } /** * Utility method for FeatureType construction. * <p> * @param namespace Typename namespace used to qualify the provided name * @param name Typename name, as qualified by namespace * @param typeSpec Definition of attributes, for details see {@link #createType(String, String)} * @throws SchemaException */ public static SimpleFeatureType createType(String namespace, String name, String typeSpec) throws SchemaException { SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder(); builder.setName(name); builder.setNamespaceURI(namespace); String[] types = typeSpec.split(","); AttributeDescriptor attributeType; builder.setCRS(null); // not interested in warnings from this simple method 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]); builder.add(attributeType); if (defaultGeometry) { builder.setDefaultGeometry(attributeType.getLocalName()); } } return builder.buildFeatureType(); } // // Encoding support for PropertyFeatureWriter // /** * Encode the provided featureType as a String suitable for use with {@link #createType}. * <p> * The format of this string acts as the "header" information for the PropertyDataStore * implementations. * * Example:<pre><code>String header = "_="+DataUtilities.encodeType(schema);</code></pre> * <p> * For more information please review the PropertyDataStore tutorials. * * @param featureType * @return String representation of featureType suitable for use with {@link #createType} */ public static String encodeType(SimpleFeatureType 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; int srid = toSRID( gd.getCoordinateReferenceSystem() ); if( srid != -1 ){ buf.append(":srid=" + srid); } } buf.append(","); } buf.delete(buf.length() - 1, buf.length()); // remove last "," return buf.toString(); } /** * Quickly review provided crs checking for an "EPSG:SRID" reference identifier. * <p> * @see CRS#lookupEpsgCode(CoordinateReferenceSystem, boolean) for full search * @param crs * @return srid or -1 if not found */ private static int toSRID( CoordinateReferenceSystem crs ){ if (crs == null || crs.getIdentifiers() == null) { return -1; // not found } for (Iterator<ReferenceIdentifier> it = crs.getIdentifiers().iterator(); it .hasNext();) { ReferenceIdentifier id = it.next(); Citation authority = id.getAuthority(); if (authority != null && authority.getTitle().equals(Citations.EPSG.getTitle())) { try { return Integer.parseInt( id.getCode() ); } catch (NumberFormatException nanException ){ continue; } } } return -1; } /** * 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 * @deprecated Renamed to {@link #encodeType} for concistency with {@link #createType} */ 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(); } /** * Reads in SimpleFeature that has been encoded into a line of text. * <p> * Example:<pre> * SimpleFeatureType featureType = * DataUtilities.createType("FLAG","id:Integer|name:String|geom:Geometry:4326"); * * SimpleFeature feature = * DataUtilities.createFeature( featureType, "fid1=1|Jody Garnett\\nSteering Committee|POINT(1,2)" ); * </pre> * This format is used by the PropertyDataStore tutorials. It amounts to: * <ul> * <li> * <li>Encoding of <i>FeatureId</i> followed by the attributes</li> * <li>Attributes are seperated using the bar character</li> * <li>Geometry is handled using {@link WKTReader2}</li> * <li>Support for common escaped characters</li> * <li>Multi-line support using escaped line-feed characters</li> * <ul> * @param featureType * @param line * @return SimpleFeature defined by the provided line of text */ public static SimpleFeature createFeature( SimpleFeatureType featureType, String line ){ String fid; int fidSplit = line.indexOf('='); int barSplit = line.indexOf('|'); if( fidSplit == -1 || (barSplit != -1 && barSplit < fidSplit) ){ fid = null; // we need to generate a feature id } else { fid = line.substring(0, fidSplit); } String data = line.substring(fidSplit+1); String text[] = splitIntoText(data, featureType ); Object[] values = new Object[ text.length ]; for( int i=0; i<text.length;i++){ AttributeDescriptor descriptor = featureType.getDescriptor(i); Object value = createValue( descriptor, text[i] ); values[i] = value; } SimpleFeature feature = SimpleFeatureBuilder.build( featureType, values, fid ); return feature; } /** * Split the provided text using | charater as a seperator. * <p> * This method respects the used of \ to "escape" a | character allowing * representations such as the following:<pre> * String example="text|example of escaped \\| character|text"; * * // represents: "text|example of escaped \| character|text" * String split=splitIntoText( example );</pre> * * @param data Origional raw text as stored * @return data split using | as seperator * @throws DataSourceException if the information stored is inconsistent with the headered provided */ private static String[] splitIntoText(String data,SimpleFeatureType type) { // return data.split("|", -1); // use -1 as a limit to include empty trailing spaces // return data.split("[.-^\\\\]\\|",-1); //use -1 as limit to include empty trailing spaces String text[] = new String[type.getAttributeCount()]; int i = 0; StringBuilder item = new StringBuilder(); for (String str : data.split("\\|",-1)) { if (i == type.getAttributeCount()) { // limit reached throw new IllegalArgumentException("format error: expected " + text.length + " attributes, stopped after finding " + i + ". [" + data + "] split into " + Arrays.asList(text)); } if (str.endsWith("\\")) { item.append(str); item.append("|"); } else { item.append(str); text[i] = item.toString(); i++; item = new StringBuilder(); } } if (i < type.getAttributeCount()) { throw new IllegalArgumentException("createFeature format error: expected " + type.getAttributeCount() + " attributes, but only found " + i + ". [" + data + "] split into " + Arrays.asList(text)); } return text; } /** * Reads an attribute value out of the raw text supplied to {@link #createFeature}. * <p> * This method is responsible for: * <ul> * <li>Handling: <code>"<null>"</code> as an explicit marker flag for a null value</li> * <li>Using {@link #decodeEscapedCharacters(String)} to unpack the raw text</li> * <li>Using {@link Converters} to convert the text to the requested value</li> * @param descriptor * @param rawText * @return */ private static Object createValue(AttributeDescriptor descriptor, String rawText) { String stringValue = null; try { // read the value and decode any interesting characters stringValue = decodeEscapedCharacters(rawText); } catch (RuntimeException huh) { huh.printStackTrace(); stringValue = null; } // check for special <null> flag if ("<null>".equals(stringValue)) { stringValue = null; } if (stringValue == null) { if (descriptor.isNillable()) { return null; // it was an explicit "<null>" } } // Use of Converters to convert from String to requested java binding Object value = Converters.convert(stringValue, descriptor.getType().getBinding()); if (descriptor.getType() instanceof GeometryType) { // this is to be passed on in the geometry objects so the srs name gets encoded CoordinateReferenceSystem crs = ((GeometryType) descriptor.getType()) .getCoordinateReferenceSystem(); if (crs != null) { // must be geometry, but check anyway if (value != null && value instanceof Geometry) { ((Geometry) value).setUserData(crs); } } } return value; } /** * Produce a String encoding of SimpleFeature for use with {@link #createFeature}. * <p> * This method inlcudes the full featureId information. * @param feature feature to encode, only SimpleFeature is supported at this time * @return text encoding for use with {@link #createFeature} */ public static String encodeFeature(SimpleFeature feature){ return encodeFeature(feature, true ); } /** * Produce a String encoding of SimpleFeature for use with {@link #createFeature}. * <p> * This method inlcudes the full featureId information. * @param feature feature to encode, only SimpleFeature is supported at this time * @param includeFid true to include the optional feature id * @return text encoding for use with {@link #createFeature} */ public static String encodeFeature(SimpleFeature feature,boolean includeFid ){ StringBuilder build = new StringBuilder(); if( includeFid ){ String fid = feature.getID(); if( feature.getUserData().containsKey(Hints.PROVIDED_FID)){ fid = (String) feature.getUserData().get(Hints.PROVIDED_FID); } build.append(fid); build.append("="); } for (int i = 0; i < feature.getAttributeCount(); i++) { Object attribute = feature.getAttribute(i); if( i != 0 ){ build.append("|"); // seperator character } if (attribute == null) { build.append("<null>"); // nothing! } else if( attribute instanceof String){ String txt = encodeString((String) attribute); build.append( txt ); } else if (attribute instanceof Geometry) { Geometry geometry = (Geometry) attribute; String txt = geometry.toText(); txt = encodeString( txt ); build.append( txt ); } else { String txt = Converters.convert( attribute, String.class ); if( txt == null ){ // could not convert? txt = attribute.toString(); } txt = encodeString( txt ); build.append( txt ); } } return build.toString(); } /** * Uses {@link Converters} to parse the provided text into the correct values to create a feature. * * @param type * FeatureType * @param fid * Feature ID for new feature * @param text * Text representation of values * * @return newly created feature * @throws IllegalAttributeException */ 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); } // // Internal utility methods to support PropertyDataStore feature encoding / decoding // /** * Used to decode common whitespace chracters and escaped | characters. * * @param txt Origional raw text as stored * @return decoded text as provided for storage * @see PropertyAttributeWriter#encodeString(String) */ private static String decodeEscapedCharacters( String txt ){ // unpack whitespace constants txt = txt.replace( "\\n", "\n"); txt = txt.replaceAll("\\r", "\r" ); // unpack our our escaped characters txt = txt.replace("\\|", "|" ); // txt = txt.replace("\\\\", "\\" ); return txt; } /** * Used to encode common whitespace characters and | character for safe transport. * * @param txt * @return txt encoded for storage * @see PropertyAttributeReader#decodeString(String) */ private static String encodeString( String txt ){ // encode our escaped characters // txt = txt.replace("\\", "\\\\"); txt = txt.replace("|","\\|"); // encode whitespace constants txt = txt.replace("\n", "\\n"); txt = txt.replace("\r", "\\r"); return txt; } /** * Internal method to access java binding using readable typename. * @see #createType(String, String) */ static Class<?> type(String typeName) throws ClassNotFoundException { if (typeMap.containsKey(typeName)) { return typeMap.get(typeName); } return Class.forName(typeName); } /** * Internal method to access the readable typename for the provided class. * @see #createType(String, String) */ static String typeMap(Class<?> type) { if (typeEncode.containsKey(type)) { return typeEncode.get(type); } return type.getName(); } // // Query Support Methods for DataStore implementators // /** * Factory method to produce Comparator based on provided Query SortBy information. * <p> * This method handles: * <ul> * <li>{@link SortBy#NATURAL_ORDER}: As sorting by FeatureID * </ul> * * @param sortBy * @return Comparator suitable for use with Arrays.sort( SimpleFeature[], comparator ) */ public static Comparator<SimpleFeature> sortComparator(SortBy sortBy) { if (sortBy == null) { sortBy = SortBy.NATURAL_ORDER; } if (sortBy == SortBy.NATURAL_ORDER) { return new Comparator<SimpleFeature>() { public int compare(SimpleFeature f1, SimpleFeature f2) { return f1.getID().compareTo(f2.getID()); } }; } else if (sortBy == SortBy.REVERSE_ORDER) { return new Comparator<SimpleFeature>() { public int compare(SimpleFeature f1, SimpleFeature f2) { int compare = f1.getID().compareTo(f2.getID()); return -compare; } }; } else { final PropertyName propertyName = sortBy.getPropertyName(); final SortOrder sortOrder = sortBy.getSortOrder(); return new Comparator<SimpleFeature>() { @SuppressWarnings("unchecked") public int compare(SimpleFeature f1, SimpleFeature f2) { Object value1 = propertyName.evaluate(f1, Comparable.class); Object value2 = propertyName.evaluate(f2, Comparable.class); if (value1 == null || value2 == null) { return 0; // cannot perform comparison } if (value1 instanceof Comparable && value1.getClass().isInstance(value2)) { if (sortOrder == SortOrder.ASCENDING) { return ((Comparable<Object>) value1).compareTo(value2); } else { return ((Comparable<Object>) value2).compareTo(value1); } } else { return 0; // cannot perform comparison } } }; } } /** * 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 filters of both queries are or'ed, then simplified using * SimplifiyingFilterVisitor</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("Cannot combine two null queries"); return Query.ALL; } if (firstQuery == null || firstQuery.equals(Query.ALL)) { return secondQuery; } else if (secondQuery == null || 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 List<PropertyName> propNames = joinAttributes(firstQuery.getProperties(), secondQuery.getProperties()); // 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); } filter = SimplifyingFilterVisitor.simplify(filter); Integer start = 0; if (firstQuery.getStartIndex() != null) { start = firstQuery.getStartIndex(); } if (secondQuery.getStartIndex() != null) { start += secondQuery.getStartIndex(); } // collect all hints Hints hints = new Hints(); if (firstQuery.getHints() != null) { hints.putAll(firstQuery.getHints()); } if (secondQuery.getHints() != null) { hints.putAll(secondQuery.getHints()); } // build the mixed query String typeName = firstQuery.getTypeName() != null ? firstQuery.getTypeName() : secondQuery .getTypeName(); Query mixed = new Query(typeName, filter, maxFeatures, propNames, handle); mixed.setVersion(version); mixed.setHints(hints); if (start != 0) { mixed.setStartIndex(start); } return mixed; } /** * This method changes the query object by simplifying the filter using SimplifyingFilterVisitor */ public static Query simplifyFilter(Query query) { if (query == null) { return query; } Filter filter = SimplifyingFilterVisitor.simplify(query.getFilter()); query.setFilter(filter); return query; } /** * This method changes the query object so that all propertyName references are resolved to * simple attribute names against the schema of the feature source. * <p> * For example, this method ensures that propertyName's such as "gml:name" are rewritten as * simply "name". * </p> * This method will not rewrite empty PropertyNames. */ public static Query resolvePropertyNames(Query query, SimpleFeatureType schema) { Filter resolved = resolvePropertyNames(query.getFilter(), schema); if (resolved == query.getFilter()) { return query; } Query newQuery = new Query(query); newQuery.setFilter(resolved); return newQuery; } /** Transform provided filter; resolving property names */ public static Filter resolvePropertyNames(Filter filter, SimpleFeatureType schema) { if (filter == null || filter == Filter.INCLUDE || filter == Filter.EXCLUDE) { return filter; } return (Filter) filter.accept(new PropertyNameResolvingVisitor(schema), null); } /** * 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 List<PropertyName> joinAttributes(List<PropertyName> atts1, List<PropertyName> atts2) { if (atts1 == null && atts2 == null) { return null; } List<PropertyName> atts = new LinkedList<PropertyName>(); if (atts1 != null) { atts.addAll(atts1); } if (atts2 != null) { for (int i = 0; i < atts2.size(); i++) { if (!atts.contains(atts2.get(i))) { atts.add(atts2.get(i)); } } } return atts; } /** * Returns a list of properties of a simple feature type, including all properties from a given * list, and all mandatory (minoccurs > 0) added. * * @param type * feature type * @param propNames * given list of properties * @return list of properties including all mandatory properties * @throws IOException */ public static List<PropertyName> addMandatoryProperties(SimpleFeatureType type, List<PropertyName> oldProps) { Iterator<PropertyDescriptor> ii = type.getDescriptors().iterator(); List<PropertyName> properties = new ArrayList<PropertyName>(); while (ii.hasNext()) { PropertyDescriptor descr = ii.next(); PropertyName propName = ff.property(descr.getName()); if (oldProps != null && oldProps.contains(propName)) { properties.add(propName); } else if (((descr.getMinOccurs() > 0) && (descr.getMaxOccurs() != 0))) { // mandatory, add it properties.add(propName); } } return properties; } /** * Generate AttributeDescriptor based on String type specification (based on UML). * <p> * Will parse a String of the form: <i>"name:Type:hint" as described in {@link #createType}</i> * </p> * * @param typeSpec * @see #createType * * @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 count the number of features from the provided FeatureIterator. * This implementation is intended for FeatureCollection implementors and test case * verification. Client code should always call {@link FeatureCollection#size()} * @param collection * @return number of featuers in feature collection */ public static int count( FeatureIterator<?> iterator) { int count = 0; if( iterator != null ){ try { while (iterator.hasNext()) { @SuppressWarnings("unused") Feature feature = iterator.next(); count++; } return count; } finally { iterator.close(); } } return count; } /** * Manually count the number of features in a feature collection using using {@link FeatureCollection#features()}. * * This implementation is intended for FeatureCollection implementors and test case * verification. Client code should always call {@link FeatureCollection#size()} * @param collection * @return number of featuers in feature collection */ public static int count( FeatureCollection<? extends FeatureType, ? extends Feature> collection) { int count = 0; FeatureIterator<? extends Feature> i = collection.features(); try { while (i.hasNext()) { @SuppressWarnings("unused") Feature feature = (Feature) i.next(); count++; } return count; } finally { if( i != null ){ i.close(); } } } /** * Manually calculate the bounds from the provided FeatureIteator. * This implementation is intended for FeatureCollection implementors and test case * verification. Client code should always call {@link FeatureCollection#getBounds()}. * * @param iterator * @return */ public static ReferencedEnvelope bounds( FeatureIterator<?> iterator ){ if( iterator == null ){ return null; } try { ReferencedEnvelope bounds = null; while (iterator.hasNext()) { Feature feature = iterator.next(); ReferencedEnvelope featureEnvelope = null; if(feature != null && feature.getBounds() != null) { featureEnvelope = ReferencedEnvelope.reference(feature.getBounds()); } if(featureEnvelope != null) { if(bounds == null) { bounds = new ReferencedEnvelope(featureEnvelope); } else { bounds.expandToInclude(featureEnvelope); } } } return bounds; } finally { iterator.close(); } } /** * Manually calculates the bounds of a feature collection using {@link FeatureCollection#features()}. * <p> * This implementation is intended for FeatureCollection implementors and test case * verification. Client code should always call {@link FeatureCollection#getBounds()}. * * @param collection * @return bounds of features in feature collection */ public static ReferencedEnvelope bounds( FeatureCollection<? extends FeatureType, ? extends Feature> collection) { FeatureIterator<? extends Feature> i = collection.features(); try { // Implementation taken from DefaultFeatureCollection implementation - thanks IanS CoordinateReferenceSystem crs = collection.getSchema().getCoordinateReferenceSystem(); ReferencedEnvelope bounds = new ReferencedEnvelope(crs); while (i.hasNext()) { Feature feature = i.next(); if( feature == null ) continue; BoundingBox geomBounds = feature.getBounds(); // IanS - as of 1.3, JTS expandToInclude ignores "null" Envelope // and simply adds the new bounds... // This check ensures this behavior does not occur. if ( geomBounds != null && !geomBounds.isEmpty() ) { bounds.include(geomBounds); } } return bounds; } finally { if( i != null ){ i.close(); } } } /** * Manually visit each feature using {@link FeatureCollection#features()}. * <p> * This method is intended to assist FeatureCollection implementors, and used to verify test-case results. Client code should always call * {@link FeatureCollection#accepts(FeatureVisitor, ProgressListener)} * * @param collection * @return bounds of features in feature collection */ public static void visit(FeatureCollection<?, ?> collection, FeatureVisitor visitor, ProgressListener progress) throws IOException { FeatureIterator<?> iterator = null; float size = progress != null ? collection.size() : 0; if (progress == null) { progress = new NullProgressListener(); } try { float position = 0; progress.started(); iterator = collection.features(); while (!progress.isCanceled() && iterator.hasNext()) { Feature feature = null; try { feature = iterator.next(); visitor.visit(feature); if (size > 0) { progress.progress(position++ / size); } } catch (Exception erp) { progress.exceptionOccurred(erp); String fid = feature == null ? "feature" : feature.getIdentifier().toString(); throw new IOException("Problem with " + collection.getID() + " visiting " + fid + ":" + erp, erp); } } } finally { progress.complete(); if (iterator != null) { iterator.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); } /** * Verifies a Map of parameters against the Param information. * Primarily used by classes implementing DataAcessFactory. * <p> * It will ensure that: * <ul> * <li>params is not null * <li>Everything is of the correct type (or upcovertable * to the correct type without Error) * <li>Required Parameters are present * </ul> * </p> * </code></pre> * @param params * @param arrayParameters Array of parameters returned by DataAccessFactory.getParametersInfo() * @return true if params is in agreement with getParametersInfo, override for additional checks. */ public static boolean canProcess( Map params, Param[] arrayParameters) { if (params == null) { return false; } for (int i = 0; i < arrayParameters.length; i++) { Param param = arrayParameters[i]; Object value; if( !params.containsKey( param.key ) ){ if( param.required ){ return false; // missing required key! } else { continue; } } try { value = param.lookUp( params ); } catch (IOException e) { // could not upconvert/parse to expected type! // even if this parameter is not required // we are going to refuse to process // these params return false; } if( value == null ){ if (param.required) { return (false); } } else { if ( !param.type.isInstance( value )){ return false; // value was not of the required type } if( param.metadata != null ){ // check metadata if( param.metadata.containsKey(Param.OPTIONS)){ List<Object> options = (List<Object>) param.metadata.get(Param.OPTIONS); if( options != null && !options.contains(value) ){ return false; // invalid option } } } } } return true; } /** * 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; } }; } /** * Create a non-simple template feature from feature type schema * * @param schema the feature type * @return a template feature */ public static Feature templateFeature(FeatureType schema) { FeatureFactory ff = CommonFactoryFinder.getFeatureFactory(null); Collection<Property> value = new ArrayList<Property>(); for (PropertyDescriptor pd : schema.getDescriptors()) { if (pd instanceof AttributeDescriptor) { if (pd instanceof GeometryDescriptor) { value.add(new GeometryAttributeImpl (((AttributeDescriptor) pd).getDefaultValue(), (GeometryDescriptor) pd, null) ); } else { value.add(new AttributeImpl (((AttributeDescriptor) pd).getDefaultValue(), (AttributeDescriptor) pd, null) ); } } } return ff.createFeature(value, (FeatureType) schema, SimpleFeatureBuilder.createDefaultFeatureId()); } }