/* * Copyright 2012 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kie.workbench.common.widgets.decoratedgrid.client.widget; import org.kie.workbench.common.widgets.decoratedgrid.client.widget.data.Coordinate; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; /** * This is a wrapper around a value. The wrapper provides additional information * required to use the vanilla value in a Decision Table with merge * capabilities. One coordinate is maintained and two indexes to map to and from * HTML table coordinates. The indexes used to be maintained in SelectionManager * however it required two more N x N collections of "mapping" objects in * addition to that containing the actual data. The coordinate represents the * physical location of the cell on an (R, C) grid. One index maps the physical * coordinate of the cell to the logical coordinate of the HTML table whilst the * other index maps from the logical coordinate to the physical cell. For * example, given data (0,0), (0,1), (1,0) and (1,1) with cell at (0,0) merged * into (1,0) only the HTML coordinates (0,0), (0,1) and (1,0) exist; with * physical coordinates (0,0) and (1,0) relating to HTML coordinate (0,0) which * has a row span of 2. Therefore physical cells (0,0) and (1,0) have a * <code>mapDataToHtml</code> coordinate of (0,0) whilst physical cell (1,0) has * a <code>mapHtmlToData</code> coordinate of (1,1). * @param <T> The data-type of the value */ public class CellValue<T extends Comparable<T>> implements Comparable<CellValue<T>> { //Possible states of the cell public static enum CellState { SELECTED, GROUPED, OTHERWISE } /** * A grouped cell, containing a list of grouped cells. If a cell spanning * three rows is grouped, the normal CellValue is replaced with a * GroupedCellValue (within a GroupedDynamicDataRow). In addition to the * GroupedCellValue's value the new GroupedCellValue contains a list of the * original three cells. */ public class GroupedCellValue extends CellValue<T> { //Grouped cells private List<CellValue<T>> groupedCells = new ArrayList<CellValue<T>>(); /** * Constructor, nothing to see here, move on * @param value * @param row * @param col */ public GroupedCellValue( T value ) { super( value ); } /** * Add a cell to the group of cells * @param cell */ public void addCellToGroup( CellValue<T> cell ) { this.groupedCells.add( cell ); } /** * Ensure the children (or grouped) Cells' State reflects the parent * Grouped Cell's State change */ @Override public void addState( CellState state ) { for ( CellValue<T> cell : this.groupedCells ) { cell.addState( state ); } super.addState( state ); } /** * Does this grouped cell contain multiple values * @return */ public boolean hasMultipleValues() { return checkForMultipleValues(); } /** * Ensure the children (or grouped) Cells' State reflects the parent * Grouped Cell's State change */ @Override public void removeState( CellState state ) { for ( CellValue<T> cell : this.groupedCells ) { cell.removeState( state ); } super.removeState( state ); } /** * Ensure the children (or grouped) Cells' value reflects the parent * Grouped Cell's value change */ @Override public void setValue( Object value ) { for ( CellValue<T> cell : this.groupedCells ) { cell.setValue( value ); } super.setValue( value ); } //Check whether the cell contains multiple values private boolean checkForMultipleValues() { boolean hasMultipleValues = false; T value1 = super.getValue(); for ( CellValue<T> cell : this.groupedCells ) { if ( cell instanceof CellValue.GroupedCellValue ) { GroupedCellValue gcv = (GroupedCellValue) cell; hasMultipleValues = hasMultipleValues || gcv.checkForMultipleValues(); } T value2 = cell.getValue(); hasMultipleValues = hasMultipleValues || !equalOrNull( value1, value2 ); } return hasMultipleValues; } /** * Get the list of cells grouped * @return */ List<CellValue<T>> getGroupedCells() { return this.groupedCells; } } private T value; private int rowSpan = 1; private Coordinate coordinate = new Coordinate(); private Coordinate mapHtmlToData = new Coordinate(); private Coordinate mapDataToHtml = new Coordinate(); private EnumSet<CellState> state = EnumSet.noneOf( CellState.class ); public CellValue( T value ) { this.value = value; } public void addState( CellState state ) { this.state.add( state ); } // Used for sorting public int compareTo( CellValue<T> cv ) { if ( this.value == null ) { if ( cv.value == null ) { return 0; } return 1; } else { if ( cv.value == null ) { return -1; } } return this.value.compareTo( cv.value ); } /** * Convert a CellValue into a GroupedCellValue object * @return */ public GroupedCellValue convertToGroupedCell() { GroupedCellValue groupedCell = new GroupedCellValue( this.getValue() ); if ( this.isOtherwise() ) { groupedCell.addState( CellState.OTHERWISE ); } return groupedCell; } @Override @SuppressWarnings("rawtypes") // Used by calls to DynamicDataRow.equals() public boolean equals( Object obj ) { if ( obj == null ) { return false; } if ( !( obj instanceof CellValue ) ) { return false; } CellValue that = (CellValue) obj; return equalOrNull( this.value, that.value ) && this.rowSpan == that.rowSpan && equalOrNull( this.coordinate, that.coordinate ) && equalOrNull( this.mapHtmlToData, that.mapHtmlToData ) && equalOrNull( this.mapDataToHtml, that.mapDataToHtml ) && this.state.equals( that.state ); } public Coordinate getCoordinate() { return this.coordinate; } public Coordinate getHtmlCoordinate() { return new Coordinate( this.mapDataToHtml ); } public Coordinate getPhysicalCoordinate() { return new Coordinate( this.mapHtmlToData ); } public int getRowSpan() { return this.rowSpan; } public T getValue() { return this.value; } @Override // Used by calls to DynamicDataRow.equals() public int hashCode() { int hash = 1; hash = hash * 31 + ( value == null ? 0 : value.hashCode() ); hash = ~~hash; hash = hash * 31 + rowSpan; hash = ~~hash; hash = hash * 31 + ( coordinate == null ? 0 : coordinate.hashCode() ); hash = ~~hash; hash = hash * 31 + ( mapHtmlToData == null ? 0 : mapHtmlToData.hashCode() ); hash = ~~hash; hash = hash * 31 + ( mapDataToHtml == null ? 0 : mapDataToHtml.hashCode() ); hash = ~~hash; hash = hash * 31 + state.hashCode(); hash = ~~hash; return hash; } public boolean isEmpty() { return this.value == null; } public boolean isGrouped() { return this.state.contains( CellState.GROUPED ); } public boolean isOtherwise() { return this.state.contains( CellState.OTHERWISE ); } public boolean isSelected() { return this.state.contains( CellState.SELECTED ); } public void removeState( CellState state ) { this.state.remove( state ); } public void setCoordinate( Coordinate coordinate ) { if ( coordinate == null ) { throw new IllegalArgumentException( "Coordinate cannot be null." ); } this.coordinate = coordinate; } public void setHtmlCoordinate( Coordinate c ) { if ( c == null ) { throw new IllegalArgumentException( "Coordinate cannot be null." ); } this.mapDataToHtml = c; } public void setPhysicalCoordinate( Coordinate c ) { if ( c == null ) { throw new IllegalArgumentException( "Coordinate cannot be null." ); } this.mapHtmlToData = c; } public void setRowSpan( int rowSpan ) { if ( rowSpan < 0 ) { throw new IllegalArgumentException( "rowSpan cannot be less than zero." ); } this.rowSpan = rowSpan; } @SuppressWarnings("unchecked") public void setValue( Object value ) { this.value = (T) value; } //Check whether two values are equal or both null private boolean equalOrNull( Object o1, Object o2 ) { if ( o1 == null && o2 == null ) { return true; } if ( o1 != null && o2 == null ) { return false; } if ( o1 == null && o2 != null ) { return false; } return o1.equals( o2 ); } }