001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.collections4.trie.analyzer; 018 019import org.apache.commons.collections4.trie.KeyAnalyzer; 020 021/** 022 * An {@link KeyAnalyzer} for {@link String}s. 023 * <p> 024 * This class is stateless. 025 * </p> 026 * 027 * @since 4.0 028 */ 029public class StringKeyAnalyzer extends KeyAnalyzer<String> { 030 031 private static final long serialVersionUID = -7032449491269434877L; 032 033 /** A singleton instance of {@link StringKeyAnalyzer}. */ 034 public static final StringKeyAnalyzer INSTANCE = new StringKeyAnalyzer(); 035 036 /** The number of bits per {@link Character} plus a presence bit. */ 037 public static final int LENGTH = Character.SIZE + 1; 038 039 /** A bit mask where the first bit is 1 and the others are zero. */ 040 private static final int MSB = 0x8000; 041 042 /** Returns a bit mask where the given bit is set. */ 043 private static int mask(final int bit) { 044 return MSB >>> bit; 045 } 046 047 /** 048 * Constructs a new instance. 049 * 050 * @deprecated Use {@link #INSTANCE}. 051 */ 052 @Deprecated 053 public StringKeyAnalyzer() { 054 // empty 055 } 056 057 @Override 058 public int bitIndex(final String key, final int offsetInBits, final int lengthInBits, 059 final String other, final int otherOffsetInBits, final int otherLengthInBits) { 060 061 if (offsetInBits % LENGTH != 0 || otherOffsetInBits % LENGTH != 0 062 || lengthInBits % LENGTH != 0 || otherLengthInBits % LENGTH != 0) { 063 throw new IllegalArgumentException("The offsets and lengths must be at Character boundaries"); 064 } 065 066 final int beginIndex1 = offsetInBits / LENGTH; 067 final int beginIndex2 = otherOffsetInBits / LENGTH; 068 069 final int endIndex1 = beginIndex1 + lengthInBits / LENGTH; 070 final int endIndex2 = other == null ? beginIndex2 : beginIndex2 + otherLengthInBits / LENGTH; 071 072 final int length = Math.max(endIndex1, endIndex2); 073 074 for (int i = 0; i < length; i++) { 075 final int index1 = beginIndex1 + i; 076 final int index2 = beginIndex2 + i; 077 078 if (index1 < endIndex1 && other != null && index2 < endIndex2) { 079 final char k = key.charAt(index1); 080 final char f = other.charAt(index2); 081 082 if (k != f) { 083 final int x = k ^ f; 084 return i * LENGTH + 1 + Integer.numberOfLeadingZeros(x) - (LENGTH - 1); 085 } 086 } else { 087 // One has ended, the other has not. They differ at the presence bit of this block. 088 return i * LENGTH; 089 } 090 } 091 092 if (lengthInBits == 0 && (other == null || otherLengthInBits == 0)) { 093 return NULL_BIT_KEY; 094 } 095 096 // Both keys are equal 097 return EQUAL_BIT_KEY; 098 } 099 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}