/* * Copyright 2014 cruxframework.org. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.cruxframework.crux.core.client.dataprovider; import java.util.Comparator; import java.util.List; import org.cruxframework.crux.core.client.collection.Array; import org.cruxframework.crux.core.client.collection.CollectionFactory; import org.cruxframework.crux.core.client.dataprovider.DataProviderRecord.DataProviderRecordState; import com.google.gwt.event.shared.HandlerRegistration; /** * @author Thiago da Rosa de Bustamante */ public class StreamingDataProvider<T> extends AbstractDataProvider<T> implements StreamingProvider<T> { protected int currentPage = 0; protected StreamingDataLoader<T> dataLoader; protected StreamingDataProviderOperations<T> operations = new StreamingDataProviderOperations<T>(this); protected Array<PageLoadedHandler> pageFetchHandlers; protected Array<PageRequestedHandler> pageRequestedHandlers; protected int pageSize = 10; protected int previousPage = -1; public StreamingDataProvider() { } public StreamingDataProvider(DataProvider.EditionDataHandler<T> handler) { super(handler); } public StreamingDataProvider(DataProvider.EditionDataHandler<T> handler, StreamingDataLoader<T> dataLoader) { super(handler); this.dataLoader = dataLoader; } @Override public DataProviderRecord<T> add(int beforeIndex, T object) { return operations.insertRecord(beforeIndex, object); } @Override public DataProviderRecord<T> add(T object) { return operations.insertRecord(object); } @Override public HandlerRegistration addPageLoadedHandler(final PageLoadedHandler handler) { if (pageFetchHandlers == null) { pageFetchHandlers = CollectionFactory.createArray(); } pageFetchHandlers.add(handler); return new HandlerRegistration() { @Override public void removeHandler() { int index = pageFetchHandlers.indexOf(handler); if (index >= 0) { pageFetchHandlers.remove(index); } } }; } @Override public HandlerRegistration addPageRequestedHandler(final PageRequestedHandler handler) { if (pageRequestedHandlers == null) { pageRequestedHandlers = CollectionFactory.createArray(); } pageRequestedHandlers.add(handler); return new HandlerRegistration() { @Override public void removeHandler() { int index = pageRequestedHandlers.indexOf(handler); if (index >= 0) { pageRequestedHandlers.remove(index); } } }; } @Override public void commit() { this.operations.commit(); } @Override public Array<T> filter(DataFilter<T> filter) { Array<T> result = CollectionFactory.createArray(); if (data != null) { int size = data.size(); for (int i = 0; i < size; i++) { DataProviderRecord<T> dataProviderRecord = data.get(i); if (dataProviderRecord != null) { T object = dataProviderRecord.getRecordObject(); if (filter.accept(object)) { result.add(object); } } } } return result; } @Override public void first() { if (currentPage != 1) { ensureNotDirty(); } previousPage = currentPage; currentPage = 1; currentRecord = getPageStartRecord(); ensureCurrentPageLoaded(); } @Override public void firstOnPage() { int pageStartRecord = getPageStartRecord(); if (pageStartRecord != currentRecord) { if (currentPage != 1) { ensureNotDirty(); } currentRecord = pageStartRecord; ensureCurrentPageLoaded(); } } @Override public int getCurrentPage() { return currentPage; } @Override public int getCurrentPageSize() { int pageEndRecord = getLoadedPageEndRecord(); return pageEndRecord - getPageStartRecord() + 1; } @Override public int getCurrentPageStartRecord() { return getPageStartRecord(); } @Override public StreamingDataLoader<T> getDataLoader() { return dataLoader; } @Override public DataProviderRecord<T>[] getNewRecords() { return operations.getNewRecords(); } @Override public int getPageSize() { return pageSize; } @Override public DataProviderRecord<T> getRecord() { if (isCurrentPageLoaded() && currentRecord > -1) { return data.get(currentRecord); } else { return null; } } @Override public DataProviderRecord<T>[] getRemovedRecords() { return operations.getRemovedRecords(); } @Override public DataProviderRecord<T>[] getSelectedRecords() { return operations.getSelectedRecords(); } @Override public DataProviderRecord<T>[] getUpdatedRecords() { return operations.getUpdatedRecords(); } @Override public boolean hasNext() { return isRecordOnPage(currentRecord+1); } @Override public boolean hasNextPage() { int pageEndRecord = getPageEndRecord(); if (pageEndRecord < 0) { return this.data.size() == 0; } if (pageEndRecord < this.data.size()) { if (pageEndRecord == this.data.size() - 2) { return this.data.get(pageEndRecord) == null; } return true; } return false; } @Override public boolean hasPrevious() { return isRecordOnPage(currentRecord-1); } @Override public boolean hasPreviousPage() { return (currentPage > 1); } @Override public int indexOf(T boundObject) { return operations.getRecordIndex(boundObject); } @Override public boolean isDirty() { return operations != null && operations.isDirty(); } @Override public void load() { if(!isLoaded()) { nextPage(); } } @Override public boolean nextPage() { ensureNotDirty(); if (hasNextPage()) { previousPage = currentPage; currentPage++; updateCurrentRecord(); firePageRequestedEvent(currentPage); fetchCurrentPage(); return true; } return false; } @Override public boolean previousPage() { ensureNotDirty(); if (hasPreviousPage()) { previousPage = currentPage; currentPage--; updateCurrentRecord(); firePageRequestedEvent(currentPage); fetchCurrentPage(); return true; } return false; } @Override public DataProviderRecord<T> remove(int index) { return operations.removeRecord(index); } @Override public void reset() { super.reset(); previousPage = -1; currentPage = 0; operations.reset(); } @Override public void rollback() { this.operations.rollback(); } @Override public DataProviderRecord<T> select(int index, boolean selected) { return operations.selectRecord(index, selected, true); } @Override public DataProviderRecord<T> select(T object, boolean selected) { return operations.selectRecord(indexOf(object), selected, true); } @Override public void selectAll(boolean selected) { if (getSelectionMode().equals(SelectionMode.multiple)) { operations.selectAllRecords(selected); } } @Override public DataProviderRecord<T> set(int index, T object) { return operations.updateRecord(index, object); } @Override public void setData(Array<T> data) { if (data == null) { Array<DataProviderRecord<T>> array = CollectionFactory.createArray(); update(array); } else { Array<DataProviderRecord<T>> ret = CollectionFactory.createArray(data.size()); for (int i=0; i<data.size(); i++) { DataProviderRecord<T> record = new DataProviderRecord<T>(this); record.setRecordObject(data.get(i)); ret.set(i, record); } update(ret); } } @Override public void setData(Array<T> data, int startRecord) { if (data != null) { int dataSize = data.size(); Array<DataProviderRecord<T>> ret = CollectionFactory.createArray(dataSize); for (int i = 0; i < dataSize; i++) { DataProviderRecord<T> record = new DataProviderRecord<T>(this); record.setRecordObject(data.get(i)); ret.set(i, record); } update(ret, startRecord, startRecord+dataSize-1); } } @Override public void setData(List<T> data) { if (data == null) { Array<DataProviderRecord<T>> array = CollectionFactory.createArray(); update(array); } else { Array<DataProviderRecord<T>> ret = CollectionFactory.createArray(data.size()); for (int i=0; i<data.size(); i++) { DataProviderRecord<T> record = new DataProviderRecord<T>(this); record.setRecordObject(data.get(i)); ret.set(i, record); } update(ret); } } @Override public void setData(List<T> data, int startRecord) { if (data != null) { int dataSize = data.size(); Array<DataProviderRecord<T>> ret = CollectionFactory.createArray(dataSize); for (int i = 0; i < dataSize; i++) { DataProviderRecord<T> record = new DataProviderRecord<T>(this); record.setRecordObject(data.get(i)); ret.set(i, record); } update(ret, startRecord, startRecord+dataSize-1); } } @Override public void setData(T[] data) { if (data == null) { Array<DataProviderRecord<T>> array = CollectionFactory.createArray(); update(array); } else { Array<DataProviderRecord<T>> ret = CollectionFactory.createArray(data.length); for (int i=0; i<data.length; i++) { DataProviderRecord<T> record = new DataProviderRecord<T>(this); record.setRecordObject(data[i]); ret.set(i, record); } update(ret); } } @Override public void setData(T[] data, int startRecord) { if (data != null) { int dataSize = data.length; Array<DataProviderRecord<T>> ret = CollectionFactory.createArray(dataSize); for (int i = 0; i < dataSize; i++) { DataProviderRecord<T> record = new DataProviderRecord<T>(this); record.setRecordObject(data[i]); ret.set(i, record); } update(ret, startRecord, startRecord+dataSize-1); } } @Override public void setDataLoader(StreamingDataLoader<T> dataLoader) { this.dataLoader = dataLoader; } @Override public void setPageSize(int pageSize) { if (pageSize < 1) { pageSize = 1; } boolean loaded = data.size() > 0; this.pageSize = pageSize; if(loaded) { updateCurrentRecord(); fetchCurrentPage(); firePageRequestedEvent(currentPage); } } @Override public DataProviderRecord<T> setReadOnly(int index, boolean readOnly) { return operations.setReadOnly(index, readOnly); } @Override public DataProviderRecord<T> setReadOnly(T object, boolean readOnly) { return operations.setReadOnly(indexOf(object), readOnly); } @Override public void sort(Comparator<T> comparator) { if (isCurrentPageLoaded() && currentRecord > -1) { Array<DataProviderRecord<T>> pageData = CollectionFactory.createArray(pageSize); int startPageRecord = getPageStartRecord(); int endPageRecord = getLoadedPageEndRecord(); int pageSize = endPageRecord - startPageRecord + 1; for (int i = 0; i<pageSize; i++) { pageData.add(data.get(i+startPageRecord)); } sortArray(pageData, comparator); updatePageRecords(startPageRecord, endPageRecord, pageData); fireSortedEvent(false); } } @Override public void stopLoading() { previousPage = currentPage; currentPage--; updateCurrentRecord(); super.stopLoading(); } protected void ensureCurrentPageLoaded() { boolean loaded = isCurrentPageLoaded(); if (!loaded) { throw new DataProviderException("Error processing requested operation. DataProvider is not loaded yet."); } } protected void ensureLoaded() { if (!isLoaded()) { throw new DataProviderException("Error processing requested operation. DataProvider is not loaded yet."); } } protected void fetchCurrentPage() { if (!isCurrentPageLoaded()) { if (dataLoader != null) { dataLoader.onFetchData(new FetchDataEvent<T>(this, getPageUnloadedStartRecord(), getPageEndRecord())); } } else { firePageLoadedEvent(getPageStartRecord(), getPageEndRecord()); } } protected int getPageUnloadedStartRecord() { int pageStartRecord = getPageStartRecord(); int pageEndRecord = Math.min(getPageEndRecord(), data.size()); for (int i = pageStartRecord; i < pageEndRecord; i++) { if (data.get(i) == null) { return i; } } return pageEndRecord; } protected void firePageLoadedEvent(int start, int end) { if (pageFetchHandlers != null) { PageLoadedEvent event = new PageLoadedEvent(this, start, end, previousPage, currentPage); for (int i = 0; i< pageFetchHandlers.size(); i++) { pageFetchHandlers.get(i).onPageLoaded(event); } } } protected void firePageRequestedEvent(int pageNumber) { if (pageRequestedHandlers != null) { PageRequestedEvent event = new PageRequestedEvent(this, pageNumber); for (int i = 0; i< pageRequestedHandlers.size(); i++) { pageRequestedHandlers.get(i).onPageRequested(event); } } } protected int getLoadedPageEndRecord() { int pageStartRecord = getPageStartRecord(); int end = Math.min(getPageEndRecord(), data.size()-1); if (end >= 0) { while (end >= pageStartRecord && data.get(end) == null) { end--; } } return end; } protected int getPageEndRecord() { int pageEndRecord = (currentPage * pageSize) - 1; int pageStartRecord = getPageStartRecord(); if (pageEndRecord >= this.data.size()) { if (this.data.size() > 0 && this.data.size() > pageStartRecord && this.data.get(this.data.size()-1) == null) { pageEndRecord = this.data.size()-2; } } return pageEndRecord + operations.getNewRecordsCount() - operations.getRemovedRecordsCount(); } protected int getPageForRecord(int recordNumber) { int pageSize = getPageSize(); int index = recordNumber + 1; int result = (index / pageSize) + (index%pageSize==0?0:1); return result; } protected int getPageStartRecord() { return getPageStartRecord(currentPage); } protected int getPageStartRecord(int page) { return (page - 1) * pageSize; } protected Array<DataProviderRecord<T>> getTransactionRecords() { Array<DataProviderRecord<T>> currentPageRecordsArray = CollectionFactory.createArray(); int start = getPageStartRecord(); int end = getLoadedPageEndRecord(); for (int i = start; i <= end; i++) { currentPageRecordsArray.add(data.get(i).clone()); } return currentPageRecordsArray; } protected boolean isCurrentPageLoaded() { int pageStartRecord = getPageStartRecord(); return (data.size() > pageStartRecord); } public boolean isPageLoaded(int pageNumber) { if (!isLoaded()) { return false; } int startPageRecord = getPageStartRecord(pageNumber); return (data.size() > 0 && data.get(startPageRecord) != null ); } protected boolean isRecordOnPage(int record) { if (!isCurrentPageLoaded()) { return false; } int startPageRecord = getPageStartRecord(); int endPageRescord = getPageEndRecord(); if (endPageRescord >= data.size()) { endPageRescord = data.size()-1; } return (record >= startPageRecord) && (record <= endPageRescord) && (data.get(record) != null); } protected int lockRecordForEdition(int recordIndex) { int pageForRecord = getPageForRecord(recordIndex); setCurrentPage(pageForRecord); return getPageStartRecord(pageForRecord); } protected void replaceTransactionData(Array<DataProviderRecord<T>> transactionRecords) { int start = getPageStartRecord(); int end = getLoadedPageEndRecord(); int count = end-start+1; if (count > 0) { data.remove(start, count); } for (int i=0; i<transactionRecords.size(); i++) { data.insert(start+i, transactionRecords.get(i)); } } protected boolean setCurrentPage(int pageNumber) { if (pageNumber < currentPage) { previousPage = currentPage; currentPage = pageNumber; updateCurrentRecord(); return true; } return false; } protected void sortArray(Array<DataProviderRecord<T>> array, final Comparator<T> comparator) { array.sort(new Comparator<DataProviderRecord<T>>(){ public int compare(DataProviderRecord<T> o1, DataProviderRecord<T> o2) { return comparator.compare(o1.getRecordObject(), o2.getRecordObject()); } }); firstOnPage(); } protected void unselectAllRecords() { operations.selectAllRecords(false); } protected void update(Array<DataProviderRecord<T>> records) { int recordCount = records!= null?records.size():0; data = CollectionFactory.createArray(recordCount); int startRecord = 0; int endRecord = recordCount -1; if (!isLoaded()) { if (startRecord == 0) { setLoaded(); } else { return; } } int updateRecordsCount = updateRecords(startRecord, endRecord, records); if (updateRecordsCount > 0) { previousPage = -1; currentPage = 0; currentRecord = -1; if (!isLoaded()) { setLoaded(); } nextPage(); } } protected void update(Array<DataProviderRecord<T>> records, int startRecord, int endRecord) { if (!isLoaded()) { if (startRecord == 0) { setLoaded(); } else { return; } } int updateRecordsCount = updateRecords(startRecord, endRecord, records); if (updateRecordsCount > 0) { if (!isLoaded()) { setLoaded(); } firePageLoadedEvent(startRecord, startRecord+updateRecordsCount-1); } else { firePageLoadedEvent(-1, -1); } } protected void updateCurrentRecord() { currentRecord = getPageStartRecord(); } protected int updatePageRecords(int startRecord, int endRecord, Array<DataProviderRecord<T>> records) { int ret = 0; if (records != null) { int count = Math.min(endRecord - startRecord + 1, records.size()); for (ret = 0; ret < count ; ret++) { this.data.set(ret+startRecord, records.get(ret)); } } return ret; } protected int updateRecords(int startRecord, int endRecord, Array<DataProviderRecord<T>> records) { int ret = 0; if (records != null) { int count = Math.min(endRecord - startRecord + 1, records.size()); for (ret = 0; ret < count ; ret++) { this.data.add(records.get(ret)); } } if ((endRecord < startRecord) || (ret < (endRecord - startRecord))) { this.data.add(null); } return ret; } @Override protected void updateState(DataProviderRecord<T> record, DataProviderRecordState previousState) { operations.updateState(record, previousState); } }