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