001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.client;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertNotNull;
022
023import java.io.IOException;
024import java.lang.reflect.Type;
025import java.nio.ByteBuffer;
026import java.util.Arrays;
027import java.util.List;
028import java.util.Map;
029import org.apache.hadoop.hbase.Cell;
030import org.apache.hadoop.hbase.CellComparatorImpl;
031import org.apache.hadoop.hbase.CellUtil;
032import org.apache.hadoop.hbase.HBaseClassTestRule;
033import org.apache.hadoop.hbase.HConstants;
034import org.apache.hadoop.hbase.KeyValue;
035import org.apache.hadoop.hbase.filter.BinaryComparator;
036import org.apache.hadoop.hbase.filter.ColumnCountGetFilter;
037import org.apache.hadoop.hbase.filter.ColumnPaginationFilter;
038import org.apache.hadoop.hbase.filter.ColumnPrefixFilter;
039import org.apache.hadoop.hbase.filter.ColumnRangeFilter;
040import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
041import org.apache.hadoop.hbase.filter.DependentColumnFilter;
042import org.apache.hadoop.hbase.filter.FamilyFilter;
043import org.apache.hadoop.hbase.filter.Filter;
044import org.apache.hadoop.hbase.filter.FilterList;
045import org.apache.hadoop.hbase.filter.FilterList.Operator;
046import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
047import org.apache.hadoop.hbase.filter.InclusiveStopFilter;
048import org.apache.hadoop.hbase.filter.KeyOnlyFilter;
049import org.apache.hadoop.hbase.filter.MultipleColumnPrefixFilter;
050import org.apache.hadoop.hbase.filter.PageFilter;
051import org.apache.hadoop.hbase.filter.PrefixFilter;
052import org.apache.hadoop.hbase.filter.QualifierFilter;
053import org.apache.hadoop.hbase.filter.RowFilter;
054import org.apache.hadoop.hbase.filter.SingleColumnValueExcludeFilter;
055import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
056import org.apache.hadoop.hbase.filter.SkipFilter;
057import org.apache.hadoop.hbase.filter.TimestampsFilter;
058import org.apache.hadoop.hbase.filter.ValueFilter;
059import org.apache.hadoop.hbase.filter.WhileMatchFilter;
060import org.apache.hadoop.hbase.testclassification.ClientTests;
061import org.apache.hadoop.hbase.testclassification.SmallTests;
062import org.apache.hadoop.hbase.util.BuilderStyleTest;
063import org.apache.hadoop.hbase.util.Bytes;
064import org.apache.hadoop.hbase.util.GsonUtil;
065import org.junit.Assert;
066import org.junit.ClassRule;
067import org.junit.Test;
068import org.junit.experimental.categories.Category;
069
070import org.apache.hbase.thirdparty.com.google.common.reflect.TypeToken;
071import org.apache.hbase.thirdparty.com.google.gson.Gson;
072
073/**
074 * Run tests that use the functionality of the Operation superclass for Puts, Gets, Deletes, Scans,
075 * and MultiPuts.
076 */
077@Category({ ClientTests.class, SmallTests.class })
078public class TestOperation {
079  @ClassRule
080  public static final HBaseClassTestRule CLASS_RULE =
081    HBaseClassTestRule.forClass(TestOperation.class);
082
083  private static byte[] ROW = Bytes.toBytes("testRow");
084  private static byte[] FAMILY = Bytes.toBytes("testFamily");
085  private static byte[] QUALIFIER = Bytes.toBytes("testQualifier");
086  private static byte[] VALUE = Bytes.toBytes("testValue");
087
088  private static Gson GSON = GsonUtil.createGson().create();
089
090  private static List<Long> TS_LIST = Arrays.asList(2L, 3L, 5L);
091  private static TimestampsFilter TS_FILTER = new TimestampsFilter(TS_LIST);
092  private static String STR_TS_FILTER = TS_FILTER.getClass().getSimpleName() + " (3/3): [2, 3, 5]";
093
094  private static List<Long> L_TS_LIST = Arrays.asList(0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L);
095  private static TimestampsFilter L_TS_FILTER = new TimestampsFilter(L_TS_LIST);
096  private static String STR_L_TS_FILTER =
097    L_TS_FILTER.getClass().getSimpleName() + " (5/11): [0, 1, 2, 3, 4]";
098
099  private static String COL_NAME_1 = "col1";
100  private static ColumnPrefixFilter COL_PRE_FILTER =
101    new ColumnPrefixFilter(Bytes.toBytes(COL_NAME_1));
102  private static String STR_COL_PRE_FILTER =
103    COL_PRE_FILTER.getClass().getSimpleName() + " " + COL_NAME_1;
104
105  private static String COL_NAME_2 = "col2";
106  private static ColumnRangeFilter CR_FILTER =
107    new ColumnRangeFilter(Bytes.toBytes(COL_NAME_1), true, Bytes.toBytes(COL_NAME_2), false);
108  private static String STR_CR_FILTER =
109    CR_FILTER.getClass().getSimpleName() + " [" + COL_NAME_1 + ", " + COL_NAME_2 + ")";
110
111  private static int COL_COUNT = 9;
112  private static ColumnCountGetFilter CCG_FILTER = new ColumnCountGetFilter(COL_COUNT);
113  private static String STR_CCG_FILTER = CCG_FILTER.getClass().getSimpleName() + " " + COL_COUNT;
114
115  private static int LIMIT = 3;
116  private static int OFFSET = 4;
117  private static ColumnPaginationFilter CP_FILTER = new ColumnPaginationFilter(LIMIT, OFFSET);
118  private static String STR_CP_FILTER =
119    CP_FILTER.getClass().getSimpleName() + " (" + LIMIT + ", " + OFFSET + ")";
120
121  private static String STOP_ROW_KEY = "stop";
122  private static InclusiveStopFilter IS_FILTER =
123    new InclusiveStopFilter(Bytes.toBytes(STOP_ROW_KEY));
124  private static String STR_IS_FILTER = IS_FILTER.getClass().getSimpleName() + " " + STOP_ROW_KEY;
125
126  private static String PREFIX = "prefix";
127  private static PrefixFilter PREFIX_FILTER = new PrefixFilter(Bytes.toBytes(PREFIX));
128  private static String STR_PREFIX_FILTER = "PrefixFilter " + PREFIX;
129
130  private static byte[][] PREFIXES = { Bytes.toBytes("0"), Bytes.toBytes("1"), Bytes.toBytes("2") };
131  private static MultipleColumnPrefixFilter MCP_FILTER = new MultipleColumnPrefixFilter(PREFIXES);
132  private static String STR_MCP_FILTER =
133    MCP_FILTER.getClass().getSimpleName() + " (3/3): [0, 1, 2]";
134
135  private static byte[][] L_PREFIXES =
136    { Bytes.toBytes("0"), Bytes.toBytes("1"), Bytes.toBytes("2"), Bytes.toBytes("3"),
137      Bytes.toBytes("4"), Bytes.toBytes("5"), Bytes.toBytes("6"), Bytes.toBytes("7") };
138  private static MultipleColumnPrefixFilter L_MCP_FILTER =
139    new MultipleColumnPrefixFilter(L_PREFIXES);
140  private static String STR_L_MCP_FILTER =
141    L_MCP_FILTER.getClass().getSimpleName() + " (5/8): [0, 1, 2, 3, 4]";
142
143  private static int PAGE_SIZE = 9;
144  private static PageFilter PAGE_FILTER = new PageFilter(PAGE_SIZE);
145  private static String STR_PAGE_FILTER = PAGE_FILTER.getClass().getSimpleName() + " " + PAGE_SIZE;
146
147  private static SkipFilter SKIP_FILTER = new SkipFilter(L_TS_FILTER);
148  private static String STR_SKIP_FILTER =
149    SKIP_FILTER.getClass().getSimpleName() + " " + STR_L_TS_FILTER;
150
151  private static WhileMatchFilter WHILE_FILTER = new WhileMatchFilter(L_TS_FILTER);
152  private static String STR_WHILE_FILTER =
153    WHILE_FILTER.getClass().getSimpleName() + " " + STR_L_TS_FILTER;
154
155  private static KeyOnlyFilter KEY_ONLY_FILTER = new KeyOnlyFilter();
156  private static String STR_KEY_ONLY_FILTER = KEY_ONLY_FILTER.getClass().getSimpleName();
157
158  private static FirstKeyOnlyFilter FIRST_KEY_ONLY_FILTER = new FirstKeyOnlyFilter();
159  private static String STR_FIRST_KEY_ONLY_FILTER =
160    FIRST_KEY_ONLY_FILTER.getClass().getSimpleName();
161
162  private static CompareOp CMP_OP = CompareOp.EQUAL;
163  private static byte[] CMP_VALUE = Bytes.toBytes("value");
164  private static BinaryComparator BC = new BinaryComparator(CMP_VALUE);
165  private static DependentColumnFilter DC_FILTER =
166    new DependentColumnFilter(FAMILY, QUALIFIER, true, CMP_OP, BC);
167  private static String STR_DC_FILTER = String.format("%s (%s, %s, %s, %s, %s)",
168    DC_FILTER.getClass().getSimpleName(), Bytes.toStringBinary(FAMILY),
169    Bytes.toStringBinary(QUALIFIER), true, CMP_OP.name(), Bytes.toStringBinary(BC.getValue()));
170
171  private static FamilyFilter FAMILY_FILTER = new FamilyFilter(CMP_OP, BC);
172  private static String STR_FAMILY_FILTER =
173    FAMILY_FILTER.getClass().getSimpleName() + " (EQUAL, value)";
174
175  private static QualifierFilter QUALIFIER_FILTER = new QualifierFilter(CMP_OP, BC);
176  private static String STR_QUALIFIER_FILTER =
177    QUALIFIER_FILTER.getClass().getSimpleName() + " (EQUAL, value)";
178
179  private static RowFilter ROW_FILTER = new RowFilter(CMP_OP, BC);
180  private static String STR_ROW_FILTER = ROW_FILTER.getClass().getSimpleName() + " (EQUAL, value)";
181
182  private static ValueFilter VALUE_FILTER = new ValueFilter(CMP_OP, BC);
183  private static String STR_VALUE_FILTER =
184    VALUE_FILTER.getClass().getSimpleName() + " (EQUAL, value)";
185
186  private static SingleColumnValueFilter SCV_FILTER =
187    new SingleColumnValueFilter(FAMILY, QUALIFIER, CMP_OP, CMP_VALUE);
188  private static String STR_SCV_FILTER = String.format("%s (%s, %s, %s, %s)",
189    SCV_FILTER.getClass().getSimpleName(), Bytes.toStringBinary(FAMILY),
190    Bytes.toStringBinary(QUALIFIER), CMP_OP.name(), Bytes.toStringBinary(CMP_VALUE));
191
192  private static SingleColumnValueExcludeFilter SCVE_FILTER =
193    new SingleColumnValueExcludeFilter(FAMILY, QUALIFIER, CMP_OP, CMP_VALUE);
194  private static String STR_SCVE_FILTER = String.format("%s (%s, %s, %s, %s)",
195    SCVE_FILTER.getClass().getSimpleName(), Bytes.toStringBinary(FAMILY),
196    Bytes.toStringBinary(QUALIFIER), CMP_OP.name(), Bytes.toStringBinary(CMP_VALUE));
197
198  private static FilterList AND_FILTER_LIST = new FilterList(Operator.MUST_PASS_ALL,
199    Arrays.asList((Filter) TS_FILTER, L_TS_FILTER, CR_FILTER));
200  private static String STR_AND_FILTER_LIST = String.format("%s AND (3/3): [%s, %s, %s]",
201    AND_FILTER_LIST.getClass().getSimpleName(), STR_TS_FILTER, STR_L_TS_FILTER, STR_CR_FILTER);
202
203  private static FilterList OR_FILTER_LIST = new FilterList(Operator.MUST_PASS_ONE,
204    Arrays.asList((Filter) TS_FILTER, L_TS_FILTER, CR_FILTER));
205  private static String STR_OR_FILTER_LIST = String.format("%s OR (3/3): [%s, %s, %s]",
206    AND_FILTER_LIST.getClass().getSimpleName(), STR_TS_FILTER, STR_L_TS_FILTER, STR_CR_FILTER);
207
208  private static FilterList L_FILTER_LIST = new FilterList(Arrays.asList((Filter) TS_FILTER,
209    L_TS_FILTER, CR_FILTER, COL_PRE_FILTER, CCG_FILTER, CP_FILTER, PREFIX_FILTER, PAGE_FILTER));
210  private static String STR_L_FILTER_LIST = String.format("%s AND (5/8): [%s, %s, %s, %s, %s, %s]",
211    L_FILTER_LIST.getClass().getSimpleName(), STR_TS_FILTER, STR_L_TS_FILTER, STR_CR_FILTER,
212    STR_COL_PRE_FILTER, STR_CCG_FILTER, STR_CP_FILTER);
213
214  private static Filter[] FILTERS = { TS_FILTER, // TimestampsFilter
215    L_TS_FILTER, // TimestampsFilter
216    COL_PRE_FILTER, // ColumnPrefixFilter
217    CP_FILTER, // ColumnPaginationFilter
218    CR_FILTER, // ColumnRangeFilter
219    CCG_FILTER, // ColumnCountGetFilter
220    IS_FILTER, // InclusiveStopFilter
221    PREFIX_FILTER, // PrefixFilter
222    PAGE_FILTER, // PageFilter
223    SKIP_FILTER, // SkipFilter
224    WHILE_FILTER, // WhileMatchFilter
225    KEY_ONLY_FILTER, // KeyOnlyFilter
226    FIRST_KEY_ONLY_FILTER, // FirstKeyOnlyFilter
227    MCP_FILTER, // MultipleColumnPrefixFilter
228    L_MCP_FILTER, // MultipleColumnPrefixFilter
229    DC_FILTER, // DependentColumnFilter
230    FAMILY_FILTER, // FamilyFilter
231    QUALIFIER_FILTER, // QualifierFilter
232    ROW_FILTER, // RowFilter
233    VALUE_FILTER, // ValueFilter
234    SCV_FILTER, // SingleColumnValueFilter
235    SCVE_FILTER, // SingleColumnValueExcludeFilter
236    AND_FILTER_LIST, // FilterList
237    OR_FILTER_LIST, // FilterList
238    L_FILTER_LIST, // FilterList
239  };
240
241  private static String[] FILTERS_INFO = { STR_TS_FILTER, // TimestampsFilter
242    STR_L_TS_FILTER, // TimestampsFilter
243    STR_COL_PRE_FILTER, // ColumnPrefixFilter
244    STR_CP_FILTER, // ColumnPaginationFilter
245    STR_CR_FILTER, // ColumnRangeFilter
246    STR_CCG_FILTER, // ColumnCountGetFilter
247    STR_IS_FILTER, // InclusiveStopFilter
248    STR_PREFIX_FILTER, // PrefixFilter
249    STR_PAGE_FILTER, // PageFilter
250    STR_SKIP_FILTER, // SkipFilter
251    STR_WHILE_FILTER, // WhileMatchFilter
252    STR_KEY_ONLY_FILTER, // KeyOnlyFilter
253    STR_FIRST_KEY_ONLY_FILTER, // FirstKeyOnlyFilter
254    STR_MCP_FILTER, // MultipleColumnPrefixFilter
255    STR_L_MCP_FILTER, // MultipleColumnPrefixFilter
256    STR_DC_FILTER, // DependentColumnFilter
257    STR_FAMILY_FILTER, // FamilyFilter
258    STR_QUALIFIER_FILTER, // QualifierFilter
259    STR_ROW_FILTER, // RowFilter
260    STR_VALUE_FILTER, // ValueFilter
261    STR_SCV_FILTER, // SingleColumnValueFilter
262    STR_SCVE_FILTER, // SingleColumnValueExcludeFilter
263    STR_AND_FILTER_LIST, // FilterList
264    STR_OR_FILTER_LIST, // FilterList
265    STR_L_FILTER_LIST, // FilterList
266  };
267
268  static {
269    assertEquals("The sizes of static arrays do not match: " + "[FILTERS: %d <=> FILTERS_INFO: %d]",
270      FILTERS.length, FILTERS_INFO.length);
271  }
272
273  /**
274   * Test the client Operations' JSON encoding to ensure that produced JSON is parseable and that
275   * the details are present and not corrupted.
276   * @throws IOException if the JSON conversion fails
277   */
278  @Test
279  public void testOperationJSON() throws IOException {
280    // produce a Scan Operation
281    Scan scan = new Scan(ROW);
282    scan.addColumn(FAMILY, QUALIFIER);
283    // get its JSON representation, and parse it
284    String json = scan.toJSON();
285    Type typeOfHashMap = new TypeToken<Map<String, Object>>() {
286    }.getType();
287    Map<String, Object> parsedJSON = GSON.fromJson(json, typeOfHashMap);
288    // check for the row
289    assertEquals("startRow incorrect in Scan.toJSON()", Bytes.toStringBinary(ROW),
290      parsedJSON.get("startRow"));
291    // check for the family and the qualifier.
292    List familyInfo = (List) ((Map) parsedJSON.get("families")).get(Bytes.toStringBinary(FAMILY));
293    assertNotNull("Family absent in Scan.toJSON()", familyInfo);
294    assertEquals("Qualifier absent in Scan.toJSON()", 1, familyInfo.size());
295    assertEquals("Qualifier incorrect in Scan.toJSON()", Bytes.toStringBinary(QUALIFIER),
296      familyInfo.get(0));
297
298    // produce a Get Operation
299    Get get = new Get(ROW);
300    get.addColumn(FAMILY, QUALIFIER);
301    // get its JSON representation, and parse it
302    json = get.toJSON();
303    parsedJSON = GSON.fromJson(json, typeOfHashMap);
304    // check for the row
305    assertEquals("row incorrect in Get.toJSON()", Bytes.toStringBinary(ROW), parsedJSON.get("row"));
306    // check for the family and the qualifier.
307    familyInfo = (List) ((Map) parsedJSON.get("families")).get(Bytes.toStringBinary(FAMILY));
308    assertNotNull("Family absent in Get.toJSON()", familyInfo);
309    assertEquals("Qualifier absent in Get.toJSON()", 1, familyInfo.size());
310    assertEquals("Qualifier incorrect in Get.toJSON()", Bytes.toStringBinary(QUALIFIER),
311      familyInfo.get(0));
312
313    // produce a Put operation
314    Put put = new Put(ROW);
315    put.addColumn(FAMILY, QUALIFIER, VALUE);
316    // get its JSON representation, and parse it
317    json = put.toJSON();
318    parsedJSON = GSON.fromJson(json, typeOfHashMap);
319    // check for the row
320    assertEquals("row absent in Put.toJSON()", Bytes.toStringBinary(ROW), parsedJSON.get("row"));
321    // check for the family and the qualifier.
322    familyInfo = (List) ((Map) parsedJSON.get("families")).get(Bytes.toStringBinary(FAMILY));
323    assertNotNull("Family absent in Put.toJSON()", familyInfo);
324    assertEquals("KeyValue absent in Put.toJSON()", 1, familyInfo.size());
325    Map kvMap = (Map) familyInfo.get(0);
326    assertEquals("Qualifier incorrect in Put.toJSON()", Bytes.toStringBinary(QUALIFIER),
327      kvMap.get("qualifier"));
328    assertEquals("Value length incorrect in Put.toJSON()", VALUE.length,
329      ((Number) kvMap.get("vlen")).intValue());
330
331    // produce a Delete operation
332    Delete delete = new Delete(ROW);
333    delete.addColumn(FAMILY, QUALIFIER);
334    // get its JSON representation, and parse it
335    json = delete.toJSON();
336    parsedJSON = GSON.fromJson(json, typeOfHashMap);
337    // check for the row
338    assertEquals("row absent in Delete.toJSON()", Bytes.toStringBinary(ROW), parsedJSON.get("row"));
339    // check for the family and the qualifier.
340    familyInfo = (List) ((Map) parsedJSON.get("families")).get(Bytes.toStringBinary(FAMILY));
341    assertNotNull("Family absent in Delete.toJSON()", familyInfo);
342    assertEquals("KeyValue absent in Delete.toJSON()", 1, familyInfo.size());
343    kvMap = (Map) familyInfo.get(0);
344    assertEquals("Qualifier incorrect in Delete.toJSON()", Bytes.toStringBinary(QUALIFIER),
345      kvMap.get("qualifier"));
346  }
347
348  @Test
349  public void testPutCreationWithByteBuffer() {
350    Put p = new Put(ROW);
351    List<Cell> c = p.get(FAMILY, QUALIFIER);
352    Assert.assertEquals(0, c.size());
353    Assert.assertEquals(HConstants.LATEST_TIMESTAMP, p.getTimestamp());
354
355    p.addColumn(FAMILY, ByteBuffer.wrap(QUALIFIER), 1984L, ByteBuffer.wrap(VALUE));
356    c = p.get(FAMILY, QUALIFIER);
357    Assert.assertEquals(1, c.size());
358    Assert.assertEquals(1984L, c.get(0).getTimestamp());
359    Assert.assertArrayEquals(VALUE, CellUtil.cloneValue(c.get(0)));
360    Assert.assertEquals(HConstants.LATEST_TIMESTAMP, p.getTimestamp());
361    Assert.assertEquals(0, CellComparatorImpl.COMPARATOR.compare(c.get(0), new KeyValue(c.get(0))));
362
363    p = new Put(ROW);
364    p.addColumn(FAMILY, ByteBuffer.wrap(QUALIFIER), 2013L, null);
365    c = p.get(FAMILY, QUALIFIER);
366    Assert.assertEquals(1, c.size());
367    Assert.assertEquals(2013L, c.get(0).getTimestamp());
368    Assert.assertArrayEquals(new byte[] {}, CellUtil.cloneValue(c.get(0)));
369    Assert.assertEquals(HConstants.LATEST_TIMESTAMP, p.getTimestamp());
370    Assert.assertEquals(0, CellComparatorImpl.COMPARATOR.compare(c.get(0), new KeyValue(c.get(0))));
371
372    p = new Put(ByteBuffer.wrap(ROW));
373    p.addColumn(FAMILY, ByteBuffer.wrap(QUALIFIER), 2001L, null);
374    c = p.get(FAMILY, QUALIFIER);
375    Assert.assertEquals(1, c.size());
376    Assert.assertEquals(2001L, c.get(0).getTimestamp());
377    Assert.assertArrayEquals(new byte[] {}, CellUtil.cloneValue(c.get(0)));
378    Assert.assertArrayEquals(ROW, CellUtil.cloneRow(c.get(0)));
379    Assert.assertEquals(HConstants.LATEST_TIMESTAMP, p.getTimestamp());
380    Assert.assertEquals(0, CellComparatorImpl.COMPARATOR.compare(c.get(0), new KeyValue(c.get(0))));
381
382    p = new Put(ByteBuffer.wrap(ROW), 1970L);
383    p.addColumn(FAMILY, ByteBuffer.wrap(QUALIFIER), 2001L, null);
384    c = p.get(FAMILY, QUALIFIER);
385    Assert.assertEquals(1, c.size());
386    Assert.assertEquals(2001L, c.get(0).getTimestamp());
387    Assert.assertArrayEquals(new byte[] {}, CellUtil.cloneValue(c.get(0)));
388    Assert.assertArrayEquals(ROW, CellUtil.cloneRow(c.get(0)));
389    Assert.assertEquals(1970L, p.getTimestamp());
390    Assert.assertEquals(0, CellComparatorImpl.COMPARATOR.compare(c.get(0), new KeyValue(c.get(0))));
391  }
392
393  @Test
394  @SuppressWarnings("rawtypes")
395  public void testOperationSubClassMethodsAreBuilderStyle() {
396    /*
397     * All Operation subclasses should have a builder style setup where setXXX/addXXX methods can be
398     * chainable together: . For example: Scan scan = new Scan() .setFoo(foo) .setBar(bar)
399     * .setBuz(buz) This test ensures that all methods starting with "set" returns the declaring
400     * object
401     */
402
403    // TODO: We should ensure all subclasses of Operation is checked.
404    Class[] classes = new Class[] { Operation.class, OperationWithAttributes.class, Mutation.class,
405      Query.class, Delete.class, Increment.class, Append.class, Put.class, Get.class, Scan.class };
406
407    BuilderStyleTest.assertClassesAreBuilderStyle(classes);
408  }
409}