View Javadoc

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 }