1 /**
2 * Copyright 2011 The Apache Software Foundation
3 *
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20 package org.apache.hadoop.hbase.util;
21
22 import java.util.Collection;
23 import java.util.Comparator;
24 import java.util.Map.Entry;
25 import java.util.TreeSet;
26
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.apache.hadoop.hbase.util.Bytes.ByteArrayComparator;
30
31 import com.google.common.collect.ArrayListMultimap;
32 import com.google.common.collect.Multimap;
33 import com.google.common.collect.TreeMultimap;
34
35 /**
36 * This is a generic region split calculator. It requires Ranges that provide
37 * start, end, and a comparator. It works in two phases -- the first adds ranges
38 * and rejects backwards ranges. Then one calls calcRegions to generate the
39 * multimap that has a start split key as a key and possibly multiple Ranges as
40 * members.
41 *
42 * To traverse, one normally would get the split set, and iterate through the
43 * calcRegions. Normal regions would have only one entry, holes would have zero,
44 * and any overlaps would have multiple entries.
45 *
46 * The interface is a bit cumbersome currently but is exposed this way so that
47 * clients can choose how to iterate through the region splits.
48 *
49 * @param <R>
50 */
51 public class RegionSplitCalculator<R extends KeyRange> {
52 final static Log LOG = LogFactory.getLog(RegionSplitCalculator.class);
53
54 private final Comparator<R> rangeCmp;
55 /**
56 * This contains a sorted set of all the possible split points
57 *
58 * Invariant: once populated this has 0 entries if empty or at most n+1 values
59 * where n == number of added ranges.
60 */
61 private final TreeSet<byte[]> splits = new TreeSet<byte[]>(BYTES_COMPARATOR);
62
63 /**
64 * This is a map from start key to regions with the same start key.
65 *
66 * Invariant: This always have n values in total
67 */
68 private final Multimap<byte[], R> starts = ArrayListMultimap.create();
69
70 /**
71 * SPECIAL CASE
72 */
73 private final static byte[] ENDKEY = null;
74
75 public RegionSplitCalculator(Comparator<R> cmp) {
76 rangeCmp = cmp;
77 }
78
79 public final static Comparator<byte[]> BYTES_COMPARATOR = new ByteArrayComparator() {
80 @Override
81 public int compare(byte[] l, byte[] r) {
82 if (l == null && r == null)
83 return 0;
84 if (l == null)
85 return 1;
86 if (r == null)
87 return -1;
88 return super.compare(l, r);
89 }
90 };
91
92 /**
93 * SPECIAL CASE wrapper for empty end key
94 *
95 * @return ENDKEY if end key is empty, else normal endkey.
96 */
97 private byte[] specialEndKey(R range) {
98 byte[] end = range.getEndKey();
99 if (end.length == 0) {
100 return ENDKEY;
101 }
102 return end;
103 }
104
105 /**
106 * Adds an edge to the split calculator
107 *
108 * @return true if is included, false if backwards/invalid
109 */
110 public boolean add(R range) {
111 byte[] start = range.getStartKey();
112 byte[] end = specialEndKey(range);
113
114 if (end != ENDKEY && Bytes.compareTo(start, end) > 0) {
115 // don't allow backwards edges
116 LOG.debug("attempted to add backwards edge: " + Bytes.toString(start)
117 + " " + Bytes.toString(end));
118 return false;
119 }
120
121 splits.add(start);
122 splits.add(end);
123 starts.put(start, range);
124 return true;
125 }
126
127 /**
128 * Generates a coverage multimap from split key to Regions that start with the
129 * split key.
130 *
131 * @return coverage multimap
132 */
133 public Multimap<byte[], R> calcCoverage() {
134 // This needs to be sorted to force the use of the comparator on the values,
135 // otherwise byte array comparison isn't used
136 Multimap<byte[], R> regions = TreeMultimap.create(BYTES_COMPARATOR,
137 rangeCmp);
138
139 // march through all splits from the start points
140 for (Entry<byte[], Collection<R>> start : starts.asMap().entrySet()) {
141 byte[] key = start.getKey();
142 for (R r : start.getValue()) {
143 regions.put(key, r);
144
145 for (byte[] coveredSplit : splits.subSet(r.getStartKey(),
146 specialEndKey(r))) {
147 regions.put(coveredSplit, r);
148 }
149 }
150 }
151 return regions;
152 }
153
154 public TreeSet<byte[]> getSplits() {
155 return splits;
156 }
157
158 public Multimap<byte[], R> getStarts() {
159 return starts;
160 }
161
162 }