/*******************************************************************************
*
* 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;
}
}