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    *      http://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   * @since 4.0
27   */
28  public class StringKeyAnalyzer extends KeyAnalyzer<String> {
29  
30      private static final long serialVersionUID = -7032449491269434877L;
31  
32      /** A singleton instance of {@link StringKeyAnalyzer}. */
33      public static final StringKeyAnalyzer INSTANCE = new StringKeyAnalyzer();
34  
35      /** The number of bits per {@link Character}. */
36      public static final int LENGTH = Character.SIZE;
37  
38      /** A bit mask where the first bit is 1 and the others are zero. */
39      private static final int MSB = 0x8000;
40  
41      /** Returns a bit mask where the given bit is set. */
42      private static int mask(final int bit) {
43          return MSB >>> bit;
44      }
45  
46      /**
47       * Constructs a new instance.
48       *
49       * @deprecated Use {@link #INSTANCE}.
50       */
51      @Deprecated
52      public StringKeyAnalyzer() {
53          // empty
54      }
55  
56      @Override
57      public int bitIndex(final String key, final int offsetInBits, final int lengthInBits,
58                          final String other, final int otherOffsetInBits, final int otherLengthInBits) {
59  
60          boolean allNull = true;
61  
62          if (offsetInBits % LENGTH != 0 || otherOffsetInBits % LENGTH != 0
63                  || lengthInBits % LENGTH != 0 || otherLengthInBits % LENGTH != 0) {
64              throw new IllegalArgumentException("The offsets and lengths must be at Character boundaries");
65          }
66  
67          final int beginIndex1 = offsetInBits / LENGTH;
68          final int beginIndex2 = otherOffsetInBits / LENGTH;
69  
70          final int endIndex1 = beginIndex1 + lengthInBits / LENGTH;
71          final int endIndex2 = beginIndex2 + otherLengthInBits / LENGTH;
72  
73          final int length = Math.max(endIndex1, endIndex2);
74  
75          // Look at each character, and if they're different
76          // then figure out which bit makes the difference
77          // and return it.
78          char k = 0;
79          char f = 0;
80          for (int i = 0; i < length; i++) {
81              final int index1 = beginIndex1 + i;
82              final int index2 = beginIndex2 + i;
83  
84              if (index1 >= endIndex1) {
85                  k = 0;
86              } else {
87                  k = key.charAt(index1);
88              }
89  
90              if (other == null || index2 >= endIndex2) {
91                  f = 0;
92              } else {
93                  f = other.charAt(index2);
94              }
95  
96              if (k != f) {
97                  final int x = k ^ f;
98                  return i * LENGTH + Integer.numberOfLeadingZeros(x) - LENGTH;
99              }
100 
101             if (k != 0) {
102                 allNull = false;
103             }
104         }
105 
106         // All bits are 0
107         if (allNull) {
108             return NULL_BIT_KEY;
109         }
110 
111         // Both keys are equal
112         return EQUAL_BIT_KEY;
113     }
114 
115     @Override
116     public int bitsPerElement() {
117         return LENGTH;
118     }
119 
120     @Override
121     public boolean isBitSet(final String key, final int bitIndex, final int lengthInBits) {
122         if (key == null || bitIndex >= lengthInBits) {
123             return false;
124         }
125 
126         final int index = bitIndex / LENGTH;
127         final int bit = bitIndex % LENGTH;
128 
129         return (key.charAt(index) & mask(bit)) != 0;
130     }
131 
132     @Override
133     public boolean isPrefix(final String prefix, final int offsetInBits,
134                             final int lengthInBits, final String key) {
135         if (offsetInBits % LENGTH != 0 || lengthInBits % LENGTH != 0) {
136             throw new IllegalArgumentException(
137                     "Cannot determine prefix outside of Character boundaries");
138         }
139 
140         final String s1 = prefix.substring(offsetInBits / LENGTH, lengthInBits / LENGTH);
141         return key.startsWith(s1);
142     }
143 
144     @Override
145     public int lengthInBits(final String key) {
146         return key != null ? key.length() * LENGTH : 0;
147     }
148 }