1 /*
   2  * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javafx.collections.transformation;
  26 
  27 import javafx.collections.ListChangeListener.Change;
  28 import java.util.List;
  29 import javafx.collections.ListChangeListener;
  30 import javafx.collections.ObservableList;
  31 import javafx.collections.ObservableListBase;
  32 import javafx.collections.WeakListChangeListener;
  33 
  34 /**
  35  * A base class for all lists that wrap another list in a way that changes
  36  * (transforms) the wrapped list's elements, order, size, or structure.
  37  *
  38  * If the source list is observable, a listener is automatically added to it
  39  * and the events are delegated to {@link #sourceChanged(javafx.collections.ListChangeListener.Change)}
  40  *
  41  * @param <E> the type parameter of this list
  42  * @param <F> the upper bound of the type of the source list
  43  * @since JavaFX 8.0
  44  */
  45 public abstract class TransformationList<E, F> extends ObservableListBase<E> implements ObservableList<E> {
  46 
  47     /**
  48      * Contains the source list of this transformation list.
  49      * This is never null and should be used to directly access source list content
  50      */
  51     private ObservableList<? extends F> source;
  52     /**
  53      * This field contains the result of expression "source instanceof {@link javafx.collections.ObservableList}".
  54      * If this is true, it is possible to do transforms online.
  55      */
  56     private ListChangeListener<F> sourceListener;
  57 
  58     /**
  59      * Creates a new Transformation list wrapped around the source list.
  60      * @param source the wrapped list
  61      */
  62     @SuppressWarnings("unchecked")
  63     protected TransformationList(ObservableList<? extends F> source) {
  64         if (source == null) {
  65             throw new NullPointerException();
  66         }
  67         this.source = source;
  68         source.addListener(new WeakListChangeListener<>(getListener()));
  69     }
  70 
  71     /**
  72      * The source list specified in the constructor of this transformation list.
  73      * @return The List that is directly wrapped by this TransformationList
  74      */
  75     public final ObservableList<? extends F> getSource() {
  76         return source;
  77     }
  78 
  79     /**
  80      * Checks whether the provided list is in the chain under this
  81      * {@code TransformationList}.
  82      *
  83      * This means the list is either the direct source as returned by
  84      * {@link #getSource()} or the direct source is a {@code TransformationList},
  85      * and the list is in it's transformation chain.
  86      * @param list the list to check
  87      * @return true if the list is in the transformation chain as specified above.
  88      */
  89     public final boolean isInTransformationChain(ObservableList<?> list) {
  90         if (source == list) {
  91             return true;
  92         }
  93         List<?> currentSource = source;
  94         while(currentSource instanceof TransformationList) {
  95             currentSource = ((TransformationList)currentSource).source;
  96             if (currentSource == list) {
  97                 return true;
  98             }
  99         }
 100         return false;
 101     }
 102 
 103     private ListChangeListener<F> getListener() {
 104         if (sourceListener == null) {
 105             sourceListener = c -> {
 106                 TransformationList.this.sourceChanged(c);
 107             };
 108         }
 109         return sourceListener;
 110     }
 111 
 112     /**
 113      * Called when a change from the source is triggered.
 114      * @param c the change
 115      */
 116     protected abstract void sourceChanged(Change<? extends F> c);
 117 
 118     /**
 119      * Maps the index of this list's element to an index in the direct source list.
 120      * @param index the index in this list
 121      * @return the index of the element's origin in the source list
 122      * @see #getSource()
 123      */
 124     public abstract int getSourceIndex(int index);
 125 
 126     /**
 127      * Maps the index of this list's element to an index of the provided {@code list}.
 128      *
 129      * The {@code list} must be in the transformation chain.
 130      *
 131      * @param list a list from the transformation chain
 132      * @param index the index of an element in this list
 133      * @return the index of the element's origin in the provided list
 134      * @see #isInTransformationChain(javafx.collections.ObservableList)
 135      */
 136     public final int getSourceIndexFor(ObservableList<?> list, int index) {
 137         if (!isInTransformationChain(list)) {
 138             throw new IllegalArgumentException("Provided list is not in the transformation chain of this"
 139                     + "transformation list");
 140         }
 141         List<?> currentSource = source;
 142         int idx = getSourceIndex(index);
 143         while(currentSource != list && currentSource instanceof TransformationList) {
 144             final TransformationList tSource = (TransformationList)currentSource;
 145             idx = tSource.getSourceIndex(idx);
 146             currentSource = tSource.source;
 147         }
 148         return idx;
 149     }
 150 
 151     /**
 152      * Maps the index of the direct source list's element to an index in this list.
 153      * @param index the index in the source list
 154      * @return the index of the element in this list if it is contained
 155      * in this list or negative value otherwise
 156      * @see #getSource()
 157      * @see #getSourceIndex(int)
 158      *
 159      * @since 9
 160      */
 161     public abstract int getViewIndex(int index);
 162 }