EMMA Coverage Report (generated Tue Oct 28 00:01:11 GMT 2008)
[all classes][org.joda.time.convert]

COVERAGE SUMMARY FOR SOURCE FILE [ConverterSet.java]

nameclass, %method, %block, %line, %
ConverterSet.java100% (2/2)100% (9/9)99%  (495/500)99%  (118.9/120)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ConverterSet100% (1/1)100% (8/8)99%  (486/491)99%  (114.9/116)
select (Class): Converter 100% (1/1)98%  (120/123)96%  (27/28)
selectSlow (ConverterSet, Class): Converter 100% (1/1)99%  (165/167)100% (37.9/38)
ConverterSet (Converter []): void 100% (1/1)100% (10/10)100% (4/4)
add (Converter, Converter []): ConverterSet 100% (1/1)100% (95/95)100% (23/23)
copyInto (Converter []): void 100% (1/1)100% (10/10)100% (2/2)
remove (Converter, Converter []): ConverterSet 100% (1/1)100% (32/32)100% (8/8)
remove (int, Converter []): ConverterSet 100% (1/1)100% (50/50)100% (12/12)
size (): int 100% (1/1)100% (4/4)100% (1/1)
     
class ConverterSet$Entry100% (1/1)100% (1/1)100% (9/9)100% (4/4)
ConverterSet$Entry (Class, Converter): void 100% (1/1)100% (9/9)100% (4/4)

1/*
2 *  Copyright 2001-2005 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 */
16package 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 */
25class 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        StringBuffer msg = new StringBuffer();
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}

[all classes][org.joda.time.convert]
EMMA 2.0.5312 (C) Vladimir Roubtsov