MappingObservableList.java
001 /*
002  * Copyright 2008-2017 the original author or authors.
003  *
004  * Licensed under the Apache License, Version 2.0 (the "License");
005  * you may not use this file except in compliance with the License.
006  * You may obtain a copy of the License at
007  *
008  *     http://www.apache.org/licenses/LICENSE-2.0
009  *
010  * Unless required by applicable law or agreed to in writing, software
011  * distributed under the License is distributed on an "AS IS" BASIS,
012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013  * See the License for the specific language governing permissions and
014  * limitations under the License.
015  */
016 package griffon.javafx.collections;
017 
018 import javafx.beans.value.ObservableValue;
019 import javafx.collections.ListChangeListener;
020 import javafx.collections.ObservableList;
021 import javafx.collections.transformation.TransformationList;
022 
023 import javax.annotation.Nonnull;
024 import java.util.ArrayList;
025 import java.util.Arrays;
026 import java.util.List;
027 import java.util.function.Function;
028 
029 import static java.util.Arrays.asList;
030 import static java.util.Objects.requireNonNull;
031 
032 /**
033  @author Andres Almiray
034  @since 2.9.0
035  */
036 public class MappingObservableList<T, S> extends TransformationList<T, S> {
037     private static final String ERROR_MAPPER_NULL = "Argument 'mapper' must not be null";
038     private static final String ERROR_SOURCE_NULL = "Argument 'source' must not be null";
039 
040     private T[] elements;
041     private Function<S, T> mapper;
042     private ObservableValue<Function<S, T>> observableMapper;
043 
044     public MappingObservableList(@Nonnull ObservableList<? extends S> source, @Nonnull Function<S, T> mapper) {
045         super(requireNonNull(source, ERROR_SOURCE_NULL));
046         this.mapper = requireNonNull(mapper, ERROR_MAPPER_NULL);
047         int size = source.size();
048         this.elements = (T[]) new Object[size];
049         for (int i = 0; i < size; ++i) {
050             this.elements[i= mapper.apply(source.get(i));
051         }
052     }
053 
054     public MappingObservableList(@Nonnull ObservableList<? extends S> source, @Nonnull ObservableValue<Function<S, T>> mapper) {
055         super(requireNonNull(source, ERROR_SOURCE_NULL));
056         this.observableMapper = requireNonNull(mapper, ERROR_MAPPER_NULL);
057         int size = source.size();
058         this.elements = (T[]) new Object[size];
059         Function<S, T> function = resolveMapper();
060 
061         for (int i = 0; i < size; ++i) {
062             this.elements[i= function.apply(source.get(i));
063         }
064 
065         mapper.addListener((v, o, n-> updateAll());
066     }
067 
068     @Nonnull
069     protected Function<S, T> resolveMapper() {
070         Function<S, T> function = observableMapper != null ? observableMapper.getValue() : mapper;
071         return requireNonNull(function, ERROR_MAPPER_NULL);
072     }
073 
074     @Override
075     public int getSourceIndex(int index) {
076         return index;
077     }
078 
079     @Override
080     public T get(int index) {
081         return elements[index];
082     }
083 
084     @Override
085     public int size() {
086         return getSource().size();
087     }
088 
089     @Override
090     protected void sourceChanged(ListChangeListener.Change<? extends S> c) {
091         beginChange();
092         while (c.next()) {
093             if (c.wasPermutated()) {
094                 permutate(c);
095             else if (c.wasReplaced()) {
096                 replace(c);
097             else if (c.wasUpdated()) {
098                 update(c);
099             else if (c.wasAdded()) {
100                 add(c);
101             else if (c.wasRemoved()) {
102                 remove(c);
103             }
104         }
105         endChange();
106     }
107 
108     private void permutate(ListChangeListener.Change<? extends S> c) {
109         int from = c.getFrom();
110         int to = c.getTo();
111         int[] perms = new int[from - to];
112         Function<S, T> function = resolveMapper();
113 
114         for (int i = from, j = 0; i < to; i++) {
115             perms[j++= c.getPermutation(i);
116             elements[i= function.apply(c.getList().get(i));
117         }
118         nextPermutation(from, to, perms);
119     }
120 
121     private void replace(ListChangeListener.Change<? extends S> c) {
122         int from = c.getFrom();
123         int to = c.getTo();
124         List<T> removed = new ArrayList<>();
125         Function<S, T> function = resolveMapper();
126 
127         for (int i = from; i < to; i++) {
128             elements[i= function.apply(c.getList().get(i));
129             removed.add(elements[i]);
130         }
131         nextReplace(from, to, removed);
132     }
133 
134     private void update(ListChangeListener.Change<? extends S> c) {
135         int from = c.getFrom();
136         int to = c.getTo();
137         Function<S, T> function = resolveMapper();
138 
139         for (int i = from; i < to; i++) {
140             elements[i= function.apply(c.getList().get(i));
141             nextUpdate(i);
142         }
143     }
144 
145     private void add(ListChangeListener.Change<? extends S> c) {
146         int from = 0;
147         int to = c.getAddedSize();
148         int offset = elements.length;
149         T[] tmp = Arrays.copyOf(elements, offset + to);
150         Function<S, T> function = resolveMapper();
151 
152         for (int i = 0; i < to; ++i) {
153             tmp[offset + i= function.apply(c.getAddedSubList().get(i));
154         }
155 
156         elements = tmp;
157         nextAdd(offset + from, offset + to);
158     }
159 
160     private void remove(ListChangeListener.Change<? extends S> c) {
161         int from = c.getFrom();
162         int size = elements.length - c.getRemovedSize();
163         int to = c.getTo();
164         to = to == from ? from + c.getRemovedSize() : to;
165         List<T> removed = new ArrayList<>();
166         T[] tmp = (T[]) new Object[size];
167 
168         for (int i = 0, j = 0; i < elements.length; i++) {
169             if (i < from || i > to) {
170                 tmp[j++= elements[i];
171             else {
172                 removed.add(elements[i]);
173             }
174         }
175 
176         elements = tmp;
177         nextRemove(from, removed);
178     }
179 
180     private void updateAll() {
181         Function<S, T> function = resolveMapper();
182         // defensive copying
183         List<S> copy = new ArrayList<>(getSource());
184         List<T> removed = asList(elements);
185         T[] tmp = (T[]) new Object[removed.size()];
186 
187         beginChange();
188         for (int i = 0; i < removed.size(); i++) {
189             tmp[i= function.apply(copy.get(i));
190         }
191         elements = tmp;
192         nextReplace(0, elements.length, removed);
193         endChange();
194     }
195 }