org.netxilia.api.impl.model.SheetActor.java Source code

Java tutorial

Introduction

Here is the source code for org.netxilia.api.impl.model.SheetActor.java

Source

/*******************************************************************************
 * 
 * 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.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.Future;

import org.netxilia.api.command.BlockCellCommandBuilder;
import org.netxilia.api.command.CellCommands;
import org.netxilia.api.command.ICellCommand;
import org.netxilia.api.command.IColumnCommand;
import org.netxilia.api.command.IMoreCellCommands;
import org.netxilia.api.command.IRowCommand;
import org.netxilia.api.command.ISheetCommand;
import org.netxilia.api.command.RowCommands;
import org.netxilia.api.concurrent.IFutureListener;
import org.netxilia.api.concurrent.IListenableFuture;
import org.netxilia.api.event.CellEvent;
import org.netxilia.api.event.CellEventType;
import org.netxilia.api.event.ColumnEvent;
import org.netxilia.api.event.ColumnEventType;
import org.netxilia.api.event.ISheetEventListener;
import org.netxilia.api.event.RowEvent;
import org.netxilia.api.event.RowEventType;
import org.netxilia.api.event.SheetEvent;
import org.netxilia.api.event.SheetEventType;
import org.netxilia.api.exception.AbandonedCalculationException;
import org.netxilia.api.exception.NetxiliaBusinessException;
import org.netxilia.api.exception.NotFoundException;
import org.netxilia.api.exception.StorageException;
import org.netxilia.api.formula.CyclicDependenciesException;
import org.netxilia.api.formula.Formula;
import org.netxilia.api.formula.FormulaParsingException;
import org.netxilia.api.formula.IPreloadedFormulaContext;
import org.netxilia.api.formula.IPreloadedFormulaContextFactory;
import org.netxilia.api.impl.IExecutorServiceFactory;
import org.netxilia.api.impl.concurrent.MutableFutureWithCounter;
import org.netxilia.api.impl.user.ISpringUserService;
import org.netxilia.api.model.CellCreator;
import org.netxilia.api.model.CellData;
import org.netxilia.api.model.CellDataWithProperties;
import org.netxilia.api.model.ColumnData;
import org.netxilia.api.model.ISheet;
import org.netxilia.api.model.IWorkbook;
import org.netxilia.api.model.RowData;
import org.netxilia.api.model.SheetData;
import org.netxilia.api.model.SheetDimensions;
import org.netxilia.api.model.SheetFullName;
import org.netxilia.api.model.SheetType;
import org.netxilia.api.model.SortSpecifier;
import org.netxilia.api.model.SortSpecifier.SortColumn;
import org.netxilia.api.reference.AreaReference;
import org.netxilia.api.reference.CellReference;
import org.netxilia.api.reference.IReferenceTransformer;
import org.netxilia.api.reference.Range;
import org.netxilia.api.reference.ReferenceTransformers;
import org.netxilia.api.utils.Matrix;
import org.netxilia.api.utils.MatrixBuilder;
import org.netxilia.api.utils.Pair;
import org.netxilia.api.value.IGenericValue;
import org.netxilia.spi.formula.IFormulaCalculator;
import org.netxilia.spi.formula.IFormulaCalculatorFactory;
import org.netxilia.spi.formula.IFormulaParser;
import org.netxilia.spi.storage.ISheetStorageService;
import org.springframework.util.Assert;

public class SheetActor {
    private static org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(SheetActor.class);

    enum SheetStatus {
        notInitiliazed, initializing, ready
    }

    private ISheet myReference;

    private final IFormulaCalculatorFactory formulaCalculatorFactory;

    private final IFormulaParser formulaParser;

    private final ISheetStorageService storageService;

    private final SheetEventSupport eventSupport;

    private final Workbook workbook;

    private final SheetFullName name;
    private final SheetType type;

    private boolean refreshEnabled = true;

    private SheetStatus status = SheetStatus.notInitiliazed;

    private final ISpringUserService userService;

    private final IMoreCellCommands moreCellCommands;

    private final IPreloadedFormulaContextFactory preloadedContextFactory;

    public SheetActor(Workbook workbook, SheetData sheetData, IFormulaCalculatorFactory formulaCalculatorFactory,
            IFormulaParser formulaParser, ISheetStorageService storageService,
            IExecutorServiceFactory executorServiceFactory, ISpringUserService userService,
            IMoreCellCommands moreCellCommands, IPreloadedFormulaContextFactory preloadedContextFactory) {
        Assert.notNull(workbook);
        Assert.notNull(sheetData);
        Assert.notNull(formulaCalculatorFactory);
        Assert.notNull(formulaParser);
        Assert.notNull(storageService);
        Assert.notNull(executorServiceFactory);
        Assert.notNull(userService);
        Assert.notNull(moreCellCommands);
        Assert.notNull(preloadedContextFactory);

        this.formulaCalculatorFactory = formulaCalculatorFactory;
        this.formulaParser = formulaParser;
        this.name = sheetData.getFullName();
        this.type = sheetData.getType();
        this.workbook = workbook;
        this.storageService = storageService;
        this.userService = userService;
        this.moreCellCommands = moreCellCommands;
        this.preloadedContextFactory = preloadedContextFactory;
        eventSupport = new SheetEventSupport(this.name, executorServiceFactory);
    }

    public ISheet getReference() {
        return myReference;
    }

    public SheetType getType() {
        return type;
    }

    public IWorkbook getWorkbook() {
        return workbook;
    }

    private void init() {
        if (status == SheetStatus.ready || status == SheetStatus.initializing) {
            return;
        }
        Assert.notNull(myReference);
        status = SheetStatus.initializing;
        if (workbook.isInitializationEnabled()) {
            new SheetInitializationProcess(this, formulaParser, workbook.getDependencyManager(),
                    workbook.getAliasDependencyManager(), moreCellCommands);
        }
        status = SheetStatus.ready;
    }

    public SheetData getSheet() {
        try {
            return storageService.loadSheet();
        } catch (StorageException e) {
            throw e;
        } catch (NotFoundException e) {
            throw new StorageException(e);
        }
    }

    public SheetDimensions getDimensions() {
        try {
            return storageService.getSheetDimensions();
        } catch (StorageException e) {
            throw e;
        } catch (NotFoundException e) {
            throw new StorageException(e);
        }
    }

    public void setActorRef(ISheet proxy) {
        this.myReference = proxy;

    }

    public Matrix<CellData> getCells(AreaReference ref) {
        Assert.notNull(ref);
        Assert.isTrue(ref.getSheetName() == null || ref.getSheetName().equals(name.getSheetName()));

        init();

        try {
            return storageService.loadCells(ref);
        } catch (StorageException e) {
            throw e;
        } catch (NotFoundException e) {
            // deleted externally !?
            throw new StorageException(e);
        }
    }

    public CellData getCell(CellReference ref) {
        Assert.notNull(ref);
        Assert.isTrue(ref.getSheetName() == null || ref.getSheetName().equals(name.getSheetName()));

        init();

        Matrix<CellData> data;
        try {
            data = storageService.loadCells(new AreaReference(ref));
        } catch (StorageException e) {
            throw e;
        } catch (NotFoundException e) {
            // deleted externally !?
            throw new StorageException(e);
        }
        if (data.getColumnCount() == 0 || data.getRowCount() == 0) {
            return null;
        }
        return data.get(0, 0);
    }

    public SheetFullName getFullName() {
        return name;
    }

    public String getName() {
        return name.getSheetName();
    }

    private List<CellData> moveRow(List<CellData> cells, int fromRow, int toRow) throws FormulaParsingException {
        List<CellData> newCells = new ArrayList<CellData>(cells.size());
        for (int c = 0; c < cells.size(); ++c) {
            CellData cell = cells.get(c);
            if (cell.getFormula() == null) {
                newCells.add(cell);
            } else {
                IReferenceTransformer transformer = ReferenceTransformers.shiftCell(
                        new CellReference(name.getSheetName(), fromRow, c),
                        new CellReference(name.getSheetName(), toRow, c));
                newCells.add(cell.withFormula(formulaParser.transformFormula(cell.getFormula(), transformer)));
            }
        }
        return newCells;
    }

    /**
     * Sort the rows of this sheet using the sort specifier. It will modify the position of the rows within the sheet.
     * It generates an update modification type for each row, to only update the rows order. The cells should also
     * update their row indices.
     * 
     * TODO should have formula's stored in a relative way. Thus no modification is needed to be done to formulas when
     * sorting.
     * 
     * @param sortSpecifier
     * @throws NetxiliaBusinessException
     * @throws CyclicDependenciesException
     */
    public int sort(SortSpecifier sortSpecifier) throws CyclicDependenciesException, NetxiliaBusinessException {
        init();

        Matrix<CellData> cells = getCells(AreaReference.ALL);
        List<RowDataHolder> sortedRows = new ArrayList<RowDataHolder>(cells.getRowCount());
        for (int i = 0; i < cells.getRowCount(); ++i) {
            sortedRows.add(new RowDataHolder(cells, i));
        }
        List<RowData> rowData = getRows(Range.ALL);

        Collections.sort(sortedRows, new RowDataHolderComparator(sortSpecifier));

        int changes = 0;
        for (int i = 0; i < sortedRows.size(); ++i) {
            RowDataHolder row = sortedRows.get(i);
            if (i == row.getIndex()) {
                // nothing changed
                continue;
            }
            // replace the content of the new row
            RowData prevRowData = rowData.get(row.getIndex());
            sendCommand(RowCommands.row(Range.range(i), prevRowData.getHeight(), prevRowData.getStyles()));

            sendCommand(
                    CellCommands.row(new AreaReference(name.getSheetName(), i, 0, i, cells.getColumnCount() - 1),
                            moveRow(cells.getRow(row.getIndex()), row.getIndex(), i)));
            changes++;

        }
        return changes;
    }

    /**
     * reorder the sheet rows using the given list of changes. In each pair the first is the index of the row to be
     * moved and the second is the position where the row should arrive
     * 
     * @param rowOrders
     */
    public void reorder(Collection<Pair<Integer, Integer>> rowOrders) {
        Assert.notNull(rowOrders);
    }

    public void addListener(ISheetEventListener listener) {
        eventSupport.removeListener(listener);
        eventSupport.addListener(listener);
    }

    public void removeListener(ISheetEventListener listener) {
        eventSupport.removeListener(listener);
    }

    public ColumnData getColumn(int colIndex) {
        init();

        List<ColumnData> result = getColumns(Range.range(colIndex));
        return result.size() > 0 ? result.get(0) : null;
    }

    public List<ColumnData> getColumns(Range range) {
        init();

        try {
            return storageService.loadColumns(range);
        } catch (StorageException e) {
            throw e;
        } catch (NotFoundException e) {
            throw new StorageException(e);
        }
    }

    public RowData getRow(int rowIndex) {
        init();

        List<RowData> result = getRows(Range.range(rowIndex));
        return result.size() > 0 ? result.get(0) : null;
    }

    public List<RowData> getRows(Range range) {
        init();

        try {
            return storageService.loadRows(range);
        } catch (StorageException e) {
            throw e;
        } catch (NotFoundException e) {
            throw new StorageException(e);
        }
    }

    public void setValue(CellReference ref, IGenericValue value)
            throws NetxiliaBusinessException, CyclicDependenciesException {
        Assert.notNull(ref);
        init();

        sendCommand(CellCommands.value(new AreaReference(ref), value));
    }

    public void setFormula(CellReference ref, Formula formula)
            throws NetxiliaBusinessException, CyclicDependenciesException {
        Assert.notNull(ref);
        init();

        sendCommand(CellCommands.formula(new AreaReference(ref), formula));
    }

    private void saveCell(CellDataWithProperties dataWithProperties, ICellCommand command)
            throws StorageException, NotFoundException {
        if (dataWithProperties.getProperties().isEmpty()) {
            return;
        }
        saveCells(Collections.singletonList(dataWithProperties), command);
    }

    private void saveCells(List<CellDataWithProperties> saveCells, ICellCommand command)
            throws StorageException, NotFoundException {
        storageService.saveCells(saveCells);
        for (CellDataWithProperties saveCell : saveCells) {
            sendEvents(saveCell, command);
            command.done(myReference, saveCell);
        }
    }

    /**
     * saves the cells and send the event
     * 
     * @param newCell
     * @param properties
     * @param eventToSend
     * @throws StorageException
     * @throws NotFoundException
     */
    private void sendEvents(CellDataWithProperties dataWithProperties, ICellCommand command)
            throws StorageException, NotFoundException {

        if (!command.isStopPropagation() && refreshEnabled) {
            workbook.getDependencyManager().getManagerForSheet(name.getSheetName())
                    .propagateCellValue(dataWithProperties.getCellData(), dataWithProperties.getProperties());
        }
        if (eventSupport.hasListeners()) {
            eventSupport.fireEvent(new CellEvent(CellEventType.modified, getFullName(),
                    dataWithProperties.getCellData(), dataWithProperties.getProperties()));
        }
    }

    /**
     * This method will launch a formula calculator for the given cell
     * 
     * @param oldCell
     * @param newCellWithProps
     * @param command
     * @throws FormulaParsingException
     * @throws CyclicDependenciesException
     * @throws StorageException
     * @throws NotFoundException
     */
    private void calculateFormula(final MutableFutureWithCounter<ICellCommand> result, final CellData oldCell,
            final CellData newCell, final ICellCommand command)
            throws FormulaParsingException, CyclicDependenciesException, StorageException, NotFoundException {
        // check formula and set dependencies before
        final IPreloadedFormulaContext context = preloadedContextFactory.newPreloadAliasesContext(myReference,
                newCell.getReference(), newCell.getFormula(), myReference.getExecutor());

        context.load(new Runnable() {
            @Override
            public void run() {
                try {
                    workbook.getDependencyManager().setDependencies(newCell.getFormula(), context);
                    workbook.getAliasDependencyManager().setAliasDependencies(newCell.getFormula(), context);

                    // start actor
                    IFormulaCalculator calculator = formulaCalculatorFactory.getCalculator(myReference, newCell);
                    calculator.sendCalculate().addListener(
                            new FutureListenerWithUser<CellData>(userService, new IFutureListener<CellData>() {
                                @Override
                                public void ready(Future<CellData> future) {
                                    CellData data;
                                    CellDataWithProperties newCellWithProperties = null;
                                    try {
                                        data = future.get();
                                        Collection<CellData.Property> properties = CellData.diff(oldCell, data);
                                        newCellWithProperties = new CellDataWithProperties(data, properties);
                                        saveCell(newCellWithProperties, command);
                                        result.decrement();
                                    } catch (Exception e) {
                                        if (e.getCause() instanceof AbandonedCalculationException) {
                                            log.info("Abandoned:" + oldCell.getReference());
                                        } else {
                                            result.setException(e);
                                        }
                                    } finally {
                                        formulaCalculatorFactory.removeCalculator(myReference,
                                                newCell.getReference());
                                    }

                                }
                            }), myReference.getExecutor());
                } catch (Exception e) {
                    result.setException(e);
                    return;
                }

            }
        });

    }

    /**
     * Send the given command to storage and listeners
     * 
     * @param command
     * @return
     * @throws NetxiliaBusinessException
     * @throws CyclicDependenciesException
     */
    public IListenableFuture<ICellCommand> sendCommandNoUndo(ICellCommand command)
            throws CyclicDependenciesException, NetxiliaBusinessException {
        Assert.notNull(command);
        return sendCommand(command, false);
    }

    public IListenableFuture<ICellCommand> sendCommand(ICellCommand command)
            throws NetxiliaBusinessException, CyclicDependenciesException {
        return sendCommand(command, true);
    }

    private IListenableFuture<ICellCommand> sendCommand(ICellCommand command, boolean withUndo)
            throws NetxiliaBusinessException, CyclicDependenciesException {
        Assert.notNull(command);

        init();

        Matrix<CellData> cells = null;
        // special construction to add a new row
        if (command.getTarget().getFirstRowIndex() == CellReference.LAST_ROW_INDEX) {
            SheetDimensions dims = storageService.getSheetDimensions();
            cells = new MatrixBuilder<CellData>(new CellCreator(name.getSheetName(), dims.getRowCount(),
                    command.getTarget().getFirstColumnIndex())).setSize(1, command.getTarget().getColumnCount())
                            .build();
        } else {
            cells = getCells(command.getTarget());
            // build a matrix of the needed size if the returned matrix is smaller
            if (cells.getRowCount() < command.getTarget().getRowCount()
                    || cells.getColumnCount() < command.getTarget().getColumnCount()) {
                cells = new MatrixBuilder<CellData>(cells,
                        new CellCreator(name.getSheetName(), command.getTarget().getFirstRowIndex(),
                                command.getTarget().getFirstColumnIndex()))
                                        .setSize(command.getTarget().getRowCount(),
                                                command.getTarget().getColumnCount())
                                        .build();

            }
        }
        BlockCellCommandBuilder commandBuilder = withUndo ? new BlockCellCommandBuilder() : null;

        List<CellDataWithProperties> saveCells = new ArrayList<CellDataWithProperties>();
        MutableFutureWithCounter<ICellCommand> result = new MutableFutureWithCounter<ICellCommand>(cells.size());

        for (CellData cell : cells) {
            CellDataWithProperties newCellWithProps = command.apply(cell);
            if (newCellWithProps.getProperties().size() == 0) {
                // nothing changed
                result.decrement();
                continue;
            }

            if (newCellWithProps.getProperties().contains(CellData.Property.value)
                    && !newCellWithProps.getProperties().contains(CellData.Property.formula)
                    && newCellWithProps.getCellData().getFormula() != null) {
                // set formula to null when a value is set
                EnumSet<CellData.Property> newProps = EnumSet.copyOf(newCellWithProps.getProperties());
                newProps.add(CellData.Property.formula);
                newCellWithProps = new CellDataWithProperties(newCellWithProps.getCellData().withFormula(null),
                        newProps);
            }
            if (commandBuilder != null) {
                commandBuilder.command(CellCommands.properties(new AreaReference(cell.getReference()),
                        cell.getValue(), cell.getStyles(), cell.getFormula(), newCellWithProps.getProperties()));
            }

            if (newCellWithProps.getProperties().contains(CellData.Property.formula) && refreshEnabled) {
                // the formula changed. this needs the formula evaluation before setting the value
                calculateFormula(result, cell, newCellWithProps.getCellData(), command);
                continue;
            }

            // TODO: the storage may not be able to store one of the cells - should break all for one cell !?
            saveCells.add(newCellWithProps);
            result.decrement();
        }
        saveCells(saveCells, command);

        if (commandBuilder == null || commandBuilder.isEmpty()) {
            result.set(CellCommands.doNothing(command.getTarget()));
        } else {
            result.set(commandBuilder.build());
        }
        return result;
    }

    private List<RowData> fillRows(List<RowData> rows, int startIndex, int count) {
        List<RowData> newRows = new ArrayList<RowData>(rows);
        for (int i = 0; i < count; ++i) {
            newRows.add(new RowData(startIndex + i, 0, null));
        }
        return newRows;
    }

    private void rowEvent(RowEventType type, RowData row, Collection<RowData.Property> properties) {
        if (eventSupport.hasListeners()) {
            eventSupport.fireEvent(new RowEvent(type, getFullName(), row, properties));
        }
    }

    /**
     * Send the given command to storage and listeners
     * 
     * @param command
     * @return
     * @throws StorageException
     * @throws NetxiliaBusinessException
     */
    public IRowCommand sendCommand(IRowCommand command) throws StorageException, NetxiliaBusinessException {
        Assert.notNull(command);
        init();

        if (command.toInsert()) {
            // these are inserted rows
            List<RowData> rows = fillRows(Collections.<RowData>emptyList(), command.getTarget().getMin(),
                    command.getTarget().count());
            for (RowData row : rows) {
                RowData newRow = command.apply(row);
                Collection<RowData.Property> properties = RowData.diff(row, newRow);
                if (newRow != null) {
                    storageService.insertRow(newRow, properties);
                    workbook.getDependencyManager().getManagerForSheet(name.getSheetName())
                            .insertRow(newRow.getIndex());
                    workbook.getAliasDependencyManager().getManagerForSheet(name.getSheetName())
                            .insertRow(newRow.getIndex());
                    rowEvent(RowEventType.inserted, newRow, properties);
                } else {// the command decided not to insert the new row
                    continue;
                }
            }
            return null;
        }

        // modified or deleted rows

        List<RowData> rows = getRows(command.getTarget());
        if (!command.getTarget().equals(Range.ALL) && command.getTarget().count() != rows.size()) {
            // add needed rows
            int startInsertedRow = command.getTarget().getMin() + rows.size();
            rows = fillRows(rows, startInsertedRow, command.getTarget().count() - rows.size());
        }
        for (RowData row : rows) {
            RowData newRow = command.apply(row);
            Collection<RowData.Property> properties = RowData.diff(row, newRow);

            if (properties.size() == 0) {
                // nothing changed
                continue;
            }

            if (newRow == null) {
                storageService.deleteRow(row.getIndex());
                workbook.getDependencyManager().getManagerForSheet(name.getSheetName()).deleteRow(row.getIndex());
                workbook.getAliasDependencyManager().getManagerForSheet(name.getSheetName())
                        .deleteRow(row.getIndex());
                rowEvent(RowEventType.deleted, row, properties);
            } else {
                storageService.saveRow(newRow, properties);
                rowEvent(RowEventType.modified, newRow, properties);
            }
        }
        return null;
    }

    private List<ColumnData> fillColumns(List<ColumnData> columns, int startIndex, int count) {
        List<ColumnData> newColumns = new ArrayList<ColumnData>(columns);
        for (int i = 0; i < count; ++i) {
            newColumns.add(new ColumnData(startIndex + i, 0, null));
        }
        return newColumns;
    }

    private void columnEvent(ColumnEventType type, ColumnData column, Collection<ColumnData.Property> properties) {
        if (eventSupport.hasListeners()) {
            eventSupport.fireEvent(new ColumnEvent(type, getFullName(), column, properties));
        }
    }

    /**
     * Send the given command to storage and listeners
     * 
     * @param command
     * @return
     * @throws StorageException
     * @throws NetxiliaBusinessException
     */
    public IColumnCommand sendCommand(IColumnCommand command) throws StorageException, NetxiliaBusinessException {
        Assert.notNull(command);
        init();

        if (command.toInsert()) {
            // these are inserted rows
            List<ColumnData> columns = fillColumns(Collections.<ColumnData>emptyList(),
                    command.getTarget().getMin(), command.getTarget().count());
            for (ColumnData col : columns) {
                ColumnData newCol = command.apply(col);
                Collection<ColumnData.Property> properties = ColumnData.diff(col, newCol);
                if (newCol != null) {
                    storageService.insertColumn(newCol, properties);
                    workbook.getDependencyManager().getManagerForSheet(name.getSheetName())
                            .insertColumn(newCol.getIndex());
                    workbook.getAliasDependencyManager().getManagerForSheet(name.getSheetName())
                            .insertColumn(newCol.getIndex());
                    columnEvent(ColumnEventType.inserted, newCol, properties);
                } else {// the command decided not to insert the new row
                    continue;
                }
            }
            return null;
        }

        List<ColumnData> columns = getColumns(command.getTarget());

        if (!command.getTarget().equals(Range.ALL) && command.getTarget().count() != columns.size()) {
            // add needed columns
            int startInsertedColumn = command.getTarget().getMin() + columns.size();
            columns = fillColumns(columns, startInsertedColumn, command.getTarget().count() - columns.size());
        }

        for (ColumnData col : columns) {
            ColumnData newCol = command.apply(col);
            Collection<ColumnData.Property> properties = ColumnData.diff(col, newCol);

            if (properties.size() == 0) {
                // nothing changed
                continue;
            }

            if (newCol == null) {
                storageService.deleteColumn(col.getIndex());
                workbook.getDependencyManager().getManagerForSheet(name.getSheetName())
                        .deleteColumn(col.getIndex());
                workbook.getAliasDependencyManager().getManagerForSheet(name.getSheetName())
                        .deleteColumn(col.getIndex());
                columnEvent(ColumnEventType.deleted, col, properties);
            } else {
                storageService.saveColumn(newCol, properties);
                columnEvent(ColumnEventType.modified, newCol, properties);
            }
        }
        return null;
    }

    public ISheetCommand sendCommand(ISheetCommand command) throws StorageException, NotFoundException {
        Assert.notNull(command);
        init();

        SheetData sheet = getSheet();
        SheetData newSheet = command.apply(sheet);
        Collection<SheetData.Property> properties = SheetData.diff(sheet, newSheet);
        if (properties.size() == 0) {
            // nothing changed
            return null;
        }

        storageService.saveSheet(newSheet, properties);
        workbook.getAliasDependencyManager().getManagerForSheet(name.getSheetName()).saveSheet(newSheet,
                properties);
        eventSupport.fireEvent(new SheetEvent(SheetEventType.modified, newSheet, properties));
        return null;
    }

    /**
     * this class holds the row index while the cells are sorted.
     * 
     * @author <a href='mailto:ax.craciun@gmail.com'>Alexandru Craciun</a>
     * 
     */
    private class RowDataHolder {
        private final Matrix<CellData> cells;
        private final int index;

        public RowDataHolder(Matrix<CellData> cells, int index) {
            this.cells = cells;
            this.index = index;
        }

        public Matrix<CellData> getCells() {
            return cells;
        }

        public int getIndex() {
            return index;
        }

    }

    /**
     * compares rows according to the value in a given column
     * 
     * @author <a href='mailto:ax.craciun@gmail.com'>Alexandru Craciun</a>
     * 
     */
    public class RowDataHolderComparator implements Comparator<RowDataHolder> {
        private final SortSpecifier sortSpecifier;

        public RowDataHolderComparator(SortSpecifier sortSpecifier) {
            this.sortSpecifier = sortSpecifier;
        }

        public SortSpecifier getSortSpecifier() {
            return sortSpecifier;
        }

        private int checkNulls(Object obj1, Object obj2) {
            if (obj1 == null && obj2 == null) {
                return 0;
            }
            if (obj1 == null) {
                return -1;
            }
            if (obj2 == null) {
                return 1;
            }
            return 2;
        }

        @Override
        public int compare(RowDataHolder row1, RowDataHolder row2) {
            int result = 0;
            for (SortColumn sortColumn : sortSpecifier.getColumns()) {
                int columnIndex = CellReference.columnIndex(sortColumn.getName());
                IGenericValue cell1 = row1.getCells().get(row1.getIndex(), columnIndex).getValue();
                IGenericValue cell2 = row2.getCells().get(row2.getIndex(), columnIndex).getValue();
                result = checkNulls(cell1, cell2);
                if (result != 2) {
                    return sortColumn.getOrder() == SortSpecifier.SortOrder.ascending ? result : -result;
                }
                result = checkNulls(cell1, cell2);
                if (result != 2) {
                    return sortColumn.getOrder() == SortSpecifier.SortOrder.ascending ? result : -result;
                }
                result = cell1.compareTo(cell2);
                if (result != 0) {
                    return sortColumn.getOrder() == SortSpecifier.SortOrder.ascending ? result : -result;
                }
            }
            return 0;
        }
    }

    public void setRefreshEnabled(boolean enabled) {
        refreshEnabled = enabled;

    }

}