blob: 35e95ad14fa176fa1b632ffb1b2a215d76443e81 [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.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.eclipse.epsilon.common.util.StringUtil;
import org.eclipse.epsilon.emc.spreadsheets.ISpreadsheetMetadata.SpreadsheetColumnMetadata;
import org.eclipse.epsilon.emc.spreadsheets.ISpreadsheetMetadata.SpreadsheetWorksheetMetadata;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.epsilon.eol.exceptions.models.EolModelLoadingException;
/**
* This class represents a worksheet. Worksheets are assumed to be valid only if
* they have a name. A worksheet may or may not exist in a spreadsheet (model)
* and this can be indicated through the constructor. If a worksheet does not
* exist in the spreadsheet it is expected to be created the first time is is
* accessed.
*
* @author Martins Francis
*/
public abstract class SpreadsheetWorksheet {
protected SpreadsheetModel model;
protected SpreadsheetWorksheetHeader header;
protected boolean existsInSpreadsheet;
protected String name;
protected String alias;
protected boolean dataTypeStrict;
public SpreadsheetWorksheet(final SpreadsheetModel model, final String name, final boolean existsInSpreadsheet) {
if (StringUtil.isEmpty(name)) {
String message = "Worksheet must have a name";
throw new IllegalArgumentException(message);
}
this.model = model;
this.name = name.trim();
this.alias = null;
this.existsInSpreadsheet = existsInSpreadsheet;
this.dataTypeStrict = SpreadsheetConstants.DEFAULT_WORKSHEET_DT_STRICT;
this.header = new SpreadsheetWorksheetHeader(this);
}
public SpreadsheetModel getModel() {
return this.model;
}
public SpreadsheetWorksheetHeader getHeader() {
return this.header;
}
public boolean getExistsInSpreadsheet() {
return this.existsInSpreadsheet;
}
public boolean getDoesNotExistInSpreadsheet() {
return !this.existsInSpreadsheet;
}
public String getName() {
return this.name;
}
public String getAlias() {
return this.alias;
}
public boolean isDataTypeStrict() {
return this.dataTypeStrict;
}
/**
* A worksheet can be identified by either it's name or alias.
*
* @param identifier
* @return true if identifier describes this worksheet
*/
public boolean isIdentifiablyBy(final String identifier) {
if (!StringUtil.isEmpty(identifier)) {
return this.getName().equals(identifier) || Objects.equals(this.getAlias(), identifier);
}
return false;
}
/**
* The purpose of this method is to create this worksheet in the spreadsheet
*
* @throws RuntimeException
*/
protected abstract void createInSpreadsheet();
/**
* Loads header row information - index and name - from the spreadsheet if the
* worksheet exists in the spreadsheet
*
* @throws EolModelLoadingException
*/
protected abstract void loadHeader() throws EolModelLoadingException;
/**
* Adds worksheet metadata to the worksheet
*
* @param metadata
* @throws IllegalArgumentException
*/
protected void addWorksheetMetadata(final SpreadsheetWorksheetMetadata metadata) throws IllegalArgumentException {
if (this.getName().equals(metadata.getName())) {
if (!StringUtil.isEmpty(metadata.getAlias())) {
this.alias = metadata.getAlias();
}
if (!StringUtil.isEmpty(metadata.getDataTypeStrict())) {
this.dataTypeStrict = Boolean.parseBoolean(metadata.getDataTypeStrict());
}
}
else {
final String message = "Incorrect metadata for worksheet '" + this.getName() + "'";
throw new IllegalArgumentException(message);
}
}
/**
* Gets the column with the matching identifier
*
* @param identifier
* @return column with the given identifier
*/
public SpreadsheetColumn getColumn(final String identifier) {
for (final SpreadsheetColumn column : this.getHeader().getColumns()) {
if (column.isIdentifiableBy(identifier)) {
return column;
}
}
return null;
}
/**
* Gets the header column with the matching index
*
* @param index
* @return column with the given index
*/
public SpreadsheetColumn getColumn(final int index) {
for (final SpreadsheetColumn column : this.getHeader().getColumns()) {
if (column.getIndex() == index) {
return column;
}
}
return null;
}
/**
* Adds information about an existing header column to the worksheet.
*
* @param index
* @param name
* @return SpreadsheetColumn
*/
public SpreadsheetColumn addColumn(final int index, final String name) {
final SpreadsheetColumnMetadata metadata = new SpreadsheetColumnMetadata(Integer.toString(index), name);
return this.addColumn(metadata);
}
/**
* Adds information about an existing header column to the worksheet. If the
* column described by the given metadata does not exist the column is created.
*
* @param metadata
* @return SpreadsheetColumn
*/
public SpreadsheetColumn addColumn(final SpreadsheetColumnMetadata metadata) {
final SpreadsheetColumn column = this.findOrCreateColumn(metadata);
this.attachMetadataToColumn(column, metadata);
return column;
}
private SpreadsheetColumn findOrCreateColumn(final SpreadsheetColumnMetadata metadata) {
SpreadsheetColumn column = null;
if (!StringUtil.isEmpty(metadata.getIndex())) {
column = this.getColumn(Integer.parseInt(metadata.getIndex()));
}
if (column == null && !StringUtil.isEmpty(metadata.getName())) {
column = this.getColumn(metadata.getName());
}
if (column == null) {
if (!StringUtil.isEmpty(metadata.getIndex())) {
column = this.createColumn(Integer.parseInt(metadata.getIndex()));
this.header.addColumn(column);
}
else {
final String message = "Unable to construct column from metadata '" + metadata + "'";
throw new IllegalArgumentException(message);
}
}
return column;
}
private void attachMetadataToColumn(final SpreadsheetColumn column, final SpreadsheetColumnMetadata metadata) {
if (!StringUtil.isEmpty(metadata.getName())) {
if (metadata.getName().contains(SpreadsheetConstants.HEADER_NAME_SPLIT_CHARS)) {
column.setName(this.getValueBeforeDash(metadata.getName()));
}
else {
column.setName(metadata.getName());
}
}
if (!StringUtil.isEmpty(metadata.getAlias())) {
column.setAlias(metadata.getAlias());
}
column.setDataType(SpreadsheetDataType.convert(metadata.getDataType()));
if (!StringUtil.isEmpty(metadata.getMany())) {
column.setMany(Boolean.parseBoolean(metadata.getMany()));
}
if (!StringUtil.isEmpty(metadata.getDelimiter())) {
column.setDelimiter(metadata.getDelimiter());
}
}
private String getValueBeforeDash(final String value) {
final String[] splitValue = value.split(SpreadsheetConstants.HEADER_NAME_SPLIT_CHARS);
if (splitValue.length != 0 && !StringUtil.isEmpty(splitValue[0])) {
return splitValue[0].trim();
}
return null;
}
/**
* Creates a column with the specified column index
*
* @param index
* @return column
*/
protected abstract SpreadsheetColumn createColumn(int index);
/**
* Gets all rows contained by the worksheet
*
* @return List of rows
*/
public abstract List<SpreadsheetRow> getRows();
/**
* Writes a row to the worksheet. The Map links column identifiers with desired
* cell values. Cells that do not have a value provided will contain value
* returned by {@link #getDefaultEmptyCellValue()} or the default value for the
* column's data type if worksheet is data type strict.
*
* @param parameters
* @return the newly written row
* @throws IllegalArgumentException if a column identifier could not be mapped
* to a column in the worksheet
*/
public SpreadsheetRow addRow(final Map<String, Object> parameters) {
final Map<SpreadsheetColumn, Object> rowValues = new HashMap<>();
for (final Map.Entry<String, Object> entry : parameters.entrySet()) {
final SpreadsheetColumn column = this.header.getColumn(entry.getKey());
if (column != null) {
rowValues.put(column, entry.getValue());
}
else {
final String message = String.format("Column id '%s' is unknown", entry.getKey());
throw new IllegalArgumentException(message);
}
}
return this.addRowWithValuesInColumns(rowValues);
}
/**
* Writes a row to the worksheet. The Map links columns with desired cell
* values. Cells that do not have a value provided will contain value returned
* by {@link #getDefaultEmptyCellValue()} or the default value for the column's
* data type if worksheet is data type strict.
*
* @param parameters
* @return the newly written row
*/
public SpreadsheetRow addRowWithValuesInColumns(final Map<SpreadsheetColumn, Object> parameters) {
final Map<SpreadsheetColumn, String> values = this.getValuesForEmptyRow();
for (final Map.Entry<SpreadsheetColumn, Object> entry : parameters.entrySet()) {
Object value = entry.getValue();
final boolean columnIsReferencing = this.model.getReferencesBySource(this, entry.getKey()).size() > 0;
if (columnIsReferencing) {
value = this.getValueToWriteToReferencingCell(entry.getKey(), value);
}
value = this.getValueToWriteToCell(entry.getKey(), value);
values.put(entry.getKey(), String.valueOf(value));
}
return this.insertRow(values);
}
/**
* The purpose of this method is to return a map where each column of this
* worksheet is associated with its default value.
*
* @return Map<SpreadsheetColumn, String>
*/
public Map<SpreadsheetColumn, String> getValuesForEmptyRow() {
final Map<SpreadsheetColumn, String> row = new HashMap<>();
for (final SpreadsheetColumn column : this.getHeader().getColumns()) {
String value = this.getDefaultEmptyCellValue();
if (this.isDataTypeStrict()) {
value = SpreadsheetDataType.getDefaultDTValue(column.getDataType());
}
row.put(column, value);
}
return row;
}
/**
* This method returns the default value to be written to a cell when a new
* empty row is created in a worksheet that is not enforcing data type
* strictness.
*
* @return String
*/
public abstract String getDefaultEmptyCellValue();
/**
* This method returns all values to be written to the referencing column.
*
* @param column
* @param value
* @return list of values to write
*/
private List<String> getValueToWriteToReferencingCell(final SpreadsheetColumn column, final Object value) {
final List<SpreadsheetRow> rows = SpreadsheetUtils.extractAllRowsFromObject(value);
if (rows == null || rows.isEmpty()) {
final String message = "At least one row must be provided when writing to a referencing cell";
throw new IllegalArgumentException(message);
}
final List<String> valuesToWrite = new ArrayList<>();
for (final SpreadsheetRow row : rows) {
final Set<SpreadsheetReference> references = this.model.getReferencesBySource(this, column);
for (final SpreadsheetReference reference : references) {
final boolean thisRowIsBeingReferenced = reference.getReferencedWorksheet() == row.getWorksheet();
if (thisRowIsBeingReferenced) {
valuesToWrite.addAll(row.getAllVisibleCellValuesAsIs(reference.getReferencedColumn()));
}
}
}
if (column.isNotMany() && valuesToWrite != null && !valuesToWrite.isEmpty()) {
final String oneValueToWrite = valuesToWrite.get(0);
valuesToWrite.clear();
valuesToWrite.add(oneValueToWrite);
}
return valuesToWrite;
}
/**
* This method parses the given value and prepares it to be written to the given
* column.
*
* @param column
* @param value
* @return the value to be written to the column
*/
private String getValueToWriteToCell(final SpreadsheetColumn column, final Object inputValue) {
final StringBuilder valueToWrite = new StringBuilder();
final String value = SpreadsheetUtils.convertObjectToString(column, inputValue);
if (column.isMany() && this.isDataTypeStrict()) {
for (final String splitValue : value.split(column.getDelimiter())) {
final Object castValue = SpreadsheetDataType.castColumnValue(column.getDataType(), splitValue.trim());
valueToWrite.append(castValue);
valueToWrite.append(column.getDelimiter());
}
SpreadsheetUtils.removeLast(valueToWrite, column.getDelimiter());
}
else if (this.isDataTypeStrict()) {
final Object castValue = SpreadsheetDataType.castColumnValue(column.getDataType(), value.trim());
valueToWrite.append(castValue);
}
else {
valueToWrite.append(value);
}
return valueToWrite.toString();
}
/**
* Inserts a new row in the worksheet with given values in the given columns.
*
* @param values
* @return the newly created row
*/
protected abstract SpreadsheetRow insertRow(Map<SpreadsheetColumn, String> values);
/**
* Deletes the given row from the worksheet. This method handles deletions of
* referencing rows, referenced rows and regular rows. It also takes care of
* cascading updates if specified to do so.
*
* @param row
* @throws EolRuntimeException
*/
public void deleteRow(final SpreadsheetRow row) throws EolRuntimeException {
final boolean rowBelongsToThisWorksheet = row.getWorksheet() == this;
if (rowBelongsToThisWorksheet) {
Collection<? extends SpreadsheetReference> worksheetTargetRefs = this.model.getReferencesByTarget(this);
if (worksheetTargetRefs != null && !worksheetTargetRefs.isEmpty()) {
final SpreadsheetPropertySetter setter = new SpreadsheetPropertySetter(this.model);
for (final SpreadsheetReference reference : worksheetTargetRefs) {
if (reference.isCascadingUpdates()) {
setter.editReferencedCell(row, reference.getReferencedColumn(), this.getDefaultEmptyCellValue());
}
}
}
this.removeRow(row);
}
else {
final String message = "Row " + row + " does not belong to worksheet " + this;
throw new EolRuntimeException(message);
}
}
/**
* Deletes the given row from the actual worksheet.
*
* @param row
* @throws EolRuntimeException
*/
public abstract void removeRow(SpreadsheetRow row);
/**
* Finds all rows in the worksheet whose cell, corresponding to the given
* column, has the provided value. By 'has' it means that if the cell may
* contain multiple values then if one of those values match the search value
* then the row is returned. If the cell may not contain multiple values the
* cell's value must be an exact match to the search value.
*
* @param column
* @param value
* @return List of rows whose cell contains value
*/
public List<SpreadsheetRow> findRows(final SpreadsheetColumn column, final String value) {
this.checkThatWorksheetExists();
final List<SpreadsheetRow> rows = new ArrayList<>();
for (final SpreadsheetRow row : this.getRows()) {
final List<String> cellValues = row.getAllVisibleCellValues(column);
if (cellValues.contains(value.trim())) {
rows.add(row);
}
}
return rows;
}
/**
* This method checks if this worksheet exists in the spreadsheet.
*
* @throws IllegalStateException if worksheet does not exist
*/
protected void checkThatWorksheetExists() throws IllegalStateException {
if (this.getDoesNotExistInSpreadsheet()) {
final String message = this.getNonexistentWorksheetMessage();
throw new IllegalStateException(message);
}
}
protected String getNonexistentWorksheetMessage() {
return String.format("Worksheet '%s' does not exist in spreadsheet '%s'", this.getName(), this.model.getName());
}
@Override
public String toString() {
final StringBuilder stringObject = new StringBuilder("\n" + this.getName());
stringObject.append(" [");
if (!StringUtil.isEmpty(this.getAlias())) {
stringObject.append("alias='" + this.getAlias() + "', ");
}
stringObject.append("existsInSpreadsheet='" + this.existsInSpreadsheet + "' ")
.append("dataTypeStrict='" + this.dataTypeStrict + "' ");
for (SpreadsheetColumn column : this.getHeader().getColumns()) {
stringObject.append(column.toString());
}
stringObject.append("]\n");
return stringObject.toString();
}
}