1 /*
2 * Copyright 2001-2009 Stephen Colebourne
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.joda.time.convert;
17
18 /**
19 * A set of converters, which allows exact converters to be quickly
20 * selected. This class is threadsafe because it is (essentially) immutable.
21 *
22 * @author Brian S O'Neill
23 * @since 1.0
24 */
25 class ConverterSet {
26 private final Converter[] iConverters;
27
28 // A simple immutable hashtable: closed hashing, linear probing, sized
29 // power of 2, at least one null slot.
30 private Entry[] iSelectEntries;
31
32 ConverterSet(Converter[] converters) {
33 // Since this is a package private constructor, we trust ourselves not
34 // to alter the array outside this class.
35 iConverters = converters;
36 iSelectEntries = new Entry[1 << 4]; // 16
37 }
38
39 /**
40 * Returns the closest matching converter for the given type, or null if
41 * none found.
42 *
43 * @param type type to select, which may be null
44 * @throws IllegalStateException if multiple converters match the type
45 * equally well
46 */
47 Converter select(Class<?> type) throws IllegalStateException {
48 // Check the hashtable first.
49 Entry[] entries = iSelectEntries;
50 int length = entries.length;
51 int index = type == null ? 0 : type.hashCode() & (length - 1);
52
53 Entry e;
54 // This loop depends on there being at least one null slot.
55 while ((e = entries[index]) != null) {
56 if (e.iType == type) {
57 return e.iConverter;
58 }
59 if (++index >= length) {
60 index = 0;
61 }
62 }
63
64 // Not found in the hashtable, so do actual work.
65
66 Converter converter = selectSlow(this, type);
67 e = new Entry(type, converter);
68
69 // Save the entry for future selects. This class must be threadsafe,
70 // but there is no synchronization. Since the hashtable is being used
71 // as a cache, it is okay to destroy existing entries. This isn't
72 // likely to occur unless there is a high amount of concurrency. As
73 // time goes on, cache updates will occur less often, and the cache
74 // will fill with all the necessary entries.
75
76 // Do all updates on a copy: slots in iSelectEntries must not be
77 // updated by multiple threads as this can allow all null slots to be
78 // consumed.
79 entries = (Entry[])entries.clone();
80
81 // Add new entry.
82 entries[index] = e;
83
84 // Verify that at least one null slot exists!
85 for (int i=0; i<length; i++) {
86 if (entries[i] == null) {
87 // Found a null slot, swap in new hashtable.
88 iSelectEntries = entries;
89 return converter;
90 }
91 }
92
93 // Double capacity and re-hash.
94
95 int newLength = length << 1;
96 Entry[] newEntries = new Entry[newLength];
97 for (int i=0; i<length; i++) {
98 e = entries[i];
99 type = e.iType;
100 index = type == null ? 0 : type.hashCode() & (newLength - 1);
101 while (newEntries[index] != null) {
102 if (++index >= newLength) {
103 index = 0;
104 }
105 }
106 newEntries[index] = e;
107 }
108
109 // Swap in new hashtable.
110 iSelectEntries = newEntries;
111 return converter;
112 }
113
114 /**
115 * Returns the amount of converters in the set.
116 */
117 int size() {
118 return iConverters.length;
119 }
120
121 /**
122 * Copies all the converters in the set to the given array.
123 */
124 void copyInto(Converter[] converters) {
125 System.arraycopy(iConverters, 0, converters, 0, iConverters.length);
126 }
127
128 /**
129 * Returns a copy of this set, with the given converter added. If a
130 * matching converter is already in the set, the given converter replaces
131 * it. If the converter is exactly the same as one already in the set, the
132 * original set is returned.
133 *
134 * @param converter converter to add, must not be null
135 * @param removed if not null, element 0 is set to the removed converter
136 * @throws NullPointerException if converter is null
137 */
138 ConverterSet add(Converter converter, Converter[] removed) {
139 Converter[] converters = iConverters;
140 int length = converters.length;
141
142 for (int i=0; i<length; i++) {
143 Converter existing = converters[i];
144 if (converter.equals(existing)) {
145 // Already in the set.
146 if (removed != null) {
147 removed[0] = null;
148 }
149 return this;
150 }
151
152 if (converter.getSupportedType() == existing.getSupportedType()) {
153 // Replace the converter.
154 Converter[] copy = new Converter[length];
155
156 for (int j=0; j<length; j++) {
157 if (j != i) {
158 copy[j] = converters[j];
159 } else {
160 copy[j] = converter;
161 }
162 }
163
164 if (removed != null) {
165 removed[0] = existing;
166 }
167 return new ConverterSet(copy);
168 }
169 }
170
171 // Not found, so add it.
172 Converter[] copy = new Converter[length + 1];
173 System.arraycopy(converters, 0, copy, 0, length);
174 copy[length] = converter;
175
176 if (removed != null) {
177 removed[0] = null;
178 }
179 return new ConverterSet(copy);
180 }
181
182 /**
183 * Returns a copy of this set, with the given converter removed. If the
184 * converter was not in the set, the original set is returned.
185 *
186 * @param converter converter to remove, must not be null
187 * @param removed if not null, element 0 is set to the removed converter
188 * @throws NullPointerException if converter is null
189 */
190 ConverterSet remove(Converter converter, Converter[] removed) {
191 Converter[] converters = iConverters;
192 int length = converters.length;
193
194 for (int i=0; i<length; i++) {
195 if (converter.equals(converters[i])) {
196 return remove(i, removed);
197 }
198 }
199
200 // Not found.
201 if (removed != null) {
202 removed[0] = null;
203 }
204 return this;
205 }
206
207 /**
208 * Returns a copy of this set, with the converter at the given index
209 * removed.
210 *
211 * @param index index of converter to remove
212 * @param removed if not null, element 0 is set to the removed converter
213 * @throws IndexOutOfBoundsException if the index is invalid
214 */
215 ConverterSet remove(final int index, Converter[] removed) {
216 Converter[] converters = iConverters;
217 int length = converters.length;
218 if (index >= length) {
219 throw new IndexOutOfBoundsException();
220 }
221
222 if (removed != null) {
223 removed[0] = converters[index];
224 }
225
226 Converter[] copy = new Converter[length - 1];
227
228 int j = 0;
229 for (int i=0; i<length; i++) {
230 if (i != index) {
231 copy[j++] = converters[i];
232 }
233 }
234
235 return new ConverterSet(copy);
236 }
237
238 /**
239 * Returns the closest matching converter for the given type, but not very
240 * efficiently.
241 */
242 private static Converter selectSlow(ConverterSet set, Class<?> type) {
243 Converter[] converters = set.iConverters;
244 int length = converters.length;
245 Converter converter;
246
247 for (int i=length; --i>=0; ) {
248 converter = converters[i];
249 Class<?> supportedType = converter.getSupportedType();
250
251 if (supportedType == type) {
252 // Exact match.
253 return converter;
254 }
255
256 if (supportedType == null || (type != null && !supportedType.isAssignableFrom(type))) {
257 // Eliminate the impossible.
258 set = set.remove(i, null);
259 converters = set.iConverters;
260 length = converters.length;
261 }
262 }
263
264 // Haven't found exact match, so check what remains in the set.
265
266 if (type == null || length == 0) {
267 return null;
268 }
269 if (length == 1) {
270 // Found the one best match.
271 return converters[0];
272 }
273
274 // At this point, there exist multiple potential converters.
275
276 // Eliminate supertypes.
277 for (int i=length; --i>=0; ) {
278 converter = converters[i];
279 Class<?> supportedType = converter.getSupportedType();
280 for (int j=length; --j>=0; ) {
281 if (j != i && converters[j].getSupportedType().isAssignableFrom(supportedType)) {
282 // Eliminate supertype.
283 set = set.remove(j, null);
284 converters = set.iConverters;
285 length = converters.length;
286 i = length - 1;
287 }
288 }
289 }
290
291 // Check what remains in the set.
292
293 if (length == 1) {
294 // Found the one best match.
295 return converters[0];
296 }
297
298 // Class c implements a, b {}
299 // Converters exist only for a and b. Which is better? Neither.
300
301 StringBuilder msg = new StringBuilder();
302 msg.append("Unable to find best converter for type \"");
303 msg.append(type.getName());
304 msg.append("\" from remaining set: ");
305 for (int i=0; i<length; i++) {
306 converter = converters[i];
307 Class<?> supportedType = converter.getSupportedType();
308
309 msg.append(converter.getClass().getName());
310 msg.append('[');
311 msg.append(supportedType == null ? null : supportedType.getName());
312 msg.append("], ");
313 }
314
315 throw new IllegalStateException(msg.toString());
316 }
317
318 static class Entry {
319 final Class<?> iType;
320 final Converter iConverter;
321
322 Entry(Class<?> type, Converter converter) {
323 iType = type;
324 iConverter = converter;
325 }
326 }
327
328 }