/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2012, 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.feature.wrapper; import java.io.InvalidClassException; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.util.ArrayList; import org.geotools.feature.NameImpl; import org.opengis.feature.ComplexAttribute; import org.opengis.feature.Property; import org.opengis.feature.type.Name; /** * You can make feature wrappers for specific types by extending this class and annotating the descendant class's fields with {@link XSDMapping} to * show what they correspond to in the XSD. */ public abstract class FeatureWrapper { /** * Backing field for the underlying complex attribute. */ private ComplexAttribute underlyingComplexAttribute; /** * Gets the underlying complex attribute. That is, the complex attribute that was wrapped. NB: This could be a Feature. * * @return the underlying complex attribute. */ public ComplexAttribute getUnderlyingComplexAttribute() { return this.underlyingComplexAttribute; } /** * Sets the underlying complex attribute. That is, the complex attribute that was wrapped. NB: This could be a Feature. * * @param underlyingComplexAttribute */ public void setUnderlyingComplexAttribute(ComplexAttribute underlyingComplexAttribute) { this.underlyingComplexAttribute = underlyingComplexAttribute; } /** * Attempt to wrap the feature in a FeatureWrapper class. * * @param feature The feature to wrap. * @param clazz The class you want the feature to be wrapped as. (This will be the type that is returned). * @return An object of T which is the wrapped feature. */ public static <T extends FeatureWrapper> T wrap(ComplexAttribute complexAttribute, Class<T> clazz) throws InvalidClassException { try { // Create a new instance of the class: T wrapper; try { wrapper = clazz.newInstance(); } catch (InstantiationException e) { throw new InvalidClassException(String.format( "Unable instantiate class of type '%s'.", clazz)); } wrapper.setUnderlyingComplexAttribute(complexAttribute); String defaultNamespace = null; String defaultSeparator = null; // Get class-level XSDMapping: XSDMapping classLevelXSDMapping = clazz.getAnnotation(XSDMapping.class); if (classLevelXSDMapping != null) { defaultNamespace = classLevelXSDMapping.namespace(); defaultSeparator = classLevelXSDMapping.separator(); } // Look through the fields of the class you're trying to create: for (Field field : clazz.getFields()) { XSDMapping xsdMapping = field.getAnnotation(XSDMapping.class); if (xsdMapping != null) { Class<?> fieldType = field.getType(); String path = xsdMapping.path(); String namespace = xsdMapping.namespace().equals("") ? defaultNamespace : xsdMapping.namespace(); String separator = xsdMapping.separator().equals("") ? defaultSeparator : xsdMapping.separator(); Name xsdName = new NameImpl(namespace, separator, xsdMapping.local()); ComplexAttribute targetAttribute = complexAttribute; // See if the field has a path to a deeper value: if (!path.equals("")) { String[] steps = path.split("/"); for (int i = 0; i < steps.length; i++) { if (targetAttribute == null) { throw new InvalidClassException( String.format( "Unable to wrap attribute in class '%s'. Reference to %s could not be found in the attribute.", clazz, xsdMapping.local())); } // Dig through the attribute to get to the end node. targetAttribute = (ComplexAttribute) targetAttribute .getProperty(steps[i]); } } // What kind of field is it? if (FeatureWrapper.class.isAssignableFrom(fieldType)) { // The field's type is actually a FeatureWrapper itself // so we need to recurse. // Because we know it's a FeatureWrapper it's safe to // assume that the value is a complex attribute. // The featureWrapperAttribute is like: // ComplexAttributeImpl:MineName<MineNameType // id=MINENAMETYPE_TYPE_1>=[...] ComplexAttribute featureWrapperAttribute = (ComplexAttribute) targetAttribute .getProperty(xsdName); if (featureWrapperAttribute == null) { // What's wrong is that MineName is not being added // to MineNamePropertyType throw new InvalidClassException( String.format( "Unable to wrap attribute in class '%s'. '%s' doesn't have required property '%s'.", clazz.getName(), targetAttribute.getName(), xsdName)); } // We get the name of its type and then use that name to // access the actual property, which then gets wrapped: Name typeName = featureWrapperAttribute.getType().getName(); ComplexAttribute nestedComplexAttribute = (ComplexAttribute) featureWrapperAttribute.getProperty(typeName); if (nestedComplexAttribute == null) { // What's wrong is that MineName's properties are // missing the mine type throw new InvalidClassException( String.format( "Unable to wrap attribute in class '%s'. '%s' doesn't have required property '%s'.", clazz.getName(), xsdName, typeName)); } // Look for this field in the complexAttribute: FeatureWrapper property = wrap(nestedComplexAttribute, (Class<FeatureWrapper>) fieldType); field.set(wrapper, property); } else if (ArrayList.class.isAssignableFrom(fieldType)) { // Collections aren't too dissimilar, you just have to // build up an array list which gets set as the field's // value. // What is the collection actually of? // All this line is doing is taking a type like: // Collection<MineNamePropertyType> and giving me // MineNamePropertyType. Class<?> collectionType = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; ArrayList<Object> collection = new ArrayList<Object>(); if (FeatureWrapper.class.isAssignableFrom(collectionType)) { // The collection is complex. for (Property property : targetAttribute.getProperties(xsdName)) { collection.add(wrap((ComplexAttribute) property, (Class<FeatureWrapper>) collectionType)); } } else { // The collection is simple. for (Property property : targetAttribute.getProperties(xsdName)) { collection.add(property.getValue()); } } field.set(wrapper, collection); } else { Property property = targetAttribute.getProperty(xsdName); if (property == null) { throw new InvalidClassException( String.format( "Unable to wrap attribute in class '%s'. %s could not be found in the attribute.", clazz, xsdName)); } field.set(wrapper, property.getValue()); } } } return wrapper; } catch (IllegalAccessException iae) { throw new InvalidClassException( String.format( "Unable to wrap attribute in class '%s'. Exception of type: '%s' was thrown with message: '%s'", clazz, iae.getClass(), iae.getMessage())); } } }