/**
  * Sets the sorter that the output of the filter pipeline is piped through. This is the sorter
  * that is installed interactively on a view by a user action.
  *
  * <p>This method is responsible for doing all the bookkeeping to assign/cleanup pipeline/adapter
  * assignments.
  *
  * @param sorter the interactive sorter, if any; null otherwise.
  */
 protected void setSorter(Sorter sorter) {
   Sorter oldSorter = getSorter();
   if (oldSorter == sorter) return;
   if (oldSorter != null) {
     oldSorter.assign((FilterPipeline) null);
   }
   this.sorter = sorter;
   if (sorter != null) {
     sorter.assign((FilterPipeline) null);
     sorter.assign(this);
     if (adapter != null) {
       sorter.assign(adapter);
       sorter.refresh();
     }
   }
   if ((sorter == null) && isAssigned()) {
     fireContentsChanged();
   }
 }
 /**
  * Called when the specified filter has changed. Cascades <b><code>filterChanged</code></b>
  * notifications to the next filter in the pipeline after the specified filter. If the specified
  * filter is the last filter in the pipeline, this method broadcasts a <b><code>filterChanged
  * </code></b> notification to all <b><code>PipelineListener</code></b> objects registered with
  * this pipeline.
  *
  * @param filter a filter in this pipeline that has changed in any way
  */
 protected void filterChanged(Filter filter) {
   // JW: quick partial fix for #370-swingx: don't
   // fire if there are no active filters/and no sorter.
   // can't do anything if we have filters/sorters
   // because we have no notion to turn "auto-flush on model update" off
   // (Mustang does - and has it off by default)
   // JW: reverted - wrong place, depends on a specific
   // sortController implementation and "ripples"
   // (JXList selection not correctly updated)
   //        if ((filter instanceof IdentityFilter) && (getSorter() == null)) return;
   Filter next = next(filter);
   if (next == null) {
     // prepared for additional event type
     //            if (filter == getSorter()) {
     //                fireSortOrderChanged();
     //            }
     fireContentsChanged();
   } else {
     next.refresh(); // Cascade to next filter
   }
 }