sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jso...@apache.org
Subject svn commit: r1737814 [1/2] - in /sis/branches/JDK8/core/sis-feature/src: main/java/org/apache/sis/feature/ main/java/org/apache/sis/internal/ main/java/org/apache/sis/internal/feature/ test/java/org/apache/sis/feature/ test/java/org/apache/sis/internal...
Date Tue, 05 Apr 2016 09:55:35 GMT
Author: jsorel
Date: Tue Apr  5 09:55:35 2016
New Revision: 1737814

URL: http://svn.apache.org/viewvc?rev=1737814&view=rev
Log:
New aggregate and bounds feature operations, add featuretypebuilder and attributetypebuilder

Added:
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AggregateOperation.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/BoundsOperation.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeTypeBuilder.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureTypeBuilder.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/AggregateOperationTest.java
      - copied, changed from r1737194, sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/LinkOperationTest.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/BoundsOperationTest.java
      - copied, changed from r1737194, sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/LinkOperationTest.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeConventionTest.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeTypeBuilderTest.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/FeatureTypeBuilderTest.java
Modified:
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/FeaturesTest.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java

Added: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AggregateOperation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AggregateOperation.java?rev=1737814&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AggregateOperation.java (added)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AggregateOperation.java Tue Apr  5 09:55:35 2016
@@ -0,0 +1,177 @@
+/*
+ *    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.Collections;
+import java.util.Map;
+import static org.apache.sis.feature.AbstractIdentifiedType.NAME_KEY;
+import static org.apache.sis.feature.LinkOperation.parameters;
+import org.apache.sis.util.ObjectConverters;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.IdentifiedType;
+import org.opengis.feature.MultiValuedPropertyException;
+import org.opengis.feature.Property;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.util.GenericName;
+
+/**
+ * An aggregate operation concatenate the values of multiple properties.
+ * This operation is mainly used as an id generator for multiple primary key properties.
+ * <br>
+ * This operation support both reading and writing. When setting the operation value,
+ * the value will be split and forwarded to each attribute element.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+final class AggregateOperation extends AbstractOperation {
+
+    private static final AttributeType<String> TYPE = new DefaultAttributeType<>(
+            Collections.singletonMap(NAME_KEY, "String"),String.class,1,1,null);
+
+    /**
+     * The parameter descriptor for the "Aggregate" operation, which does not take any parameter.
+     */
+    private static final ParameterDescriptorGroup EMPTY_PARAMS = parameters("Link", 1);
+
+    private final GenericName[] attributeNames;
+    private final String prefix;
+    private final String suffix;
+    private final String separator;
+
+    AggregateOperation(Map<String, ?> identification, String prefix, String suffix, String separator, GenericName ... attributeNames) {
+        super(identification);
+        this.attributeNames = attributeNames;
+        this.prefix = prefix==null ? "" : prefix;
+        this.suffix = suffix==null ? "" : suffix;
+        this.separator = separator;
+    }
+
+    /**
+     * Aggregation operation do not require any parameter.
+     *
+     * @return empty parameter group.
+     */
+    @Override
+    public ParameterDescriptorGroup getParameters() {
+        return EMPTY_PARAMS;
+    }
+
+    /**
+     * Aggregation operation generates a String result.
+     *
+     * @return aggregation string type
+     */
+    @Override
+    public IdentifiedType getResult() {
+        return TYPE;
+    }
+
+    /**
+     * {@inheritDoc }
+     *
+     * @param  feature    The feature on which to execute the operation.
+     *                    Can not be {@code null}.
+     * @param  parameters The parameters to use for executing the operation.
+     *                    Can be {@code null}.
+     * @return The operation result, never null.
+     */
+    @Override
+    public Property apply(Feature feature, ParameterValueGroup parameters) {
+        return new AggregateAttribute(feature);
+    }
+
+    /**
+     * Operation attribute.
+     * Value is calculated each time it is accessed.
+     */
+    private final class AggregateAttribute extends AbstractAttribute<String> {
+
+        private final Feature feature;
+        
+        public AggregateAttribute(final Feature feature) {
+            super(TYPE);
+            this.feature = feature;
+        }
+
+        @Override
+        public String getValue() throws MultiValuedPropertyException {
+            final StringBuilder sb = new StringBuilder();
+            sb.append(prefix);
+
+            for(int i=0;i<attributeNames.length;i++){
+                if(i!=0) sb.append(separator);
+                sb.append(feature.getPropertyValue(attributeNames[i].toString()));
+            }
+
+            sb.append(suffix);
+            return sb.toString();
+        }
+
+        @Override
+        public void setValue(String value) {
+            //check prefix
+            if(!value.startsWith(prefix)){
+                throw new IllegalArgumentException("Unvalid string, does not start with "+prefix);
+            }
+            if(!value.endsWith(suffix)){
+                throw new IllegalArgumentException("Unvalid string, does not end with "+suffix);
+            }
+
+            //split values, we don't use the regex split to avoid possible reserverd regex characters
+            final Object[] values = new Object[attributeNames.length];
+            int i=0;
+            int offset = 0;
+            //remove prefix and suffix
+            value = value.substring(prefix.length(), value.length()-suffix.length());
+            while(true){
+                if(i>=values.length) throw new IllegalArgumentException("Unvalid string, expected "+values.length+" values, but found more");
+                final int idx = value.indexOf(separator,offset);
+                if (idx==-1) {
+                    //last element
+                    values[i] = value.substring(offset);
+                    i++;
+                    break;
+                } else {
+                    values[i] = value.substring(offset, idx);
+                    i++;
+                    offset = (idx + separator.length());
+                }
+            }
+
+            if(i!=values.length){
+                throw new IllegalArgumentException("Unvalid string, number of values do not match, found "+(i)+" but expected "+values.length);
+            }
+
+            //set values, convert them if necessary
+            final FeatureType type = feature.getType();
+            for(int k=0;k<values.length;k++){
+                final String propName = attributeNames[k].toString();
+                final AttributeType pt = (AttributeType) type.getProperty(propName);
+                final Object val = ObjectConverters.convert(values[k], pt.getValueClass());
+                feature.setPropertyValue(propName, val);
+            }
+        }
+        
+    }
+    
+}

