/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2015, Geomatys * * 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.apache.sis.feature; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import org.apache.sis.feature.builder.FeatureTypeBuilder; import org.apache.sis.util.Static; import org.geotoolkit.util.NamesExt; import org.opengis.feature.FeatureAssociationRole; import org.opengis.feature.FeatureType; import org.opengis.feature.MismatchedFeatureException; import org.opengis.feature.PropertyNotFoundException; import org.opengis.feature.PropertyType; import org.opengis.parameter.GeneralParameterDescriptor; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.util.GenericName; import org.apache.sis.internal.feature.AttributeConvention; import org.opengis.feature.AttributeType; /** * * @author Johann Sorel (Geomatys) */ public final class FeatureTypeExt extends Static { /** * * @param featureType * @param properties * @return * @throws MismatchedFeatureException */ public static FeatureType createSubType(final FeatureType featureType, final String ... properties) throws MismatchedFeatureException{ if (properties == null) { return featureType; } final FeatureTypeBuilder ftb = new FeatureTypeBuilder(featureType); ftb.properties().clear(); //rebuild type, preserve original property order boolean same = true; loop: for (PropertyType pt : featureType.getProperties(true)) { for (String name : properties) { if(featureType.getProperty(name).equals(pt)){ ftb.addProperty(pt); continue loop; } } same = false; } return same ? featureType : ftb.build(); } public static FeatureType createSubType(final FeatureType featureType, final GenericName[] properties) throws MismatchedFeatureException{ if (properties == null) { return featureType; } final String[] names = new String[properties.length]; for(int i=0;i<names.length;i++){ names[i] = properties[i].toString(); } return createSubType(featureType, names); } /** * Create a derived FeatureType * * <p></p> * * @param featureType * @param properties - if null, every property of the feature type in input will be used * @param override * @throws MismatchedFeatureException */ public static FeatureType createSubType(final FeatureType featureType, final String[] properties, final CoordinateReferenceSystem override) throws MismatchedFeatureException{ FeatureType type = featureType; if (properties!=null) { type = createSubType(featureType, properties); } if (override!=null) { final FeatureTypeBuilder ftb = new FeatureTypeBuilder(); ftb.setAbstract(type.isAbstract()); ftb.setDefinition(type.getDefinition()); ftb.setDescription(type.getDescription()); ftb.setDesignation(type.getDesignation()); ftb.setName(type.getName()); ftb.setSuperTypes(type.getSuperTypes().toArray(new FeatureType[0])); for (PropertyType property : type.getProperties(true)) { //replace operations by ViewOperation if (AttributeConvention.isGeometryAttribute(property) && property instanceof AttributeType) { ftb.addAttribute((AttributeType) property).setCRS(override); } else { ftb.addProperty(property); } } type = ftb.build(); } return type; } /** * * @param featureType * @param properties * @return true if property list contains all feature type properties. */ public static boolean isAllProperties(final FeatureType featureType, String ... properties) { final int size = featureType.getProperties(true).size(); if(size>properties.length) return false; //check list contains all properties, using a Set to avoid duplicated names final Set<GenericName> names = new HashSet<>(); for (String name : properties) { try{ names.add(featureType.getProperty(name).getName()); }catch(PropertyNotFoundException ex) { return false; } } return names.size() == size; } /** * Test field equality ignoring convention properties. * * @param type1 * @param type2 * @return */ public static boolean equalsIgnoreConvention(FeatureType type1, FeatureType type2){ if (type1 == type2) { return true; } //check base properties if (!Objects.equals(type1.getName(), type2.getName()) || !Objects.equals(type1.getDefinition(), type2.getDefinition()) || !Objects.equals(type1.getDesignation(), type2.getDesignation()) || !Objects.equals(type1.getDesignation(), type2.getDesignation()) || !Objects.equals(type1.isAbstract(), type2.isAbstract())){ return false; } //check super types final Set<? extends FeatureType> super1 = type1.getSuperTypes(); final Set<? extends FeatureType> super2 = type2.getSuperTypes(); if(super1.size() != super2.size()) return false; final Iterator<? extends FeatureType> site1 = super1.iterator(); final Iterator<? extends FeatureType> site2 = super2.iterator(); while(site1.hasNext()){ if(!equalsIgnoreConvention(site1.next(), site2.next())) return false; } //check properties final Set<GenericName> visited = new HashSet<>(); for (PropertyType pt1 : type1.getProperties(true)) { visited.add(pt1.getName()); if (AttributeConvention.contains(pt1.getName())) continue; try { final PropertyType pt2 = type2.getProperty(pt1.getName().toString()); if (!equalsIgnoreConvention(pt1, pt2)) return false; } catch (PropertyNotFoundException ex) { return false; } } for (PropertyType pt2 : type2.getProperties(true)) { if (AttributeConvention.contains(pt2.getName()) || visited.contains(pt2.getName())) continue; try { final PropertyType pt1 = type1.getProperty(pt2.getName().toString()); if (!equalsIgnoreConvention(pt1, pt2)) return false; } catch (PropertyNotFoundException ex) { return false; } } return true; } private static boolean equalsIgnoreConvention(PropertyType pt1, PropertyType pt2){ if(pt1 instanceof FeatureAssociationRole){ if(pt2 instanceof FeatureAssociationRole){ final FeatureAssociationRole far1 = (FeatureAssociationRole) pt1; final FeatureAssociationRole far2 = (FeatureAssociationRole) pt2; //check base properties if (!Objects.equals(far1.getName(), far2.getName()) || !Objects.equals(far1.getDefinition(), far2.getDefinition()) || !Objects.equals(far1.getDesignation(), far2.getDesignation()) || !Objects.equals(far1.getDesignation(), far2.getDesignation())){ return false; } if(far1.getMinimumOccurs()!=far2.getMinimumOccurs()|| far1.getMaximumOccurs()!=far2.getMaximumOccurs()){ return false; } if(!equalsIgnoreConvention(far1.getValueType(), far2.getValueType())){ return false; } }else{ return false; } }else if(!pt1.equals(pt2)){ return false; } return true; } //////////////////////////////////////////////////////////////////////////// // PARAMETERS API MAPPING OPERATIONS /////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// /** * Convert given parameter descriptor to a feature type. * the original parameter descriptor will be store in the user map with key "origin" * * @param desc * @return ComplexType */ public static FeatureType toFeatureType(final ParameterDescriptorGroup desc){ final FeatureTypeBuilder ftb = new FeatureTypeBuilder(); ftb.setName(NamesExt.valueOf(desc.getName().getCode())); for(GeneralParameterDescriptor sd : desc.descriptors()){ final PropertyType pt = toPropertyType(sd); ftb.addProperty(pt); } return ftb.build(); } /** * Convert given parameter descriptor to a feature type. * the original parameter descriptor will be store in the user map with key "origin" * * @param descriptor * @return PropertyType */ public static PropertyType toPropertyType(final GeneralParameterDescriptor descriptor){ if(descriptor instanceof ParameterDescriptor){ final ParameterDescriptor desc = (ParameterDescriptor) descriptor; final SingleAttributeTypeBuilder atb = new SingleAttributeTypeBuilder(); atb.setName(NamesExt.valueOf(desc.getName().getCode())); atb.setDescription(desc.getRemarks()); atb.setValueClass(desc.getValueClass()); atb.setMinimumOccurs(desc.getMinimumOccurs()); atb.setMaximumOccurs(desc.getMaximumOccurs()); atb.setDefaultValue(desc.getDefaultValue()); final Set validValues = desc.getValidValues(); if(validValues != null && !validValues.isEmpty()){ atb.setPossibleValues(validValues); } return atb.build(); }else if (descriptor instanceof ParameterDescriptorGroup){ final ParameterDescriptorGroup desc = (ParameterDescriptorGroup) descriptor; final FeatureType type = toFeatureType(desc); final Map params = new HashMap(); params.put(DefaultAssociationRole.NAME_KEY, NamesExt.valueOf(desc.getName().getCode())); return new DefaultAssociationRole(params, type, desc.getMinimumOccurs(), desc.getMaximumOccurs()); }else{ throw new IllegalArgumentException("Unsupported type : " + descriptor.getClass()); } } /** * Search in the given feature type for a property whose name matches given pattern. * Comparison only occurs on local part of the attribute names. * * @param regex The regex used to describe wanted name. * @param toSearchIn The feature type in which we'll perform the search. * @return The name of all the attributes which are compliant with given pattern. * Can return an empty list, but never null. */ public static List<GenericName> hasNameLike(final String regex, final FeatureType toSearchIn) { final List<GenericName> names = new ArrayList<>(); for (final PropertyType desc : toSearchIn.getProperties(true)) { final GenericName name = desc.getName(); if (name != null && name.tip().toString().matches(regex)) { names.add(name); } } return names; } /** * Returns true if property is a component of the feature type primary key. * * @param type * @param propertyName * @return */ public static boolean isPartOfPrimaryKey(FeatureType type, String propertyName) { PropertyType property; try{ property = type.getProperty(AttributeConvention.IDENTIFIER_PROPERTY.toString()); } catch(PropertyNotFoundException ex) { //no identifier property return false; } if(property instanceof AbstractOperation){ final Set<String> dependencies = ((AbstractOperation)property).getDependencies(); return dependencies.contains(propertyName); } return false; } }