/**
   * Decide whether the current title record should be displayed, based on whether it is a duplicate
   * of the previous record, given the combination of output fields included in the ordering. If
   * range fields are included in the output, or if identifying properties change between titles,
   * the title will be shown.
   *
   * <p>If there are neither range fields nor id fields in the output, we can't tell if it is a
   * duplicate so we show it anyway. Output with no id fields is pretty meaningless though.
   *
   * <p>Note that the method also sets the last output title if the response is true. Thus calling
   * this method multiple times on the same title will give false after the first (unless there are
   * no identifying fields).
   *
   * @param currentTitle the current title
   * @return whether to show currentTitle
   */
  public boolean isTitleForOutput(KbartTitle currentTitle) {
    // The approach is to trueify this variable. If it becomes true, at the
    // end of the method the lastOutputTitle is set before the result is
    // returned. Do not return early!
    boolean isOutput = false;
    // Show title if it has no range or id fields by which we can decide duplicates
    if (!rangeFieldsIncludedInDisplay && !idFieldsIncludedInDisplay) {
      isOutput = true;
    }

    // Show the title if it is the first or the output includes range fields
    if (lastOutputTitle == null || rangeFieldsIncludedInDisplay) {
      isOutput = true;
    } else if (!isOutput) { // don't do this check if we've already trueified the var
      // At this point there are no range fields and this is not the first title
      // Show the title if any visible idField differs between titles
      for (Field f : idFields) {
        if (visibleColumnOrdering.getFields().contains(f)
            && !lastOutputTitle.getField(f).equals(currentTitle.getField(f))) {
          isOutput = true;
          break;
        }
      }
    }
    // Finally, we refuse to output the title if it has no id and
    // excludeNoIdTitles is true.
    if (excludeNoIdTitles && StringUtil.isNullString(currentTitle.getField(Field.TITLE_ID)))
      isOutput = false;

    // Record the previous title
    if (isOutput) lastOutputTitle = currentTitle;
    return isOutput;
  }
 /**
  * Get a list of strings representing the labels for display columns. This will include all the
  * labels of visible fields, plus any extra columns such as health rating.
  *
  * @param scope the scope of the export
  * @return a list of labels for the exported columns of data
  */
 public List<String> getColumnLabels(ContentScope scope) {
   List<String> l = visibleColumnOrdering.getOrderedLabels();
   // Health rating is only added if showHealthRatings is true and the scope
   // is not ALL - neither of these should be true when processing external
   // data.
   if (showHealthRatings && scope != ContentScope.ALL) l.add(HEALTH_FIELD_LABEL);
   return l;
 }
 public void sortTitlesByFirstTwoFields() {
   // Sort on just the first 2 field columns (max):
   StringBuilder sb = new StringBuilder("Sort by ");
   List<Field> fields = visibleColumnOrdering.getOrderedFields();
   sb.append(fields.get(0));
   if (fields.size() > 1) sb.append(" | ").append(fields.get(1));
   log.debug(sb.toString());
   sortTitlesByFields(fields.subList(0, Math.min(2, fields.size())));
 }
 /**
  * An alternative constructor that allows one to specify whether or not to show columns which are
  * entirely empty. If <code>omitEmptyFields</code> is true, the <code>emptyFields</code> set is
  * filled with the names of the empty fields. This is an expensive operation which requires
  * iteration over potentially all of the titles, so is performed here at construction.
  *
  * <p>Due to this processing, it is not possible to accept an iterator instead of a list, which
  * would be less memory intensive.
  *
  * @param titles the titles to be exported
  * @param ordering an ordering to impose on the fields of each title
  * @param omitEmptyFields whether to omit empty field columns from the output
  */
 public KbartExportFilter(
     List<KbartTitle> titles,
     ColumnOrdering ordering,
     boolean omitEmptyFields,
     boolean omitHeader,
     boolean excludeNoIdTitles,
     boolean showHealthRatings) {
   this.titles = titles;
   this.columnOrdering = ordering;
   this.omitEmptyFields = omitEmptyFields;
   this.omitHeaderRow = omitHeader;
   this.excludeNoIdTitles = excludeNoIdTitles;
   this.showHealthRatings = showHealthRatings;
   // Work out the list of empty fields if necessary
   this.emptyFields = omitEmptyFields ? findEmptyFields() : EnumSet.noneOf(Field.class);
   // Create a list of the visible (non-omitted) fields out of the supplied ordering
   this.visibleColumnOrdering =
       CustomColumnOrdering.copy(columnOrdering).removeFields(emptyFields);
   this.rangeFieldsIncludedInDisplay =
       !CollectionUtil.isDisjoint(visibleColumnOrdering.getFields(), rangeFields);
   this.idFieldsIncludedInDisplay =
       !CollectionUtil.isDisjoint(visibleColumnOrdering.getFields(), idFields);
 }
 /**
  * Create a custom column ordering from the columns in another column ordering.
  *
  * @param other
  * @return
  */
 public static CustomColumnOrdering copy(ColumnOrdering other) {
   return createUnchecked(other.getOrderedLabels());
 }
 /**
  * Whether fields were omitted manually. That is, the specified field ordering is shorter than the
  * available number of fields.
  *
  * @return whether empty fields were omitted.
  */
 public boolean omittedFieldsManually() {
   return columnOrdering.getFields().size() < Field.values().length;
 }
 /**
  * Get the set of fields from the filter's field ordering which were omitted due to being empty.
  *
  * @return the set of empty fields which were omitted from the ordering
  */
 public Set<Field> getOmittedEmptyFields() {
   Set<Field> empties = EnumSet.copyOf(columnOrdering.getFields());
   empties.retainAll(emptyFields);
   return empties;
 }