/**
   * Implementation for tests of fid -> fid unmapping.
   *
   * @param idExpression
   * @throws Exception
   */
  private void checkUnrollIdExpression(Expression idExpression) throws Exception {
    AttributeMapping featureMapping = null;
    Name featurePath = mapping.getTargetFeature().getName();
    QName featureName = Types.toQName(featurePath);
    for (Iterator it = mapping.getAttributeMappings().iterator(); it.hasNext(); ) {
      AttributeMapping attMapping = (AttributeMapping) it.next();
      StepList targetXPath = attMapping.getTargetXPath();
      if (targetXPath.size() > 1) {
        continue;
      }
      Step step = (Step) targetXPath.get(0);
      if (featureName.equals(step.getName())) {
        featureMapping = attMapping;
        break;
      }
    }
    featureMapping.setIdentifierExpression(idExpression);
    this.visitor = new UnmappingFilterVisitor(this.mapping);

    // retrieve a single sample feature
    Feature sourceFeature = DataUtilities.first(mapping.getSource().getFeatures());
    String fid = sourceFeature.getIdentifier().toString();
    Id fidFilter = ff.id(Collections.singleton(ff.featureId(fid)));
    Filter unrolled = (Filter) fidFilter.accept(visitor, null);
    assertNotNull(unrolled);
    assertTrue(unrolled instanceof Id);

    FeatureCollection<SimpleFeatureType, SimpleFeature> results =
        mapping.getSource().getFeatures(unrolled);
    assertEquals(1, getCount(results));

    SimpleFeature unmappedFeature = DataUtilities.first(results);

    assertEquals(fid, unmappedFeature.getID());
  }
 private void setIdValues(AttributeMapping mapping, List<Feature> features, List<Object> values) {
   Expression exp = null;
   if (mapping == null || mapping.getIdentifierExpression() == Expression.NIL) {
     // no specific attribute mapping or that idExpression is not mapped
     // use primary key
     exp = CommonFactoryFinder.getFilterFactory(null).property("@id");
   } else {
     exp = mapping.getIdentifierExpression();
   }
   if (exp != null) {
     for (Feature f : features) {
       Object value = getValue(exp, f);
       if (value != null) {
         values.add(value);
       }
     }
   }
 }
 /**
  * Find the source expression if the step is a client property.
  *
  * @param nextRootStep the step
  * @param fMapping feature type mapping to get namespaces from
  * @param mapping attribute mapping
  * @param targetXPath the full target xpath
  * @return
  */
 private Expression getClientPropertyExpression(AttributeMapping mapping, Step lastStep) {
   if (lastStep.isXmlAttribute()) {
     Map<Name, Expression> clientProperties = mapping.getClientProperties();
     QName lastStepQName = lastStep.getName();
     Name lastStepName;
     if (lastStepQName.getPrefix() != null
         && lastStepQName.getPrefix().length() > 0
         && (lastStepQName.getNamespaceURI() == null
             || lastStepQName.getNamespaceURI().length() == 0)) {
       String prefix = lastStepQName.getPrefix();
       String uri = namespaceSupport.getURI(prefix);
       lastStepName = Types.typeName(uri, lastStepQName.getLocalPart());
     } else {
       lastStepName = Types.toTypeName(lastStepQName);
     }
     if (clientProperties.containsKey(lastStepName)) {
       // end NC - added
       return (Expression) clientProperties.get(lastStepName);
     }
   }
   return null;
 }
  private List<Object> getValues(
      int startIndex,
      int endIndex,
      List<Feature> roots,
      FeatureTypeMapping fMapping,
      AttributeMapping prevMapping) {
    List<Object> values = new ArrayList<Object>();

    if (startIndex > fullSteps.size() || endIndex > fullSteps.size()) {
      return values;
    }

    while (startIndex <= endIndex) {
      List<AttributeMapping> attMappings = new ArrayList<AttributeMapping>();
      StepList steps = null;
      if (isLastStep(endIndex)) {
        // exhausted all paths
        return values;
      }

      while (attMappings.isEmpty() && endIndex < fullSteps.size()) {
        endIndex++;
        steps = fullSteps.subList(startIndex, endIndex);
        attMappings = fMapping.getAttributeMappingsIgnoreIndex(steps);

        if (steps.size() == 1) {
          if (Types.equals(fMapping.getTargetFeature().getName(), steps.get(0).getName())
              && !(Types.isSimpleContentType(fMapping.getTargetFeature().getType()))) {
            // skip element type name, but not when it's a simple content
            // like gml:name because it wouldn't have the element type name in the xpath
            startIndex++;
            endIndex = startIndex;
            steps = fullSteps.subList(startIndex, endIndex);
            attMappings = fMapping.getAttributeMappingsIgnoreIndex(steps);
            continue;
          } else if (attMappings.isEmpty() && steps.get(0).isId()) {
            // sometimes there's no explicit attribute mapping for top element name
            // but id should still resolve to primary key by default
            // e.g. gsml:GeologicUnit/@gml:id should resolve even though there's no
            // AttributeMapping for gsml:GeologicUnit
            setIdValues(null, roots, values);
            return values;
          }
        }
      }

      if (attMappings.isEmpty()) {
        // not found here, but might be found in other nodes if multi-valued
        // and polymorphic
        continue;
      }

      for (AttributeMapping mapping : attMappings) {
        if (mapping instanceof NestedAttributeMapping) {
          if (isClientProperty(endIndex)) {
            // check for client properties
            boolean isNestedXlinkHref = isXlinkHref(mapping);
            boolean valueFound = false;
            if (!isNestedXlinkHref) {
              // check if client properties are set in the parent attributeMapping in root mapping
              // file
              valueFound = getClientProperties(mapping, values, roots);
            }
            if (!valueFound) {
              // or if they're set in the attributeMapping in feature chained mapping file
              getNestedClientProperties(
                  (NestedAttributeMapping) mapping, roots, values, isNestedXlinkHref);
            }
          } else {
            boolean isSimpleContent =
                Types.isSimpleContent(steps, fMapping.getTargetFeature().getType());
            // if simple content, then it doesn't need to increment the next starting
            // index
            // since there will be no type name in the xpath, e.g. when gml:name is
            // feature
            // chained
            // the path stays as gml:name.. but if it's a complex type with complex
            // content,
            // e.g. gsml:specification
            // the path will be gsml:specification/gsml:GeologicUnit/<some leaf
            // attribute to
            // filter by>
            getNestedValues(
                (NestedAttributeMapping) mapping,
                roots,
                values,
                isSimpleContent ? startIndex : startIndex + 1);
          }
        } else {
          // normal attribute mapping
          if (endIndex == fullSteps.size()) {
            Expression exp = mapping.getSourceExpression();
            for (Feature f : roots) {
              Object value = getValue(exp, f);
              if (value != null) {
                values.add(value);
              }
            }
          } else if (isClientProperty(endIndex)) {
            // peek at the next attribute to check for client properties
            if (getLastStep().isId()) {
              setIdValues(mapping, roots, values);
            } else {
              getClientProperties(mapping, values, roots);
            }
          } else {
            // increment the xpath
            List<Object> nestedValues = getValues(startIndex, endIndex, roots, fMapping, mapping);
            if (nestedValues != null) {
              values.addAll(nestedValues);
            }
          }
        }
      }

      return values;
    }
    return values;
  }