blob: ee8b6c02f69a5f297cefd001d3b425748bdc1854 [file] [log] [blame]
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.apache.felix.cm.impl;
20
21
Felix Meschbergera0903df2009-01-19 10:40:28 +000022import java.lang.reflect.Array;
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +000023import java.util.ArrayList;
24import java.util.Collection;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000025import java.util.Collections;
26import java.util.Dictionary;
27import java.util.Enumeration;
28import java.util.Hashtable;
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +000029import java.util.Iterator;
Felix Meschbergera0903df2009-01-19 10:40:28 +000030import java.util.Map;
31import java.util.Vector;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000032
33
34/**
35 * The <code>CaseInsensitiveDictionary</code> is a
36 * <code>java.util.Dictionary</code> which conforms to the requirements laid
37 * out by the Configuration Admin Service Specification requiring the property
38 * names to keep case but to ignore case when accessing the properties.
Felix Meschberger40d4e1b2008-03-26 10:41:00 +000039 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000040 * @author fmeschbe
41 */
42class CaseInsensitiveDictionary extends Dictionary
43{
44
45 /**
46 * The backend dictionary with lower case keys.
47 */
48 private Hashtable internalMap;
49
50 /**
Felix Meschberger40d4e1b2008-03-26 10:41:00 +000051 * Mapping of lower case keys to original case keys as last used to set a
52 * property value.
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000053 */
54 private Hashtable originalKeys;
55
56
57 CaseInsensitiveDictionary()
58 {
59 internalMap = new Hashtable();
60 originalKeys = new Hashtable();
61 }
62
63
64 CaseInsensitiveDictionary( Dictionary props )
65 {
66 this();
67
68 Enumeration keys = props.keys();
69 while ( keys.hasMoreElements() )
70 {
71 Object key = keys.nextElement();
Felix Meschberger40d4e1b2008-03-26 10:41:00 +000072
73 // check the correct syntax of the key
74 checkKey( key );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000075
76 // check uniqueness of key
77 String lowerCase = ( ( String ) key ).toLowerCase();
78 if ( internalMap.containsKey( lowerCase ) )
79 {
80 throw new IllegalArgumentException( "Key [" + key + "] already present in different case" );
81 }
82
83 // check the value
84 Object value = props.get( key );
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +000085 value = checkValue( value );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000086
87 // add the key/value pair
88 internalMap.put( lowerCase, value );
89 originalKeys.put( lowerCase, key );
90 }
91 }
92
93
Felix Meschbergera0903df2009-01-19 10:40:28 +000094 CaseInsensitiveDictionary( CaseInsensitiveDictionary props, boolean deepCopy )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000095 {
Felix Meschbergera0903df2009-01-19 10:40:28 +000096 Hashtable tmp = new Hashtable( Math.max( 2 * props.internalMap.size(), 11 ), 0.75f );
97 if ( deepCopy )
98 {
99 Iterator entries = props.internalMap.entrySet().iterator();
100 while ( entries.hasNext() )
101 {
102 Map.Entry entry = ( Map.Entry ) entries.next();
103 Object value = entry.getValue();
104 if ( value.getClass().isArray() )
105 {
106 // copy array
107 int length = Array.getLength( value );
108 Object newValue = Array.newInstance( value.getClass().getComponentType(), length );
109 System.arraycopy( value, 0, newValue, 0, length );
110 value = newValue;
111 }
112 else if ( value instanceof Collection )
113 {
114 // copy collection, create Vector
115 // a Vector is created because the R4 and R4.1 specs
116 // state that the values must be simple, array or
117 // Vector. And even though we accept Collection nowadays
118 // there might be clients out there still written against
119 // R4 and R4.1 spec expecting Vector
120 value = new Vector( ( Collection ) value );
121 }
122 tmp.put( entry.getKey(), value );
123 }
124 }
125 else
126 {
127 tmp.putAll( props.internalMap );
128 }
129
130 internalMap = tmp;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000131 originalKeys = new Hashtable( props.originalKeys );
132 }
133
134
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000135 /*
136 * (non-Javadoc)
137 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000138 * @see java.util.Dictionary#elements()
139 */
140 public Enumeration elements()
141 {
142 return Collections.enumeration( internalMap.values() );
143 }
144
145
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000146 /*
147 * (non-Javadoc)
148 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000149 * @see java.util.Dictionary#get(java.lang.Object)
150 */
151 public Object get( Object key )
152 {
153 if ( key == null )
154 {
155 throw new NullPointerException( "key" );
156 }
157
158 String stringKey = String.valueOf( key ).toLowerCase();
159 return internalMap.get( stringKey );
160 }
161
162
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000163 /*
164 * (non-Javadoc)
165 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000166 * @see java.util.Dictionary#isEmpty()
167 */
168 public boolean isEmpty()
169 {
170 return internalMap.isEmpty();
171 }
172
173
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000174 /*
175 * (non-Javadoc)
176 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000177 * @see java.util.Dictionary#keys()
178 */
179 public Enumeration keys()
180 {
181 return Collections.enumeration( originalKeys.values() );
182 }
183
184
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000185 /*
186 * (non-Javadoc)
187 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000188 * @see java.util.Dictionary#put(java.lang.Object, java.lang.Object)
189 */
190 public Object put( Object key, Object value )
191 {
192 if ( key == null || value == null )
193 {
194 throw new NullPointerException( "key or value" );
195 }
196
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000197 checkKey( key );
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000198 value = checkValue( value );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000199
200 String lowerCase = String.valueOf( key ).toLowerCase();
201 originalKeys.put( lowerCase, key );
202 return internalMap.put( lowerCase, value );
203 }
204
205
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000206 /*
207 * (non-Javadoc)
208 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000209 * @see java.util.Dictionary#remove(java.lang.Object)
210 */
211 public Object remove( Object key )
212 {
213 if ( key == null )
214 {
215 throw new NullPointerException( "key" );
216 }
217
218 String lowerCase = String.valueOf( key ).toLowerCase();
219 originalKeys.remove( lowerCase );
220 return internalMap.remove( lowerCase );
221 }
222
223
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000224 /*
225 * (non-Javadoc)
226 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000227 * @see java.util.Dictionary#size()
228 */
229 public int size()
230 {
231 return internalMap.size();
232 }
233
234
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000235 // ---------- internal -----------------------------------------------------
236
237 /**
238 * Ensures the <code>key</code> complies with the <em>symbolic-name</em>
239 * production of the OSGi core specification (1.3.2):
240 *
241 * <pre>
242 * symbolic-name :: = token('.'token)*
243 * digit ::= [0..9]
244 * alpha ::= [a..zA..Z]
245 * alphanum ::= alpha | digit
246 * token ::= ( alphanum | ’_’ | ’-’ )+
247 * </pre>
248 *
249 * If the key does not comply an <code>IllegalArgumentException</code> is
250 * thrown.
251 *
252 * @param key
253 * The configuration property key to check.
254 * @throws IllegalArgumentException
255 * if the key does not comply with the symbolic-name production.
256 */
257 static void checkKey( Object keyObject )
258 {
259 if ( !( keyObject instanceof String ) )
260 {
261 throw new IllegalArgumentException( "Key [" + keyObject + "] must be a String" );
262 }
263
264 String key = ( String ) keyObject;
265 if ( key.startsWith( "." ) || key.endsWith( "." ) )
266 {
267 throw new IllegalArgumentException( "Key [" + key + "] must not start or end with a dot" );
268 }
269
270 int lastDot = Integer.MIN_VALUE;
271 for ( int i = 0; i < key.length(); i++ )
272 {
273 char c = key.charAt( i );
274 if ( c == '.' )
275 {
276 if ( lastDot == i - 1 )
277 {
278 throw new IllegalArgumentException( "Key [" + key + "] must not have consecutive dots" );
279 }
280 lastDot = i;
281 }
282 else if ( ( c < '0' || c > '9' ) && ( c < 'a' || c > 'z' ) && ( c < 'A' || c > 'Z' ) && c != '_'
283 && c != '-' )
284 {
285 throw new IllegalArgumentException( "Key [" + key + "] contains illegal character" );
286 }
287 }
288 }
289
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000290
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000291 static Object checkValue( Object value )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000292 {
293 Class type;
Felix Meschberger88d6d102009-01-19 09:48:53 +0000294 if ( value == null )
295 {
296 // null is illegal
297 throw new IllegalArgumentException( "Value must not be null" );
298
299 }
300 else if ( value.getClass().isArray() )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000301 {
302 // check simple or primitive
303 type = value.getClass().getComponentType();
304
Felix Meschberger88d6d102009-01-19 09:48:53 +0000305 // check for primitive type (simple types are checked below)
306 // note: void[] cannot be created, so we ignore this here
307 if ( type.isPrimitive() )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000308 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000309 return value;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000310 }
311
312 }
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000313 else if ( value instanceof Collection )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000314 {
315 // check simple
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000316 Collection collection = ( Collection ) value;
317 if ( collection.isEmpty() )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000318 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000319 throw new IllegalArgumentException( "Collection must not be empty" );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000320 }
321
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000322 // ensure all elements have the same type and to internal list
323 Collection internalValue = new ArrayList( collection.size() );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000324 type = null;
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000325 for ( Iterator ci = collection.iterator(); ci.hasNext(); )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000326 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000327 Object el = ci.next();
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000328 if ( el == null )
329 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000330 throw new IllegalArgumentException( "Collection must not contain null elements" );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000331 }
332 if ( type == null )
333 {
334 type = el.getClass();
335 }
336 else if ( type != el.getClass() )
337 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000338 throw new IllegalArgumentException( "Collection element types must not be mixed" );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000339 }
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000340 internalValue.add( el );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000341 }
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000342 value = internalValue;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000343 }
Felix Meschberger88d6d102009-01-19 09:48:53 +0000344 else
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000345 {
346 // get the type to check (must be simple)
347 type = value.getClass();
348
349 }
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000350
351 // check for simple type
352 if ( type == String.class || type == Integer.class || type == Long.class || type == Float.class
353 || type == Double.class || type == Byte.class || type == Short.class || type == Character.class
354 || type == Boolean.class )
355 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000356 return value;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000357 }
358
359 // not a valid type
360 throw new IllegalArgumentException( "Value [" + value + "] has unsupported (base-) type " + type );
361 }
362
363
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000364 // ---------- Object Overwrites --------------------------------------------
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000365
366 public String toString()
367 {
368 return internalMap.toString();
369 }
370
371}