blob: b4076c75c63aabbdeeccba46c9896f7094026e88 [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
Felix Meschberger007c50e2011-10-20 12:39:38 +000066 if ( props != null )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000067 {
Felix Meschberger007c50e2011-10-20 12:39:38 +000068 Enumeration keys = props.keys();
69 while ( keys.hasMoreElements() )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000070 {
Felix Meschberger007c50e2011-10-20 12:39:38 +000071 Object key = keys.nextElement();
72
73 // check the correct syntax of the key
74 checkKey( key );
75
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 );
85 value = checkValue( value );
86
87 // add the key/value pair
88 internalMap.put( lowerCase, value );
89 originalKeys.put( lowerCase, key );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000090 }
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000091 }
92 }
93
94
Felix Meschbergera0903df2009-01-19 10:40:28 +000095 CaseInsensitiveDictionary( CaseInsensitiveDictionary props, boolean deepCopy )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +000096 {
Felix Meschbergera0903df2009-01-19 10:40:28 +000097 Hashtable tmp = new Hashtable( Math.max( 2 * props.internalMap.size(), 11 ), 0.75f );
98 if ( deepCopy )
99 {
100 Iterator entries = props.internalMap.entrySet().iterator();
101 while ( entries.hasNext() )
102 {
103 Map.Entry entry = ( Map.Entry ) entries.next();
104 Object value = entry.getValue();
105 if ( value.getClass().isArray() )
106 {
107 // copy array
108 int length = Array.getLength( value );
109 Object newValue = Array.newInstance( value.getClass().getComponentType(), length );
110 System.arraycopy( value, 0, newValue, 0, length );
111 value = newValue;
112 }
113 else if ( value instanceof Collection )
114 {
115 // copy collection, create Vector
116 // a Vector is created because the R4 and R4.1 specs
117 // state that the values must be simple, array or
118 // Vector. And even though we accept Collection nowadays
119 // there might be clients out there still written against
120 // R4 and R4.1 spec expecting Vector
121 value = new Vector( ( Collection ) value );
122 }
123 tmp.put( entry.getKey(), value );
124 }
125 }
126 else
127 {
128 tmp.putAll( props.internalMap );
129 }
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000130
Felix Meschbergera0903df2009-01-19 10:40:28 +0000131 internalMap = tmp;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000132 originalKeys = new Hashtable( props.originalKeys );
133 }
134
135
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000136 /*
137 * (non-Javadoc)
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000138 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000139 * @see java.util.Dictionary#elements()
140 */
141 public Enumeration elements()
142 {
143 return Collections.enumeration( internalMap.values() );
144 }
145
146
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000147 /*
148 * (non-Javadoc)
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000149 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000150 * @see java.util.Dictionary#get(java.lang.Object)
151 */
152 public Object get( Object key )
153 {
154 if ( key == null )
155 {
156 throw new NullPointerException( "key" );
157 }
158
159 String stringKey = String.valueOf( key ).toLowerCase();
160 return internalMap.get( stringKey );
161 }
162
163
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000164 /*
165 * (non-Javadoc)
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000166 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000167 * @see java.util.Dictionary#isEmpty()
168 */
169 public boolean isEmpty()
170 {
171 return internalMap.isEmpty();
172 }
173
174
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000175 /*
176 * (non-Javadoc)
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000177 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000178 * @see java.util.Dictionary#keys()
179 */
180 public Enumeration keys()
181 {
182 return Collections.enumeration( originalKeys.values() );
183 }
184
185
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000186 /*
187 * (non-Javadoc)
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000188 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000189 * @see java.util.Dictionary#put(java.lang.Object, java.lang.Object)
190 */
191 public Object put( Object key, Object value )
192 {
193 if ( key == null || value == null )
194 {
195 throw new NullPointerException( "key or value" );
196 }
197
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000198 checkKey( key );
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000199 value = checkValue( value );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000200
201 String lowerCase = String.valueOf( key ).toLowerCase();
202 originalKeys.put( lowerCase, key );
203 return internalMap.put( lowerCase, value );
204 }
205
206
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000207 /*
208 * (non-Javadoc)
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000209 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000210 * @see java.util.Dictionary#remove(java.lang.Object)
211 */
212 public Object remove( Object key )
213 {
214 if ( key == null )
215 {
216 throw new NullPointerException( "key" );
217 }
218
219 String lowerCase = String.valueOf( key ).toLowerCase();
220 originalKeys.remove( lowerCase );
221 return internalMap.remove( lowerCase );
222 }
223
224
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000225 /*
226 * (non-Javadoc)
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000227 *
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000228 * @see java.util.Dictionary#size()
229 */
230 public int size()
231 {
232 return internalMap.size();
233 }
234
235
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000236 // ---------- internal -----------------------------------------------------
237
238 /**
239 * Ensures the <code>key</code> complies with the <em>symbolic-name</em>
240 * production of the OSGi core specification (1.3.2):
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000241 *
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000242 * <pre>
243 * symbolic-name :: = token('.'token)*
244 * digit ::= [0..9]
245 * alpha ::= [a..zA..Z]
246 * alphanum ::= alpha | digit
247 * token ::= ( alphanum | ’_’ | ’-’ )+
248 * </pre>
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000249 *
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000250 * If the key does not comply an <code>IllegalArgumentException</code> is
251 * thrown.
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000252 *
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000253 * @param key
254 * The configuration property key to check.
255 * @throws IllegalArgumentException
256 * if the key does not comply with the symbolic-name production.
257 */
258 static void checkKey( Object keyObject )
259 {
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000260 // check for wrong type or null key
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000261 if ( !( keyObject instanceof String ) )
262 {
263 throw new IllegalArgumentException( "Key [" + keyObject + "] must be a String" );
264 }
265
266 String key = ( String ) keyObject;
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000267
268 // check for empty string
269 if ( key.length() == 0 )
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000270 {
Felix Meschberger5e18fac2009-10-09 10:28:53 +0000271 throw new IllegalArgumentException( "Key [" + key + "] must not be an empty string" );
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000272 }
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000273 }
274
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000275
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000276 static Object checkValue( Object value )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000277 {
278 Class type;
Felix Meschberger88d6d102009-01-19 09:48:53 +0000279 if ( value == null )
280 {
281 // null is illegal
282 throw new IllegalArgumentException( "Value must not be null" );
283
284 }
285 else if ( value.getClass().isArray() )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000286 {
287 // check simple or primitive
288 type = value.getClass().getComponentType();
289
Felix Meschberger88d6d102009-01-19 09:48:53 +0000290 // check for primitive type (simple types are checked below)
291 // note: void[] cannot be created, so we ignore this here
292 if ( type.isPrimitive() )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000293 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000294 return value;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000295 }
296
297 }
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000298 else if ( value instanceof Collection )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000299 {
300 // check simple
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000301 Collection collection = ( Collection ) value;
302 if ( collection.isEmpty() )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000303 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000304 throw new IllegalArgumentException( "Collection must not be empty" );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000305 }
306
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000307 // ensure all elements have the same type and to internal list
308 Collection internalValue = new ArrayList( collection.size() );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000309 type = null;
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000310 for ( Iterator ci = collection.iterator(); ci.hasNext(); )
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000311 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000312 Object el = ci.next();
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000313 if ( el == null )
314 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000315 throw new IllegalArgumentException( "Collection must not contain null elements" );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000316 }
317 if ( type == null )
318 {
319 type = el.getClass();
320 }
321 else if ( type != el.getClass() )
322 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000323 throw new IllegalArgumentException( "Collection element types must not be mixed" );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000324 }
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000325 internalValue.add( el );
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000326 }
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000327 value = internalValue;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000328 }
Felix Meschberger88d6d102009-01-19 09:48:53 +0000329 else
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000330 {
331 // get the type to check (must be simple)
332 type = value.getClass();
333
334 }
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000335
336 // check for simple type
337 if ( type == String.class || type == Integer.class || type == Long.class || type == Float.class
338 || type == Double.class || type == Byte.class || type == Short.class || type == Character.class
339 || type == Boolean.class )
340 {
Felix Meschbergeraeb8c5c2009-01-14 19:52:32 +0000341 return value;
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000342 }
343
344 // not a valid type
345 throw new IllegalArgumentException( "Value [" + value + "] has unsupported (base-) type " + type );
346 }
347
348
Felix Meschberger40d4e1b2008-03-26 10:41:00 +0000349 // ---------- Object Overwrites --------------------------------------------
Felix Meschbergeradd2b4a2007-04-11 18:12:33 +0000350
351 public String toString()
352 {
353 return internalMap.toString();
354 }
355
356}