@Override
 public FeatureResult<PosTaggedTokenWrapper> checkInternal(
     ParseConfigurationWrapper wrapper, RuntimeEnvironment env) {
   ParseConfiguration configuration = wrapper.getParseConfiguration();
   PosTaggedToken resultToken = null;
   FeatureResult<Integer> indexResult = indexFeature.check(configuration, env);
   if (indexResult != null) {
     int index = indexResult.getOutcome();
     if (index >= 0 && index < configuration.getPosTagSequence().size())
       resultToken = configuration.getPosTagSequence().get(index);
   }
   FeatureResult<PosTaggedTokenWrapper> featureResult = null;
   if (resultToken != null) featureResult = this.generateResult(resultToken);
   return featureResult;
 }
  @Override
  public FeatureResult<String> checkInternal(
      PossibleSentenceBoundary context, RuntimeEnvironment env) {
    FeatureResult<String> result = null;

    FeatureResult<Integer> nResult = nFeature.check(context, env);
    if (nResult != null) {
      int n = nResult.getOutcome();

      int endIndex = context.getIndex() + n;
      if (endIndex < context.getText().length()) {
        String nextLetters = context.getText().substring(context.getIndex() + 1, endIndex + 1);
        result = this.generateResult(nextLetters);
      }
    } // have n

    return result;
  }
  @Override
  public FeatureResult<PosTaggedTokenWrapper> checkInternal(
      ParseConfigurationWrapper context, RuntimeEnvironment env) {
    FeatureResult<PosTaggedTokenWrapper> featureResult = null;

    PosTagSequence posTagSequence = context.getParseConfiguration().getPosTagSequence();
    int startIndex = 0;
    int endIndex = posTagSequence.size() - 1;

    if (startIndexFeature != null) {
      FeatureResult<Integer> startIndexResult = startIndexFeature.check(context, env);
      if (startIndexResult != null) {
        startIndex = startIndexResult.getOutcome();
      } else {
        return featureResult;
      }
    }

    if (endIndexFeature != null) {
      FeatureResult<Integer> endIndexResult = endIndexFeature.check(context, env);
      if (endIndexResult != null) {
        endIndex = endIndexResult.getOutcome();
      } else {
        return featureResult;
      }
    }

    if (startIndex < 0) startIndex = 0;

    if (endIndex < 0) endIndex = 0;

    if (startIndex >= posTagSequence.size()) startIndex = posTagSequence.size() - 1;
    if (endIndex >= posTagSequence.size()) endIndex = posTagSequence.size() - 1;

    int step = -1;
    if (endIndex > startIndex) step = 1;

    PosTaggedToken matchingToken = null;

    boolean findFirst = true;
    if (findFirstFeature != null) {
      FeatureResult<Boolean> findFirstResult = this.findFirstFeature.check(context, env);
      if (findFirstResult == null) {
        return null;
      }
      findFirst = findFirstResult.getOutcome();
    }

    ParseConfigurationAddress parseConfigurationAddress = new ParseConfigurationAddress(env);
    parseConfigurationAddress.setParseConfiguration(context.getParseConfiguration());

    int currentSkip = -1;
    for (int i = startIndex;
        (step < 0 && i >= 0 && i >= endIndex)
            || (step > 0 && i < posTagSequence.size() && i <= endIndex);
        i += step) {
      PosTaggedToken oneToken = posTagSequence.get(i);
      parseConfigurationAddress.setPosTaggedToken(oneToken);

      if (currentSkip < 0) {
        FeatureResult<Boolean> criterionResult =
            this.criterion.check(parseConfigurationAddress, env);
        if (criterionResult != null && criterionResult.getOutcome()) {
          matchingToken = oneToken;
          if (findFirst) break;
        }
      }

      boolean endSkip = false;
      if (skipCriteria != null && skipCriteria.length > 0) {
        if (currentSkip < 0) {
          for (int j = 0; j < skipCriteria.length; j += 2) {
            BooleanFeature<PosTaggedTokenWrapper> skipCriterion = skipCriteria[j];
            FeatureResult<Boolean> skipResult = skipCriterion.check(parseConfigurationAddress, env);
            if (skipResult != null && skipResult.getOutcome()) {
              currentSkip = j;
              break;
            }
          }
        } else {
          int j = currentSkip + 1;
          BooleanFeature<PosTaggedTokenWrapper> endSkipCriterion = skipCriteria[j];
          FeatureResult<Boolean> endSkipResult =
              endSkipCriterion.check(parseConfigurationAddress, env);
          if (endSkipResult != null && endSkipResult.getOutcome()) {
            endSkip = true;
          }
        }
      }

      if (currentSkip < 0) {
        if (stopCriterion != null) {
          FeatureResult<Boolean> stopCriterionResult =
              this.stopCriterion.check(parseConfigurationAddress, env);
          if (stopCriterionResult != null && stopCriterionResult.getOutcome()) {
            break;
          }
        }
      }

      if (endSkip) currentSkip = -1;
    }
    if (matchingToken != null) {
      featureResult = this.generateResult(matchingToken);
    }

    return featureResult;
  }
  /* (non-Javadoc)
   * @see com.joliciel.talismane.maxent.MaxentObserver#onAnalyse(java.util.List, java.util.Collection)
   */
  @Override
  public void onAnalyse(
      Object event, List<FeatureResult<?>> featureResults, Collection<Decision<T>> decisions) {
    try {
      Map<String, Double> outcomeTotals = new TreeMap<String, Double>();
      for (String outcome : modelParams.getOutcomes()) outcomeTotals.put(outcome, 0.0);

      writer.append("####### Event: " + event.toString() + "\n");

      writer.append("### Feature results:\n");
      for (FeatureResult<?> featureResult : featureResults) {
        if (featureResult.getOutcome() instanceof List) {
          @SuppressWarnings("unchecked")
          FeatureResult<List<WeightedOutcome<String>>> stringCollectionResult =
              (FeatureResult<List<WeightedOutcome<String>>>) featureResult;
          for (WeightedOutcome<String> stringOutcome : stringCollectionResult.getOutcome()) {
            String featureName =
                featureResult.getTrainingName()
                    + "|"
                    + featureResult.getTrainingOutcome(stringOutcome.getOutcome());
            String featureOutcome = stringOutcome.getOutcome();
            double value = stringOutcome.getWeight();
            this.writeFeatureResult(featureName, featureOutcome, value, outcomeTotals);
          }

        } else {
          double value = 1.0;
          if (featureResult.getFeature() instanceof DoubleFeature) {
            value = (Double) featureResult.getOutcome();
          }
          this.writeFeatureResult(
              featureResult.getTrainingName(),
              featureResult.getOutcome().toString(),
              value,
              outcomeTotals);
        }
      }

      List<Integer> featureIndexList = new ArrayList<Integer>();
      List<Double> featureValueList = new ArrayList<Double>();
      modelParams.prepareData(featureResults, featureIndexList, featureValueList);
      double[] results = decisionMaker.predict(featureIndexList, featureValueList);

      writer.append("### Outcome totals:\n");

      writer.append(
          String.format("%1$-30s", "outcome")
              + String.format("%1$#15s", "total")
              + String.format("%1$#15s", "normalised")
              + "\n");

      int j = 0;
      for (String outcome : modelParams.getOutcomes()) {
        double total = outcomeTotals.get(outcome);
        double normalised = results[j++];
        writer.append(
            String.format("%1$-30s", outcome)
                + String.format("%1$#15s", decFormat.format(total))
                + String.format("%1$#15s", decFormat.format(normalised))
                + "\n");
      }
      writer.append("\n");

      Map<String, Double> outcomeWeights = new TreeMap<String, Double>();
      for (Decision<T> decision : decisions) {
        outcomeWeights.put(decision.getCode(), decision.getProbability());
      }

      writer.append("### Outcome list:\n");
      Set<WeightedOutcome<String>> weightedOutcomes = new TreeSet<WeightedOutcome<String>>();
      for (String outcome : modelParams.getOutcomes()) {
        Double weightObj = outcomeWeights.get(outcome);
        double weight = (weightObj == null ? 0.0 : weightObj.doubleValue());
        WeightedOutcome<String> weightedOutcome = new WeightedOutcome<String>(outcome, weight);
        weightedOutcomes.add(weightedOutcome);
      }
      for (WeightedOutcome<String> weightedOutcome : weightedOutcomes) {
        writer.append(
            String.format("%1$-30s", weightedOutcome.getOutcome())
                + String.format("%1$#15s", decFormat.format(weightedOutcome.getWeight()))
                + "\n");
      }
      writer.append("\n");
      writer.flush();
    } catch (IOException ioe) {
      throw new RuntimeException(ioe);
    }
  }