package org.netxilia.spi.impl.formula; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.Future; import org.netxilia.api.concurrent.IFutureListener; import org.netxilia.api.exception.EvaluationException; import org.netxilia.api.exception.NetxiliaBusinessException; import org.netxilia.api.exception.NotFoundException; import org.netxilia.api.exception.StorageException; import org.netxilia.api.formula.Formula; import org.netxilia.api.formula.FormulaParsingException; import org.netxilia.api.formula.IPreloadedFormulaContext; import org.netxilia.api.impl.model.FutureListenerWithUser; import org.netxilia.api.impl.user.ISpringUserService; import org.netxilia.api.model.AbsoluteAlias; import org.netxilia.api.model.Alias; import org.netxilia.api.model.CellData; import org.netxilia.api.model.ISheet; import org.netxilia.api.model.SheetData; import org.netxilia.api.reference.AreaReference; import org.netxilia.api.reference.CellReference; import org.netxilia.api.utils.Matrix; import org.netxilia.api.value.ErrorValueType; import org.netxilia.api.value.IGenericValue; import org.netxilia.spi.formula.IFormulaParser; /** * This context is used by the formula actor to return the values needed in the evaluations. The values are pre-filled * by browsing the formula's tree before evaluating the formula. * * @author <a href='mailto:ax.craciun@gmail.com'>Alexandru Craciun</a> * */ public class AsyncPreloadedFormulaContextImpl implements IPreloadedFormulaContext { private static org.apache.log4j.Logger log = org.apache.log4j.Logger .getLogger(AsyncPreloadedFormulaContextImpl.class); private final ISheet sheet; private final CellReference cell; private final Formula formula; private final boolean loadValues; private final Map<AreaReference, Matrix<CellData>> areaValues = new HashMap<AreaReference, Matrix<CellData>>(); private final Map<AbsoluteAlias, AreaReference> aliases = new HashMap<AbsoluteAlias, AreaReference>(); private final IFutureListener<SheetData> receiveAliasesListener; private final IFutureListener<Matrix<CellData>> receiveCellsListener; private final IFormulaParser formulaParser; private final Executor executor; private List<AbsoluteAlias> toLoadAliases; private List<AreaReference> toLoadReferences; private Runnable callWhenDone; public AsyncPreloadedFormulaContextImpl(ISheet sheet, CellReference cell, Formula formula, boolean loadValues, Executor executor, IFormulaParser formulaParser, ISpringUserService userService) { this.cell = cell; this.sheet = sheet; this.formula = formula; this.executor = executor; this.formulaParser = formulaParser; this.loadValues = loadValues; this.receiveAliasesListener = new FutureListenerWithUser<SheetData>(userService, new IFutureListener<SheetData>() { @Override public void ready(Future<SheetData> future) { try { receivedAliases(future.get()); } catch (Exception e) { log.error("Cannot get value:" + e, e); } } }); this.receiveCellsListener = new FutureListenerWithUser<Matrix<CellData>>(userService, new IFutureListener<Matrix<CellData>>() { @Override public void ready(Future<Matrix<CellData>> future) { try { receivedCells(future.get()); } catch (Exception e) { log.error("Cannot get aliases:" + e, e); } } }); } public void load(Runnable callWhenDone) throws StorageException, NotFoundException, FormulaParsingException { this.callWhenDone = callWhenDone; if (formula == null) { toLoadAliases = Collections.emptyList(); } else { toLoadAliases = formulaParser.getAliases(formula, this); } loadNextData(); } private void loadNextData() { try { if (toLoadAliases.size() > 0) { AbsoluteAlias alias = toLoadAliases.get(0); getSheet(alias.getSheetName()).receiveSheet().addListener(receiveAliasesListener, executor); return; } if (loadValues && formula != null) { if (toLoadReferences == null) { try { toLoadReferences = formulaParser.getDependencies(formula, this); } catch (FormulaParsingException e) { // should not arrive as the formula was already parsed previously; } } if (toLoadReferences.size() > 0) { AreaReference ref = toLoadReferences.get(0); getSheet(ref.getSheetName()).receiveCells(ref).addListener(receiveCellsListener, executor); return; } } } catch (Exception e) { if (toLoadAliases.size() > 0) { toLoadAliases.remove(0); } else if (toLoadReferences.size() > 0) { toLoadReferences.remove(0); } loadNextData(); } // call done callWhenDone.run(); } private void receivedAliases(SheetData sheetData) { toLoadAliases.remove(0); for (Map.Entry<Alias, AreaReference> entry : sheetData.getAliases().entrySet()) { aliases.put(new AbsoluteAlias(sheetData.getName(), entry.getKey()), entry.getValue()); } loadNextData(); } private void receivedCells(Matrix<CellData> cells) { AreaReference ref = toLoadReferences.remove(0); areaValues.put(ref, cells); loadNextData(); } @Override public CellReference getCell() { return cell; } private ISheet getSheet(String sheetName) throws StorageException, NotFoundException { if (sheetName == null || sheetName.equals(sheet.getName())) { return sheet; } return sheet.getWorkbook().getSheet(sheetName); } @Override public AreaReference resolveAlias(AbsoluteAlias alias) { AreaReference ref = aliases.get(alias); if (ref == null) { return null; } // ref may not have the sheet name set. make sure the area reference is absolute return ref.withSheetName(alias.getSheetName()); } @Override public ISheet getSheet() { return sheet; } @Override public IGenericValue getCellValue(CellReference ref) { Matrix<CellData> matrix = areaValues.get(new AreaReference(ref)); if (matrix == null) { throw new EvaluationException(ErrorValueType.REF, new NetxiliaBusinessException("Reference " + ref + " was not found")); } return matrix.getRowCount() > 0 && matrix.getColumnCount() > 0 ? matrix.get(0, 0).getValue() : null; } @Override public Iterator<CellData> getCellIterator(AreaReference ref) { Matrix<CellData> matrix = areaValues.get(ref); return matrix != null ? matrix.iterator() : null; } }