blob: 8902b731439d1657cf23dcfd82e424e25cdf836b [file] [log] [blame]
/*********************************************************************
* Copyright (c) 2008 The University of York.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.epsilon.emc.spreadsheets;
import java.util.*;
import org.eclipse.epsilon.common.module.ModuleElement;
import org.eclipse.epsilon.eol.exceptions.EolIllegalPropertyException;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.epsilon.eol.execute.context.IEolContext;
import org.eclipse.epsilon.eol.execute.introspection.java.JavaPropertySetter;
/**
* This class allows setting values of spreadsheet row cells.
*
* @author Martins Francis
*/
public class SpreadsheetPropertySetter extends JavaPropertySetter {
protected final SpreadsheetModel model;
public SpreadsheetPropertySetter(final SpreadsheetModel model) {
this.model = model;
}
@Override
public void invoke(Object object, String property, Object value, IEolContext context) throws EolRuntimeException {
if (object instanceof Collection<?>) {
this.edit((Collection<?>) object, value, property, context);
}
else if (object instanceof SpreadsheetRow) {
this.edit((SpreadsheetRow) object, value, property, context);
}
else {
super.invoke(object, property, value, context);
}
}
/**
* Convenience method for editing a row and column.
*
* @param row
* @param column
* @param value
* @throws EolRuntimeException
*/
public void invoke(SpreadsheetRow row, SpreadsheetColumn column, Object value, ModuleElement ast, IEolContext context) throws EolRuntimeException {
invoke(row, column.getIdentifier(), value, context);
}
public void edit(Collection<?> rows, Object value, String property, IEolContext context) throws EolRuntimeException {
for (final Object row : rows) {
final SpreadsheetPropertySetter setter = new SpreadsheetPropertySetter(this.model);
setter.invoke(row, property, value, context);
}
}
public void edit(SpreadsheetRow row, Object value, String property, IEolContext context) throws EolRuntimeException {
final SpreadsheetColumn column = row.getColumn(property);
if (column == null) {
throw new EolIllegalPropertyException(row, property, context);
}
Collection<?> rowSourceRefs = row.getReferencesBySource(column);
Collection<?> rowTargetRefs = row.getReferencesByTarget(column);
if (rowSourceRefs != null && !rowSourceRefs.isEmpty()) {
this.editReferencingCell(row, column, value);
}
else if (rowTargetRefs != null && !rowTargetRefs.isEmpty()) {
this.editReferencedCell(row, column, value);
}
else {
this.editPlainCell(row, column, value);
}
}
public void editReferencingCell(final SpreadsheetRow row, final SpreadsheetColumn column, final Object value) {
final List<SpreadsheetRow> referencedRows = SpreadsheetUtils.extractAllRowsFromObject(value);
if (referencedRows.isEmpty()) {
final String message = "Referencing cell can be edited by passing one row or collection of rows";
throw new IllegalArgumentException(message);
}
final Set<SpreadsheetReference> references = row.getReferencesBySource(column);
final List<String> valuesToWrite = this.getReferencedValues(referencedRows, references);
this.editReferencingCell(row, column, valuesToWrite);
}
private List<String> getReferencedValues(final List<SpreadsheetRow> rows,
final Set<SpreadsheetReference> references) {
final List<String> values = new ArrayList<>();
for (final SpreadsheetRow row : rows) {
boolean thisRowIsReferenced = false;
for (final SpreadsheetReference reference : references) {
thisRowIsReferenced = reference.getReferencedWorksheet() == row.getWorksheet();
if (thisRowIsReferenced) {
values.addAll(row.getAllVisibleCellValues(reference.getReferencedColumn()));
}
}
if (!thisRowIsReferenced) {
throw new IllegalArgumentException("Row is not referenced");
}
}
return values;
}
private void editReferencingCell(final SpreadsheetRow row, final SpreadsheetColumn column,
final List<String> values) {
final boolean moreThanOneValue = column.isNotMany() && values.size() > 1;
if (moreThanOneValue) {
final String firstValue = values.iterator().next();
values.clear();
values.add(firstValue);
}
Collection<?> colTargetRefs = row.getReferencesByTarget(column);
if (colTargetRefs != null && !colTargetRefs.isEmpty()) {
this.editReferencedCell(row, column, values);
}
else {
row.writeVisibleCellValues(column, values);
}
}
public void editReferencedCell(final SpreadsheetRow row, final SpreadsheetColumn column, final Object value) {
final List<String> currentValues = row.getAllVisibleCellValues(column);
this.editPlainCell(row, column, value);
final List<String> newValues = row.getAllVisibleCellValues(column);
currentValues.removeAll(newValues);
final List<String> removedValues = currentValues;
final Set<SpreadsheetReference> targetReferences = row.getReferencesByTarget(column);
for (final SpreadsheetReference reference : targetReferences) {
if (reference.isCascadingUpdates()) {
this.cascadeChangesToReference(reference, row, column, removedValues, newValues);
}
}
}
private void cascadeChangesToReference(final SpreadsheetReference reference, final SpreadsheetRow row,
final SpreadsheetColumn column, final List<String> removedValues, final List<String> newCellValues) {
for (final String removedValue : removedValues) {
final List<SpreadsheetRow> referencedRowsWithValue = row.getWorksheet().findRows(column, removedValue);
if (referencedRowsWithValue != null && !referencedRowsWithValue.isEmpty()) {
continue; // another referenced row has the value thus no need to cascade it
}
final SpreadsheetWorksheet referencingWorksheet = reference.getReferencingWorksheet();
final SpreadsheetColumn referencingColumn = reference.getReferencingColumn();
final List<SpreadsheetRow> referencingRows = referencingWorksheet.findRows(referencingColumn, removedValue);
for (final SpreadsheetRow referencingRow : referencingRows) {
this.cascadeChangesToReferencingRow(referencingRow, reference, removedValue, newCellValues);
}
}
}
private void cascadeChangesToReferencingRow(final SpreadsheetRow referencingRow,
final SpreadsheetReference reference, final String removedValue, final List<String> newValues) {
final SpreadsheetColumn referencingColumn = reference.getReferencingColumn();
final List<String> cellValues = referencingRow.getAllVisibleCellValues(referencingColumn);
final Set<String> cellValueSet = new LinkedHashSet<>(cellValues);
cellValueSet.remove(removedValue);
// Only when reference column relationship is many to not many we can be sure
// that the new value replaces old
// and thus should be cascaded to referencing rows
final boolean knowWhichValueIsReplaced = reference.getReferencedColumn().isNotMany();
if (knowWhichValueIsReplaced) {
cellValueSet.addAll(newValues);
}
this.editReferencingCell(referencingRow, referencingColumn, new ArrayList<>(cellValueSet));
}
public void editPlainCell(final SpreadsheetRow row, final SpreadsheetColumn column, final Object newCellValues) {
List<String> valuesToWrite = new ArrayList<>();
if (column.isMany()) {
if (newCellValues instanceof Collection<?>) {
valuesToWrite.addAll(SpreadsheetUtils.convertObjectToList(newCellValues));
}
else {
valuesToWrite.addAll(Arrays.asList(String.valueOf(newCellValues).split(column.getDelimiter())));
}
}
else {
valuesToWrite.add(SpreadsheetUtils.convertObjectToString(column, newCellValues));
}
row.writeVisibleCellValues(column, valuesToWrite);
}
}