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;
  }