Added: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/BoundsOperation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/BoundsOperation.java?rev=1737814&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/BoundsOperation.java (added)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/BoundsOperation.java Tue Apr  5 09:55:35 2016
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.feature;
+
+import org.apache.sis.internal.feature.AttributeConvention;
+import com.esri.core.geometry.Geometry;
+import java.util.Collections;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import static org.apache.sis.feature.AbstractIdentifiedType.NAME_KEY;
+import static org.apache.sis.feature.LinkOperation.parameters;
+import org.apache.sis.geometry.Envelopes;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.IdentifiedType;
+import org.opengis.feature.MultiValuedPropertyException;
+import org.opengis.feature.Property;
+import org.opengis.feature.PropertyType;
+import org.opengis.geometry.Envelope;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.TransformException;
+
+/**
+ * Calculate feature bounds operation.
+ * <br>
+ * This operation loops on all first level geometry properties and compute the
+ * resulting envelope.
+ * <br>
+ * Geometries can be in different coordinate reference systems, they will be
+ * transformed to the operation crs.
+ * <br>
+ * If the operation CRS has not been defined then the first none-empty geometry
+ * crs will be used.
+ * <br>
+ * This operation can only be read, not setted.
+ * 
+ * @author Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+final class BoundsOperation extends AbstractOperation {
+
+    private static final AttributeType<Envelope> TYPE = new DefaultAttributeType<>(
+            Collections.singletonMap(NAME_KEY, "Envelope"),Envelope.class,1,1,null);
+
+    /**
+     * The parameter descriptor for the "Bounds" operation, which does not take any parameter.
+     */
+    private static final ParameterDescriptorGroup EMPTY_PARAMS = parameters("Link", 1);
+
+    private final CoordinateReferenceSystem crs;
+
+    /**
+     * 
+     * @param identification The name and other information to be given to this operation.
+     * @param crs result envelope CRS, can be {@code null}
+     */
+    BoundsOperation(Map<String, ?> identification, CoordinateReferenceSystem crs) {
+        super(identification);
+        this.crs = crs;
+    }
+
+    /**
+     * Bounds operation do not require any parameter.
+     *
+     * @return empty parameter group.
+     */
+    @Override
+    public ParameterDescriptorGroup getParameters() {
+        return EMPTY_PARAMS;
+    }
+
+    /**
+     * Bounds operation generates a Envelope result, or {@code null}.
+     *
+     * @return bounds envelope type
+     */
+    @Override
+    public IdentifiedType getResult() {
+        return TYPE;
+    }
+
+    /**
+     * {@inheritDoc }
+     *
+     * @param  feature    The feature on which to execute the operation.
+     *                    Can not be {@code null}.
+     * @param  parameters The parameters to use for executing the operation.
+     *                    Can be {@code null}.
+     * @return The operation result, never null.
+     */
+    @Override
+    public Property apply(Feature feature, ParameterValueGroup parameters) {
+        return new BoundsAttribute(feature);
+    }
+
+    /**
+     * Operation attribute.
+     * Value is calculated each time it is accessed.
+     */
+    private final class BoundsAttribute extends AbstractAttribute<Envelope> {
+
+        private final Feature feature;
+
+        public BoundsAttribute(final Feature feature) {
+            super(TYPE);
+            this.feature = feature;
+        }
+
+        @Override
+        public Envelope getValue() throws MultiValuedPropertyException {
+            try {
+                return calculate(feature, crs);
+            } catch (TransformException ex) {
+                Logger.getLogger("org.apache.sis.feature").log(Level.WARNING, ex.getLocalizedMessage(), ex);
+            }
+            return null;
+        }
+
+        @Override
+        public void setValue(Envelope value) {
+            throw new UnsupportedOperationException("Bounds operation attribute can not be set.");
+        }
+
+    }
+
+    static GeneralEnvelope calculate(Feature feature, CoordinateReferenceSystem crs) throws TransformException {
+
+        GeneralEnvelope bounds = null;
+
+        final FeatureType type = feature.getType();
+        for(PropertyType pt : type.getProperties(true)){
+            if(!AttributeConvention.isGeometryAttribute(pt)) continue;
+
+            final Object val = feature.getPropertyValue(pt.getName().toString());
+
+            if(val instanceof Geometry){
+                final Geometry geom = (Geometry)val;
+
+                final com.esri.core.geometry.Envelope env = new com.esri.core.geometry.Envelope();
+                geom.queryEnvelope(env);
+                if(env.isEmpty()) continue;
+
+                //extract geometry enveloppe
+                final CoordinateReferenceSystem geomCrs = AttributeConvention.getCRSCharacteristic(pt);
+                Envelope genv;
+                if(geomCrs!=null){
+                    genv = new GeneralEnvelope(geomCrs);
+                }else{
+                    genv = new GeneralEnvelope(2);
+                }
+                ((GeneralEnvelope)genv).setRange(0, env.getXMin(), env.getXMax());
+                ((GeneralEnvelope)genv).setRange(1, env.getYMin(), env.getYMax());
+
+                //ensure it is in the wanted crs
+                if(crs!=null){
+                    genv = Envelopes.transform(genv, crs);
+                }
+
+                if(bounds==null){
+                    if(crs==null){
+                        bounds = (GeneralEnvelope)genv;
+                    }else{
+                        bounds = new GeneralEnvelope(crs);
+                        bounds.setEnvelope(genv);
+                    }
+                }else{
+                    bounds.add(genv);
+                }
+            }
+        }
+
+        return bounds;
+    }
+    
+}

Added: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java?rev=1737814&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java (added)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java Tue Apr  5 09:55:35 2016
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.feature;
+
+import java.util.Collections;
+import org.apache.sis.util.Static;
+import org.opengis.feature.Operation;
+import org.opengis.feature.PropertyType;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.util.GenericName;
+
+/**
+ * FeatureType operations utility methods.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public final class FeatureOperations extends Static {
+
+    /**
+     * Create a link operation property type.
+     *
+     * @param name name of the property
+     * @param linkAttributeType linked property type
+     * @return Operation
+     */
+    public static Operation link(GenericName name, PropertyType linkAttributeType){
+        return new LinkOperation(Collections.singletonMap("name", name), linkAttributeType);
+    }
+
+    /**
+     * Create an aggregation operation property type.
+     *
+     * @param name name of the property
+     * @param prefix prefix of the resulting aggregated string
+     * @param suffix suffix of the resulting aggregated string
+     * @param separator separator between each value
+     * @param aggAttributeNames aggregated attribute values
+     * @return Operation
+     */
+    public static Operation aggregate(GenericName name, String prefix, String suffix, String separator, GenericName ... aggAttributeNames){
+        return new AggregateOperation(Collections.singletonMap("name", name), prefix, suffix, separator, aggAttributeNames);
+    }
+
+    /**
+     * Create a calculate bounds operation type.
+     *
+     * @param name name of the property
+     * @param baseCrs created envelope crs
+     * @return Operation
+     */
+    public static Operation bounds(GenericName name, CoordinateReferenceSystem baseCrs){
+        return new BoundsOperation(Collections.singletonMap("name", name), baseCrs);
+    }
+
+}

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java?rev=1737814&r1=1737813&r2=1737814&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java [UTF-8] Tue Apr  5 09:55:35 2016
@@ -22,14 +22,25 @@ import org.apache.sis.util.resources.Err
 // Branch-dependent imports
 import org.opengis.feature.Attribute;
 import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.InvalidPropertyValueException;
+import org.opengis.feature.Property;
+import org.opengis.feature.PropertyType;
+import org.opengis.metadata.maintenance.ScopeCode;
+import org.opengis.metadata.quality.ConformanceResult;
+import org.opengis.metadata.quality.DataQuality;
+import org.opengis.metadata.quality.Element;
+import org.opengis.metadata.quality.Result;
 
 
 /**
  * Static methods working on features or attributes.
  *
  * @author  Martin Desruisseaux (Geomatys)
+ * @author  Johann Sorel (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.7
  * @module
  */
 public final class Features extends Static {
@@ -96,4 +107,67 @@ public final class Features extends Stat
         }
         return (Attribute<V>) attribute;
     }
+
+
+    /**
+     * Validate feature state.
+     * <br>
+     * This method is a shortcut to loop on feature data quality results.
+     * <br>
+     * If one ConformanceResult is false then an IllegalArgumentException is throw,
+     * otherwise the function return doing nothing.
+     *
+     * @param feature tested feature.
+     * @throws InvalidPropertyValueException
+     */
+    public static void validate(Feature feature) throws InvalidPropertyValueException {
+
+        //Get data quality of the feature
+        final DataQuality quality;
+        if(feature instanceof AbstractFeature){
+            quality = ((AbstractFeature)feature).quality();
+        }else{
+            //use default validator
+            final Validator v = new Validator(ScopeCode.FEATURE);
+            final FeatureType type = feature.getType();
+            for (final PropertyType pt : type.getProperties(true)) {
+                final Property property = feature.getProperty(pt.getName().toString());
+                final DataQuality pq;
+                if (property instanceof AbstractAttribute<?>) {
+                    pq = ((AbstractAttribute<?>) property).quality();
+                } else if (property instanceof AbstractAssociation) {
+                    pq = ((AbstractAssociation) property).quality();
+                } else {
+                    continue;
+                }
+                if (pq != null) { // Should not be null, but let be safe.
+                    v.quality.getReports().addAll(pq.getReports());
+                }
+            }
+            quality = v.quality;
+        }
+
+        //loop on quality elements and check conformance results
+        boolean valid = true;
+        search:
+        for(Element element : quality.getReports()){
+            for(Result result : element.getResults()){
+                //NOTE : other type of result are ignored for now
+                // other results may requiere threshold and other informations
+                // to be evaluated
+                if(result instanceof ConformanceResult){
+                    final Boolean pass = ((ConformanceResult)result).pass();
+                    if(Boolean.FALSE.equals(pass)){
+                        valid = false;
+                        break search;
+                    }
+                }
+            }
+        }
+
+        if(!valid){
+            throw new InvalidPropertyValueException(quality.toString());
+        }
+    }
+
 }

