/******************************************************************************* * * Copyright 2010 Alexandru Craciun, and individual contributors as indicated * by the @authors tag. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. ******************************************************************************/ package org.netxilia.api.impl.dependencies; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.netxilia.api.command.IMoreCellCommands; import org.netxilia.api.event.IWorkbookEventListener; import org.netxilia.api.event.SheetEvent; import org.netxilia.api.exception.NotFoundException; import org.netxilia.api.exception.StorageException; import org.netxilia.api.formula.Formula; import org.netxilia.api.formula.IFormulaContext; import org.netxilia.api.model.AbsoluteAlias; import org.netxilia.api.model.Alias; import org.netxilia.api.model.ISheet; import org.netxilia.api.model.IWorkbook; import org.netxilia.api.reference.AreaReference; import org.netxilia.api.reference.CellReference; import org.netxilia.api.user.AclPrivilegedMode; import org.netxilia.spi.formula.IFormulaParser; import org.springframework.util.Assert; /** * * @author <a href='mailto:ax.craciun@gmail.com'>Alexandru Craciun</a> * */ public class WorkbookAliasDependencyManager implements IWorkbookEventListener { private static org.apache.log4j.Logger log = org.apache.log4j.Logger .getLogger(WorkbookAliasDependencyManager.class); private final ConcurrentMap<String, SheetAliasDependencyManager> sheetDependencyManagers = new ConcurrentHashMap<String, SheetAliasDependencyManager>(); private final IWorkbook workbook; private final IFormulaParser formulaParser; private final IMoreCellCommands moreCellCommands; private WorkbookAliasDependencyManager(IWorkbook workbook, IFormulaParser formulaParser, IMoreCellCommands moreCellCommands) { this.workbook = workbook; this.formulaParser = formulaParser; this.moreCellCommands = moreCellCommands; } /** * sets a new formula in the given cell. If the formula contains a reference to an alias, it will be added to the * internal structure. * * @param ref * @param formula * @throws StorageException * @throws NotFoundException */ public synchronized void setAliasDependencies(Formula formula, IFormulaContext context) throws StorageException, NotFoundException { getManagerForSheet(context.getCell().getSheetName()).setAliasDependencies(formula, context); } /** * * @param sheetName * @param alias * @return all the cells that reference the given alias in the given sheet. * @throws StorageException * @throws NotFoundException */ public synchronized Collection<AreaReference> getAliasDependants(String sheetName, AbsoluteAlias alias) throws StorageException, NotFoundException { return getManagerForSheet(sheetName).getAliasDependants(alias); } public synchronized SheetAliasDependencyManager getManagerForSheet(String sheetName) throws StorageException, NotFoundException { SheetAliasDependencyManager mgr = sheetDependencyManagers.get(sheetName); if (mgr == null) { ISheet sheet = workbook.getSheet(sheetName); SheetAliasDependencyManager newMgr = SheetAliasDependencyManager.newInstance(this, sheet, formulaParser); mgr = sheetDependencyManagers.putIfAbsent(sheetName, newMgr); if (mgr == null) { mgr = newMgr; } } return mgr; } public void deleteSheet(String sheetName) throws NotFoundException { // make a diff between the stored version of aliases and the new list SheetAliasDependencyManager sheetMgr = sheetDependencyManagers.remove(sheetName); if (sheetMgr == null) { return; } boolean wasPrivileged = false; try { wasPrivileged = AclPrivilegedMode.set(); for (SheetAliasDependencyManager mgr : sheetDependencyManagers.values()) { List<AreaReference> affectedAreas = new ArrayList<AreaReference>(); // new aliases are not taken into account - they affect no formula // deleted aliases are taken into account for (Alias alias : sheetMgr.getAliases().keySet()) { affectedAreas.addAll(mgr .getAliasDependants(new AbsoluteAlias(sheetMgr.getSheet().getName(), alias))); } List<CellReference> refs = new ArrayList<CellReference>(); for (AreaReference area : affectedAreas) { for (CellReference ref : area) { refs.add(ref); } } mgr.getSheet().sendCommandNoUndo(moreCellCommands.refresh(refs, false)); } } finally { if (!wasPrivileged) { AclPrivilegedMode.clear(); } } } @Override public void onDeletedSheet(SheetEvent sheetEvent) { try { deleteSheet(sheetEvent.getSheetName().getSheetName()); } catch (NotFoundException e) { log.error("Cannot find sheet for while processing event :" + sheetEvent + ":" + e, e); } } @Override public void onNewSheet(SheetEvent sheetEvent) { try { getManagerForSheet(sheetEvent.getSheetName().getSheetName()); } catch (StorageException e) { log.error("Cannot load sheet for while processing event :" + sheetEvent + ":" + e, e); } catch (NotFoundException e) { log.error("Cannot find sheet for while processing event :" + sheetEvent + ":" + e, e); } } synchronized void refreshAliases(String sheetName, Collection<Alias> modified, Collection<Alias> deleted) { for (SheetAliasDependencyManager mgr : sheetDependencyManagers.values()) { List<AreaReference> affectedAreas = new ArrayList<AreaReference>(); // new aliases are not taken into account - they affect no formula // deleted aliases are taken into account for (Alias alias : deleted) { affectedAreas.addAll(mgr.getAliasDependants(new AbsoluteAlias(sheetName, alias))); } // modified aliases are also taken into account for (Alias alias : modified) { affectedAreas.addAll(mgr.getAliasDependants(new AbsoluteAlias(sheetName, alias))); } List<CellReference> refs = new ArrayList<CellReference>(); for (AreaReference area : affectedAreas) { for (CellReference ref : area) { refs.add(ref); } } if (refs.size() > 0) { mgr.getSheet().sendCommandNoUndo(moreCellCommands.refresh(refs, false)); } } } public static WorkbookAliasDependencyManager newInstance(IWorkbook workbook, IFormulaParser formulaParser, IMoreCellCommands moreCellCommands) { Assert.notNull(workbook); Assert.notNull(formulaParser); Assert.notNull(moreCellCommands); WorkbookAliasDependencyManager manager = new WorkbookAliasDependencyManager(workbook, formulaParser, moreCellCommands); manager.workbook.addListener(manager); // add a listener for all existing sheets for (ISheet sheet : workbook.getSheets()) { try { manager.getManagerForSheet(sheet.getName()); } catch (StorageException e) { log.error("Could not register depedency manager for sheet: " + sheet.getName() + ":" + e, e); } catch (NotFoundException e) { log.error("Could not register depedency manager for sheet: " + sheet.getName() + ":" + e, e); } } return manager; } }