blob: 7c118cae4288afab5fa121e9fd83e6f0b9b9483f [file] [log] [blame]
Felix Meschberger5e18fac2009-10-09 10:28:53 +00001/*
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +00002 * 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 Meschbergeradd2b4a2007-04-11 18:12:33 +000039 */
40class CaseInsensitiveDictionary extends Dictionary
41{
42
43 /**
44 * The backend dictionary with lower case keys.
45 */
46 private Hashtable internalMap;
47
48 /**
Felix Meschberger40d4e1b2008-03-26 10:41:00 +000049 * Mapping of lower case keys to original case keys as last used to set a
50 * property value.
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000051 */
52 private Hashtable originalKeys;
53
54
55 CaseInsensitiveDictionary()
56 {
57 internalMap = new Hashtable();
58 originalKeys = new Hashtable();
59 }
60
61
62 CaseInsensitiveDictionary( Dictionary props )
63 {
64 this();
65
66 Enumeration keys = props.keys();
67 while ( keys.hasMoreElements() )
68 {
69 Object key = keys.nextElement();
Felix Meschberger40d4e1b2008-03-26 10:41:00 +000070
71 // check the correct syntax of the key
72 checkKey( key );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000073
74 // check uniqueness of key
75 String lowerCase = ( ( String ) key ).toLowerCase();
76 if ( internalMap.containsKey( lowerCase ) )
77 {
78 throw new IllegalArgumentException( "Key [" + key + "] already present in different case" );
79 }
80
81 // check the value
82 Object value = props.get( key );
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +000083 value = checkValue( value );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000084
85 // add the key/value pair
86 internalMap.put( lowerCase, value );
87 originalKeys.put( lowerCase, key );
88 }
89 }
90
91
Felix Meschbergera0903df2009-01-19 10:40:28 +000092 CaseInsensitiveDictionary( CaseInsensitiveDictionary props, boolean deepCopy )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000093 {
Felix Meschbergera0903df2009-01-19 10:40:28 +000094 Hashtable tmp = new Hashtable( Math.max( 2 * props.internalMap.size(), 11 ), 0.75f );
95 if ( deepCopy )
96 {
97 Iterator entries = props.internalMap.entrySet().iterator();
98 while ( entries.hasNext() )
99 {
100 Map.Entry entry = ( Map.Entry ) entries.next();
101 Object value = entry.getValue();
102 if ( value.getClass().isArray() )
103 {
104 // copy array
105 int length = Array.getLength( value );
106 Object newValue = Array.newInstance( value.getClass().getComponentType(), length );
107 System.arraycopy( value, 0, newValue, 0, length );
108 value = newValue;
109 }
110 else if ( value instanceof Collection )
111 {
112 // copy collection, create Vector
113 // a Vector is created because the R4 and R4.1 specs
114 // state that the values must be simple, array or
115 // Vector. And even though we accept Collection nowadays
116 // there might be clients out there still written against
117 // R4 and R4.1 spec expecting Vector
118 value = new Vector( ( Collection ) value );
119 }
120 tmp.put( entry.getKey(), value );
121 }
122 }
123 else
124 {
125 tmp.putAll( props.internalMap );
126 }
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000127
Felix Meschbergera0903df2009-01-19 10:40:28 +0000128 internalMap = tmp;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000129 originalKeys = new Hashtable( props.originalKeys );
130 }
131
132
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000133 /*
134 * (non-Javadoc)
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000135 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000136 * @see java.util.Dictionary#elements()
137 */
138 public Enumeration elements()
139 {
140 return Collections.enumeration( internalMap.values() );
141 }
142
143
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000144 /*
145 * (non-Javadoc)
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000146 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000147 * @see java.util.Dictionary#get(java.lang.Object)
148 */
149 public Object get( Object key )
150 {
151 if ( key == null )
152 {
153 throw new NullPointerException( "key" );
154 }
155
156 String stringKey = String.valueOf( key ).toLowerCase();
157 return internalMap.get( stringKey );
158 }
159
160
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000161 /*
162 * (non-Javadoc)
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000163 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000164 * @see java.util.Dictionary#isEmpty()
165 */
166 public boolean isEmpty()
167 {
168 return internalMap.isEmpty();
169 }
170
171
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000172 /*
173 * (non-Javadoc)
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000174 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000175 * @see java.util.Dictionary#keys()
176 */
177 public Enumeration keys()
178 {
179 return Collections.enumeration( originalKeys.values() );
180 }
181
182
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000183 /*
184 * (non-Javadoc)
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000185 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000186 * @see java.util.Dictionary#put(java.lang.Object, java.lang.Object)
187 */
188 public Object put( Object key, Object value )
189 {
190 if ( key == null || value == null )
191 {
192 throw new NullPointerException( "key or value" );
193 }
194
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000195 checkKey( key );
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000196 value = checkValue( value );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000197
198 String lowerCase = String.valueOf( key ).toLowerCase();
199 originalKeys.put( lowerCase, key );
200 return internalMap.put( lowerCase, value );
201 }
202
203
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000204 /*
205 * (non-Javadoc)
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000206 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000207 * @see java.util.Dictionary#remove(java.lang.Object)
208 */
209 public Object remove( Object key )
210 {
211 if ( key == null )
212 {
213 throw new NullPointerException( "key" );
214 }
215
216 String lowerCase = String.valueOf( key ).toLowerCase();
217 originalKeys.remove( lowerCase );
218 return internalMap.remove( lowerCase );
219 }
220
221
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000222 /*
223 * (non-Javadoc)
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000224 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000225 * @see java.util.Dictionary#size()
226 */
227 public int size()
228 {
229 return internalMap.size();
230 }
231
232
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000233 // ---------- internal -----------------------------------------------------
234
235 /**
236 * Ensures the <code>key</code> complies with the <em>symbolic-name</em>
237 * production of the OSGi core specification (1.3.2):
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000238 *
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000239 * <pre>
240 * symbolic-name :: = token('.'token)*
241 * digit ::= [0..9]
242 * alpha ::= [a..zA..Z]
243 * alphanum ::= alpha | digit
244 * token ::= ( alphanum | ’_’ | ’-’ )+
245 * </pre>
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000246 *
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000247 * If the key does not comply an <code>IllegalArgumentException</code> is
248 * thrown.
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000249 *
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000250 * @param key
251 * The configuration property key to check.
252 * @throws IllegalArgumentException
253 * if the key does not comply with the symbolic-name production.
254 */
255 static void checkKey( Object keyObject )
256 {
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000257 // check for wrong type or null key
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000258 if ( !( keyObject instanceof String ) )
259 {
260 throw new IllegalArgumentException( "Key [" + keyObject + "] must be a String" );
261 }
262
263 String key = ( String ) keyObject;
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000264
265 // check for empty string
266 if ( key.length() == 0 )
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000267 {
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000268 throw new IllegalArgumentException( "Key [" + key + "] must not be an empty string" );
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000269 }
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000270 }
271
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000272
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000273 static Object checkValue( Object value )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000274 {
275 Class type;
Felix Meschberger88d6d102009-01-19 09:48:53 +0000276 if ( value == null )
277 {
278 // null is illegal
279 throw new IllegalArgumentException( "Value must not be null" );
280
281 }
282 else if ( value.getClass().isArray() )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000283 {
284 // check simple or primitive
285 type = value.getClass().getComponentType();
286
Felix Meschberger88d6d102009-01-19 09:48:53 +0000287 // check for primitive type (simple types are checked below)
288 // note: void[] cannot be created, so we ignore this here
289 if ( type.isPrimitive() )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000290 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000291 return value;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000292 }
293
294 }
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000295 else if ( value instanceof Collection )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000296 {
297 // check simple
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000298 Collection collection = ( Collection ) value;
299 if ( collection.isEmpty() )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000300 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000301 throw new IllegalArgumentException( "Collection must not be empty" );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000302 }
303
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000304 // ensure all elements have the same type and to internal list
305 Collection internalValue = new ArrayList( collection.size() );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000306 type = null;
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000307 for ( Iterator ci = collection.iterator(); ci.hasNext(); )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000308 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000309 Object el = ci.next();
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000310 if ( el == null )
311 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000312 throw new IllegalArgumentException( "Collection must not contain null elements" );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000313 }
314 if ( type == null )
315 {
316 type = el.getClass();
317 }
318 else if ( type != el.getClass() )
319 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000320 throw new IllegalArgumentException( "Collection element types must not be mixed" );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000321 }
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000322 internalValue.add( el );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000323 }
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000324 value = internalValue;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000325 }
Felix Meschberger88d6d102009-01-19 09:48:53 +0000326 else
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000327 {
328 // get the type to check (must be simple)
329 type = value.getClass();
330
331 }
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000332
333 // check for simple type
334 if ( type == String.class || type == Integer.class || type == Long.class || type == Float.class
335 || type == Double.class || type == Byte.class || type == Short.class || type == Character.class
336 || type == Boolean.class )
337 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000338 return value;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000339 }
340
341 // not a valid type
342 throw new IllegalArgumentException( "Value [" + value + "] has unsupported (base-) type " + type );
343 }
344
345
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000346 // ---------- Object Overwrites --------------------------------------------
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000347
348 public String toString()
349 {
350 return internalMap.toString();
351 }
352
353}