/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * *** * * Community License: GPL 3.0 * * This file is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * *** * * Available Commercial License: GraniteDS SLA 1.0 * * This is the appropriate option if you are creating proprietary * applications and you are not prepared to distribute and share the * source code of your application under the GPL v3 license. * * Please visit http://www.granitedataservices.com/license for more * details. */ package org.granite.client.javafx.tide.collections; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javafx.beans.InvalidationListener; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyIntegerProperty; import javafx.beans.property.ReadOnlyIntegerWrapper; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.MapChangeListener; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; import javax.annotation.PreDestroy; import javax.inject.Named; import org.granite.client.javafx.util.ListListenerHelper; import org.granite.client.javafx.util.ListenerHelper; import org.granite.client.tide.collection.AbstractPagedCollection; import org.granite.client.tide.collection.PageFilterFinder; import org.granite.client.tide.collection.SimpleFilterFinder; import org.granite.client.tide.server.Component; import org.granite.client.tide.server.ServerSession; import org.granite.client.tide.server.TideResultEvent; import org.granite.client.tide.server.TideRpcEvent; /** * @author William DRAI */ @Named public class PagedQuery<E, F> extends AbstractPagedCollection<E, F> implements ObservableList<E> { private List<E> internalWrappedList = new ArrayList<E>(); protected ObservableList<E> wrappedList; private ListenerHelper<PageChangeListener<E, F>> pageChangeHelper = new ListenerHelper<PageChangeListener<E, F>>(PageChangeListener.class); private Map<String, Object> internalFilterMap; private ObservableMap<String, Object> filterMap; private ObjectProperty<F> filter; private final ReadOnlyIntegerWrapper count = new ReadOnlyIntegerWrapper(this, "count", 0); protected PagedQuery() { // CDI proxying... } public PagedQuery(ServerSession serverSession) { super(serverSession); initWrappedList(); } public PagedQuery(Component remoteComponent, String methodName, int maxResults) { super(remoteComponent, methodName, maxResults); initWrappedList(); } public PagedQuery(Component remoteComponent, PageFilterFinder<E> finder, int maxResults) { super(remoteComponent, finder, maxResults); initWrappedList(); } public PagedQuery(Component remoteComponent, SimpleFilterFinder<E> finder, int maxResults) { super(remoteComponent, finder, maxResults); initWrappedList(); } @Override protected List<E> getInternalWrappedList() { return internalWrappedList; } @Override protected List<E> getWrappedList() { return wrappedList; } private void initWrappedList() { wrappedList = FXCollections.observableList(internalWrappedList); wrappedList.addListener(new WrappedListListChangeListener()); } public ReadOnlyIntegerProperty countProperty() { return count.getReadOnlyProperty(); } @Override protected void updateCount(int cnt) { super.updateCount(cnt); count.set(cnt); } @Override protected void initFilter() { this.internalFilterMap = new HashMap<String, Object>(); this.filterMap = FXCollections.observableMap(Collections.synchronizedMap(internalFilterMap)); this.filterMap.addListener(new MapChangeListener<String, Object>() { @Override public void onChanged(MapChangeListener.Change<? extends String, ?> change) { fullRefresh = true; filterRefresh = true; } }); this.filter = new SimpleObjectProperty<F>(this, "filter"); } public ObjectProperty<F> filterProperty() { return filter; } @SuppressWarnings("unchecked") public F getFilter() { if (filter.get() != null) return filter.get(); try { return (F)filterMap; } catch (ClassCastException e) { return null; } } public void setFilter(F filter) { if (filter == null) internalFilterMap.clear(); else this.filter.set(filter); } @SuppressWarnings("unchecked") @Override protected F cloneFilter() { if (this.filter.get() != null) return this.filter.get(); else { // Copy filter map to avoid concurrent modifications synchronized (internalFilterMap) { return (F)new HashMap<String, Object>(internalFilterMap); } } } @Override public boolean setAll(Collection<? extends E> coll) { if (!initializing) return fullRefresh(); return false; } @Override @PreDestroy public void clear() { super.clear(); helper.clear(); pageChangeHelper.clear(); } private ListListenerHelper<E> helper = new ListListenerHelper<E>(); public void addListener(ListChangeListener<? super E> listener) { helper.addListener(listener); } public void removeListener(ListChangeListener<? super E> listener) { helper.removeListener(listener); } public void addListener(InvalidationListener listener) { helper.addListener(listener); } public void removeListener(InvalidationListener listener) { helper.removeListener(listener); } public void addListener(PageChangeListener<E, F> listener) { pageChangeHelper.addListener(listener); } public void removeListener(PageChangeListener<E, F> listener) { pageChangeHelper.removeListener(listener); } public void firePageChange(TideRpcEvent event, int previousFirst, int previousLast, List<E> savedSnapshot) { if (event instanceof TideResultEvent<?>) fireItemsUpdated(0, Math.min(super.count, this.last)-this.first, previousFirst, previousLast, savedSnapshot); pageChangeHelper.fireEvent(this, event); } public class WrappedListListChangeListener implements ListChangeListener<E> { @Override public void onChanged(ListChangeListener.Change<? extends E> change) { ListChangeWrapper wrappedChange = new ListChangeWrapper(wrappedList, change); helper.fireValueChangedEvent(wrappedChange); } } public void fireItemsUpdated(final int from, final int to, int previousFrom, int previousTo, final List<E> savedSnapshot) { if (savedSnapshot != null) { // Detect removals final List<Integer> removals = new ArrayList<Integer>(); for (int i = 0; i < savedSnapshot.size(); i++) { if (!getInternalWrappedList().contains(savedSnapshot.get(i))) removals.add(i); } // Detect additions final List<Integer> adds = new ArrayList<Integer>(); if (from == previousFrom && to == previousTo) { for (int i = 0; i < getInternalWrappedList().size(); i++) { if (!savedSnapshot.contains(getInternalWrappedList().get(i))) adds.add(i); } } // Detect permutations int start = -1; for (int i = 0; i < savedSnapshot.size(); i++) { if (getInternalWrappedList().contains(savedSnapshot.get(i))) { start = i; break; } } final List<Integer> permutations = new ArrayList<Integer>(); if (start >= 0) { for (int i = start; i < savedSnapshot.size(); i++) { int idx = getInternalWrappedList().indexOf(savedSnapshot.get(i)); if (idx < 0) break; permutations.add(first+idx); } } final int[] perms = permutations.size() > 0 ? new int[permutations.size()] : null; for (int i = 0; i < permutations.size(); i++) perms[i] = permutations.get(i); final int permutationStart = start; // Notify of elements removed ListChangeListener.Change<E> change = new ListChangeListener.Change<E>(wrappedList) { private int changeIndex = -1; @Override public int getFrom() { if (changeIndex < removals.size()) return first+removals.get(changeIndex); if (changeIndex >= removals.size() && changeIndex < removals.size()+adds.size()) return first+adds.get(changeIndex-removals.size()); if (perms != null) return first+permutationStart; return -1; } @Override public int getTo() { if (changeIndex < removals.size()) return first+removals.get(changeIndex); if (changeIndex >= removals.size() && changeIndex < removals.size()+adds.size()) return first+adds.get(changeIndex-removals.size()); if (perms != null) return first+permutationStart+perms.length; return -1; } @Override public boolean wasUpdated() { return false; } @Override public boolean wasAdded() { return changeIndex >= removals.size() && changeIndex < removals.size()+adds.size(); } @Override public int getAddedSize() { if (changeIndex >= removals.size() && changeIndex < removals.size()+adds.size()) return 1; return -1; } @Override public List<E> getAddedSubList() { if (changeIndex >= removals.size() && changeIndex < removals.size()+adds.size()) return internalWrappedList.subList(adds.get(changeIndex), adds.get(changeIndex)+1); return Collections.emptyList(); } @Override protected int[] getPermutation() { if (changeIndex == removals.size()+adds.size()) return perms; return EMPTY_PERMUTATION; } @Override public int getPermutation(int i) { if (changeIndex < removals.size()+adds.size()) { throw new IllegalStateException("Not a permutation change"); } if (i-getFrom() >= 0 && i-getFrom() < perms.length) return perms[i - getFrom()]; return -1; } @Override public boolean wasRemoved() { return changeIndex < removals.size(); } @Override public List<E> getRemoved() { if (changeIndex < removals.size()) return Collections.singletonList(savedSnapshot.get(removals.get(changeIndex))); return Collections.emptyList(); } @Override public boolean next() { changeIndex++; return perms != null ? changeIndex <= removals.size()+adds.size() : changeIndex < removals.size()+adds.size(); } @Override public void reset() { changeIndex = -1; } }; helper.fireValueChangedEvent(change); return; } if (to < from) return; // Notify of content change ListChangeListener.Change<E> change = new ListChangeListener.Change<E>(wrappedList) { private boolean next = true; @Override public int getFrom() { return from; } @Override public int getTo() { return to; } @Override public boolean wasUpdated() { return true; } @Override protected int[] getPermutation() { return EMPTY_PERMUTATION; } @Override public List<E> getRemoved() { return Collections.emptyList(); } @Override public boolean next() { if (next) { next = false; return true; } return false; } @Override public void reset() { next = true; } }; helper.fireValueChangedEvent(change); } private static final int[] EMPTY_PERMUTATION = new int[0]; public class ListChangeWrapper extends ListChangeListener.Change<E> { private final ListChangeListener.Change<? extends E> wrappedChange; public ListChangeWrapper(ObservableList<E> list, ListChangeListener.Change<? extends E> wrappedChange) { super(list); this.wrappedChange = wrappedChange; } @Override public int getAddedSize() { return wrappedChange.getAddedSize(); } @SuppressWarnings("unchecked") @Override public List<E> getAddedSubList() { return (List<E>)wrappedChange.getAddedSubList(); } @Override public int getRemovedSize() { return wrappedChange.getRemovedSize(); } @Override public boolean wasAdded() { return wrappedChange.wasAdded(); } @Override public boolean wasPermutated() { return wrappedChange.wasPermutated(); } @Override public boolean wasRemoved() { return wrappedChange.wasRemoved(); } @Override public boolean wasReplaced() { return wrappedChange.wasReplaced(); } @Override public boolean wasUpdated() { return wrappedChange.wasUpdated(); } @Override public int getFrom() { int from = wrappedChange.getFrom(); return from+first; } @Override public int getTo() { int to = wrappedChange.getTo(); return to+first; } @Override protected int[] getPermutation() { return EMPTY_PERMUTATION; } @Override public int getPermutation(int num) { return wrappedChange.getPermutation(num); } @SuppressWarnings("unchecked") @Override public List<E> getRemoved() { return (List<E>)wrappedChange.getRemoved(); } @Override public boolean next() { return wrappedChange.next(); } @Override public void reset() { wrappedChange.reset(); } } @Override public Object[] toArray() { return internalWrappedList.toArray(); } @Override public <T> T[] toArray(T[] a) { return internalWrappedList.toArray(a); } @Override public boolean add(E e) { throw new UnsupportedOperationException(); } @Override public void add(int index, E element) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends E> c) { throw new UnsupportedOperationException(); } @Override public boolean addAll(int index, Collection<? extends E> c) { throw new UnsupportedOperationException(); } @SuppressWarnings("unchecked") @Override public boolean addAll(E... elements) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public E remove(int index) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public void remove(int from, int to) { throw new UnsupportedOperationException(); } @SuppressWarnings("unchecked") @Override public boolean removeAll(E... elements) { throw new UnsupportedOperationException(); } @SuppressWarnings("unchecked") @Override public boolean retainAll(E... elements) { throw new UnsupportedOperationException(); } @SuppressWarnings("unchecked") @Override public boolean setAll(E... elements) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public E set(int index, E element) { throw new UnsupportedOperationException(); } @Override public List<E> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); } }