protected boolean skipTopElement(
     Name topElement, AttributeMapping attMapping, AttributeType type) {
   // don't skip if there's OCQL
   return XPath.equals(topElement, attMapping.getTargetXPath())
       && (attMapping.getSourceExpression() == null
           || Expression.NIL.equals(attMapping.getSourceExpression()));
 }
  public void skipNestedMapping(AttributeMapping attMapping, List<Feature> sources)
      throws IOException {
    if (attMapping instanceof JoiningNestedAttributeMapping) {

      for (Feature source : sources) {
        Object value =
            getValues(attMapping.isMultiValued(), attMapping.getSourceExpression(), source);

        if (value instanceof Collection) {
          for (Object val : (Collection) value) {
            ((JoiningNestedAttributeMapping) attMapping).skip(this, val, getIdValues(source));
          }
        } else {
          ((JoiningNestedAttributeMapping) attMapping).skip(this, value, getIdValues(source));
        }
      }
    }
  }
  protected Feature computeNext() throws IOException {

    String id = getNextFeatureId();
    List<Feature> sources = getSources(id);

    final Name targetNodeName = targetFeature.getName();

    AttributeBuilder builder = new AttributeBuilder(attf);
    builder.setDescriptor(targetFeature);
    Feature target = (Feature) builder.build(id);

    for (AttributeMapping attMapping : selectedMapping) {
      try {
        if (skipTopElement(targetNodeName, attMapping, targetFeature.getType())) {
          // ignore the top level mapping for the Feature itself
          // as it was already set
          continue;
        }
        if (attMapping.isList()) {
          Attribute instance =
              setAttributeValue(
                  target,
                  null,
                  sources.get(0),
                  attMapping,
                  null,
                  null,
                  selectedProperties.get(attMapping));
          if (sources.size() > 1 && instance != null) {
            List<Object> values = new ArrayList<Object>();
            Expression sourceExpr = attMapping.getSourceExpression();
            for (Feature source : sources) {
              values.add(getValue(sourceExpr, source));
            }
            String valueString = StringUtils.join(values.iterator(), " ");
            StepList fullPath = attMapping.getTargetXPath();
            StepList leafPath = fullPath.subList(fullPath.size() - 1, fullPath.size());
            if (instance instanceof ComplexAttributeImpl) {
              // xpath builder will work out the leaf attribute to set values on
              xpathAttributeBuilder.set(
                  instance, leafPath, valueString, null, null, false, sourceExpr);
            } else {
              // simple attributes
              instance.setValue(valueString);
            }
          }
        } else if (attMapping.isMultiValued()) {
          // extract the values from multiple source features of the same id
          // and set them to one built feature
          for (Feature source : sources) {
            setAttributeValue(
                target, null, source, attMapping, null, null, selectedProperties.get(attMapping));
          }
        } else {
          String indexString = attMapping.getSourceIndex();
          // if not specified, get the first row by default
          int index = 0;
          if (indexString != null) {
            if (ComplexFeatureConstants.LAST_INDEX.equals(indexString)) {
              index = sources.size() - 1;
            } else {
              index = Integer.parseInt(indexString);
            }
          }
          setAttributeValue(
              target,
              null,
              sources.get(index),
              attMapping,
              null,
              null,
              selectedProperties.get(attMapping));
          // When a feature is not multi-valued but still has multiple rows with the same ID in
          // a denormalised table, by default app-schema only takes the first row and ignores
          // the rest (see above). The following line is to make sure that the cursors in the
          // 'joining nested mappings'skip any extra rows that were linked to those rows that are
          // being ignored.
          // Otherwise the cursor will stay there in the wrong spot and none of the following
          // feature chaining
          // will work. That can really only occur if the foreign key is not unique for the ID of
          // the parent
          // feature (otherwise all of those rows would be already passed when creating the feature
          // based on
          // the first row). This never really occurs in practice I have noticed, but it is a
          // theoretic
          // possibility, as there is no requirement for the foreign key to be unique per id.
          skipNestedMapping(attMapping, sources.subList(1, sources.size()));
        }
      } catch (Exception e) {
        throw new RuntimeException(
            "Error applying mapping with targetAttribute " + attMapping.getTargetXPath(), e);
      }
    }
    cleanEmptyElements(target);

    return target;
  }
  /**
   * Sets the values of grouping attributes.
   *
   * @param target
   * @param source
   * @param attMapping
   * @param values
   * @return Feature. Target feature sets with simple attributes
   */
  protected Attribute setAttributeValue(
      Attribute target,
      String id,
      final Object source,
      final AttributeMapping attMapping,
      Object values,
      StepList inputXpath,
      List<PropertyName> selectedProperties)
      throws IOException {

    final Expression sourceExpression = attMapping.getSourceExpression();
    final AttributeType targetNodeType = attMapping.getTargetNodeInstance();
    StepList xpath = inputXpath == null ? attMapping.getTargetXPath().clone() : inputXpath;

    Map<Name, Expression> clientPropsMappings = attMapping.getClientProperties();
    boolean isNestedFeature = attMapping.isNestedAttribute();

    if (id == null && Expression.NIL != attMapping.getIdentifierExpression()) {
      id = extractIdForAttribute(attMapping.getIdentifierExpression(), source);
    }
    if (attMapping.isNestedAttribute()) {
      NestedAttributeMapping nestedMapping = ((NestedAttributeMapping) attMapping);
      Object mappingName = nestedMapping.getNestedFeatureType(source);
      if (mappingName != null) {
        if (nestedMapping.isSameSource() && mappingName instanceof Name) {
          // data type polymorphism mapping
          return setPolymorphicValues(
              (Name) mappingName, target, id, nestedMapping, source, xpath, clientPropsMappings);
        } else if (mappingName instanceof String) {
          // referential polymorphism mapping
          return setPolymorphicReference(
              (String) mappingName, clientPropsMappings, target, xpath, targetNodeType);
        }
      } else {
        // polymorphism could result in null, to skip the attribute
        return null;
      }
    }
    if (values == null && source != null) {
      values = getValues(attMapping.isMultiValued(), sourceExpression, source);
    }
    boolean isHRefLink = isByReference(clientPropsMappings, isNestedFeature);
    if (isNestedFeature) {
      if (values == null) {
        // polymorphism use case, if the value doesn't match anything, don't encode
        return null;
      }
      // get built feature based on link value
      if (values instanceof Collection) {
        ArrayList<Attribute> nestedFeatures =
            new ArrayList<Attribute>(((Collection) values).size());
        for (Object val : (Collection) values) {
          if (val instanceof Attribute) {
            val = ((Attribute) val).getValue();
            if (val instanceof Collection) {
              val = ((Collection) val).iterator().next();
            }
            while (val instanceof Attribute) {
              val = ((Attribute) val).getValue();
            }
          }
          if (isHRefLink) {
            // get the input features to avoid infinite loop in case the nested
            // feature type also have a reference back to this type
            // eg. gsml:GeologicUnit/gsml:occurence/gsml:MappedFeature
            // and gsml:MappedFeature/gsml:specification/gsml:GeologicUnit
            nestedFeatures.addAll(
                ((NestedAttributeMapping) attMapping)
                    .getInputFeatures(
                        this,
                        val,
                        getIdValues(source),
                        source,
                        reprojection,
                        selectedProperties,
                        includeMandatory));
          } else {
            nestedFeatures.addAll(
                ((NestedAttributeMapping) attMapping)
                    .getFeatures(
                        this,
                        val,
                        getIdValues(source),
                        reprojection,
                        source,
                        selectedProperties,
                        includeMandatory));
          }
        }
        values = nestedFeatures;
      } else if (isHRefLink) {
        // get the input features to avoid infinite loop in case the nested
        // feature type also have a reference back to this type
        // eg. gsml:GeologicUnit/gsml:occurence/gsml:MappedFeature
        // and gsml:MappedFeature/gsml:specification/gsml:GeologicUnit
        values =
            ((NestedAttributeMapping) attMapping)
                .getInputFeatures(
                    this,
                    values,
                    getIdValues(source),
                    source,
                    reprojection,
                    selectedProperties,
                    includeMandatory);
      } else {
        values =
            ((NestedAttributeMapping) attMapping)
                .getFeatures(
                    this,
                    values,
                    getIdValues(source),
                    reprojection,
                    source,
                    selectedProperties,
                    includeMandatory);
      }
      if (isHRefLink) {
        // only need to set the href link value, not the nested feature properties
        setXlinkReference(target, clientPropsMappings, values, xpath, targetNodeType);
        return null;
      }
    }
    Attribute instance = null;
    if (values instanceof Collection) {
      // nested feature type could have multiple instances as the whole purpose
      // of feature chaining is to cater for multi-valued properties
      for (Object singleVal : (Collection) values) {
        ArrayList valueList = new ArrayList();
        // copy client properties from input features if they're complex features
        // wrapped in app-schema data access
        if (singleVal instanceof Attribute) {
          // copy client properties from input features if they're complex features
          // wrapped in app-schema data access
          Map<Name, Expression> valueProperties = getClientProperties((Attribute) singleVal);
          if (!valueProperties.isEmpty()) {
            clientPropsMappings.putAll(valueProperties);
          }
        }
        if (!isNestedFeature) {
          if (singleVal instanceof Attribute) {
            singleVal = ((Attribute) singleVal).getValue();
          }
          if (singleVal instanceof Collection) {
            valueList.addAll((Collection) singleVal);
          } else {
            valueList.add(singleVal);
          }
        } else {
          valueList.add(singleVal);
        }
        instance =
            xpathAttributeBuilder.set(
                target, xpath, valueList, id, targetNodeType, false, sourceExpression);
        setClientProperties(instance, source, clientPropsMappings);
      }
    } else {
      if (values instanceof Attribute) {
        // copy client properties from input features if they're complex features
        // wrapped in app-schema data access
        Map<Name, Expression> newClientProps = getClientProperties((Attribute) values);
        if (!newClientProps.isEmpty()) {
          newClientProps.putAll(clientPropsMappings);
          clientPropsMappings = newClientProps;
        }
        values = ((Attribute) values).getValue();
      }
      instance =
          xpathAttributeBuilder.set(
              target, xpath, values, id, targetNodeType, false, sourceExpression);
      setClientProperties(instance, source, clientPropsMappings);
    }
    if (instance != null && attMapping.encodeIfEmpty()) {
      instance.getDescriptor().getUserData().put("encodeIfEmpty", attMapping.encodeIfEmpty());
    }
    return instance;
  }