/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.dataprocessing; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import com.servoy.j2db.persistence.AggregateVariable; import com.servoy.j2db.persistence.Column; import com.servoy.j2db.persistence.ITable; import com.servoy.j2db.persistence.Table; import com.servoy.j2db.querybuilder.IQueryBuilder; import com.servoy.j2db.scripting.UsedDataProviderTracker; import com.servoy.j2db.util.DataSourceUtils; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.ServoyException; /** * This class is used by printing to handle subsum results and convert those to states, which can be rendered to the screen or paper * * @author jblok */ public class SubSummaryFoundSet implements IFoundSetInternal { private final IDataSet data; private final SQLSheet sheet; private final IFoundSetInternal relatedInfoLookup; protected RowManager rowManager; private final IFoundSetManagerInternal fsm; public SubSummaryFoundSet(IFoundSetManagerInternal fsm, IFoundSetInternal set, SortColumn[] groupByFields, List<AggregateVariable> aggregates, IDataSet data, Table table) throws ServoyException { this.fsm = fsm; relatedInfoLookup = set; this.data = data; rowManager = ((FoundSetManager)fsm).getRowManager(fsm.getDataSource(table)); // rowManager.register(this); not needed in printing foundset sheet = ((FoundSetManager)fsm).getSQLGenerator().getNewTableSQLSheet(fsm.getDataSource(table)); HashMap<String, Integer> columnIndexes = new HashMap<String, Integer>(); for (int i = 0; i < groupByFields.length; i++) { SortColumn s = groupByFields[i]; columnIndexes.put(s.getName(), new Integer(i)); } for (int i = 0; i < aggregates.size(); i++) { AggregateVariable ag = aggregates.get(i); columnIndexes.put(ag.getName(), new Integer(i + groupByFields.length)); } sheet.setDataProviderIDsColumnMap(columnIndexes); } /** * @see com.servoy.j2db.dataprocessing.IFireCollectable#completeFire(java.util.List) */ public void completeFire(List<Object> entries) { } public IFoundSetManagerInternal getFoundSetManager() { return fsm; } public String getRelationName() { return null; } public IFoundSetInternal copy(boolean unrelate) throws ServoyException { return this; } /** * @see com.servoy.j2db.dataprocessing.IFoundSetInternal#getSize() */ public int getSize() { return data.getRowCount(); } /** * @see com.servoy.j2db.dataprocessing.IFoundSetInternal#getState(int) */ public IRecordInternal getRecord(int row) { return new PrintState(this, new Row(rowManager, data.getRow(row), sheet.getAllUnstoredCalculationNamesWithNoValue(), false) { @Override protected void handleCalculationDependencies(Column column, String dataProviderID) { // do nothing - as this requires a pk hashkey and doesn't make sense for a subsummary record; it would also produce ArrayIndexOutOfBoundsException // because column data & sheet column indexes used in this row are not in sync } }); } public IRecordInternal[] getRecords(int startrow, int count) { List<IRecordInternal> retval = new ArrayList<IRecordInternal>(); for (int i = startrow; i < Math.min(startrow + count, getSize()); i++) { retval.add(getRecord(i)); } return retval.toArray(new IRecordInternal[retval.size()]); } public IRecordInternal getRecord(Object[] pk) { return null; } /** * @see com.servoy.j2db.dataprocessing.IFoundSetInternal#getParentState() */ public IRecordInternal getParentRecord() { return null; } public int getRecordIndex(IRecord record) { return -1; } /** * @see com.servoy.j2db.dataprocessing.IFoundSetInternal#getSheet() */ public SQLSheet getSQLSheet() { return sheet; } public void loadAllRecords() throws ServoyException { // ignore } @Deprecated public void browseAll() throws ServoyException { loadAllRecords(); } public IFoundSetInternal copyCurrentRecordFoundSet() throws ServoyException { return null; } /** * @see com.servoy.j2db.dataprocessing.IFoundSetInternal#deleteState(int) */ public void deleteRecord(int row) throws ServoyException { // ignore } /** * @see com.servoy.j2db.dataprocessing.IFoundSetInternal#deleteRecord(com.servoy.j2db.dataprocessing.IRecordInternal) */ public void deleteRecord(IRecordInternal record) throws ServoyException { // ignore } @Deprecated public void deleteAll() throws ServoyException { deleteAllRecords(); } public void deleteAllRecords() throws ServoyException { // ignore } public void deleteAllInternal() throws ServoyException { // ignore } /** * @see com.servoy.j2db.dataprocessing.IFoundSetInternal#newRecord() */ public int newRecord(boolean addOnTop) throws ServoyException { return 0; } public int newRecord(boolean addOnTop, boolean changeSelection) throws ServoyException { return 0; } public int newRecord(int indexToAdd, boolean changeSelection) throws ServoyException { return 0; } public int newRecord() throws ServoyException { return 0; } public void sort(List<SortColumn> sortColumns, boolean defer) throws ServoyException { } public void sort(List<SortColumn> sortColumns) throws ServoyException { } public void setSort(String sortString) throws ServoyException { } public String getSort() { return null; } public int duplicateRecord(int row, boolean addOnTop) throws ServoyException { return 0; } public int duplicateRecord(int recordIndex, int indexToAdd) throws ServoyException { return 0; } public boolean isValidRelation(String name) { return relatedInfoLookup.isValidRelation(name); } /** * Get related foundset, relationName may be multiple-levels deep */ public IFoundSetInternal getRelatedFoundSet(IRecordInternal state, String relationName, List<SortColumn> defaultSortColumns) throws ServoyException { if (delegate != null && delegate.getSize() > 0) { return delegate.getRecord(0).getRelatedFoundSet(relationName, defaultSortColumns); } else { return null;//impossible to query because the relation RH keys does never exists } } public Object getCalculationValue(IRecordInternal state, String dataProviderID, Object[] vargs, UsedDataProviderTracker usedDataProviderTracker) { // return relatedFoundSetLookup.getCalculationValue(state, dataProviderID); // return null;//it is just impossible to get calcs values from^ a printState if (delegate != null) { return delegate.getCalculationValue(delegate.getRecord(0), dataProviderID, vargs, null /* do not manage calc dependencies */); } return null; } private IFoundSetInternal delegate; void setDelegate(IFoundSetInternal d) { this.delegate = d; } /** * @see com.servoy.j2db.dataprocessing.IFoundSetInternal#containsDataProvider(String) */ public boolean containsDataProvider(String dataProviderID) { if (delegate != null) { return delegate.containsDataProvider(dataProviderID); } return false; } /** * @see com.servoy.j2db.dataprocessing.IFoundSetInternal#getDataProviderValue(String, boolean) */ public Object getDataProviderValue(String dataProviderID) { if (delegate != null) { Object retval = delegate.getDataProviderValue(dataProviderID); return retval; } return null; } public Object setDataProviderValue(String dataProviderID, Object value) //used by globals { //ignore return null; } /** * @see com.servoy.j2db.dataprocessing.IFoundSetInternal#setStateToStringDataProviderID(String) */ public void setRecordToStringDataProviderID(String dataProviderID) { // ignore } /** * @see com.servoy.j2db.dataprocessing.IFoundSetInternal#getStateToStringDataProviderID() */ public String getRecordToStringDataProviderID() { return null; } public int getSelectedIndex() { return -1; } /** * Sets the selectedRow. * * @param selectedRow The selectedRow to set */ public void setSelectedIndex(int selectedRow) { // ignore } @Override public int getRecordIndex(String pkHash, int hintStart) { // ignore return -1; } public boolean isRecordEditable(int rowIndex) { return false; } public boolean isInFindMode() { return false; } public boolean find() { return false; } @Override public int search() throws Exception { return 0; } public static class PrintState extends Record { PrintState(IFoundSetInternal parent, Row columndata) { super(parent, columndata); } private IFoundSetInternal delegate; public void setDelegate(IFoundSetInternal d) { this.delegate = d; } /* * _____________________________________________________________ Related states implementation,instead of normal state cache and prevent lookup for * printing (==needed) */ private final Map<String, IFoundSetInternal> relatedStates = new HashMap<String, IFoundSetInternal>(3); //relationID -> subState /** * Get related foundset, relationName may be multiple-levels deep */ @Override public IFoundSetInternal getRelatedFoundSet(String relationName, List<SortColumn> defaultSortColumns) { if (relationName == null) return null; ((SubSummaryFoundSet)parent).setDelegate(delegate); IFoundSetInternal sub = relatedStates.get(relationName); if (sub == null) { try { sub = parent.getRelatedFoundSet(this, relationName, defaultSortColumns); if (sub != null) { relatedStates.put(relationName, sub); } } catch (Exception ex) { Debug.error(ex); } } return sub; } //prevent any value to be set causes index out of bounds (sets occurs if calc value not up-to-date) @Override public Object setValue(String dataProviderID, Object value) { return null;//ignore } //prevent any value to be set causes index out of bounds (sets occurs if calc value not up-to-date) @Override public Object setValue(String dataProviderID, Object value, boolean checkIsEditing) { return null; } private final Map<String, Object> dataproviderValueCache = new HashMap<String, Object>();//especially to cache aggregate results! @Override public Object getValue(String dataProviderID, boolean converted) { ((SubSummaryFoundSet)parent).setDelegate(delegate); Object retval = dataproviderValueCache.get(dataProviderID); if (retval == null && !dataproviderValueCache.containsKey(dataProviderID)) { int columnIndex = parent.getSQLSheet().getColumnIndex(dataProviderID); if (columnIndex != -1) { //subsumfoundset has different sqlsheet with different column indexes retval = getRawData().getRawValue(columnIndex, true); // does not use a converter } else { retval = super.getValue(dataProviderID, converted); // I think this could be directly replaced same as below - delegate.getRecord(0).getValue(dataProviderID); - except for hardcoded dataprovider strings as seen in Record.getValue() and then we can get rid of all the method overrides that want to avoid ArrayIndexOutOfBounds exceptions } if (retval == null) retval = delegate.getRecord(0).getValue(dataProviderID, converted); dataproviderValueCache.put(dataProviderID, retval); } return retval; } public void setDelegate(IRecordInternal rec)//to make calcs work { parent = rec.getParentFoundSet(); } /** * */ public void doAggregatesLookup() { ((SubSummaryFoundSet)parent).setDelegate(delegate); String[] aggregates = parent.getSQLSheet().getAggregateNames(); for (String element : aggregates) { getValue(element);//cache them all in this printstate } } @Override public String toString() { return "PrintState " + super.toString(); } } /** * @see com.servoy.j2db.dataprocessing.IFoundSetInternal#getTable() */ public ITable getTable() { return sheet.getTable(); } public void addParent(IRecordInternal record) { // no aggregates support in printing...?? } public void fireAggregateChangeWithEvents(IRecordInternal record) { // no aggregates support in printing...?? } public List<SortColumn> getSortColumns() { return null; } public PrototypeState getPrototypeState() { return new PrototypeState(this); } public void clear() { // ignore } @Deprecated public void makeEmpty() { clear(); } public void addFoundSetEventListener(IFoundSetEventListener l) { // ignore } public void removeFoundSetEventListener(IFoundSetEventListener l) { // ignore } public void addAggregateModificationListener(IModificationListener listener) { // ignore } public void removeAggregateModificationListener(IModificationListener listener) { // ignore } public String[] getDataProviderNames(int type) { // ignore return null; } public String getDataSource() { return DataSourceUtils.createDBTableDataSource(sheet.getServerName(), sheet.getTable().getName()); } public boolean hadMoreRows() { return data.hadMoreRows(); } public void sort(Comparator<Object[]> recordPKComparator) { } public boolean loadByQuery(IQueryBuilder query) throws ServoyException { return false; } public IQueryBuilder getQuery() { return null; } @Override public Object forEach(IRecordCallback callback) { return null; } @Override public Iterator<IRecord> iterator() { return null; } /* * (non-Javadoc) * * @see com.servoy.j2db.dataprocessing.IFoundSet#setMultiSelect(boolean) */ @Override public void setMultiSelect(boolean multiSelect) { // TODO Auto-generated method stub } @Override public boolean isMultiSelect() { return false; } @Override public void setSelectedIndexes(int[] indexes) { // ignore } @Override public int[] getSelectedIndexes() { return new int[0]; } }