ElementObservableList.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.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 }