/* * Copyright 2011 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.datasource; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import org.cruxframework.crux.core.client.ClientMessages; import org.cruxframework.crux.core.client.Legacy; import org.cruxframework.crux.core.client.datasource.DataSourceRecord.DataSourceRecordState; import org.cruxframework.crux.core.client.utils.StringUtils; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.ui.HasValue; /** * @author Thiago da Rosa de Bustamante * */ public abstract class RemoteStreamingDataSource<T> implements StreamingDataSource<T> { protected StreamingDataSourceOperations<T> editableOperations = new StreamingDataSourceOperations<T>(this); /** * @see org.cruxframework.crux.core.client.datasource.DataSource#insertRecord(int) */ public DataSourceRecord<T> insertRecord(int index) { return editableOperations.insertRecord(index); } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#removeRecord(int) */ public DataSourceRecord<T> removeRecord(int index) { return editableOperations.removeRecord(index); } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#updateState(org.cruxframework.crux.core.client.datasource.DataSourceRecord, org.cruxframework.crux.core.client.datasource.DataSourceRecord.DataSourceRecordState) */ public void updateState(DataSourceRecord<T> record, DataSourceRecordState previousState) { editableOperations.updateState(record, previousState); } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#getNewRecords() */ public DataSourceRecord<T>[] getNewRecords() { return editableOperations.getNewRecords(); } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#getRemovedRecords() */ public DataSourceRecord<T>[] getRemovedRecords() { return editableOperations.getRemovedRecords(); } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#getUpdatedRecords() */ public DataSourceRecord<T>[] getUpdatedRecords() { return editableOperations.getUpdatedRecords(); } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#getSelectedRecords() */ public DataSourceRecord<T>[] getSelectedRecords() { return editableOperations.getSelectedRecords(); } private void checkChanges() { if (editableOperations.isDirty()) { throw new DataSourceExcpetion(messages.remoteDataSourcePageDirty()); } } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#clearChanges() */ public void clearChanges() { this.editableOperations.reset(); } protected List<DataSourceRecord<T>> data = new ArrayList<DataSourceRecord<T>>(); protected int currentRecord = -1; protected int pageSize = 10; protected int currentPage = 0; protected ColumnDefinitions<T> definitions = new ColumnDefinitions<T>(); protected RemoteDataSourceCallback fetchCallback = null; protected ClientMessages messages = GWT.create(ClientMessages.class); /** * @see org.cruxframework.crux.core.client.datasource.RemoteDataSource#setCallback(org.cruxframework.crux.core.client.datasource.RemoteDataSourceCallback) */ public void setCallback(RemoteDataSourceCallback callback) { this.fetchCallback = callback; } /** * @see org.cruxframework.crux.core.client.datasource.RemoteDataSource#update(R[]) */ public void update(DataSourceRecord<T>[] records) { int startRecord = getPageStartRecord(); int endRecord = (currentPage * pageSize) - 1; int updateRecordsCount = updateRecords(startRecord, endRecord, records); if (updateRecordsCount > 0) { if (this.fetchCallback != null) { fetchCallback.execute(startRecord, startRecord+updateRecordsCount-1); } } else { if (this.fetchCallback != null) { fetchCallback.execute(-1, -1); } } } /** * * @param startRecord * @param endRecord * @param records * @return */ protected int updateRecords(int startRecord, int endRecord, DataSourceRecord<T>[] records) { int ret = 0; if (records != null) { int count = Math.min(endRecord - startRecord + 1, records.length); for (ret = 0; ret < count ; ret++) { this.data.add(records[ret]); } } if (ret < (endRecord - startRecord)) { this.data.add(null); } return ret; } /** * * @param startRecord * @param endRecord * @param records * @return */ protected int updatePageRecords(int startRecord, int endRecord, DataSourceRecord<T>[] records) { int ret = 0; if (records != null) { int count = Math.min(endRecord - startRecord + 1, records.length); for (ret = 0; ret < count ; ret++) { int index = ret+startRecord; if(records.length > ret && this.data.size() > index) { this.data.set(index, records[ret]); } } } return ret; } /** * @see org.cruxframework.crux.core.client.datasource.RemoteDataSource#setData(E[]) */ public void updateData(T[] data) { } public void updateData(List<T> data) { } public void copyValueToWidget(HasValue<?> valueContainer, String key, DataSourceRecord<?> dataSourceRecord) { } public void setValue(Object value, String columnKey, DataSourceRecord<?> dataSourceRecord) { } public int getRecordIndex(T boundObject) { return editableOperations.getRecordIndex(boundObject); } public void selectRecord(int index, boolean selected) { editableOperations.selectRecord(index, selected); } /** * @see org.cruxframework.crux.core.client.datasource.RemoteDataSource#cancelFetching() */ public void cancelFetching() { currentPage--; updateCurrentRecord(); this.fetchCallback.cancelFetching(); } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#getColumnDefinitions() */ public ColumnDefinitions<T> getColumnDefinitions() { return definitions; } /** * @param columnDefinitions */ public void setColumnDefinitions(ColumnDefinitions<T> columnDefinitions) { this.definitions = columnDefinitions; } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#getRecord() */ public DataSourceRecord<T> getRecord() { ensureCurrentPageLoaded(); if (currentRecord > -1) { return data.get(currentRecord); } else { return null; } } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#getValue(java.lang.String) */ public Object getValue(String columnName) { ensureCurrentPageLoaded(); if (currentRecord > -1) { DataSourceRecord<T> dataSourceRow = data.get(currentRecord); return getValue(columnName, dataSourceRow); } return null; } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#hasNextRecord() */ public boolean hasNextRecord() { ensureCurrentPageLoaded(); return isRecordOnPage(currentRecord+1); } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#nextRecord() */ public void nextRecord() { if (hasNextRecord()) { currentRecord++; } } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#firstRecord() */ public void firstRecord() { if (currentPage != 1) { checkChanges(); } currentRecord = getPageStartRecord(); ensureCurrentPageLoaded(); } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#hasPreviousRecord() */ public boolean hasPreviousRecord() { ensureCurrentPageLoaded(); return isRecordOnPage(currentRecord-1); } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#previousRecord() */ public void previousRecord() { if (hasPreviousRecord()) { currentRecord--; } } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#reset() */ public void reset() { this.data.clear(); currentRecord = -1; currentPage = 0; editableOperations.reset(); } protected void ensureCurrentPageLoaded() { boolean loaded = isCurrentPageLoaded(); if (!loaded) { throw new DataSourceExcpetion(messages.dataSourceNotLoaded()); } } protected boolean isCurrentPageLoaded() { int pageStartRecord = getPageStartRecord(); return (data.size() > pageStartRecord); } protected boolean isRecordOnPage(int record) { ensureCurrentPageLoaded(); if (data == null) { 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 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 + editableOperations.getNewRecordsCount() - editableOperations.getRemovedRecordsCount(); } protected int getPageStartRecord() { return (currentPage - 1) * pageSize; } /** * @see org.cruxframework.crux.core.client.datasource.PagedDataSource#getCurrentPage() */ public int getCurrentPage() { return currentPage; } /** * @see org.cruxframework.crux.core.client.datasource.PagedDataSource#getCurrentPageSize() */ public int getCurrentPageSize() { //int pageEndRecord = (getPageEndRecord() == this.data.size()) || (getPageEndRecord()-1 == this.data.size())? getPageEndRecord() -1 : getPageEndRecord(); int pageEndRecord = (getPageEndRecord() == this.data.size()) ? getPageEndRecord() -1 : getPageEndRecord(); //int pageEndRecord = getPageEndRecord(); return pageEndRecord - getPageStartRecord() + 1; } /** * @see org.cruxframework.crux.core.client.datasource.PagedDataSource#getPageSize() */ public int getPageSize() { return pageSize; } /** * @see org.cruxframework.crux.core.client.datasource.PagedDataSource#hasNextPage() */ 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; } /** * @see org.cruxframework.crux.core.client.datasource.PagedDataSource#hasPreviousPage() */ public boolean hasPreviousPage() { return (currentPage > 1); } /** * @see org.cruxframework.crux.core.client.datasource.PagedDataSource#nextPage() */ public boolean nextPage() { checkChanges(); if (hasNextPage()) { currentPage++; updateCurrentRecord(); fetchCurrentPage(); return true; } return false; } /** * @see org.cruxframework.crux.core.client.datasource.PagedDataSource#previousPage() */ public boolean previousPage() { checkChanges(); if (hasPreviousPage()) { currentPage--; updateCurrentRecord(); fetchCurrentPage(); return true; } return false; } /** * @see org.cruxframework.crux.core.client.datasource.PagedDataSource#setPageSize(int) */ public void setPageSize(int pageSize) { if (pageSize < 1) { pageSize = 1; } boolean loaded = data.size() > 0; this.pageSize = pageSize; if(loaded) { fetchCurrentPage(); updateCurrentRecord(); } } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#sort(java.lang.String, boolean) */ @Override public void sort(String columnName, boolean ascending) { sort(columnName, ascending, false); } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#sort(java.lang.String, boolean) */ @SuppressWarnings("unchecked") public void sort(String columnName, boolean ascending, boolean caseSensitive) { if (currentRecord > -1) { DataSourceRecord<T>[] pageData = new DataSourceRecord[pageSize]; int startPageRecord = getPageStartRecord(); int endPageRecord = getPageEndRecord(); int pageSize = endPageRecord - startPageRecord + 1; for (int i = 0; i<pageSize; i++) { if(this.data.size() > i && pageData.length > i) { pageData[i] = data.get(i+startPageRecord); } } sortArray(pageData,columnName, ascending, caseSensitive); updatePageRecords(startPageRecord, endPageRecord, pageData); } } protected void sortArray(DataSourceRecord<T>[] array, final String columnName, final boolean ascending, final boolean caseSensitive) { if (!definitions.getColumn(columnName).isSortable()) { throw new DataSourceExcpetion(messages.dataSourceErrorColumnNotComparable(columnName)); } //optimization: infer column type only once final boolean isStringColumn = getValue(columnName, array[0]) instanceof String; Arrays.sort(array, new Comparator<DataSourceRecord<T>>(){ public int compare(DataSourceRecord<T> o1, DataSourceRecord<T> o2) { // Null elements must always be considered greater, because datasources uses the first null record to identify the page end. if (o1 == null) { return o2 == null ? 0 : 1; } if (o2 == null) { return -1; } Object value1 = getValue(columnName,o1); Object value2 = getValue(columnName,o2); if (ascending) { if (value1==null) return (value2 == null ? 0 : -1); if (value2==null) return 1; } else { if (value1==null) return (value2 == null ? 0 : 1); if (value2==null) return -1; } return compareNonNullValuesByType(value1,value2,ascending,caseSensitive, isStringColumn); } @SuppressWarnings({ "rawtypes", "unchecked" }) private int compareNonNullValuesByType(Object value1, Object value2, boolean ascending, boolean caseSensitive, boolean isStringColumns) { if(isStringColumns) { if (ascending) { return StringUtils.localeCompare((String)value1, (String)value2, caseSensitive); } else { return StringUtils.localeCompare((String)value2, (String)value1, caseSensitive); } } if (ascending) { return ((Comparable)value1).compareTo(value2); } else { return ((Comparable)value2).compareTo(value1); } } }); firstRecord(); } protected void updateCurrentRecord() { currentRecord = getPageStartRecord(); } /** * */ protected void fetchCurrentPage() { if (!isCurrentPageLoaded()) { fetch(getPageStartRecord(), getPageEndRecord()); } else { fetchCallback.execute(getPageStartRecord(), getPageEndRecord()); } } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#getValue(java.lang.String, org.cruxframework.crux.core.client.datasource.DataSourceRecord) */ @SuppressWarnings("unchecked") public Object getValue(String columnName, DataSourceRecord<?> dataSourceRecord) { ColumnDefinition<?, T> column = definitions.getColumn(columnName); if (column != null) { return column.getValue((T) dataSourceRecord.getRecordObject()); } return null; } /** * @return * @deprecated Use getBoundObject instead */ @Deprecated @Legacy public T getBindedObject() { return getBindedObject(getRecord()); } /** * @param record * @return * @deprecated Use getBoundObject instead */ @Deprecated @Legacy public T getBindedObject(DataSourceRecord<T> record) { return getBoundObject(record); } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#getBoundObject() */ public T getBoundObject() { return getBoundObject(getRecord()); } /** * @see org.cruxframework.crux.core.client.datasource.DataSource#getBoundObject(org.cruxframework.crux.core.client.datasource.DataSourceRecord) */ public T getBoundObject(DataSourceRecord<T> record) { return null; } public T cloneDTO(DataSourceRecord<?> record) { return null; } }