001    /*
002     * Created on Dec 28, 2009
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005     * in compliance with the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License
010     * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011     * or implied. See the License for the specific language governing permissions and limitations under
012     * the License.
013     *
014     * Copyright @2009-2010 the original author or authors.
015     */
016    package org.fest.swing.data;
017    
018    import static org.fest.swing.edt.GuiActionRunner.execute;
019    import static org.fest.swing.exception.ActionFailedException.actionFailure;
020    import static org.fest.util.Arrays.format;
021    import static org.fest.util.Objects.areEqual;
022    import static org.fest.util.Strings.concat;
023    
024    import javax.swing.JTable;
025    
026    import org.fest.swing.annotation.RunsInCurrentThread;
027    import org.fest.swing.annotation.RunsInEDT;
028    import org.fest.swing.cell.JTableCellReader;
029    import org.fest.swing.edt.GuiQuery;
030    import org.fest.swing.exception.ActionFailedException;
031    
032    /**
033     * Understands lookup of a cell in the first row in <code>{@link JTable}</code> whose values match the given ones.
034     * <p>
035     * Example:
036     * <pre>
037     * // import static org.fest.swing.data.TableCellInSelectedRow.row;
038     * <code>{@link TableCell}</code> cell = dialog.table("records").cell({@link TableCellInRowByValue#rowWithValue(String...) rowWithValue}("column1", "column2", "column3").column(2));
039     * </pre>
040     * </p>
041     *
042     * @author Alex Ruiz
043     *
044     * @since 1.2
045     */
046    public class TableCellInRowByValue implements TableCellFinder {
047    
048      /**
049       * Starting point for the creation of a <code>{@link TableCellInRowByValue}</code>.
050       * <p>
051       * Example:
052       * <pre>
053       * // import static org.fest.swing.data.TableCellInRowByValue.rowWithValue;
054       * TableCellByColumnId cell = rowWithValue("column1", "column2", "column3").column(3);
055       * </pre>
056       * </p>
057       * @param values the values in the cells of the row we are looking for.
058       * @return the created builder.
059       * @throws NullPointerException if the given array of values is <code>null</code>.
060       */
061      public static TableCellBuilder rowWithValue(String...values) {
062        if (values == null) throw new NullPointerException("The array of values should not be null");
063        return new TableCellBuilder(values);
064      }
065    
066      /**
067       * Understands creation of <code>{@link TableCellInSelectedRow}</code>s.
068       *
069       * @author Alex Ruiz
070       */
071      public static class TableCellBuilder {
072        private final String[] values;
073    
074        /**
075         * Creates a new </code>{@link TableCellBuilder}</code>.
076         * @param values the values of the cells of the row to find.
077         */
078        TableCellBuilder(String[] values) {
079          this.values = values;
080        }
081    
082        /**
083         * Creates a new table cell finder using the row cell values specified in
084         * <code>{@link TableCellInRowByValue#rowWithValue(String...)}</code>
085         * and the column index specified as the argument in this method.
086         * @param column the index of the column in the table cell to find.
087         * @return the created finder.
088         */
089        public TableCellInRowByValue column(int column) {
090          return new TableCellInRowByValue(values, column);
091        }
092      }
093    
094      private final String[] values;
095      private final int column;
096    
097      /**
098       * Creates a new </code>{@link TableCellInRowByValue}</code>.
099       * @param values the values in the cells of the row we are looking for.
100       * @param column the index of the column in the table cell to find.
101       */
102      protected TableCellInRowByValue(String[] values, int column) {
103        this.values = values;
104        this.column = column;
105      }
106    
107      /**
108       * Finds a cell in the given <code>{@link JTable}</code> that:
109       * <ol>
110       * <li>is located in the first row whose values match the given ones</li>
111       * <li>has a matching row index</li>
112       * </ol>
113       * @param table the target <code>JTable</code>.
114       * @param cellReader knows how to read the contents of a cell in a <code>JTable</code>.
115       * @return the cell found, if any.
116       * @throws IllegalStateException if the size of values to look up is not equal to the number of columns in the given
117       * <code>JTable</code>.
118       * @throws ActionFailedException if a matching cell could not be found.
119       */
120      @RunsInEDT
121      public TableCell findCell(JTable table, JTableCellReader cellReader) {
122        int row = findRowIndex(table, cellReader, values);
123        if (row == -1)
124          throw actionFailure(concat("Unable to find a row with values:<", format(values), ">"));
125        return new TableCell(row, column);
126      }
127    
128      @RunsInEDT
129      private static int findRowIndex(final JTable table, final JTableCellReader cellReader, final String[] values) {
130        return execute(new GuiQuery<Integer>() {
131          protected Integer executeInEDT() {
132            validateEqualSize(table, values);
133            int rowCount = table.getRowCount();
134            for (int row = 0; row < rowCount; row++)
135              if (matchingRow(table, cellReader, values, row)) return row;
136            return -1;
137          }
138    
139        });
140      }
141    
142      @RunsInCurrentThread
143      private static void validateEqualSize(final JTable table, final String[] values) {
144        int columnCount = table.getColumnCount();
145        if (values.length != columnCount)
146          throw new IllegalStateException(concat("The array of values should have size:<", columnCount,">"));
147      }
148    
149      @RunsInCurrentThread
150      private static boolean matchingRow(JTable table, JTableCellReader cellReader, String[] values, int row) {
151        int columnCount = table.getColumnCount();
152        for (int col = 0; col < columnCount; col++) {
153          if (!areEqual(cellReader.valueAt(table, row, col), values[col])) return false;
154        }
155        return true;
156      }
157    
158      @Override public String toString() {
159        return concat(getClass().getName(), "[values=", format(values), ", column=", column, "]");
160      }
161    }