/*
* 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.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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 org.cruxframework.crux.core.client.dataprovider.FilterableProvider.FilterRegistration;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.shared.HandlerRegistration;
/**
* An Helper class to implement common operations for different {@link DataProvider}s
* @author Thiago da Rosa de Bustamante
*/
class DataProviderOperations<T>
{
protected List<DataProviderRecord<T>> changedRecords = new ArrayList<DataProviderRecord<T>>();
protected Array<DataFilterHandler<T>> dataFilterHandlers;
protected AbstractScrollableDataProvider<T> dataProvider;
protected Array<DataFilter<T>> filters;
protected Array<DataProviderRecord<T>> initialData;
protected List<DataProviderRecord<T>> newRecords = new ArrayList<DataProviderRecord<T>>();
protected Set<DataProviderRecord<T>> readOnlyRecords = new HashSet<DataProviderRecord<T>>();
protected List<DataProviderRecord<T>> removedRecords = new ArrayList<DataProviderRecord<T>>();
protected List<DataProviderRecord<T>> selectedRecords = new ArrayList<DataProviderRecord<T>>();
protected Array<DataProviderRecord<T>> transactionOriginalData = null;
DataProviderOperations(AbstractScrollableDataProvider<T> dataProvider)
{
this.dataProvider = dataProvider;
}
HandlerRegistration addDataFilterHandler(final DataFilterHandler<T> handler)
{
if (dataFilterHandlers == null)
{
dataFilterHandlers = CollectionFactory.createArray();
}
dataFilterHandlers.add(handler);
return new HandlerRegistration()
{
@Override
public void removeHandler()
{
int index = dataFilterHandlers.indexOf(handler);
if (index >= 0)
{
dataFilterHandlers.remove(index);
}
}
};
}
FilterRegistration<T> addFilter(final DataFilter<T> filter)
{
assert(this.dataProvider instanceof FilterableProvider) : "Invalid operation. Can not add filters on not FilterableDataProvider";
if (filters == null)
{
filters = CollectionFactory.createArray();
}
filters.add(filter);
applyFilter(filter);
return new FilterRegistration<T>()
{
private DataFilter<T> bindFilter = filter;
@Override
public void remove()
{
int index = filters.indexOf(bindFilter);
if (index >= 0)
{
filters.remove(index);
applyAllFilters(true);
}
}
@Override
public void replace(DataFilter<T> newFilter, boolean incrementalFiltering)
{
int index = filters.indexOf(bindFilter);
if (index >= 0)
{
filters.remove(index);
filters.insert(index, newFilter);
bindFilter = newFilter;
if (incrementalFiltering)
{
applyFilter(newFilter);
}
else
{
applyAllFilters(true);
}
}
}
};
}
void beginTransaction(int recordIndex)
{
if(transactionOriginalData == null)
{
int firstRecordToLock = dataProvider.lockRecordForEdition(recordIndex);
this.transactionOriginalData = dataProvider.getTransactionRecords();
dataProvider.fireTransactionStartEvent(firstRecordToLock);
}
}
void commit()
{
for(DataProviderRecord<T> newRecord : newRecords)
{
newRecord.setCreated(false);
newRecord.setDirty(false);
}
for(DataProviderRecord<T> changedRecord : changedRecords)
{
changedRecord.setCreated(false);
changedRecord.setDirty(false);
}
dataProvider.concludeEdition(true);
endTransaction();
}
Array<T> filter(DataFilter<T> filter)
{
Array<T> result = CollectionFactory.createArray();
Array<DataProviderRecord<T>> toSearch = (initialData != null? initialData : dataProvider.data);
if (toSearch != null)
{
int size = toSearch.size();
for (int i = 0; i < size; i++)
{
DataProviderRecord<T> dataProviderRecord = toSearch.get(i);
if (dataProviderRecord != null)
{
T object = dataProviderRecord.getRecordObject();
if (filter.accept(object))
{
result.add(object);
}
}
}
}
return result;
}
Array<T> getData()
{
if (dataProvider.data != null)
{
int size = dataProvider.data.size();
Array<T> allData = CollectionFactory.createArray(size);
for (int i = 0; i < size; i++)
{
allData.add(dataProvider.data.get(i).getRecordObject());
}
return allData;
}
return null;
}
@SuppressWarnings("unchecked")
DataProviderRecord<T>[] getNewRecords()
{
return newRecords.toArray(new DataProviderRecord[newRecords.size()]);
}
/**
* @return
*/
int getNewRecordsCount()
{
return newRecords.size();
}
@SuppressWarnings("unchecked")
DataProviderRecord<T>[] getReadOnlyRecords()
{
return readOnlyRecords.toArray(new DataProviderRecord[readOnlyRecords.size()]);
}
int getRecordIndex(T boundObject)
{
for(int i = 0; i < this.dataProvider.data.size(); i++)
{
if(this.dataProvider.data.get(i) != null && this.dataProvider.data.get(i).recordObject.equals(boundObject))
{
return i;
}
}
return -1;
}
@SuppressWarnings("unchecked")
DataProviderRecord<T>[] getRemovedRecords()
{
return removedRecords.toArray(new DataProviderRecord[removedRecords.size()]);
}
/**
* @return
*/
int getRemovedRecordsCount()
{
return removedRecords.size();
}
@SuppressWarnings("unchecked")
DataProviderRecord<T>[] getSelectedRecords()
{
return selectedRecords.toArray(new DataProviderRecord[selectedRecords.size()]);
}
@SuppressWarnings("unchecked")
DataProviderRecord<T>[] getUpdatedRecords()
{
return changedRecords.toArray(new DataProviderRecord[changedRecords.size()]);
}
DataProviderRecord<T> insertRecord(int index, T object)
{
this.dataProvider.ensureLoaded();
checkRange(index, true);
beginTransaction(index);
DataProviderRecord<T> record = new DataProviderRecord<T>(this.dataProvider);
record.setCreated(true);
record.set(object);
this.dataProvider.data.insert(index, record);
newRecords.add(record);
if (hasFilters())
{
index = initialData.indexOf(this.dataProvider.data.get(index+1));
initialData.insert(index, record);
}
this.dataProvider.fireDataChangedEvent(record, index);
return record;
}
DataProviderRecord<T> insertRecord(T object)
{
int index = this.dataProvider.data.size();
return insertRecord(index, object);
}
boolean isDirty()
{
return (newRecords.size() > 0) || (removedRecords.size() > 0) || (changedRecords.size() > 0);
}
void removeFilters()
{
boolean forceDataFilterEvent = filters.size() > 0;
filters.clear();
applyAllFilters(forceDataFilterEvent);
}
DataProviderRecord<T> removeRecord(int index)
{
this.dataProvider.ensureLoaded();
checkRange(index, false);
beginTransaction(index);
DataProviderRecord<T> record = this.dataProvider.data.get(index);
DataProviderRecordState previousState = record.getCurrentState();
if (previousState.isReadOnly())
{
throw new DataProviderException("Can not update a read only information");//TODO i18n
}
record.setRemoved(true);
this.dataProvider.data.remove(index);
if (hasFilters())
{
index = initialData.indexOf(record);
initialData.remove(index);
}
updateState(record, previousState);
this.dataProvider.fireDataChangedEvent(record, index);
return record;
}
void reset()
{
newRecords.clear();
removedRecords.clear();
changedRecords.clear();
selectedRecords.clear();
readOnlyRecords.clear();
transactionOriginalData = null;
}
void rollback()
{
if(transactionOriginalData != null)
{
dataProvider.concludeEdition(false);
dataProvider.replaceTransactionData(this.transactionOriginalData);
Scheduler.get().scheduleDeferred(new ScheduledCommand()
{
@Override
public void execute()
{
dataProvider.unselectAllRecords();
}
});
endTransaction();
}
}
void saveInitialData(Array<DataProviderRecord<T>> records)
{
this.initialData = records;
dataProvider.setLoaded();
applyAllFilters(false);
}
void selectAllRecords(boolean selected)
{
Array<DataProviderRecord<T>> changedRecords = CollectionFactory.createArray();;
for(int i = 0; i < this.dataProvider.data.size(); i++)
{
DataProviderRecord<T> record = this.dataProvider.data.get(i);
if(record != null && record.isSelected() != selected)
{
changedRecords.add(record);
record.state.setSelected(selected);
if (selected)
{
selectedRecords.add(record);
}
else
{
selectedRecords.remove(record);
}
}
}
if (changedRecords.size() > 0)
{
dataProvider.fireDataSelectionEvent(changedRecords);
}
}
DataProviderRecord<T> selectRecord(int index, boolean selected, boolean fireEvents)
{
checkRange(index, false);
DataProviderRecord<T> record = this.dataProvider.data.get(index);
record.setSelected(selected, fireEvents);
return record;
}
DataProviderRecord<T> setReadOnly(int index, boolean readOnly)
{
checkRange(index, false);
DataProviderRecord<T> record = this.dataProvider.data.get(index);
record.setReadOnly(readOnly);
readOnlyRecords.add(record);
return record;
}
DataProviderRecord<T> updateRecord(int index, T object)
{
this.dataProvider.ensureLoaded();
checkRange(index, false);
beginTransaction(index);
DataProviderRecord<T> record = this.dataProvider.data.get(index);
if (record != null)
{
if (record.isReadOnly())
{
throw new DataProviderException("Can not update a read only information");//TODO i18n
}
record.set(object);
}
this.dataProvider.fireDataChangedEvent(record, index);
return record;
}
void updateState(DataProviderRecord<T> record, DataProviderRecordState previousState)
{
this.dataProvider.ensureLoaded();
if (record.isCreated())
{
if (record.isRemoved())
{
newRecords.remove(record);
}
}
else if (record.isRemoved())
{
if (!previousState.isRemoved())
{
removedRecords.add(record);
if (previousState.isDirty())
{
changedRecords.remove(record);
}
if (previousState.isSelected())
{
selectedRecords.remove(record);
}
}
}
else if (record.isDirty() && !previousState.isDirty())
{
changedRecords.add(record);
}
if (record.isSelected() && !previousState.isSelected() && !record.isRemoved())
{
selectedRecords.add(record);
}
else if (!record.isSelected() && previousState.isSelected())
{
selectedRecords.remove(record);
}
}
private void applyAllFilters(boolean forceDataFilterEvent)
{
if (initialData != null)
{
if (hasFilters())
{
Array<DataProviderRecord<T>> array = CollectionFactory.createArray();
int size = initialData.size();
int filtersSize = filters.size();
for (int i = 0; i < size; i++)
{
DataProviderRecord<T> record = initialData.get(i);
T recordObject = record.getRecordObject();
if (checkFilter(filtersSize, record, recordObject))
{
array.add(record);
}
}
dataProvider.data = array;
}
else
{
dataProvider.data = initialData;
}
dataProvider.setFirstPosition(false);
if (forceDataFilterEvent || hasFilters())
{
fireDataFilterEvent();
}
}
}
private void applyFilter(final DataFilter<T> filter)
{
Array<DataProviderRecord<T>> array = CollectionFactory.createArray();
Array<DataProviderRecord<T>> data = dataProvider.data;
if (data != null && data.size() > 0)
{
int size = data.size();
for (int i = 0; i < size; i++)
{
DataProviderRecord<T> record = data.get(i);
if (filter.accept(record.getRecordObject()))
{
array.add(record);
}
}
dataProvider.data = array;
dataProvider.setFirstPosition(false);
fireDataFilterEvent();
}
}
private boolean checkFilter(int filtersSize, DataProviderRecord<T> record, T recordObject)
{
for (int f = 0; f < filtersSize; f ++)
{
if (!filters.get(f).accept(recordObject))
{
return false;
}
}
return true;
}
private void checkRange(int index, boolean mayExpand)
{
if (index < 0 || index > this.dataProvider.data.size())
{
throw new IndexOutOfBoundsException();
}
if (!mayExpand && index == this.dataProvider.data.size())
{
throw new IndexOutOfBoundsException();
}
}
private void endTransaction()
{
newRecords.clear();
removedRecords.clear();
changedRecords.clear();
this.transactionOriginalData = null;
}
@SuppressWarnings("unchecked")
private void fireDataFilterEvent()
{
if (dataFilterHandlers != null)
{
DataFilterEvent<T> event = new DataFilterEvent<T>((FilterableProvider<T>) dataProvider);
for (int i = 0; i< dataFilterHandlers.size(); i++)
{
dataFilterHandlers.get(i).onFiltered(event);
}
}
}
private boolean hasFilters()
{
return filters != null && filters.size() > 0;
}
}