/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package photoSpreadTable;
import inputOutput.XMLProcessor;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.Map.Entry;
import javax.swing.JOptionPane;
import photoSpread.PhotoSpreadException;
import photoSpread.PhotoSpreadException.BadUUIDStringError;
import photoSpread.PhotoSpreadException.FormulaError;
import photoSpread.PhotoSpreadException.FormulaSyntaxError;
import photoSpreadLoaders.PhotoSpreadFileImporter;
import photoSpreadObjects.PhotoSpreadObject;
import photoSpreadObjects.PhotoSpreadStringObject;
import photoSpreadParser.photoSpreadExpression.PhotoSpreadConstantExpression;
import photoSpreadParser.photoSpreadExpression.PhotoSpreadExpression;
import photoSpreadParser.photoSpreadNormalizedExpression.PhotoSpreadNormalizedExpression;
import photoSpreadTable.DnDSupport.PhotoSpreadCellFlavor;
import photoSpreadTable.DnDSupport.PhotoSpreadFileListFlavor;
import photoSpreadUtilities.CellCoordinates;
import photoSpreadUtilities.Const;
import photoSpreadUtilities.MetadataIndexer;
import photoSpreadUtilities.Misc;
import photoSpreadUtilities.ObjectUniquenessReference;
import photoSpreadUtilities.PhotoSpreadComparatorFactory;
import photoSpreadUtilities.PhotoSpreadHelpers;
import photoSpreadUtilities.PhotoSpreadObjIndexerFinder;
import photoSpreadUtilities.TreeSetRandomSubsetIterable;
/**
*
* @author skandel
*/
public class PhotoSpreadCell
implements Transferable, ObjectUniquenessReference<PhotoSpreadObject> {
public static String FILEPATH = "@filename";
public static String OBJECT_ID = "@ID";
private PhotoSpreadExpression _expression;
private TreeSetRandomSubsetIterable<PhotoSpreadObject> _objects;
private Comparator<PhotoSpreadObject> _currentComparator = null;
// private PhotoSpreadComparatorFactory.MetadataComparator _metadataComparator = null;
private String _formula;
private PhotoSpreadTableModel _tableModel;
private ArrayList<PhotoSpreadCell> _dependents;
private ArrayList<PhotoSpreadCell> _references;
private PhotoSpreadNormalizedExpression _normalizedExpression;
private int _row;
private int _col;
private HashMap<PhotoSpreadObject, PhotoSpreadObject> _selectedObjects;
private MetadataIndexer _metadataIndexer = null;
/****************************************************
* Constructor(s)
*****************************************************/
public PhotoSpreadCell(PhotoSpreadTableModel tableModel, int row, int col, String formula){
this(tableModel, row, col);
setFormula(formula, Const.DO_EVAL, Const.DO_REDRAW);
tableModel.fireTableCellUpdated(row, col);
}
public PhotoSpreadCell(PhotoSpreadTableModel tableModel, int row, int col){
this._tableModel = tableModel;
this._col = col;
this._row = row;
_formula = "";
_expression = new PhotoSpreadConstantExpression("", this);
_objects = new TreeSetRandomSubsetIterable<PhotoSpreadObject>(
PhotoSpreadComparatorFactory.createPSMetadataComparator());
// Have the stored objects indexed. This is *not* an in
// index on the metadata! Just an index to ensure that
// we can identify a String object by its string.
_objects.setIndexer(new PhotoSpreadObjIndexerFinder());
_selectedObjects = new HashMap<PhotoSpreadObject, PhotoSpreadObject>();
_dependents = new ArrayList<PhotoSpreadCell>();
_references = new ArrayList<PhotoSpreadCell>();
_normalizedExpression = null;
// PhotoSpread.trace("New cell: " + this);
}
/****************************************************
* Class CSVRecord
*****************************************************/
/**
* Wrapper class around ArrayList<String>. Each element
* is the value of one column in a CSVSpreadsheet.
*
*/
public class CSVRecord extends ArrayList<String> {
private static final long serialVersionUID = 1L;
}
/****************************************************
* Class CSVSpreadsheet
*****************************************************/
/**
* Wrapper class around ArrayList<CSVRecord>. An instance
* of this class holds one spreadsheet in CSV format.
* Each element is one CSVRecord, i.e. one row of values
* in the spreadsheet.
*
*/
public class CSVSpreadsheet extends ArrayList<CSVRecord> {
private static final long serialVersionUID = 1L;
private HashSet<String> _attrs = new HashSet<String>();
/**
* Add the metadata keys of one object to an
* existing hash set of other objects' keys.
*
* @param obj PhotoSpread object whose metadata keys are to be added to the attrs set.
* @param attrs Possibly empty HashSet<String> with other metadata keys.
*/
public void addMetadataKeys (PhotoSpreadObject obj) {
_attrs.addAll(obj.getMetaDataKeySet());
}
public void addHeaderRowFromKeys () {
CSVRecord csvRecord = new CSVRecord();
// Create the header row field by field.
// First field (a.k.a. column) is always the file path to the object:
//csvRecord.add(FILEPATH);
// Second column is UUID:
//csvRecord.add(OBJECT_ID);
Iterator<String> keysIt = _attrs.iterator();
while(keysIt.hasNext()){
csvRecord.add(keysIt.next());
}
add(csvRecord);
}
/**
* Given one PhotoSpreadObject, add its metadata to
* this spreadsheet as one row (i.e. one CSVRecord).
* <b>Note</b>: You must call addMetadataKeys() first
* (repeatedly if you wish) to let this spreadsheet
* know about all of the attributes (a.k.a. metadata
* keys) that will be in this spreadsheet. You may
* also wish first to call addHeaderRowFromKeys() to
* put a header row as the first record. This call
* is not required if this spreadsheet is not to contain
* such a header row.
*
* @param obj Object whose metadata is to be added.
*/
public void addMetadata (PhotoSpreadObject obj) {
CSVRecord csvRecord = new CSVRecord();
//csvRecord.add((String) obj.toString());
//csvRecord.add((String) obj.);
//csvRecord.add(obj.getObjectID().toString());
Iterator<String> keysIt = _attrs.iterator();
while(keysIt.hasNext()){
String key = keysIt.next();
String value = obj.getMetaData(key);
if(key.equals(PhotoSpreadCell.OBJECT_ID)) value = "=\'"+value+"\'";
csvRecord.add(value);
}
add(csvRecord);
}
}
/****************************************************
* Methods
*****************************************************/
public String toString(){
//return "Cell at " + _col + " col " + getRow() + " row with formula " + _formula;
return "<Cell " + getCellAddress() + " with formula '" + _formula + "'>";
}
/**
* Obtain cell's Excel-like address.
* @return Excel-like address: A2, C5, AC149, ...
*/
public String getCellAddress() {
//
if (_col == 0) return "";
return getColumnName() + getRowOriginOne();
}
public CellCoordinates getCellCoordinates () {
return new CellCoordinates(_row, _col);
}
/**
* Returns the column name of this cell
* @return Column name (A, B, C, ...)
*/
public String getColumnName () {
// If this cell is in column 0 (the row numbers)?
if (_col == 0) return "";
return Misc.intToExcelCol(_col);
}
/**
* Returns the row integer, origin 1.
*
* @return the _row
*/
public int getRowOriginOne() {
return _row + 1; // (_row is 0-origin)
}
/**
* Returns cell's row number with origin 1.
* @return Row number (origin 1)
*/
public int getRow () {
return _row;
}
/**
* @return Column number of this cell object (origin 0)
*/
public int getColumn() {
return _col;
}
public String getFormula(){
return _formula;
}
public Comparator<PhotoSpreadObject> getComparator() {
return _currentComparator;
}
public void setMetadataIndexer(MetadataIndexer _metadataIndexer) {
this._metadataIndexer = _metadataIndexer;
}
public MetadataIndexer getMetadataIndexer() {
return _metadataIndexer;
}
public boolean hasMetadataIndexer () {
return (_metadataIndexer != null);
}
public void invalidateMetadataIndexer (
PhotoSpreadObject obj,
String attr,
String oldValue,
String newValue) {
if (_metadataIndexer != null)
_metadataIndexer.updateIndex (obj, attr, oldValue, newValue);
}
public void clearMetadataIndexer() {
if (hasMetadataIndexer())
_metadataIndexer.clear();
}
/* Andreas: added second and third parms and made evaluate() and redraw
* contingent on these new parameters. Refactored
* all callers to set these to true.
*/
public void setFormula(String value, Boolean reEvaluateCell, Boolean reDrawTable){
String savedFormula = _formula;
ArrayList<PhotoSpreadCell> savedDependencyParents = new ArrayList<PhotoSpreadCell>();
ArrayList<PhotoSpreadCell> savedReferences = new ArrayList<PhotoSpreadCell>();
_formula = value;
// Go through the list of cells that this cell
// references. On the way, take two actions:
// save the refered-to cell, and remember every
// cell that we depend on (removeDependent(this)
// returns true):
for(PhotoSpreadCell reference : _references) {
savedReferences.add(reference);
if (reference.removeDependent(this))
savedDependencyParents.add(reference);
}
clearReferences();
if (reEvaluateCell) {
// Each cell that gets
// changed by this following evaluate()
// will fire its own changed-event:
while (true) {
try {
evaluate(reDrawTable);
break;
} catch (PhotoSpreadException.FormulaError e) {
// Bad formula; either syntax or parameter values.
// Put up a dialog box that lets
// user modify the formula and click a Resubmit
// or a Cancel button. If resubmit, we do that
// by hopping up to the start of the loop. If
// cancel, we restore the cell's state to what
// it was before the change attempt:
if (offerFormulaCorrection(e)) {
getTable().getFormulaEditor().repaint();
continue;
}
else {
_formula = savedFormula;
clearReferences();
for (PhotoSpreadCell reference : savedReferences)
_references.add(reference);
for (PhotoSpreadCell dependenceParent : savedDependencyParents)
dependenceParent.addDependent(this);
break; // ... out of the while(true) loop
}
}
}
}
// Indicate that this cell's value has changed:\
if (reDrawTable)
_tableModel.fireTableCellUpdated(_row, _col);
}
private boolean offerFormulaCorrection(PhotoSpreadException.FormulaError e) {
String correctedFormula = (String) JOptionPane.showInputDialog (
getTable(), // Window to center dialog box in
e.getMessage(),
"Formula Error", // Text in the title bar
JOptionPane.ERROR_MESSAGE, // Type of msg (e.g. for the icon)
null, // icon
null, // array of selection values
_formula // initial selection value
);
// Did user cancel out?
if (correctedFormula == null)
return false;
_formula = correctedFormula;
return true;
}
public void pasteFormula(PhotoSpreadCell copiedCell){
int rowOffset = this.getRowOriginOne() - copiedCell.getRowOriginOne();
int colOffset = this._col - copiedCell._col;
String equals = "";
if(copiedCell.isFormulaCell()){
equals = "=";
}
this.setFormula(equals + copiedCell.getExpression().copyExpression(rowOffset, colOffset),
Const.DO_EVAL,
Const.DO_REDRAW);
}
public PhotoSpreadTable getTable () {
return getTableModel().getTable();
}
public void requestFocus () {
if (getTable().getSelectedCell() == this)
releaseFocus();
getTable().setColumnSelectionInterval(_col, _col);
getTable().setRowSelectionInterval(_row, _row);
}
public void releaseFocus () {
// getTable().clearSelection();
// Requesting focus on the non-focusable col 0 has
// the effect of releasing focus on whatever cell is
// selected.
getTable().setColumnSelectionInterval(0, 0);
getTable().setRowSelectionInterval(_row, _row);
}
public PhotoSpreadExpression getExpression(){
return _expression;
}
public PhotoSpreadNormalizedExpression getNormalizedExpression() {
return _normalizedExpression;
}
public void setNormalizedExpression(PhotoSpreadNormalizedExpression _normalizedExpression) {
this._normalizedExpression = _normalizedExpression;
}
public TreeSetRandomSubsetIterable<PhotoSpreadObject> getObjects() {
return _objects;
}
/**
* evaluates the cell using the cells formula and then updating any cells that refer to this cell
* @param redrawTable TODO
* @throws FormulaSyntaxError
* */
public void evaluate(boolean redrawTable) throws FormulaError{
PhotoSpreadExpression expression = _tableModel.evaluate(_formula, this);
this._expression = expression;
try {
setObjects(expression.evaluate(this));
} catch (PhotoSpreadException.IllegalArgumentException e) {
throw new RuntimeException(e.getMessage());
}
this._normalizedExpression = expression.normalize(this);
for(int i = 0; i < _dependents.size(); i++){
_dependents.get(i).evaluate(redrawTable);
}
if (redrawTable)
_tableModel.fireTableCellUpdated(_row, _col);
}
public void addDependent(PhotoSpreadCell dependent){
if ((!_dependents.contains(dependent)) &&
(dependent != this)) {
_dependents.add(dependent);
}
}
public boolean removeDependent(PhotoSpreadCell dependent){
if(_dependents.contains(dependent)){
_dependents.remove(dependent);
return true;
} else
return false;
}
public void addReference(PhotoSpreadCell reference){
if(!_references.contains(reference)){
_references.add(reference);
}
}
private void clearReferences(){
_references.clear();
}
public PhotoSpreadTableModel getTableModel(){
return this._tableModel;
}
/**
* Set this cell's objects store. This store must
* already contain an object indexer. Else we throw
* an error.
* @param _objects The new store with or without objects inside.
*/
public void setObjects(TreeSetRandomSubsetIterable<PhotoSpreadObject> objects)
throws PhotoSpreadException.IllegalArgumentException {
if (! _objects.hasIndexer())
throw new PhotoSpreadException.IllegalArgumentException(
"Cell object store must have an indexer.");
this._objects = objects;
}
public void addObject(PhotoSpreadObject object){
_objects.add(object);
}
public void addObject(File file) throws BadUUIDStringError, FileNotFoundException, IOException{
_objects.add(PhotoSpreadFileImporter.importFile(file, this));
}
public void addObjects(ArrayList<PhotoSpreadObject> toAdd){
_objects.addAll(toAdd);
}
public void addObjects(TreeSetRandomSubsetIterable<PhotoSpreadObject> toAdd) {
_objects.addAll(toAdd);
}
/**
* Add COPIES of these objects to this cell AND set those
* copieed object's home cell to this cell:
* @param toAdd
*/
public void assimilateObjects(ArrayList<PhotoSpreadObject> toAdd) {
for (PhotoSpreadObject obj : toAdd) {
PhotoSpreadObject objCopy = obj.copyObject();
objCopy.setCell(this);
_objects.add(objCopy);
}
}
/**
* Clear this cell.
*/
public void clear() {
clear(Const.DO_EVAL, Const.DO_REDRAW);
}
public void clear(boolean evaluate, boolean redraw) {
clearObjects();
_expression = new PhotoSpreadConstantExpression("", this);
_selectedObjects.clear();
// Ensure that all dependents update
// themselves, given that this cell is
// now empty:
if (evaluate)
try {
evaluate(redraw);
} catch (Exception e) {
Misc.showErrorMsgAndStackTrace(e, "");
//e.printStackTrace();
}
_dependents.clear();
_references.clear();
_normalizedExpression = null;
setFormula("", evaluate, redraw);
}
public void clearObjects(){
_objects.clear();
}
public void removeObject(PhotoSpreadObject object){
_objects.remove(object);
}
public void removeObjects(ArrayList<PhotoSpreadObject> toRemove){
_objects.removeAll(toRemove);
}
public Iterator<PhotoSpreadObject> getObjectsIterator(){
return _objects.iterator();
}
public void selectAllObjects() {
Iterator<PhotoSpreadObject> it = getObjectsIterator();
while(it.hasNext()) {
selectObject(it.next());
}
}
public void selectObject(PhotoSpreadObject object){
_selectedObjects.put(object, object);
}
public void deselectObject(PhotoSpreadObject object){
_selectedObjects.remove(object);
}
public void toggleSelectionObject(PhotoSpreadObject object){
if(_selectedObjects.containsKey(object)){
deselectObject(object);
}
else{
selectObject(object);
}
}
public void deselectAll(){
_selectedObjects.clear();
}
public ArrayList<PhotoSpreadObject> getSelectedObjects(){
ArrayList<PhotoSpreadObject> objects = new ArrayList<PhotoSpreadObject>();
Iterator<Entry<PhotoSpreadObject, PhotoSpreadObject>> it = _selectedObjects.entrySet().iterator();
while(it.hasNext()){
PhotoSpreadObject object = it.next().getKey();
objects.add(object);
}
return objects;
}
public boolean isObjectSelected(PhotoSpreadObject object){
return (_selectedObjects.containsKey(object));
}
public boolean isFormulaCell(){
return _formula.startsWith("=");
}
public boolean isObjectCollection(){
return _formula == Const.OBJECTS_COLLECTION_INTERNAL_TOKEN;
}
public PhotoSpreadCell forceObject(PhotoSpreadObject object, Boolean reEvaluateCell, Boolean reDrawTable){
return _normalizedExpression.forceObject(object, reEvaluateCell, reDrawTable);
}
/****************************************************
* Methods for DnD, Copy/Paste, and Export
*****************************************************/
/* public Object getTransferData(DataFlavor requestedFlavor) throws UnsupportedFlavorException, IOException {
return this;
}
*/
public Object getTransferData(DataFlavor requestedFlavor) throws UnsupportedFlavorException, IOException {
try {
if (requestedFlavor.getRepresentationClass().getName().equals("java.util.List"))
return getTransferData(new PhotoSpreadFileListFlavor());
if (requestedFlavor.getRepresentationClass().getName().equals("photoSpreadTable.PhotoSpreadCell"))
return getTransferData((PhotoSpreadCellFlavor) requestedFlavor);
} catch (ClassNotFoundException e) {
// let the throw below do the throwing.
}
throw new UnsupportedFlavorException(requestedFlavor);
// Can't throw this nicer msg, b/c getTransferData() must throw
// only exactly the prescribed exceptions.
/* throw new PhotoSpreadException.UnsupportedDataFlavor(
"PhotoSpread cannot deliver drag/drop data using DataFlavor '" +
requestedFlavor.getHumanPresentableName() +
".'");
*/ }
public Object getTransferData(PhotoSpreadCellFlavor requestedFlavor) throws UnsupportedFlavorException, IOException {
return this;
}
public Object getTransferData(PhotoSpreadFileListFlavor requestedFlavor) throws UnsupportedFlavorException, IOException {
// String tmpDir = System.getProperty("java.io.tmpdir");
// String imgFileSuffix = ".jpg";
// String csvFileSuffix = ".csv";
ArrayList<File> objFilesList = new ArrayList<File>();
// Go through all selected objects in this cell and
// put its file name (packaged in a File obj) into
// the result list. In addition, for each obj, generate
// a tmporary csv file containing the respective obj's
// metadata. The csv files will be deleted when the
// app exits.
Iterator<PhotoSpreadObject> it = getSelectedObjects().iterator();
while (it.hasNext()) {
// First the PhotoSpread object itself:
PhotoSpreadObject selectedObj = it.next();
String objPath = (String) selectedObj.toString();
objFilesList.add(new File(objPath));
// Now build the tmp csv file:
// **** TODO.
}
return objFilesList;
}
public DataFlavor[] getTransferDataFlavors() {
return DnDSupport.supportedImportExportFlavorsForTable;
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
return (DnDSupport.supportedImportExportFlavorsForTableArrayList.contains(flavor));
}
public String toXML() {
StringBuffer xml = new StringBuffer();
xml.append("<" + XMLProcessor.CELL_ELEMENT + ">" + System.getProperty("line.separator"));
xml.append(PhotoSpreadHelpers.getXMLElement(XMLProcessor.CELL_FORMULA_ELEMENT, _formula));
if(this._formula.equals(Const.OBJECTS_COLLECTION_INTERNAL_TOKEN)){
xml.append(PhotoSpreadHelpers.getXMLElement(XMLProcessor.OBJECTS_ELEMENT, true));
Iterator<PhotoSpreadObject> it = _objects.iterator();
while(it.hasNext()){
xml.append(it.next().toXML());
}
xml.append(PhotoSpreadHelpers.getXMLElement(XMLProcessor.OBJECTS_ELEMENT, false));
}
xml.append("</" + XMLProcessor.CELL_ELEMENT + ">" + System.getProperty("line.separator"));
return xml.toString();
}
public void toXML(BufferedWriter out){
String xmlStr = toXML();
try{
out.write(xmlStr);
}
catch(java.io.IOException e){
throw new RuntimeException(
"Could not write XML to disk. Error: " +
e.getMessage());
}
}
/**
* Generates a spreadsheet of this cell in csv
* format.
* @return ArrayList of ArrayLists. Each inner ArrayList
* is a row that holds the metadata of one object.
*/
public CSVSpreadsheet toCSV(){
CSVSpreadsheet resSheet = new CSVSpreadsheet();
// Iterator across all the cell's objects:
Iterator<PhotoSpreadObject> objectIt = _objects.iterator();
// Collect the keys for all the metadata for all objects:
while(objectIt.hasNext()){
PhotoSpreadObject nextObject = objectIt.next();
resSheet.addMetadataKeys(nextObject);
}
resSheet.addHeaderRowFromKeys ();
objectIt = _objects.iterator();
while(objectIt.hasNext())
resSheet.addMetadata(objectIt.next());
return resSheet;
}
public void doSort (
TreeSetRandomSubsetIterable<PhotoSpreadObject> newObjectSet,
Comparator<PhotoSpreadObject> comp) {
// Note: we assume here that the passed-in set already
// has an indexer installed. We ensure that in
// all callers to this method.
newObjectSet.addAll(_objects);
_objects = newObjectSet;
_currentComparator = comp;
_tableModel.fireTableCellUpdated(_row, _col);
}
public void sortObjects (Comparator<PhotoSpreadObject> comp) {
TreeSetRandomSubsetIterable<PhotoSpreadObject> newObjectSet =
new TreeSetRandomSubsetIterable<PhotoSpreadObject>();
newObjectSet.setIndexer(new PhotoSpreadObjIndexerFinder());
doSort(newObjectSet, comp);
}
public void sortObjects (PhotoSpreadComparatorFactory.MetadataComparator comp, String metadataField) {
if (metadataField != null) {
comp.addMetadataSortKey(metadataField);
comp.addMetadataSortKey("@UUID");
}
TreeSetRandomSubsetIterable<PhotoSpreadObject> newObjectSet =
new TreeSetRandomSubsetIterable<PhotoSpreadObject>(comp);
newObjectSet.setIndexer(new PhotoSpreadObjIndexerFinder());
doSort(newObjectSet, comp);
}
public void sortObjects (String metadataField) {
sortObjects(PhotoSpreadComparatorFactory.createPSMetadataComparator(), metadataField);
}
public TreeSet<String> getAllMetadataKeys () {
TreeSet<String> allMetadataAttrNames = new TreeSet<String>();
Iterator<PhotoSpreadObject> it = _objects.iterator();
while (it.hasNext()) {
PhotoSpreadObject obj = it.next();
allMetadataAttrNames.addAll(obj.getMetaDataKeySet());
}
return allMetadataAttrNames;
}
@Override
public PhotoSpreadStringObject find(String str) {
return _objects.find(str);
}
@Override
public PhotoSpreadObject find(Double dbl) {
return _objects.find(dbl);
}
@Override
public PhotoSpreadObject find(File obj) {
return _objects.find(obj);
}
@Override
public PhotoSpreadObject find(PhotoSpreadTable tbl) {
return _objects.find(tbl);
}
/**
* Given a metadataKey and a metadataValue,
* return all objects in this cell who have
* metadataValue under key metadataKey.
* This method uses a metadata indexer, if
* one is installed.
*
* @param metadataKey
* @param metadataValue
* @return HashSet of qualifying objects.
*/
public HashSet<PhotoSpreadObject> find(String metadataKey, String metadataValue) {
if (hasMetadataIndexer())
return _metadataIndexer.get(metadataKey, metadataValue);
HashSet<PhotoSpreadObject> res = new HashSet<PhotoSpreadObject>();
for (PhotoSpreadObject obj : _objects)
if (obj.getMetaData(metadataKey).equals(metadataValue))
res.add(obj);
return res;
}
}