View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.trie.analyzer;
18  
19  import org.apache.commons.collections4.trie.KeyAnalyzer;
20  
21  /**
22   * An {@link KeyAnalyzer} for {@link String}s.
23   * <p>
24   * This class is stateless.
25   * </p>
26   *
27   * @since 4.0
28   */
29  public class StringKeyAnalyzer extends KeyAnalyzer<String> {
30  
31      private static final long serialVersionUID = -7032449491269434877L;
32  
33      /** A singleton instance of {@link StringKeyAnalyzer}. */
34      public static final StringKeyAnalyzer INSTANCE = new StringKeyAnalyzer();
35  
36      /** The number of bits per {@link Character} plus a presence bit. */
37      public static final int LENGTH = Character.SIZE + 1;
38  
39      /** A bit mask where the first bit is 1 and the others are zero. */
40      private static final int MSB = 0x8000;
41  
42      /** Returns a bit mask where the given bit is set. */
43      private static int mask(final int bit) {
44          return MSB >>> bit;
45      }
46  
47      /**
48       * Constructs a new instance.
49       *
50       * @deprecated Use {@link #INSTANCE}.
51       */
52      @Deprecated
53      public StringKeyAnalyzer() {
54          // empty
55      }
56  
57      @Override
58      public int bitIndex(final String key, final int offsetInBits, final int lengthInBits,
59                          final String other, final int otherOffsetInBits, final int otherLengthInBits) {
60  
61          if (offsetInBits % LENGTH != 0 || otherOffsetInBits % LENGTH != 0
62                  || lengthInBits % LENGTH != 0 || otherLengthInBits % LENGTH != 0) {
63              throw new IllegalArgumentException("The offsets and lengths must be at Character boundaries");
64          }
65  
66          final int beginIndex1 = offsetInBits / LENGTH;
67          final int beginIndex2 = otherOffsetInBits / LENGTH;
68  
69          final int endIndex1 = beginIndex1 + lengthInBits / LENGTH;
70          final int endIndex2 = other == null ? beginIndex2 : beginIndex2 + otherLengthInBits / LENGTH;
71  
72          final int length = Math.max(endIndex1, endIndex2);
73  
74          for (int i = 0; i < length; i++) {
75              final int index1 = beginIndex1 + i;
76              final int index2 = beginIndex2 + i;
77  
78              if (index1 < endIndex1 && other != null && index2 < endIndex2) {
79                  final char k = key.charAt(index1);
80                  final char f = other.charAt(index2);
81  
82                  if (k != f) {
83                      final int x = k ^ f;
84                      return i * LENGTH + 1 + Integer.numberOfLeadingZeros(x) - (LENGTH - 1);
85                  }
86              } else {
87                  // One has ended, the other has not. They differ at the presence bit of this block.
88                  return i * LENGTH;
89              }
90          }
91  
92          if (lengthInBits == 0 && (other == null || otherLengthInBits == 0)) {
93              return NULL_BIT_KEY;
94          }
95  
96          // Both keys are equal
97          return EQUAL_BIT_KEY;
98      }
99  
100     @Override
101     public int bitsPerElement() {
102         return LENGTH;
103     }
104 
105     @Override
106     public boolean isBitSet(final String key, final int bitIndex, final int lengthInBits) {
107         if (key == null || bitIndex >= lengthInBits) {
108             return false;
109         }
110 
111         final int index = bitIndex / LENGTH;
112         final int bit = bitIndex % LENGTH;
113 
114         if (bit == 0) {
115             return true;
116         }
117         return (key.charAt(index) & mask(bit - 1)) != 0;
118     }
119 
120     @Override
121     public boolean isPrefix(final String prefix, final int offsetInBits,
122                             final int lengthInBits, final String key) {
123         if (offsetInBits % LENGTH != 0 || lengthInBits % LENGTH != 0) {
124             throw new IllegalArgumentException(
125                     "Cannot determine prefix outside of Character boundaries");
126         }
127 
128         final String s1 = prefix.substring(offsetInBits / LENGTH, lengthInBits / LENGTH);
129         return key.startsWith(s1);
130     }
131 
132     @Override
133     public int lengthInBits(final String key) {
134         return key != null ? key.length() * LENGTH : 0;
135     }
136 }