/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-2011, 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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import org.opengis.feature.IllegalAttributeException;
import org.eclipse.xsd.XSDElementDeclaration;
import org.geotools.data.complex.config.AppSchemaDataAccessDTO;
import org.geotools.feature.type.AttributeTypeImpl;
import org.opengis.feature.Attribute;
import org.opengis.feature.ComplexAttribute;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.ComplexType;
import org.opengis.feature.type.Name;
import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.feature.type.PropertyType;
import org.xml.sax.helpers.NamespaceSupport;
/**
* This is a set of utility methods used when <b>implementing</b> types.
* <p>
* This set of classes captures the all important how does it work questions, particularly with
* respect to super types.
* </p>
* FIXME: These methods need a Q&A check to confirm correct use of Super TODO: Cannot tell the
* difference in intent from FeatureTypes
*
* @author Jody Garnett (Refractions Research)
* @author Justin Deoliveira (The Open Planning Project)
*
*
*
* @source $URL$
* http://svn.osgeo.org/geotools/trunk/modules/unsupported/app-schema/app-schema/src/main
* /java/org/geotools/feature/Types.java $
*/
public class Types extends org.geotools.feature.type.Types {
/**
* Returns The name of attributes defined in the type.
*
* @param type
* The type.
*
*/
public static Name[] names(ComplexType type) {
ArrayList names = new ArrayList();
for (Iterator itr = type.getDescriptors().iterator(); itr.hasNext();) {
AttributeDescriptor ad = (AttributeDescriptor) itr.next();
names.add(ad.getName());
}
return (Name[]) names.toArray(new Name[names.size()]);
}
/**
* Creates a type name from a single non-qualified string.
*
* @param name
* The name, may be null
*
* @return The name in which getLocalPart() == name and getNamespaceURI() == null. Or null if
* name == null.
*/
public static Name typeName(String name) {
if (name == null) {
return null;
}
return new NameImpl(name);
}
/**
* Creates an attribute name from a single non-qualified string.
*
* @param name
* The name, may be null
* @param namespace
* The scope or namespace, may be null.
*
* @return The name in which getLocalPart() == name and getNamespaceURI() == namespace.
*/
public static Name typeName(String namespace, String name) {
return new NameImpl(namespace, name);
}
/**
* Creates a type name from another name.
*
* @param name
* The other name.
*/
public static Name typeName(Name name) {
return new NameImpl(name.getNamespaceURI(), name.getLocalPart());
}
/**
* Creates a set of attribute names from a set of strings.
* <p>
* This method returns null if names == null.
* </p>
* <p>
* The ith name has getLocalPart() == names[i] and getNamespaceURI() == null
* </p>
*/
public static Name[] toNames(String[] names) {
if (names == null) {
return null;
}
Name[] attributeNames = new Name[names.length];
for (int i = 0; i < names.length; i++) {
attributeNames[i] = typeName(names[i]);
}
return attributeNames;
}
/**
* Creates a set of type names from a set of strings.
* <p>
* This method returns null if names == null.
* </p>
* <p>
* The ith name has getLocalPart() == names[i] and getNamespaceURI() == null
* </p>
*/
public static Name[] toTypeNames(String[] names) {
if (names == null) {
return null;
}
Name[] typeNames = new Name[names.length];
for (int i = 0; i < names.length; i++) {
typeNames[i] = typeName(names[i]);
}
return typeNames;
}
/**
* Convenience method for turning an array of qualified names into a list of non qualified
* names.
*
*/
public static String[] fromNames(Name[] attributeNames) {
if (attributeNames == null) {
return null;
}
String[] names = new String[attributeNames.length];
for (int i = 0; i < attributeNames.length; i++) {
names[i] = attributeNames[i].getLocalPart();
}
return names;
}
/**
* Convenience method for turning an array of qualified names into a list of non qualified
* names.
*
*/
public static String[] fromTypeNames(Name[] typeNames) {
if (typeNames == null)
return null;
String[] names = new String[typeNames.length];
for (int i = 0; i < typeNames.length; i++) {
names[i] = typeNames[i].getLocalPart();
}
return names;
}
/**
* Returns the first descriptor matching the given local name within the given type.
*
* @param type
* The type, non null.
* @param name
* The name, non null.
*
* @return The first descriptor, or null if no match.
*/
public static PropertyDescriptor descriptor(ComplexType type, String name) {
List match = descriptors(type, name);
if (match.isEmpty())
return null;
return (PropertyDescriptor) match.get(0);
}
/**
* Returns the first descriptor matching the given name + namespace within the given type.
*
* @param type
* The type, non null.
* @param name
* The name, non null.
* @param namespace
* The namespace, non null.
*
* @return The first descriptor, or null if no match.
*/
public static PropertyDescriptor descriptor(ComplexType type, String name, String namespace) {
return descriptor(type, new NameImpl(namespace, name));
}
/**
* Returns the first descriptor matching the given name within the given type.
*
*
* @param type
* The type, non null.
* @param name
* The name, non null.
*
* @return The first descriptor, or null if no match.
*/
public static PropertyDescriptor descriptor(ComplexType type, Name name) {
List match = descriptors(type, name);
if (match.isEmpty())
return null;
return (PropertyDescriptor) match.get(0);
}
/**
* Returns the set of descriptors matching the given local name within the given type.
*
* @param type
* The type, non null.
* @param name
* The name, non null.
*
* @return The list of descriptors named 'name', or an empty list if none such match.
*/
public static List/* <PropertyDescriptor> */descriptors(ComplexType type, String name) {
if (name == null)
return Collections.EMPTY_LIST;
List match = new ArrayList();
for (Iterator itr = type.getDescriptors().iterator(); itr.hasNext();) {
PropertyDescriptor descriptor = (PropertyDescriptor) itr.next();
String localPart = descriptor.getName().getLocalPart();
if (name.equals(localPart)) {
match.add(descriptor);
}
}
// only look up in the super type if the descriptor is not found
// as a direct child definition
if (match.size() == 0) {
AttributeType superType = type.getSuper();
if (superType instanceof ComplexType) {
List superDescriptors = descriptors((ComplexType) superType, name);
match.addAll(superDescriptors);
}
}
return match;
}
/**
* Returns the set of descriptors matching the given name.
*
* @param type
* The type, non null.
* @param name
* The name, non null.
*
* @return The list of descriptors named 'name', or an empty list if none such match.
*/
public static List/* <PropertyDescriptor> */descriptors(ComplexType type, Name name) {
if (name == null)
return Collections.EMPTY_LIST;
List match = new ArrayList();
for (Iterator itr = type.getDescriptors().iterator(); itr.hasNext();) {
PropertyDescriptor descriptor = (PropertyDescriptor) itr.next();
Name descriptorName = descriptor.getName();
if (name.equals(descriptorName)) {
match.add(descriptor);
}
}
// only look up in the super type if the descriptor is not found
// as a direct child definition
if (match.size() == 0) {
AttributeType superType = type.getSuper();
if (superType instanceof ComplexType) {
List superDescriptors = descriptors((ComplexType) superType, name);
match.addAll(superDescriptors);
}
}
return match;
}
/**
* Returns the set of all descriptors of a complex type, including from supertypes.
*
* @param type
* The type, non null.
*
* @return The list of all descriptors.
*/
public static List<PropertyDescriptor> descriptors(ComplexType type) {
//get list of descriptors from types and all supertypes
List<PropertyDescriptor> children = new ArrayList<PropertyDescriptor>();
ComplexType loopType = type;
while (loopType != null) {
children.addAll(loopType.getDescriptors());
loopType = loopType.getSuper() instanceof ComplexType? (ComplexType) loopType.getSuper() : null;
}
return children;
}
/**
* Find a descriptor, taking in to account supertypes AND substitution groups
*
* @param parentType type
* @param name name of descriptor
* @return descriptor, null if not found
*/
public static PropertyDescriptor findDescriptor( ComplexType parentType, Name name) {
//get list of descriptors from types and all supertypes
List<PropertyDescriptor> descriptors = descriptors(parentType);
//find matching descriptor
for (Iterator<PropertyDescriptor> it = descriptors.iterator(); it.hasNext();) {
PropertyDescriptor d = it.next();
if (d.getName().equals(name)) {
return d;
}
}
// nothing found, perhaps polymorphism?? let's loop again
for (Iterator<PropertyDescriptor> it = descriptors.iterator(); it.hasNext();) {
List<AttributeDescriptor> substitutionGroup = (List<AttributeDescriptor>) it.next().getUserData().get("substitutionGroup");
if (substitutionGroup != null){
for (Iterator<AttributeDescriptor> it2 = substitutionGroup.iterator(); it2.hasNext();) {
AttributeDescriptor d = it2.next();
if (d.getName().equals(name)) { //BINGOOO !!
return d;
}
}
}
}
return null;
}
/**
* Find a descriptor, taking in to account supertypes AND substitution groups
*
* @param parentType type
* @param name name of descriptor
* @return descriptor, null if not found
*/
public static PropertyDescriptor findDescriptor( ComplexType parentType, String name) {
//get list of descriptors from types and all supertypes
List<PropertyDescriptor> descriptors = descriptors(parentType);
//find matching descriptor
for (Iterator<PropertyDescriptor> it = descriptors.iterator(); it.hasNext();) {
PropertyDescriptor d = it.next();
if (d.getName().getLocalPart().equals(name)) {
return d;
}
}
// nothing found, perhaps polymorphism?? let's loop again
for (Iterator<PropertyDescriptor> it = descriptors.iterator(); it.hasNext();) {
List<AttributeDescriptor> substitutionGroup = (List<AttributeDescriptor>) it.next().getUserData().get("substitutionGroup");
if (substitutionGroup != null){
for (Iterator<AttributeDescriptor> it2 = substitutionGroup.iterator(); it2.hasNext();) {
AttributeDescriptor d = it2.next();
if (d.getName().getLocalPart().equals(name)) { //BINGOOO !!
return d;
}
}
}
}
return null;
}
/**
* Determines if <code>parent</code> is a super type of <code>type</code>
*
* @param type
* The type in question.
* @param parent
* The possible parent type.
*
*/
public static boolean isSuperType(PropertyType type, PropertyType parent) {
while (type.getSuper() != null) {
type = type.getSuper();
if (type.equals(parent))
return true;
}
return false;
}
/**
* Converts content into a format which is used to store it internally within an attribute of a
* specific type.
*
* @param value
* the object to attempt parsing of.
*
* @throws IllegalArgumentException
* if parsing is attempted and is unsuccessful.
*/
public static Object parse(AttributeType type, Object content) throws IllegalArgumentException {
// JD: TODO: this is pretty lame
if (type instanceof AttributeTypeImpl) {
AttributeTypeImpl hack = (AttributeTypeImpl) type;
Object parsed = hack.parse(content);
if (parsed != null) {
return parsed;
}
}
return content;
}
/**
* Validates anattribute. <br>
* <p>
* Same result as calling:
*
* <pre>
* <code>
* validate(attribute.type(), attribute)
* </code>
* </pre>
*
* </p>
*
* @param attribute
* The attribute.
*
* @throws IllegalAttributeException
* In the event that content violates any restrictions specified by the attribute.
*/
public static void validate(Attribute attribute) throws IllegalAttributeException {
validate(attribute, attribute.getValue());
}
public static void validate(ComplexAttribute attribute) throws IllegalArgumentException {
}
public static void validate(ComplexAttribute attribute, Collection content)
throws IllegalArgumentException {
}
protected static void validate(ComplexType type, ComplexAttribute attribute, Collection content)
throws IllegalAttributeException {
// do normal validation
validate((AttributeType) type, (Attribute) attribute, (Object) content, false);
if (content == null) {
// not really much else we can do
return;
}
Collection schema = type.getDescriptors();
int index = 0;
for (Iterator itr = content.iterator(); itr.hasNext();) {
Attribute att = (Attribute) itr.next();
// att shall not be null
if (att == null) {
throw new NullPointerException("Attribute at index " + index
+ " is null. Attributes "
+ "can't be null. Do you mean Attribute.get() == null?");
}
// and has to be of one of the allowed types
AttributeType attType = att.getType();
boolean contains = false;
for (Iterator sitr = schema.iterator(); sitr.hasNext();) {
AttributeDescriptor ad = (AttributeDescriptor) sitr.next();
if (ad.getType().equals(attType)) {
contains = true;
break;
}
}
if (!contains) {
throw new IllegalArgumentException("Attribute of type " + attType.getName()
+ " found at index " + index
+ " but this type is not allowed by this descriptor");
}
index++;
}
// empty is allows, in such a case, content should be empty
if (type.getDescriptors().isEmpty()) {
if (!content.isEmpty()) {
throw new IllegalAttributeException(attribute.getDescriptor(),
"Type indicates empty attribute collection, content does not");
}
// we are done
return;
}
validateAll(type, attribute, content);
if (type.getSuper() != null) {
validate((ComplexType) type.getSuper(), attribute, content);
}
}
private static void validateAll(ComplexType type, ComplexAttribute att, Collection content)
throws IllegalAttributeException {
processAll(type.getDescriptors(), content);
}
private static void processAll(Collection/* <AttributeDescriptor> */all, Collection/*
* <Attribute>
*/content)
throws IllegalAttributeException {
// TODO: JD: this can be definitley be optimzed, as written its O(n^2)
// for each descriptor, count occurences of each matching attribute
ArrayList remaining = new ArrayList(content);
for (Iterator itr = all.iterator(); itr.hasNext();) {
AttributeDescriptor ad = (AttributeDescriptor) itr.next();
int min = ad.getMinOccurs();
int max = ad.getMaxOccurs();
int occurences = 0;
for (Iterator citr = remaining.iterator(); citr.hasNext();) {
Attribute a = (Attribute) citr.next();
if (a.getName().equals(ad.getName())) {
occurences++;
citr.remove();
}
}
if (occurences < ad.getMinOccurs() || occurences > ad.getMaxOccurs()) {
throw new IllegalAttributeException(ad, "Found " + occurences + " of "
+ ad.getName() + " when type" + "specifies between " + min + " and " + max);
}
}
if (!remaining.isEmpty()) {
throw new IllegalAttributeException((AttributeDescriptor) remaining.iterator().next(),
"Extra content found beyond the specified in the schema: " + remaining);
}
}
public static QName toQName(Name featurePath) {
return toQName(featurePath, null);
}
public static QName toQName(Name featurePath, NamespaceSupport ns) {
if (featurePath == null) {
return null;
}
String namespace = featurePath.getNamespaceURI();
String localName = featurePath.getLocalPart();
QName qName;
if (null == namespace) {
qName = new QName(localName);
} else {
if (ns != null) {
String prefix = ns.getPrefix(namespace);
if (prefix != null) {
qName = new QName(namespace, localName, prefix);
return qName;
}
}
qName = new QName(namespace, localName);
}
return qName;
}
/**
*
* @param name
* @return
* @deprecated use {@link #toTypeName(QName}
*/
public static Name toName(QName name) {
return toTypeName(name);
}
/**
* Return true if an attribute from a type is an element.
*
* @param type
* The type to search in.
* @param att
* The attribute name.
* @return True if the attribute exists in the type and is an element.
*/
public static boolean isElement(ComplexType type, Name att) {
PropertyDescriptor descriptor = Types.descriptor(type, att);
if (descriptor == null) {
return false;
}
Map<Object, Object> userData = descriptor.getUserData();
if (userData.isEmpty()) {
return false;
}
return userData.get(XSDElementDeclaration.class) != null;
}
public static Name toTypeName(QName name) {
if (XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI())) {
return typeName(name.getLocalPart());
}
return typeName(name.getNamespaceURI(), name.getLocalPart());
}
public static boolean equals(Name name, QName qName) {
if (name == null && qName != null) {
return false;
}
if (qName == null && name != null) {
return false;
}
if (XMLConstants.NULL_NS_URI.equals(qName.getNamespaceURI())) {
if (null != name.getNamespaceURI()) {
return false;
} else {
return name.getLocalPart().equals(qName.getLocalPart());
}
}
if (null == name.getNamespaceURI()
&& !XMLConstants.NULL_NS_URI.equals(qName.getNamespaceURI())) {
return false;
}
return name.getNamespaceURI().equals(qName.getNamespaceURI())
&& name.getLocalPart().equals(qName.getLocalPart());
}
/**
* Takes a prefixed attribute name and returns an {@link Name} by looking which namespace
* belongs the prefix to in {@link AppSchemaDataAccessDTO#getNamespaces()}.
*
* @param prefixedName
* , namespaces
* @return
* @throws IllegalArgumentException
* if <code>prefixedName</code> has no declared namespace in app-schema config file.
*/
public static Name degloseName(String prefixedName, NamespaceSupport namespaces)
throws IllegalArgumentException {
Name name = null;
if (prefixedName == null) {
return null;
}
int prefixIdx = prefixedName.indexOf(':');
if (prefixIdx == -1) {
return Types.typeName(prefixedName);
// throw new IllegalArgumentException(prefixedName + " is not
// prefixed");
}
String nsPrefix = prefixedName.substring(0, prefixIdx);
String localName = prefixedName.substring(prefixIdx + 1);
String nsUri = namespaces.getURI(nsPrefix);
// handles undeclared namespaces in the app-schema mapping file
if (nsUri == null) {
throw new IllegalArgumentException("No namespace set: The namespace has not"
+ " been declared in the app-schema mapping file for name: " + nsPrefix + ":"
+ localName + " [Check the Namespaces section in the config file] ");
}
name = Types.typeName(nsUri, localName);
return name;
}
}