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() - 1 : 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 }
|