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.property.Property;
019 import javafx.beans.value.ChangeListener;
020 import javafx.collections.FXCollections;
021 import javafx.collections.ListChangeListener;
022 import javafx.collections.ObservableList;
023
024 import javax.annotation.Nonnull;
025 import java.util.ArrayList;
026 import java.util.Collections;
027 import java.util.LinkedHashMap;
028 import java.util.List;
029 import java.util.Map;
030
031 /**
032 * @author Andres Almiray
033 * @since 2.10.0
034 */
035 public class ElementObservableList<E extends ElementObservableList.PropertyContainer> extends DelegatingObservableList<E> {
036 public interface PropertyContainer {
037 Property<?>[] properties();
038 }
039
040 private final Map<E, List<ListenerSubscription>> subscriptions = new LinkedHashMap<>();
041
042 public ElementObservableList() {
043 this(FXCollections.observableArrayList());
044 }
045
046 public ElementObservableList(@Nonnull ObservableList<E> delegate) {
047 super(delegate);
048 }
049
050 @Override
051 protected void sourceChanged(@Nonnull ListChangeListener.Change<? extends E> c) {
052 while (c.next()) {
053 if (c.wasAdded()) {
054 c.getAddedSubList().forEach(this::registerListeners);
055 } else if (c.wasRemoved()) {
056 c.getRemoved().forEach(this::unregisterListeners);
057 }
058 }
059 fireChange(c);
060 }
061
062 private void registerListeners(@Nonnull E contact) {
063 if (subscriptions.containsKey(contact)) {
064 return;
065 }
066
067 List<ListenerSubscription> elementSubscriptions = new ArrayList<>();
068 for (Property<?> property : contact.properties()) {
069 elementSubscriptions.add(createChangeListener(contact, property));
070 }
071 subscriptions.put(contact, elementSubscriptions);
072 }
073
074 @Nonnull
075 @SuppressWarnings("unchecked")
076 private ListenerSubscription createChangeListener(@Nonnull final E contact, @Nonnull final Property<?> property) {
077 final ChangeListener listener = (observable, oldValue, newValue) -> fireChange(changeFor(contact));
078 property.addListener(listener);
079 return () -> property.removeListener(listener);
080 }
081
082 @Nonnull
083 private ListChangeListener.Change<? extends E> changeFor(@Nonnull final E contact) {
084 final int position = indexOf(contact);
085 final int[] permutations = new int[0];
086
087 return new ListChangeListener.Change<E>(this) {
088 private boolean invalid = true;
089
090 @Override
091 public boolean next() {
092 if (invalid) {
093 invalid = false;
094 return true;
095 }
096 return false;
097 }
098
099 @Override
100 public void reset() {
101 invalid = true;
102 }
103
104 @Override
105 public int getFrom() {
106 return position;
107 }
108
109 @Override
110 public int getTo() {
111 return position + 1;
112 }
113
114 @Override
115 public List<E> getRemoved() {
116 return Collections.emptyList();
117 }
118
119 @Override
120 protected int[] getPermutation() {
121 return permutations;
122 }
123
124 @Override
125 public boolean wasUpdated() {
126 return true;
127 }
128 };
129 }
130
131 private void unregisterListeners(@Nonnull E contact) {
132 List<ListenerSubscription> registeredSubscriptions = subscriptions.remove(contact);
133 if (registeredSubscriptions != null) {
134 registeredSubscriptions.forEach(ListenerSubscription::unsubscribe);
135 }
136 }
137
138
139 private interface ListenerSubscription {
140 void unsubscribe();
141 }
142 }
|