Added: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java?rev=1737814&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java (added)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java Tue Apr  5 09:55:35 2016
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.feature;
+
+import com.esri.core.geometry.Geometry;
+import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.util.Static;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.Operation;
+import org.opengis.feature.PropertyType;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.util.GenericName;
+import org.opengis.util.NameFactory;
+
+/**
+ * Features are basically improved java Maps, this convention helps keeping
+ * a coherency for the most common use cases.
+ *
+ *
+ * @author Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public final class AttributeConvention extends Static {
+
+    /**
+     * Scope used by SIS for convention properties.
+     */
+    private static final String SIS_SCOPE = "http://sis.apache.org/feature";
+
+    /**
+     * Convention name of the feature identifier attribute if there is one in the feature type.
+     * <br>
+     * This attribute should be an Operation which aggregate other attributes.
+     */
+    public static final GenericName ATTRIBUTE_ID;
+    /**
+     * Convention name of the default geometry attribute if there is one in the feature type.
+     * <br>
+     * This attribute should be an Operation acting as a redirection to another attribute.
+     */
+    public static final GenericName ATTRIBUTE_DEFAULT_GEOMETRY;
+    /**
+     * Convention name of the feature bounds.
+     * <br>
+     * This attribute should be an Operation.
+     * <br>
+     * Most feature types have a single geometry but in case several geometries
+     * exist the value returned is the concatenation of all first depth geometries.
+     */
+    public static final GenericName ATTRIBUTE_BOUNDS;
+    /**
+     * Attribute types which store geometries or coverages can have a CoordinateReferenceSystem defined.
+     * <br>
+     * The crs information is stored as default value in the AttributeTypes with this name
+     * of the geometry attribute type.
+     */
+    public static final GenericName CHARACTERISTIC_CRS;
+    /**
+     * Attribute type which store the string maximum length.
+     */
+    public static final GenericName CHARACTERISTIC_LENGTH;
+    /**
+     * Attribute type which store the enumeration of possible values.
+     */
+    public static final GenericName CHARACTERISTIC_ENUM;
+
+    static {
+        final NameFactory factory = DefaultFactories.forBuildin(NameFactory.class);
+        ATTRIBUTE_ID = factory.createGenericName(null, SIS_SCOPE,  "@id");
+        ATTRIBUTE_DEFAULT_GEOMETRY = factory.createGenericName(null, SIS_SCOPE,  "@defaultgeom");
+        ATTRIBUTE_BOUNDS = factory.createGenericName(null, SIS_SCOPE,  "@bounds");
+        CHARACTERISTIC_CRS = factory.createGenericName(null, SIS_SCOPE,  "@crs");
+        CHARACTERISTIC_LENGTH = factory.createGenericName(null, SIS_SCOPE,  "@length");
+        CHARACTERISTIC_ENUM = factory.createGenericName(null, SIS_SCOPE,  "@enum");
+    }
+
+    /**
+     * Convention properties are properties added to the real feature type
+     * to offer access to common informations.
+     * <br>
+     * Such properties should not be stored when writing, those are calculate
+     * most of the time and have only a meaning within SIS.
+     *
+     * @param property tested property, not null
+     * @return true if property is a convention property.
+     */
+    public static boolean isConventionProperty(PropertyType property){
+        return property.getName().toString().startsWith(SIS_SCOPE);
+    }
+    
+    /**
+     * Indicate of given PropertyType is a geometry type.
+     * <br>
+     * This implies property is an attribute and the value class is a type of Geometry.
+     *
+     * @param propertyType tested property type, not null
+     * @return true if property type is for geometries
+     */
+    public static boolean isGeometryAttribute(PropertyType propertyType){
+        if(propertyType instanceof AttributeType){
+            return Geometry.class.isAssignableFrom(((AttributeType)propertyType).getValueClass());
+        }else{
+            return false;
+        }
+    }
+
+    /**
+     * Extract CRS characteristic if it exist.
+     * 
+     * @param type tested property type, not null
+     * @return CoordinateReferenceSystem or null
+     */
+    public static CoordinateReferenceSystem getCRSCharacteristic(PropertyType type){
+        while(type instanceof Operation){
+            type = (PropertyType) ((Operation)type).getResult();
+        }
+        if(type instanceof AttributeType){
+            final AttributeType at = (AttributeType) ((AttributeType)type).characteristics().get(CHARACTERISTIC_CRS.toString());
+            if(at!=null){
+                return (CoordinateReferenceSystem) at.getDefaultValue();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Extract field length characteristic if it exist.
+     *
+     * @param type tested property type, not null
+     * @return Length or null
+     */
+    public static Integer getLengthCharacteristic(AttributeType type){
+        final AttributeType at = (AttributeType) type.characteristics().get(CHARACTERISTIC_LENGTH.toString());
+        if(at!=null){
+            return (Integer) at.getDefaultValue();
+        }
+        return null;
+    }
+
+}

Added: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeTypeBuilder.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeTypeBuilder.java?rev=1737814&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeTypeBuilder.java (added)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeTypeBuilder.java Tue Apr  5 09:55:35 2016
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.feature;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.sis.feature.DefaultAttributeType;
+import static org.apache.sis.feature.AbstractIdentifiedType.*;
+import static org.apache.sis.internal.feature.AttributeConvention.*;
+import org.apache.sis.internal.system.DefaultFactories;
+import org.opengis.feature.AttributeType;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.util.GenericName;
+import org.opengis.util.NameFactory;
+
+/**
+ * Helper class for the creation of {@link AttributeType} instances.
+ * This builder can create the parameters to be given to {@linkplain DefaultAttributeType#DefaultAttributeType(
+ * java.util.Map, java.lang.Class, int, int, java.lang.Object, org.opengis.feature.AttributeType...)  attribute type constructor}
+ * from simpler parameters given to this builder.
+ * 
+ * @author Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public class AttributeTypeBuilder {
+
+    private final Map parameters = new HashMap();
+    private final List<AttributeType> atts = new ArrayList<>();
+    private Class valueClass = Object.class;
+    private int minimumOccurs = 1;
+    private int maximumOccurs = 1;
+    private Object defaultValue = null;
+
+    /**
+     * Reset builder parameters to there original values.
+     */
+    public void reset(){
+        parameters.clear();
+        atts.clear();
+        valueClass = Object.class;
+        minimumOccurs = 1;
+        maximumOccurs = 1;
+        defaultValue = null;
+    }
+
+    /**
+     * Copy parameters from the given attribute type.
+     *
+     * @param type attribute type to copy parameters from.
+     */
+    public void copy(AttributeType type){
+        setName(type.getName());
+        setDefinition(type.getDefinition());
+        setDescription(type.getDescription());
+        setDesignation(type.getDesignation());
+        atts.addAll(type.characteristics().values());
+        valueClass = type.getValueClass();
+        minimumOccurs = type.getMinimumOccurs();
+        maximumOccurs = type.getMaximumOccurs();
+        defaultValue = type.getDefaultValue();
+    }
+
+    /**
+     * Set attribute type name.
+     *
+     * @param localPart generic name tip part, not null
+     */
+    public void setName(String localPart){
+        this.setName(null,localPart);
+    }
+
+    /**
+     * Set attribute type name.
+     *
+     * @param scope generic name scope part, can be null
+     * @param localPart generic name tip part, not null
+     */
+    public void setName(String scope, String localPart){
+        final NameFactory factory = DefaultFactories.forBuildin(NameFactory.class);
+        if(scope==null){
+            setName(factory.createGenericName(null, localPart));
+        }else{
+            setName(factory.createGenericName(null, scope,  localPart));
+        }
+    }
+
+    /**
+     * Set attribute type name.
+     *
+     * See {@link #NAME_KEY}
+     *
+     * @param name generic name, not null
+     */
+    public void setName(GenericName name) {
+        parameters.put(NAME_KEY, name);
+    }
+
+    /**
+     * Set attribute description.
+     *
+     * See {@link #DESCRIPTION_KEY}
+     *
+     * @param description
+     */
+    public void setDescription(CharSequence description){
+        parameters.put(DESCRIPTION_KEY, description);
+    }
+
+    /**
+     * Set attribute designation.
+     *
+     * See {@link #DESIGNATION_KEY}
+     *
+     * @param designation
+     */
+    public void setDesignation(CharSequence designation){
+        parameters.put(DESIGNATION_KEY, designation);
+    }
+
+    /**
+     * Set attribute definition.
+     *
+     * See {@link #DEFINITION_KEY}
+     *
+     * @param definition
+     */
+    public void setDefinition(CharSequence definition){
+        parameters.put(DEFINITION_KEY, definition);
+    }
+
+    /**
+     * Set attribute value class.
+     *
+     * @param valueClass not null
+     */
+    public void setValueClass(Class valueClass) {
+        this.valueClass = valueClass;
+    }
+
+    /**
+     * Set default attribute value.
+     *
+     * @param defaultValue
+     */
+    public void setDefaultValue(Object defaultValue) {
+        this.defaultValue = defaultValue;
+    }
+
+    /**
+     * Set minimum occurrences of the attribute values.
+     *
+     * @param minimumOccurs
+     */
+    public void setMinimumOccurs(int minimumOccurs) {
+        this.minimumOccurs = minimumOccurs;
+    }
+
+    /**
+     * Set maximum occurrences of the attribute values.
+     *
+     * @param maximumOccurs
+     */
+    public void setMaximumOccurs(int maximumOccurs) {
+        this.maximumOccurs = maximumOccurs;
+    }
+
+    /**
+     * Set maximum attribute length.
+     * This characteristic only have a meaning with CharSequence type attributes.
+     *
+     * @param length 
+     * @return created characteristic
+     */
+    public AttributeType setLengthCharacteristic(int length){
+        return addCharacteristic(CHARACTERISTIC_LENGTH, Integer.class, 1, 1, length);
+    }
+
+    /**
+     * Set attribute {@code CoordinateReferenceSystem}.
+     * This characteristic only have a meaning with georeferenced type attributes.
+     * 
+     * @param crs
+     * @return created characteristic
+     */
+    public AttributeType setCRSCharacteristic(CoordinateReferenceSystem crs){
+        return addCharacteristic(CHARACTERISTIC_CRS, CoordinateReferenceSystem.class, 1, 1, crs);
+    }
+
+    /**
+     * Set attribute restricted values.
+     * This characteristic defines a list of possible values for the attribute.
+     *
+     * @param values
+     * @return created characteristic
+     */
+    public AttributeType setPossibleValues(Collection values){
+        return addCharacteristic(CHARACTERISTIC_ENUM, Object.class, 1, 1, values);
+    }
+
+    /**
+     * Add a user defined characteristic.
+     *
+     * @param localPart generic name tip part, not null
+     * @param valueClass characteristic value class
+     * @param minimumOccurs characteristic minimum number of occurrences
+     * @param maximumOccurs characteristic maximum number of occurrences
+     * @param defaultValue characteristic default value
+     * @return created characteristic
+     */
+    public AttributeType addCharacteristic(String localPart, Class valueClass, int minimumOccurs, int maximumOccurs, Object defaultValue){
+        final NameFactory factory = DefaultFactories.forBuildin(NameFactory.class);
+        final GenericName name = factory.createGenericName(null, localPart);
+        return addCharacteristic(name,valueClass,minimumOccurs,maximumOccurs,defaultValue);
+    }
+
+    /**
+     * Add a user defined characteristic.
+     *
+     * @param name characteristic name
+     * @param valueClass characteristic value class
+     * @param minimumOccurs characteristic minimum number of occurrences
+     * @param maximumOccurs characteristic maximum number of occurrences
+     * @param defaultValue characteristic default value
+     * @return created characteristic
+     */
+    public AttributeType addCharacteristic(GenericName name, Class valueClass, int minimumOccurs, int maximumOccurs, Object defaultValue){
+        return addCharacteristic(new DefaultAttributeType(
+                    Collections.singletonMap(NAME_KEY, name),
+                    valueClass,minimumOccurs,maximumOccurs,defaultValue));
+    }
+
+    /**
+     * Add a user defined characteristic.
+     *
+     * @param characteristic
+     * @return added characteristic
+     */
+    public AttributeType addCharacteristic(AttributeType characteristic){
+        //search and remove previous characteristic with the same id if it exist
+        for(AttributeType at : atts){
+            if(at.getName().equals(characteristic.getName())){
+                atts.remove(at);
+                break;
+            }
+        }
+        atts.add(characteristic);
+        return characteristic;
+    }
+
+    /**
+     * Create the attribute type.
+     *
+     * @return AtributeType, never null
+     */
+    public AttributeType build(){
+        return new DefaultAttributeType(parameters, valueClass, 
+                minimumOccurs, maximumOccurs,
+                defaultValue, atts.toArray(new AttributeType[atts.size()]));
+    }
+
+}

Added: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureTypeBuilder.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureTypeBuilder.java?rev=1737814&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureTypeBuilder.java (added)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureTypeBuilder.java Tue Apr  5 09:55:35 2016
@@ -0,0 +1,816 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.feature;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.sis.feature.AbstractOperation;
+import org.apache.sis.feature.DefaultAssociationRole;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.FeatureOperations;
+import static org.apache.sis.internal.feature.AttributeConvention.*;
+import static org.apache.sis.feature.DefaultFeatureType.*;
+import org.apache.sis.internal.system.DefaultFactories;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.FeatureAssociationRole;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.Operation;
+import org.opengis.feature.PropertyType;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.util.GenericName;
+import org.opengis.util.NameFactory;
+import org.opengis.util.ScopedName;
+
+/**
+ * Helper class for the creation of {@link FeatureType} instances.
+ * This builder can create the parameters to be given to {@linkplain DefaultFeatureType feature type constructor}
+ * from simpler parameters given to this builder.
+ * 
+ * @author Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public class FeatureTypeBuilder {
+
+    private final Map<GenericName,PropertyType> properties = new LinkedHashMap<>();
+    private final List<FeatureType> superTypes = new ArrayList<>();
+    private boolean isAbstract = false;
+    private final Map<String,Object> parameters = new HashMap<>();
+
+    //convention ID
+    private String idPrefix = null;
+    private String idSeparator = null;
+    private GenericName[] idAttributes = null;
+    //convention default geometry
+    private GenericName defGeomAttribute = null;
+
+    /**
+     * Reset builder parameters to there original values.
+     */
+    public void reset(){
+        properties.clear();
+        superTypes.clear();
+        isAbstract = false;
+        parameters.clear();
+        idPrefix = null;
+        idSeparator = null;
+        idAttributes = null;
+        defGeomAttribute = null;
+    }
+
+    /**
+     * Copy parameters from the given feature type.
+     * 
+     * @param type feature type to copy parameters from.
+     */
+    public void copy(FeatureType type){
+        reset();
+        setName(type.getName());
+        setDescription(type.getDescription());
+        setDefinition(type.getDefinition());
+        setDesignation(type.getDesignation());
+        setAbstract(type.isAbstract());
+        setSuperTypes(type.getSuperTypes());
+
+        for(PropertyType pt : type.getProperties(false)){
+            addProperty(pt);
+        }
+
+    }
+
+    /**
+     * Set feature type name.
+     *
+     * @param localPart generic name tip part, not null
+     */
+    public void setName(String localPart){
+        setName(create(localPart));
+    }
+
+    /**
+     * Set feature type name.
+     *
+     * @param scope generic name scope part, can be null
+     * @param localPart generic name tip part, not null
+     */
+    public void setName(String scope, String localPart){
+        setName(create(scope,localPart));
+    }
+
+    /**
+     * Set feature type name.
+     *
+     * See {@link #NAME_KEY}
+     *
+     * @param name generic name, not null
+     */
+    public void setName(GenericName name){
+        parameters.put(NAME_KEY, name);
+    }
+
+    /**
+     * Return the current feature type name.
+     *
+     * @return GenericName, can be null
+     */
+    public GenericName getName(){
+        Object val = parameters.get(DefaultFeatureType.NAME_KEY);
+        if(val instanceof GenericName){
+            return (GenericName) val;
+        }else if(val instanceof String){
+            return valueOf((String)val);
+        }
+        return null;
+    }
+
+    /**
+     * Set attribute description.
+     *
+     * See {@link #DESCRIPTION_KEY}
+     *
+     * @param description
+     */
+    public void setDescription(CharSequence description){
+        parameters.put(DESCRIPTION_KEY, description);
+    }
+
+    /**
+     * Set attribute designation.
+     *
+     * See {@link #DESIGNATION_KEY}
+     *
+     * @param designation
+     */
+    public void setDesignation(CharSequence designation){
+        parameters.put(DESIGNATION_KEY, designation);
+    }
+
+    /**
+     * Set attribute definition.
+     *
+     * See {@link #DEFINITION_KEY}
+     *
+     * @param definition
+     */
+    public void setDefinition(CharSequence definition){
+        parameters.put(DEFINITION_KEY, definition);
+    }
+
+    /**
+     * Set feature type abstract.
+     *
+     * @param abstrac
+     */
+    public void setAbstract(boolean abstrac) {
+        this.isAbstract = abstrac;
+    }
+
+    /**
+     * Set parent types.
+     * Feature type will inherit all parent properties.
+     *
+     * @param types not null
+     */
+    public void setSuperTypes(Collection<? extends FeatureType> types){
+        superTypes.clear();
+        superTypes.addAll(types);
+    }
+
+    /**
+     * Set parent types.
+     * Feature type will inherit all parent properties.
+     *
+     * @param types not null
+     */
+    public void setSuperTypes(FeatureType... types){
+        superTypes.clear();
+        superTypes.addAll(Arrays.asList(types));
+    }
+
+    /**
+     * Define an id operation composed of the given attribute.
+     * Generated id prefix will be the name of the featuretype+'.'
+     * 
+     * @param attributeName attribute id used in the id operation
+     */
+    public void setIdOperation(String attributeName){
+        setIdOperation(null, "-", create(attributeName));
+    }
+
+    /**
+     * Define an id operation composed of the given attributes.
+     * Generated id prefix will be the name of the featuretype+'.'
+     *
+     * @param attributes attributes used in the id operation
+     */
+    public void setIdOperation(GenericName ... attributes){
+        setIdOperation(null, "-", attributes);
+    }
+
+    /**
+     * Define an id operation composed of the given attribute.
+     *
+     * @param prefix generated id prefix
+     * @param attributeName attribute id used in the id operation
+     */
+    public void setIdOperation(String prefix, String attributeName){
+        setIdOperation(prefix, "-", create(attributeName));
+    }
+
+    /**
+     * Define an id operation composed of the given attributes.
+     *
+     * @param prefix generated id prefix
+     * @param attributes attributes used in the id operation
+     */
+    public void setIdOperation(String prefix, GenericName ... attributes){
+        setIdOperation(prefix, "-", attributes);
+    }
+
+    /**
+     * Define an id operation composed of the given attributes.
+     *
+     * @param prefix generated id prefix
+     * @param separator generated id separator between attribute values
+     * @param attributes attributes used in the id operation
+     */
+    public void setIdOperation(String prefix, String separator, GenericName ... attributes){
+        idPrefix = prefix;
+        idSeparator = separator;
+        idAttributes = attributes;
+        //add placeholder
+        properties.put(ATTRIBUTE_ID, null);
+    }
+
+    /**
+     * Define a default geometry link operation.
+     * 
+     * @param attribute referenced attribute
+     */
+    public void setDefaultGeometryOperation(String attribute){
+        setDefaultGeometryOperation(create(attribute));
+    }
+
+    /**
+     * Define a default geometry link operation.
+     *
+     * @param scope referenced attribute name scope
+     * @param localPart referenced name tip
+     */
+    public void setDefaultGeometryOperation(String scope, String localPart){
+        setDefaultGeometryOperation(create(scope,localPart));
+    }
+
+    /**
+     * Define a default geometry link operation.
+     * 
+     * @param attribute referenced attribute
+     */
+    public void setDefaultGeometryOperation(GenericName attribute){
+        defGeomAttribute = attribute;
+        //add placeholder
+        properties.put(ATTRIBUTE_DEFAULT_GEOMETRY, null);
+        properties.put(ATTRIBUTE_BOUNDS, null);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     * Property will have a minimum and maximum occurrence of one, no characteristics
+     * and no default value.
+     *
+     * @param localPart property generic name tip part
+     * @param valueClass property value class
+     * @return created property type
+     */
+    public AttributeType addProperty(String localPart, Class valueClass){
+        return addProperty(create(localPart), valueClass);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     * Property will have a minimum and maximum occurrence of one, no characteristics
+     * and no default value.
+     *
+     * @param scope property generic name scope part
+     * @param localPart property generic name tip part
+     * @param valueClass property value class
+     * @return created property type
+     */
+    public AttributeType addProperty(String scope, String localPart, Class valueClass){
+        return addProperty(create(scope, localPart), valueClass);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     * Property will have a minimum and maximum occurrence of one, no characteristics
+     * and no default value.
+     *
+     * @param name property name
+     * @param valueClass property value class
+     * @return created property type
+     */
+    public AttributeType addProperty(GenericName name, Class valueClass){
+        return addProperty(name, valueClass,null);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     * Property will have a minimum and maximum occurrence of one, no default value
+     * and the given {@code CoordinateReferenceSystem} characteristic.
+     *
+     * @param localPart property generic name tip part
+     * @param valueClass property value class
+     * @param crs property {@code CoordinateReferenceSystem} characteristic
+     * @return created property type
+     */
+    public AttributeType addProperty(String localPart, Class valueClass, CoordinateReferenceSystem crs){
+        return addProperty(null, localPart, valueClass,crs);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     * Property will have a minimum and maximum occurrence of one, no default value
+     * and the given {@code CoordinateReferenceSystem} characteristic.
+     *
+     * @param scope property generic name scope part
+     * @param localPart property generic name tip part
+     * @param valueClass property value class
+     * @param crs property {@code CoordinateReferenceSystem} characteristic
+     * @return created property type
+     */
+    public AttributeType addProperty(String scope, String localPart, Class valueClass, CoordinateReferenceSystem crs){
+        return addProperty(create(scope, localPart), valueClass,crs);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     * Property will have a minimum and maximum occurrence of one, no default value
+     * and the given {@code CoordinateReferenceSystem} characteristic.
+     * 
+     * @param name property name
+     * @param valueClass property value class
+     * @param crs property {@code CoordinateReferenceSystem} characteristic
+     * @return created property type
+     */
+    public AttributeType addProperty(GenericName name, Class valueClass, CoordinateReferenceSystem crs){
+        return addProperty(name, valueClass,null,crs);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     * Property will have a minimum and maximum occurrence of one and no characteristics.
+     *
+     * @param scope property generic name scope part
+     * @param localPart property generic name tip part
+     * @param valueClass property value class
+     * @param defaultValue property default value
+     * @return created property type
+     */
+    public AttributeType addProperty(String scope, String localPart, Class valueClass, Object defaultValue){
+        return addProperty(create(scope, localPart), valueClass,defaultValue,null);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     * Property will have a minimum and maximum occurrence of one and the given
+     * {@code CoordinateReferenceSystem} characteristic.
+     *
+     * @param scope property generic name scope part
+     * @param localPart property generic name tip part
+     * @param valueClass property value class
+     * @param defaultValue property default value
+     * @param crs property {@code CoordinateReferenceSystem} characteristic
+     * @return created property type
+     */
+    public AttributeType addProperty(String scope, String localPart, Class valueClass, Object defaultValue, CoordinateReferenceSystem crs){
+        return addProperty(create(scope, localPart), valueClass,1, 1, defaultValue,crs);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     * Property will have a minimum and maximum occurrence of one and no characteristics.
+     *
+     * @param name property name
+     * @param valueClass property value class
+     * @param defaultValue property default value
+     * @return created property type
+     */
+    public AttributeType addProperty(GenericName name, Class valueClass, Object defaultValue){
+        return addProperty(name, valueClass,1, 1, defaultValue);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     * Property will have a minimum and maximum occurrence of one and the given
+     * {@code CoordinateReferenceSystem} characteristic.
+     * 
+     * @param name property name
+     * @param valueClass property value class
+     * @param defaultValue property default value
+     * @param crs property {@code CoordinateReferenceSystem} characteristic
+     * @return created property type
+     */
+    public AttributeType addProperty(GenericName name, Class valueClass, Object defaultValue, CoordinateReferenceSystem crs){
+        return addProperty(name, valueClass,1, 1, defaultValue, crs);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     *
+     *
+     * @param localPart property generic name tip part
+     * @param valueClass property value class
+     * @param minimumOccurs property minimum number of occurrences
+     * @param maximumOccurs property maximum number of occurrences
+     * @param defaultValue property default value
+     * @return created property type
+     */
+    public AttributeType addProperty(String localPart, Class valueClass,
+            int minimumOccurs, int maximumOccurs, Object defaultValue){
+        return addProperty(create(localPart), valueClass,minimumOccurs, maximumOccurs, defaultValue);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     *
+     * @param scope property generic name scope part
+     * @param localPart property generic name tip part
+     * @param valueClass property value class
+     * @param minimumOccurs property minimum number of occurrences
+     * @param maximumOccurs property maximum number of occurrences
+     * @param defaultValue property default value
+     * @return created property type
+     */
+    public AttributeType addProperty(String scope, String localPart, Class valueClass,
+            int minimumOccurs, int maximumOccurs, Object defaultValue){
+        return addProperty(create(scope, localPart), valueClass,minimumOccurs, maximumOccurs, defaultValue);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     *
+     * @param scope property generic name scope part
+     * @param localPart property generic name tip part
+     * @param valueClass property value class
+     * @param minimumOccurs property minimum number of occurrences
+     * @param maximumOccurs property maximum number of occurrences
+     * @param defaultValue property default value
+     * @param crs property {@code CoordinateReferenceSystem} characteristic
+     * @return created property type
+     */
+    public AttributeType addProperty(String scope, String localPart, Class valueClass,
+            int minimumOccurs, int maximumOccurs, Object defaultValue, CoordinateReferenceSystem crs){
+        return addProperty(create(scope, localPart), valueClass,minimumOccurs, maximumOccurs, defaultValue, crs);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     *
+     * @param name property name
+     * @param valueClass property value class
+     * @param minimumOccurs property minimum number of occurrences
+     * @param maximumOccurs property maximum number of occurrences
+     * @param defaultValue property default value
+     * @return created property type
+     */
+    public AttributeType addProperty(GenericName name, Class valueClass, int minimumOccurs,
+            int maximumOccurs, Object defaultValue){
+        return addProperty(name, valueClass,minimumOccurs, maximumOccurs, defaultValue,null);
+    }
+
+    /**
+     * Add a new property to the feature type.
+     * 
+     * @param name property name
+     * @param valueClass property value class
+     * @param minimumOccurs property minimum number of occurrences
+     * @param maximumOccurs property maximum number of occurrences
+     * @param defaultValue property default value
+     * @param crs property {@code CoordinateReferenceSystem} characteristic
+     * @return created property type
+     */
+    public AttributeType addProperty(GenericName name, Class valueClass, int minimumOccurs,
+            int maximumOccurs, Object defaultValue, CoordinateReferenceSystem crs){
+        final AttributeType att;
+        if(crs!=null){
+            final AttributeType qualifier = new DefaultAttributeType(
+                    Collections.singletonMap(NAME_KEY, CHARACTERISTIC_CRS),
+                    CoordinateReferenceSystem.class,1,1,crs);
+            att = new DefaultAttributeType(
+                Collections.singletonMap(NAME_KEY, name),
+                valueClass, minimumOccurs, maximumOccurs, defaultValue, qualifier);
+        }else{
+            att = new DefaultAttributeType(
+                Collections.singletonMap(NAME_KEY, name),
+                valueClass, minimumOccurs, maximumOccurs, defaultValue);
+        }
+
+        addProperty(att);
+        return att;
+    }
+
+    /**
+     * Add a custom property to the feature type.
+     *
+     * @param property user defined property type
+     * @return created property type
+     */
+    public PropertyType addProperty(PropertyType property){
+        properties.put(property.getName(), property);
+        return property;
+    }
+
+    /**
+     * Add a new association to the feature type.
+     * Association will have a minimum occurrence of zero and maximum occurrence of one.
+     *
+     * @param scope property generic name scope part
+     * @param localPart property generic name tip part
+     * @param type associated feature type
+     * @return created association
+     */
+    public FeatureAssociationRole addAssociation(String scope, String localPart, FeatureType type){
+        return addAssociation(scope, localPart, type,0,1);
+    }
+
+    /**
+     * Add a new association to the feature type.
+     * Association will have a minimum occurrence of zero and maximum occurrence of one.
+     *
+     * @param name property name
+     * @param type associated feature type
+     * @return created association
+     */
+    public FeatureAssociationRole addAssociation(GenericName name, FeatureType type){
+        return addAssociation(name, type,0,1);
+    }
+
+    /**
+     * Add a new association to the feature type.
+     *
+     * @param scope property generic name scope part
+     * @param localPart property generic name tip part
+     * @param type associated feature type
+     * @param minimumOccurs property minimum number of occurrences
+     * @param maximumOccurs property maximum number of occurrences
+     * @return created association
+     */
+    public FeatureAssociationRole addAssociation(String scope, String localPart, FeatureType type, int minimumOccurs, int maximumOccurs){
+        return addAssociation(create(scope, localPart), type,minimumOccurs, maximumOccurs);
+    }
+
+    /**
+     * Add a new association to the feature type.
+     *
+     * @param name property name
+     * @param type associated feature type
+     * @param minimumOccurs property minimum number of occurrences
+     * @param maximumOccurs property maximum number of occurrences
+     * @return created association
+     */
+    public FeatureAssociationRole addAssociation(GenericName name, FeatureType type, int minimumOccurs, int maximumOccurs){
+        final FeatureAssociationRole role = new DefaultAssociationRole(
+                Collections.singletonMap(NAME_KEY, name), type, minimumOccurs, maximumOccurs);
+        addProperty(role);
+        return role;
+    }
+
+    /**
+     * Add a custom properties to the feature type.
+     *
+     * @param properties user defined property types
+     */
+    public void addProperties(PropertyType ... properties){
+        for(PropertyType pt : properties){
+            this.properties.put(pt.getName(), pt);
+        }
+    }
+
+    /**
+     * Add a custom properties to the feature type.
+     *
+     * @param properties user defined property types
+     */
+    public void addProperties(Collection<? extends PropertyType> properties){
+        for(PropertyType pt : properties){
+            this.properties.put(pt.getName(), pt);
+        }
+    }
+
+    /**
+     * Remove a property from the feature type.
+     *
+     * @param name property name
+     * @return removed property, can be null if property was not found
+     */
+    public PropertyType removeProperty(String name){
+        return properties.remove(valueOf(name));
+    }
+
+    /**
+     * Remove a property from the feature type.
+     * 
+     * @param name property name
+     * @return removed property, can be null if property was not found
+     */
+    public PropertyType removeProperty(GenericName name){
+        return properties.remove(name);
+    }
+
+    /**
+     * Build feature type
+     *
+     * @return FeatureType
+     */
+    public DefaultFeatureType build() throws IllegalArgumentException{
+        //build id property
+        if(idAttributes!=null){
+            //check id properties exist
+            for(GenericName n : idAttributes){
+                if(!properties.containsKey(n)){
+                    throw new IllegalArgumentException("Property "+n+" used in id does not exist");
+                }
+            }
+
+            String prefix = idPrefix;
+            if(idPrefix==null){
+                prefix = getName().tip().toString();
+            }
+
+            final Operation att = FeatureOperations.aggregate(ATTRIBUTE_ID, prefix, null, idSeparator, idAttributes);
+            properties.put(ATTRIBUTE_ID, att);
+        }
+        //build default geometry property
+        if(defGeomAttribute!=null){
+            if(!properties.containsKey(defGeomAttribute)){
+                throw new IllegalArgumentException("Property "+defGeomAttribute+" used in default geometry does not exist");
+            }
+            final AttributeType geomAtt = (AttributeType)properties.get(defGeomAttribute);
+            final CoordinateReferenceSystem crs = AttributeConvention.getCRSCharacteristic(geomAtt);
+            final Operation att = FeatureOperations.link(ATTRIBUTE_DEFAULT_GEOMETRY, geomAtt);
+            properties.put(ATTRIBUTE_DEFAULT_GEOMETRY, att);
+
+            final Operation boundAtt = FeatureOperations.bounds(ATTRIBUTE_BOUNDS, crs);
+            properties.put(ATTRIBUTE_BOUNDS, boundAtt);
+
+        }
+
+        verifyOperations();
+        
+        return new DefaultFeatureType(
+                parameters,
+                isAbstract,
+                superTypes.toArray(new FeatureType[superTypes.size()]),
+                properties.values().toArray(new PropertyType[properties.size()]));
+    }
+
+    /**
+     * Check operations have the required properties.
+     * 
+     * @throws IllegalArgumentException if some properties are missing for an operation.
+     */
+    private void verifyOperations() throws IllegalArgumentException{
+        
+        for(PropertyType pt : properties.values()){
+            if(pt instanceof AbstractOperation){
+                final Set<String> dependencies = ((AbstractOperation)pt).getDependencies();
+                depLoop:
+                for(String dep : dependencies){
+                    for(GenericName gn : properties.keySet()){
+                        if(match(gn, dep)) continue depLoop;
+                    }
+                    throw new IllegalArgumentException("Operation "+pt.getName().toString()+" requiere property "+dep+" but this property is missing.");
+                }
+                
+            }
+        }
+        
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Name utils //////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////////
+
+
+    private static GenericName create(final String local) {
+        return create(null,local);
+    }
+
+    /**
+     *
+     * @param parsedNames mandatory
+     * @return GenericName
+     */
+    private static GenericName create(final String scope, String localPart) {
+        if(scope==null){
+            return DefaultFactories.forBuildin(NameFactory.class).createGenericName(null, localPart);
+        }else{
+            return DefaultFactories.forBuildin(NameFactory.class).createGenericName(null, scope, localPart);
+        }
+    }
+
+    /**
+     * Parse a string value that can be expressed in 2 different forms :
+     * JSR-283 extended form : {uri}localpart
+     * Separator form : uri:localpart
+     *
+     * if the given string do not match any, then a Name with no namespace will be
+     * created and the localpart will be the given string.
+     *
+     * @param candidate
+     * @return Name
+     */
+    private static GenericName valueOf(final String candidate){
+
+        if(candidate.startsWith("{")){
+            //name is in extended form
+            return toSessionNamespaceFromExtended(candidate);
+        }
+
+        int index = candidate.lastIndexOf(':');
+
+        if(index <= 0){
+            return create(null, candidate);
+        }else{
+            final String uri = candidate.substring(0,index);
+            final String name = candidate.substring(index+1,candidate.length());
+            return create(uri, name);
+        }
+
+    }
+
+    private static GenericName toSessionNamespaceFromExtended(final String candidate) {
+        final int index = candidate.indexOf('}');
+
+        if(index == -1) throw new IllegalArgumentException("Invalide extended form : "+ candidate);
+
+        final String uri = candidate.substring(1, index);
+        final String name = candidate.substring(index+1, candidate.length());
+
+        return create(uri, name);
+    }
+
+    private static String toExpandedString(final GenericName name){
+        String ns = getNamespace(name);
+        if(ns==null){
+            return name.tip().toString();
+        }else{
+            return new StringBuilder("{").append(ns).append('}').append(name.tip().toString()).toString();
+        }
+    }
+
+    /**
+     * Tests that the given string representation matches the given name.
+     * String can be written with only the local part or in extendedform or JCR
+     * extended form.
+     *
+     * @param name
+     * @param candidate
+     * @return true if the string match the name
+     */
+    private static boolean match(final GenericName name, final String candidate){
+        if(candidate.startsWith("{")){
+            //candidate is in extended form
+            return candidate.equals(toExpandedString(name));
+        }
+
+        final int index = candidate.lastIndexOf(':');
+
+        if(index <= 0){
+            return candidate.equals(name.tip().toString());
+        }else{
+            final String uri = candidate.substring(0,index);
+            final String local = candidate.substring(index+1,candidate.length());
+            return uri.equals(getNamespace(name)) && local.equals(name.tip().toString());
+        }
+    }
+
+    private static String getNamespace(GenericName name){
+        return (name instanceof ScopedName) ? ((ScopedName)name).path().toString() : null;
+    }
+
+
+}

Copied: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/AggregateOperationTest.java (from r1737194, sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/LinkOperationTest.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/AggregateOperationTest.java?p2=sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/AggregateOperationTest.java&p1=sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/LinkOperationTest.java&r1=1737194&r2=1737814&rev=1737814&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/LinkOperationTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/AggregateOperationTest.java [UTF-8] Tue Apr  5 09:55:35 2016
@@ -16,57 +16,70 @@
  */
 package org.apache.sis.feature;
 
+import org.apache.sis.internal.feature.FeatureTypeBuilder;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
+import org.apache.sis.util.iso.Names;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
-import static java.util.Collections.singletonMap;
+import org.opengis.feature.PropertyType;
 
 
 /**
- * Tests {@link LinkOperation}.
+ * Tests {@link AggregateOperation}.
  *
- * @author  Martin Desruisseaux (Geomatys)
- * @since   0.6
- * @version 0.6
+ * @author  Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
  * @module
  */
 @DependsOn({
     AbstractOperationTest.class,
     DenseFeatureTest.class
 })
-public final strictfp class LinkOperationTest extends TestCase {
+public final strictfp class AggregateOperationTest extends TestCase {
     /**
-     * Creates a simple feature type with a link operation.
+     * Creates a feature type with an aggregation operation.
      * The feature contains the following properties:
      *
      * <ul>
-     *   <li>{@code city}       as a  {@link String}  (mandatory)</li>
-     *   <li>{@code population} as an {@link Integer} (mandatory)</li>
-     *   <li>{@code name} as a link to the {@code city} attribute.</li>
+     *   <li>{@code name} as a  {@link String}</li>
+     *   <li>{@code age} as an {@link Integer}</li>
+     *   <li>{@code summary} as aggregation of {@code name} and {@code age} attributes.</li>
      * </ul>
      *
      * @return The feature for a city.
      */
-    private static DefaultFeatureType city() {
-        final DefaultFeatureType city = DefaultFeatureTypeTest.city();
-        final LinkOperation link = new LinkOperation(singletonMap(DefaultFeatureType.NAME_KEY, "name"),
-                city.getProperty("city"));
-        return new DefaultFeatureType(singletonMap(DefaultFeatureType.NAME_KEY, "Metropolis"),
-                false, new DefaultFeatureType[] {city}, link);
+    private static DefaultFeatureType person() {
+        //Create type with an aggregation
+        final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
+        ftb.setName("person");
+        final PropertyType nameType = ftb.addProperty("name", String.class);
+        final PropertyType ageType = ftb.addProperty("age", Integer.class);
+        ftb.addProperty(FeatureOperations.aggregate(Names.parseGenericName(null, ":", 
+                "summary"), "prefix:", ":suffix","/",nameType.getName(), ageType.getName()));
+        return ftb.build();
     }
 
     /**
      * Implementation of the test methods.
      */
     private static void run(final AbstractFeature feature) {
-        assertEquals("Get directly",     "Utopia", feature.getPropertyValue("city"));
-        assertEquals("Get through link", "Utopia", feature.getPropertyValue("name"));
-        feature.setPropertyValue("name", "Atlantide");  // Set through link.
-        assertEquals("Get directly",     "Atlantide", feature.getPropertyValue("city"));
-        assertEquals("Get through link", "Atlantide", feature.getPropertyValue("name"));
-        assertSame(feature.getProperty("name"), feature.getProperty("name"));
+
+        //test feature
+        assertEquals("prefix:null/null:suffix", feature.getPropertyValue("summary"));
+        feature.setPropertyValue("name", "marc");
+        assertEquals("prefix:marc/null:suffix", feature.getPropertyValue("summary"));
+        feature.setPropertyValue("age", 21);
+        assertEquals("prefix:marc/21:suffix", feature.getPropertyValue("summary"));
+
+        //test setting value
+        feature.setPropertyValue("summary", "prefix:emile/37:suffix");
+        assertEquals("emile", feature.getPropertyValue("name"));
+        assertEquals(37, feature.getPropertyValue("age"));
+        assertEquals("prefix:emile/37:suffix", feature.getPropertyValue("summary"));
+
     }
 
     /**
@@ -74,7 +87,7 @@ public final strictfp class LinkOperatio
      */
     @Test
     public void testDenseFeature() {
-        run(new DenseFeature(city()));
+        run(new DenseFeature(person()));
     }
 
     /**
@@ -82,6 +95,6 @@ public final strictfp class LinkOperatio
      */
     @Test
     public void testSparseFeature() {
-        run(new SparseFeature(city()));
+        run(new SparseFeature(person()));
     }
 }

Copied: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/BoundsOperationTest.java (from r1737194, sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/LinkOperationTest.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/BoundsOperationTest.java?p2=sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/BoundsOperationTest.java&p1=sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/LinkOperationTest.java&r1=1737194&r2=1737814&rev=1737814&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/LinkOperationTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/BoundsOperationTest.java [UTF-8] Tue Apr  5 09:55:35 2016
@@ -16,65 +16,108 @@
  */
 package org.apache.sis.feature;
 
+import org.apache.sis.internal.feature.FeatureTypeBuilder;
+import com.esri.core.geometry.Point;
+import com.esri.core.geometry.Polygon;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
+import org.apache.sis.util.iso.Names;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
-import static java.util.Collections.singletonMap;
 
 
 /**
- * Tests {@link LinkOperation}.
+ * Tests {@link BoundsOperation}.
  *
- * @author  Martin Desruisseaux (Geomatys)
- * @since   0.6
- * @version 0.6
+ * @author  Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
  * @module
  */
 @DependsOn({
     AbstractOperationTest.class,
     DenseFeatureTest.class
 })
-public final strictfp class LinkOperationTest extends TestCase {
+public final strictfp class BoundsOperationTest extends TestCase {
     /**
-     * Creates a simple feature type with a link operation.
+     * Creates a feature type with a bounds operation.
      * The feature contains the following properties:
      *
      * <ul>
-     *   <li>{@code city}       as a  {@link String}  (mandatory)</li>
-     *   <li>{@code population} as an {@link Integer} (mandatory)</li>
-     *   <li>{@code name} as a link to the {@code city} attribute.</li>
+     *   <li>{@code name} as a  {@link String}</li>
+     *   <li>{@code classes} as a {@link Polygon}</li>
+     *   <li>{@code climbing wall} as a {@link Point}</li>
+     *   <li>{@code gymnasium} as a {@link Polygon}</li>
+     *   <li>{@code bounds} as the feature envelope attribute.</li>
      * </ul>
      *
      * @return The feature for a city.
      */
-    private static DefaultFeatureType city() {
-        final DefaultFeatureType city = DefaultFeatureTypeTest.city();
-        final LinkOperation link = new LinkOperation(singletonMap(DefaultFeatureType.NAME_KEY, "name"),
-                city.getProperty("city"));
-        return new DefaultFeatureType(singletonMap(DefaultFeatureType.NAME_KEY, "Metropolis"),
-                false, new DefaultFeatureType[] {city}, link);
+    private static DefaultFeatureType school() {
+        //Create type with an aggregation
+        final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
+        ftb.setName("school");
+        ftb.addProperty("name", String.class);
+        ftb.addProperty("classes", Polygon.class, CommonCRS.WGS84.geographic());
+        ftb.addProperty("climbing wall", Point.class, CommonCRS.WGS84.geographic());
+        ftb.addProperty("gymnasium", Polygon.class, CommonCRS.WGS84.normalizedGeographic());
+        ftb.addProperty(FeatureOperations.bounds(Names.parseGenericName(null,":","bounds"), CommonCRS.WGS84.geographic()));
+        return ftb.build();
     }
 
     /**
      * Implementation of the test methods.
      */
     private static void run(final AbstractFeature feature) {
-        assertEquals("Get directly",     "Utopia", feature.getPropertyValue("city"));
-        assertEquals("Get through link", "Utopia", feature.getPropertyValue("name"));
-        feature.setPropertyValue("name", "Atlantide");  // Set through link.
-        assertEquals("Get directly",     "Atlantide", feature.getPropertyValue("city"));
-        assertEquals("Get through link", "Atlantide", feature.getPropertyValue("name"));
-        assertSame(feature.getProperty("name"), feature.getProperty("name"));
+        GeneralEnvelope bounds;
+
+        //no geometry set
+        assertNull(feature.getPropertyValue("bounds"));
+
+        //set one geometry
+        Polygon classes = new Polygon();
+        classes.startPath(10, 20);
+        classes.lineTo(10, 30);
+        classes.lineTo(15, 30);
+        classes.lineTo(15, 20);
+        feature.setPropertyValue("classes", classes);
+        bounds = new GeneralEnvelope(CommonCRS.WGS84.geographic());
+        bounds.setRange(0, 10, 15);
+        bounds.setRange(1, 20, 30);
+        assertEquals(bounds,feature.getPropertyValue("bounds"));
+
+        //set second geometry
+        Point wall = new Point(18, 40);
+        feature.setPropertyValue("climbing wall", wall);
+        bounds = new GeneralEnvelope(CommonCRS.WGS84.geographic());
+        bounds.setRange(0, 10, 18);
+        bounds.setRange(1, 20, 40);
+        assertEquals(bounds,feature.getPropertyValue("bounds"));
+
+        //set third geometry, this geometry has crs axis reversed
+        Polygon gymnasium = new Polygon();
+        gymnasium.startPath(-5, -30);
+        gymnasium.lineTo(-6, -30);
+        gymnasium.lineTo(-6, -31);
+        gymnasium.lineTo(-5, -31);
+        feature.setPropertyValue("gymnasium", gymnasium);
+        bounds = new GeneralEnvelope(CommonCRS.WGS84.geographic());
+        bounds.setRange(0, -31, 18);
+        bounds.setRange(1, -6, 40);
+        assertEquals(bounds,feature.getPropertyValue("bounds"));
+
     }
 
+
     /**
      * Tests a dense type with operations.
      */
     @Test
     public void testDenseFeature() {
-        run(new DenseFeature(city()));
+        run(new DenseFeature(school()));
     }
 
     /**
@@ -82,6 +125,6 @@ public final strictfp class LinkOperatio
      */
     @Test
     public void testSparseFeature() {
-        run(new SparseFeature(city()));
+        run(new SparseFeature(school()));
     }
 }

Modified: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/FeaturesTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/FeaturesTest.java?rev=1737814&r1=1737813&r2=1737814&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/FeaturesTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/FeaturesTest.java [UTF-8] Tue Apr  5 09:55:35 2016
@@ -16,19 +16,24 @@
  */
 package org.apache.sis.feature;
 
+import org.apache.sis.internal.feature.FeatureTypeBuilder;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.InvalidPropertyValueException;
 
 
 /**
  * Tests {@link Features}.
  *
  * @author  Martin Desruisseaux (Geomatys)
+ * @author  Johann Sorel (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.7
  * @module
  */
 @DependsOn(SingletonAttributeTest.class)
@@ -68,4 +73,32 @@ public final strictfp class FeaturesTest
             assertTrue(message, message.contains("CharSequence"));
         }
     }
+
+    /**
+     * Tests {@link Features#validate(org.opengis.feature.Feature) }.
+     */
+    @Test
+    public void testValidate(){
+
+        final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
+        ftb.setName("myScope","myName");
+        ftb.addProperty("name", String.class,1,1,null);
+        final FeatureType type = ftb.build();
+
+        final Feature feature = type.newInstance();
+
+        //should not pass validation
+        try{
+            Features.validate(feature);
+            fail("Feature is unvalid, property name is missing, validation should have raised an exception.");
+        }catch(InvalidPropertyValueException ex){
+            //ok
+        }
+
+        //should pass validation
+        feature.setPropertyValue("name", "hubert");
+        Features.validate(feature);
+
+    }
+
 }

Added: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeConventionTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeConventionTest.java?rev=1737814&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeConventionTest.java (added)
+++ sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeConventionTest.java Tue Apr  5 09:55:35 2016
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.feature;
+
+import com.esri.core.geometry.Point;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.test.TestCase;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+/**
+ * Tests {@link AttributeConvention}.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public class AttributeConventionTest extends TestCase {
+
+    /**
+     * Test of isConventionProperty method, of class AttributeConvention.
+     */
+    @Test
+    public void testIsConventionProperty() {
+        final AttributeTypeBuilder atb = new AttributeTypeBuilder();
+        atb.setName("age");
+        atb.setValueClass(Integer.class);
+        assertFalse(AttributeConvention.isConventionProperty(atb.build()));
+
+        atb.reset();
+        atb.setName("http://sis.apache.org/feature","age");
+        atb.setValueClass(Integer.class);
+        assertTrue(AttributeConvention.isConventionProperty(atb.build()));
+
+    }
+
+    /**
+     * Test of isGeometryAttribute method, of class AttributeConvention.
+     */
+    @Test
+    public void testIsGeometryAttribute() {
+        final AttributeTypeBuilder atb = new AttributeTypeBuilder();
+        atb.setName("geometry");
+        atb.setValueClass(Integer.class);
+        assertFalse(AttributeConvention.isGeometryAttribute(atb.build()));
+
+        atb.reset();
+        atb.setName("geometry");
+        atb.setValueClass(Point.class);
+        assertTrue(AttributeConvention.isGeometryAttribute(atb.build()));
+    }
+
+    /**
+     * Test of getCRSCharacteristic method, of class AttributeConvention.
+     */
+    @Test
+    public void testGetCRSCharacteristic() {
+        final AttributeTypeBuilder atb = new AttributeTypeBuilder();
+        atb.setName("geometry");
+        atb.setValueClass(Point.class);
+        assertEquals(null,AttributeConvention.getCRSCharacteristic(atb.build()));
+
+        atb.reset();
+        atb.setName("geometry");
+        atb.setValueClass(Point.class);
+        atb.setCRSCharacteristic(CommonCRS.WGS84.geographic());
+        assertEquals(CommonCRS.WGS84.geographic(),AttributeConvention.getCRSCharacteristic(atb.build()));
+    }
+
+    /**
+     * Test of getLengthCharacteristic method, of class AttributeConvention.
+     */
+    @Test
+    public void testGetLengthCharacteristic() {
+        final AttributeTypeBuilder atb = new AttributeTypeBuilder();
+        atb.setName("name");
+        atb.setValueClass(String.class);
+        assertEquals((Integer)null,AttributeConvention.getLengthCharacteristic(atb.build()));
+
+        atb.reset();
+        atb.setName("name");
+        atb.setValueClass(String.class);
+        atb.setLengthCharacteristic(120);
+        assertEquals((Integer)120,AttributeConvention.getLengthCharacteristic(atb.build()));
+    }
+
+
+}




Mime
View raw message