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