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.thrift2; 019 020import static java.nio.ByteBuffer.wrap; 021import static org.junit.jupiter.api.Assertions.assertArrayEquals; 022import static org.junit.jupiter.api.Assertions.assertEquals; 023import static org.junit.jupiter.api.Assertions.assertFalse; 024import static org.junit.jupiter.api.Assertions.assertNull; 025import static org.junit.jupiter.api.Assertions.fail; 026 027import java.io.IOException; 028import java.nio.ByteBuffer; 029import java.security.PrivilegedExceptionAction; 030import java.util.ArrayList; 031import java.util.Arrays; 032import java.util.Collections; 033import java.util.Comparator; 034import java.util.List; 035import org.apache.hadoop.conf.Configuration; 036import org.apache.hadoop.hbase.HBaseTestingUtil; 037import org.apache.hadoop.hbase.TableName; 038import org.apache.hadoop.hbase.client.Admin; 039import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 040import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 041import org.apache.hadoop.hbase.client.Connection; 042import org.apache.hadoop.hbase.client.ConnectionFactory; 043import org.apache.hadoop.hbase.client.TableDescriptor; 044import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 045import org.apache.hadoop.hbase.security.User; 046import org.apache.hadoop.hbase.security.UserProvider; 047import org.apache.hadoop.hbase.security.visibility.ScanLabelGenerator; 048import org.apache.hadoop.hbase.security.visibility.SimpleScanLabelGenerator; 049import org.apache.hadoop.hbase.security.visibility.VisibilityClient; 050import org.apache.hadoop.hbase.security.visibility.VisibilityConstants; 051import org.apache.hadoop.hbase.security.visibility.VisibilityTestUtil; 052import org.apache.hadoop.hbase.security.visibility.VisibilityUtils; 053import org.apache.hadoop.hbase.testclassification.ClientTests; 054import org.apache.hadoop.hbase.testclassification.MediumTests; 055import org.apache.hadoop.hbase.thrift2.generated.TAppend; 056import org.apache.hadoop.hbase.thrift2.generated.TAuthorization; 057import org.apache.hadoop.hbase.thrift2.generated.TCellVisibility; 058import org.apache.hadoop.hbase.thrift2.generated.TColumn; 059import org.apache.hadoop.hbase.thrift2.generated.TColumnIncrement; 060import org.apache.hadoop.hbase.thrift2.generated.TColumnValue; 061import org.apache.hadoop.hbase.thrift2.generated.TDelete; 062import org.apache.hadoop.hbase.thrift2.generated.TGet; 063import org.apache.hadoop.hbase.thrift2.generated.TIllegalArgument; 064import org.apache.hadoop.hbase.thrift2.generated.TIncrement; 065import org.apache.hadoop.hbase.thrift2.generated.TPut; 066import org.apache.hadoop.hbase.thrift2.generated.TResult; 067import org.apache.hadoop.hbase.thrift2.generated.TScan; 068import org.apache.hadoop.hbase.util.Bytes; 069import org.junit.jupiter.api.AfterAll; 070import org.junit.jupiter.api.BeforeAll; 071import org.junit.jupiter.api.BeforeEach; 072import org.junit.jupiter.api.Tag; 073import org.junit.jupiter.api.Test; 074import org.slf4j.Logger; 075import org.slf4j.LoggerFactory; 076 077import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse; 078 079@Tag(ClientTests.TAG) 080@Tag(MediumTests.TAG) 081public class TestThriftHBaseServiceHandlerWithLabels { 082 083 private static final Logger LOG = 084 LoggerFactory.getLogger(TestThriftHBaseServiceHandlerWithLabels.class); 085 private static final HBaseTestingUtil UTIL = new HBaseTestingUtil(); 086 087 // Static names for tables, columns, rows, and values 088 private static byte[] tableAname = Bytes.toBytes("tableA"); 089 private static byte[] familyAname = Bytes.toBytes("familyA"); 090 private static byte[] familyBname = Bytes.toBytes("familyB"); 091 private static byte[] qualifierAname = Bytes.toBytes("qualifierA"); 092 private static byte[] qualifierBname = Bytes.toBytes("qualifierB"); 093 private static byte[] valueAname = Bytes.toBytes("valueA"); 094 private static byte[] valueBname = Bytes.toBytes("valueB"); 095 private static ColumnFamilyDescriptor[] families = new ColumnFamilyDescriptor[] { 096 ColumnFamilyDescriptorBuilder.newBuilder(familyAname).setMaxVersions(3).build(), 097 ColumnFamilyDescriptorBuilder.newBuilder(familyBname).setMaxVersions(2).build() }; 098 099 private final static String TOPSECRET = "topsecret"; 100 private final static String PUBLIC = "public"; 101 private final static String PRIVATE = "private"; 102 private final static String CONFIDENTIAL = "confidential"; 103 private final static String SECRET = "secret"; 104 private static User SUPERUSER; 105 106 private static Configuration conf; 107 108 public void assertTColumnValuesEqual(List<TColumnValue> columnValuesA, 109 List<TColumnValue> columnValuesB) { 110 assertEquals(columnValuesA.size(), columnValuesB.size()); 111 Comparator<TColumnValue> comparator = new Comparator<TColumnValue>() { 112 @Override 113 public int compare(TColumnValue o1, TColumnValue o2) { 114 return Bytes.compareTo(Bytes.add(o1.getFamily(), o1.getQualifier()), 115 Bytes.add(o2.getFamily(), o2.getQualifier())); 116 } 117 }; 118 Collections.sort(columnValuesA, comparator); 119 Collections.sort(columnValuesB, comparator); 120 121 for (int i = 0; i < columnValuesA.size(); i++) { 122 TColumnValue a = columnValuesA.get(i); 123 TColumnValue b = columnValuesB.get(i); 124 assertArrayEquals(a.getFamily(), b.getFamily()); 125 assertArrayEquals(a.getQualifier(), b.getQualifier()); 126 assertArrayEquals(a.getValue(), b.getValue()); 127 } 128 } 129 130 @BeforeAll 131 public static void beforeClass() throws Exception { 132 SUPERUSER = User.createUserForTesting(conf, "admin", new String[] { "supergroup" }); 133 conf = UTIL.getConfiguration(); 134 conf.setClass(VisibilityUtils.VISIBILITY_LABEL_GENERATOR_CLASS, SimpleScanLabelGenerator.class, 135 ScanLabelGenerator.class); 136 conf.set("hbase.superuser", SUPERUSER.getShortName()); 137 VisibilityTestUtil.enableVisiblityLabels(conf); 138 UTIL.startMiniCluster(1); 139 // Wait for the labels table to become available 140 UTIL.waitTableEnabled(VisibilityConstants.LABELS_TABLE_NAME.getName(), 50000); 141 createLabels(); 142 Admin admin = UTIL.getAdmin(); 143 TableDescriptor tableDescriptor = TableDescriptorBuilder 144 .newBuilder(TableName.valueOf(tableAname)).setColumnFamilies(Arrays.asList(families)).build(); 145 admin.createTable(tableDescriptor); 146 admin.close(); 147 setAuths(); 148 } 149 150 private static void createLabels() throws IOException, InterruptedException { 151 PrivilegedExceptionAction<VisibilityLabelsResponse> action = 152 new PrivilegedExceptionAction<VisibilityLabelsResponse>() { 153 @Override 154 public VisibilityLabelsResponse run() throws Exception { 155 String[] labels = { SECRET, CONFIDENTIAL, PRIVATE, PUBLIC, TOPSECRET }; 156 try (Connection conn = ConnectionFactory.createConnection(conf)) { 157 VisibilityClient.addLabels(conn, labels); 158 } catch (Throwable t) { 159 throw new IOException(t); 160 } 161 return null; 162 } 163 }; 164 SUPERUSER.runAs(action); 165 } 166 167 private static void setAuths() throws IOException { 168 String[] labels = { SECRET, CONFIDENTIAL, PRIVATE, PUBLIC, TOPSECRET }; 169 try { 170 VisibilityClient.setAuths(UTIL.getConnection(), labels, User.getCurrent().getShortName()); 171 } catch (Throwable t) { 172 throw new IOException(t); 173 } 174 } 175 176 @AfterAll 177 public static void afterClass() throws Exception { 178 UTIL.shutdownMiniCluster(); 179 } 180 181 @BeforeEach 182 public void setup() throws Exception { 183 184 } 185 186 private ThriftHBaseServiceHandler createHandler() throws IOException { 187 return new ThriftHBaseServiceHandler(conf, UserProvider.instantiate(conf)); 188 } 189 190 @Test 191 public void testScanWithVisibilityLabels() throws Exception { 192 ThriftHBaseServiceHandler handler = createHandler(); 193 ByteBuffer table = wrap(tableAname); 194 195 // insert data 196 TColumnValue columnValue = 197 new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname)); 198 List<TColumnValue> columnValues = new ArrayList<>(1); 199 columnValues.add(columnValue); 200 for (int i = 0; i < 10; i++) { 201 TPut put = new TPut(wrap(Bytes.toBytes("testScan" + i)), columnValues); 202 if (i == 5) { 203 put.setCellVisibility(new TCellVisibility().setExpression(PUBLIC)); 204 } else { 205 put.setCellVisibility(new TCellVisibility() 206 .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET)); 207 } 208 handler.put(table, put); 209 } 210 211 // create scan instance 212 TScan scan = new TScan(); 213 List<TColumn> columns = new ArrayList<>(1); 214 TColumn column = new TColumn(); 215 column.setFamily(familyAname); 216 column.setQualifier(qualifierAname); 217 columns.add(column); 218 scan.setColumns(columns); 219 scan.setStartRow(Bytes.toBytes("testScan")); 220 scan.setStopRow(Bytes.toBytes("testScan\uffff")); 221 222 TAuthorization tauth = new TAuthorization(); 223 List<String> labels = new ArrayList<>(2); 224 labels.add(SECRET); 225 labels.add(PRIVATE); 226 tauth.setLabels(labels); 227 scan.setAuthorizations(tauth); 228 // get scanner and rows 229 int scanId = handler.openScanner(table, scan); 230 List<TResult> results = handler.getScannerRows(scanId, 10); 231 assertEquals(9, results.size()); 232 assertFalse(Bytes.equals(results.get(5).getRow(), Bytes.toBytes("testScan" + 5))); 233 for (int i = 0; i < 9; i++) { 234 if (i < 5) { 235 assertArrayEquals(Bytes.toBytes("testScan" + i), results.get(i).getRow()); 236 } else if (i == 5) { 237 continue; 238 } else { 239 assertArrayEquals(Bytes.toBytes("testScan" + (i + 1)), results.get(i).getRow()); 240 } 241 } 242 243 // check that we are at the end of the scan 244 results = handler.getScannerRows(scanId, 9); 245 assertEquals(0, results.size()); 246 247 // close scanner and check that it was indeed closed 248 handler.closeScanner(scanId); 249 try { 250 handler.getScannerRows(scanId, 9); 251 fail("Scanner id should be invalid"); 252 } catch (TIllegalArgument e) { 253 } 254 } 255 256 @Test 257 public void testGetScannerResultsWithAuthorizations() throws Exception { 258 ThriftHBaseServiceHandler handler = createHandler(); 259 ByteBuffer table = wrap(tableAname); 260 261 // insert data 262 TColumnValue columnValue = 263 new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname)); 264 List<TColumnValue> columnValues = new ArrayList<>(1); 265 columnValues.add(columnValue); 266 for (int i = 0; i < 20; i++) { 267 TPut put = 268 new TPut(wrap(Bytes.toBytes("testGetScannerResults" + pad(i, (byte) 2))), columnValues); 269 if (i == 3) { 270 put.setCellVisibility(new TCellVisibility().setExpression(PUBLIC)); 271 } else { 272 put.setCellVisibility(new TCellVisibility() 273 .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET)); 274 } 275 handler.put(table, put); 276 } 277 278 // create scan instance 279 TScan scan = new TScan(); 280 List<TColumn> columns = new ArrayList<>(1); 281 TColumn column = new TColumn(); 282 column.setFamily(familyAname); 283 column.setQualifier(qualifierAname); 284 columns.add(column); 285 scan.setColumns(columns); 286 scan.setStartRow(Bytes.toBytes("testGetScannerResults")); 287 288 // get 5 rows and check the returned results 289 scan.setStopRow(Bytes.toBytes("testGetScannerResults05")); 290 TAuthorization tauth = new TAuthorization(); 291 List<String> labels = new ArrayList<>(2); 292 labels.add(SECRET); 293 labels.add(PRIVATE); 294 tauth.setLabels(labels); 295 scan.setAuthorizations(tauth); 296 List<TResult> results = handler.getScannerResults(table, scan, 5); 297 assertEquals(4, results.size()); 298 for (int i = 0; i < 4; i++) { 299 if (i < 3) { 300 assertArrayEquals(Bytes.toBytes("testGetScannerResults" + pad(i, (byte) 2)), 301 results.get(i).getRow()); 302 } else if (i == 3) { 303 continue; 304 } else { 305 assertArrayEquals(Bytes.toBytes("testGetScannerResults" + pad(i + 1, (byte) 2)), 306 results.get(i).getRow()); 307 } 308 } 309 } 310 311 @Test 312 public void testGetsWithLabels() throws Exception { 313 ThriftHBaseServiceHandler handler = createHandler(); 314 byte[] rowName = Bytes.toBytes("testPutGet"); 315 ByteBuffer table = wrap(tableAname); 316 317 List<TColumnValue> columnValues = new ArrayList<>(2); 318 columnValues.add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname))); 319 columnValues.add(new TColumnValue(wrap(familyBname), wrap(qualifierBname), wrap(valueBname))); 320 TPut put = new TPut(wrap(rowName), columnValues); 321 322 put.setColumnValues(columnValues); 323 put.setCellVisibility(new TCellVisibility() 324 .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET)); 325 handler.put(table, put); 326 TGet get = new TGet(wrap(rowName)); 327 TAuthorization tauth = new TAuthorization(); 328 List<String> labels = new ArrayList<>(2); 329 labels.add(SECRET); 330 labels.add(PRIVATE); 331 tauth.setLabels(labels); 332 get.setAuthorizations(tauth); 333 TResult result = handler.get(table, get); 334 assertArrayEquals(rowName, result.getRow()); 335 List<TColumnValue> returnedColumnValues = result.getColumnValues(); 336 assertTColumnValuesEqual(columnValues, returnedColumnValues); 337 } 338 339 @Test 340 public void testIncrementWithTags() throws Exception { 341 ThriftHBaseServiceHandler handler = createHandler(); 342 byte[] rowName = Bytes.toBytes("testIncrementWithTags"); 343 ByteBuffer table = wrap(tableAname); 344 345 List<TColumnValue> columnValues = new ArrayList<>(1); 346 columnValues 347 .add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(Bytes.toBytes(1L)))); 348 TPut put = new TPut(wrap(rowName), columnValues); 349 put.setColumnValues(columnValues); 350 put.setCellVisibility(new TCellVisibility().setExpression(PRIVATE)); 351 handler.put(table, put); 352 353 List<TColumnIncrement> incrementColumns = new ArrayList<>(1); 354 incrementColumns.add(new TColumnIncrement(wrap(familyAname), wrap(qualifierAname))); 355 TIncrement increment = new TIncrement(wrap(rowName), incrementColumns); 356 increment.setCellVisibility(new TCellVisibility().setExpression(SECRET)); 357 handler.increment(table, increment); 358 359 TGet get = new TGet(wrap(rowName)); 360 TAuthorization tauth = new TAuthorization(); 361 List<String> labels = new ArrayList<>(1); 362 labels.add(SECRET); 363 tauth.setLabels(labels); 364 get.setAuthorizations(tauth); 365 TResult result = handler.get(table, get); 366 367 assertArrayEquals(rowName, result.getRow()); 368 assertEquals(1, result.getColumnValuesSize()); 369 TColumnValue columnValue = result.getColumnValues().get(0); 370 assertArrayEquals(Bytes.toBytes(2L), columnValue.getValue()); 371 } 372 373 @Test 374 public void testIncrementWithTagsWithNotMatchLabels() throws Exception { 375 ThriftHBaseServiceHandler handler = createHandler(); 376 byte[] rowName = Bytes.toBytes("testIncrementWithTagsWithNotMatchLabels"); 377 ByteBuffer table = wrap(tableAname); 378 379 List<TColumnValue> columnValues = new ArrayList<>(1); 380 columnValues 381 .add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(Bytes.toBytes(1L)))); 382 TPut put = new TPut(wrap(rowName), columnValues); 383 put.setColumnValues(columnValues); 384 put.setCellVisibility(new TCellVisibility().setExpression(PRIVATE)); 385 handler.put(table, put); 386 387 List<TColumnIncrement> incrementColumns = new ArrayList<>(1); 388 incrementColumns.add(new TColumnIncrement(wrap(familyAname), wrap(qualifierAname))); 389 TIncrement increment = new TIncrement(wrap(rowName), incrementColumns); 390 increment.setCellVisibility(new TCellVisibility().setExpression(SECRET)); 391 handler.increment(table, increment); 392 393 TGet get = new TGet(wrap(rowName)); 394 TAuthorization tauth = new TAuthorization(); 395 List<String> labels = new ArrayList<>(1); 396 labels.add(PUBLIC); 397 tauth.setLabels(labels); 398 get.setAuthorizations(tauth); 399 TResult result = handler.get(table, get); 400 assertNull(result.getRow()); 401 } 402 403 @Test 404 public void testAppend() throws Exception { 405 ThriftHBaseServiceHandler handler = createHandler(); 406 byte[] rowName = Bytes.toBytes("testAppend"); 407 ByteBuffer table = wrap(tableAname); 408 byte[] v1 = Bytes.toBytes(1L); 409 byte[] v2 = Bytes.toBytes(5L); 410 List<TColumnValue> columnValues = new ArrayList<>(1); 411 columnValues 412 .add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(Bytes.toBytes(1L)))); 413 TPut put = new TPut(wrap(rowName), columnValues); 414 put.setColumnValues(columnValues); 415 put.setCellVisibility(new TCellVisibility().setExpression(PRIVATE)); 416 handler.put(table, put); 417 418 List<TColumnValue> appendColumns = new ArrayList<>(1); 419 appendColumns.add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(v2))); 420 TAppend append = new TAppend(wrap(rowName), appendColumns); 421 append.setCellVisibility(new TCellVisibility().setExpression(SECRET)); 422 handler.append(table, append); 423 424 TGet get = new TGet(wrap(rowName)); 425 TAuthorization tauth = new TAuthorization(); 426 List<String> labels = new ArrayList<>(1); 427 labels.add(SECRET); 428 tauth.setLabels(labels); 429 get.setAuthorizations(tauth); 430 TResult result = handler.get(table, get); 431 432 assertArrayEquals(rowName, result.getRow()); 433 assertEquals(1, result.getColumnValuesSize()); 434 TColumnValue columnValue = result.getColumnValues().get(0); 435 assertArrayEquals(Bytes.add(v1, v2), columnValue.getValue()); 436 } 437 438 @Test 439 public void testDeleteWithLabels() throws Exception { 440 ThriftHBaseServiceHandler handler = createHandler(); 441 byte[] rowName = "testPutGetDeleteGet".getBytes(); 442 ByteBuffer table = wrap(tableAname); 443 444 // common auths 445 TAuthorization tauth = new TAuthorization(); 446 List<String> labels = new ArrayList<String>(); 447 labels.add(SECRET); 448 labels.add(PRIVATE); 449 tauth.setLabels(labels); 450 451 // put 452 List<TColumnValue> columnValues = new ArrayList<TColumnValue>(); 453 columnValues.add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname))); 454 columnValues.add(new TColumnValue(wrap(familyBname), wrap(qualifierBname), wrap(valueBname))); 455 TPut put = new TPut(wrap(rowName), columnValues); 456 457 put.setColumnValues(columnValues); 458 put.setCellVisibility(new TCellVisibility() 459 .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET)); 460 handler.put(table, put); 461 462 // verify put 463 TGet get = new TGet(wrap(rowName)); 464 get.setAuthorizations(tauth); 465 TResult result = handler.get(table, get); 466 assertArrayEquals(rowName, result.getRow()); 467 List<TColumnValue> returnedColumnValues = result.getColumnValues(); 468 assertTColumnValuesEqual(columnValues, returnedColumnValues); 469 470 // delete 471 TDelete delete = new TDelete(wrap(rowName)); 472 delete.setCellVisibility(new TCellVisibility() 473 .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET)); 474 handler.deleteSingle(table, delete); 475 476 // verify delete 477 TGet get2 = new TGet(wrap(rowName)); 478 get2.setAuthorizations(tauth); 479 TResult result2 = handler.get(table, get2); 480 assertNull(result2.getRow()); 481 } 482 483 @Test 484 public void testDeleteWithLabelsNegativeTest() throws Exception { 485 ThriftHBaseServiceHandler handler = createHandler(); 486 byte[] rowName = "testPutGetTryDeleteGet".getBytes(); 487 ByteBuffer table = wrap(tableAname); 488 489 // common auths 490 TAuthorization tauth = new TAuthorization(); 491 List<String> labels = new ArrayList<String>(); 492 labels.add(SECRET); 493 labels.add(PRIVATE); 494 tauth.setLabels(labels); 495 496 // put 497 List<TColumnValue> columnValues = new ArrayList<TColumnValue>(); 498 columnValues.add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname))); 499 columnValues.add(new TColumnValue(wrap(familyBname), wrap(qualifierBname), wrap(valueBname))); 500 TPut put = new TPut(wrap(rowName), columnValues); 501 502 put.setColumnValues(columnValues); 503 put.setCellVisibility(new TCellVisibility() 504 .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET)); 505 handler.put(table, put); 506 507 // verify put 508 TGet get = new TGet(wrap(rowName)); 509 get.setAuthorizations(tauth); 510 TResult result = handler.get(table, get); 511 assertArrayEquals(rowName, result.getRow()); 512 List<TColumnValue> returnedColumnValues = result.getColumnValues(); 513 assertTColumnValuesEqual(columnValues, returnedColumnValues); 514 515 // _try_ delete with _no_ CellVisibility 516 TDelete delete = new TDelete(wrap(rowName)); 517 handler.deleteSingle(table, delete); 518 519 // verify delete did in fact _not_ work 520 TGet get2 = new TGet(wrap(rowName)); 521 get2.setAuthorizations(tauth); 522 TResult result2 = handler.get(table, get2); 523 assertArrayEquals(rowName, result2.getRow()); 524 } 525 526 /** 527 * Padding numbers to make comparison of sort order easier in a for loop The number to pad. 528 * @param n The number to pad. 529 * @param pad The length to pad up to. 530 * @return The padded number as a string. 531 */ 532 private String pad(int n, byte pad) { 533 String res = Integer.toString(n); 534 535 while (res.length() < pad) { 536 res = "0" + res; 537 } 538 539 return res; 540 } 541}