1 package nl.tudelft.simulation.naming.context;
2
3 import java.util.Collection;
4 import java.util.Collections;
5 import java.util.LinkedHashSet;
6 import java.util.Map;
7 import java.util.Set;
8 import java.util.TreeMap;
9
10 import javax.naming.NameAlreadyBoundException;
11 import javax.naming.NameNotFoundException;
12 import javax.naming.NamingException;
13 import javax.naming.NotContextException;
14
15 import org.djutils.event.LocalEventProducer;
16 import org.djutils.exceptions.Throw;
17
18 import nl.tudelft.simulation.naming.context.util.ContextUtil;
19
20
21
22
23
24
25
26
27
28
29
30 public class JvmContext extends LocalEventProducer implements ContextInterface
31 {
32
33 protected ContextInterface parent;
34
35
36 private String atomicName;
37
38
39 private String absolutePath;
40
41
42 protected Map<String, Object> elements = Collections.synchronizedMap(new TreeMap<String, Object>());
43
44
45
46
47
48 public JvmContext(final String atomicName)
49 {
50 this(null, atomicName);
51 }
52
53
54
55
56
57
58 public JvmContext(final ContextInterface parent, final String atomicName)
59 {
60 Throw.whenNull(atomicName, "name under which a context is registered cannot be null");
61 Throw.when(atomicName.contains(ContextInterface.SEPARATOR), IllegalArgumentException.class,
62 "name %s under which a context is registered cannot contain the separator string %s", atomicName,
63 ContextInterface.SEPARATOR);
64 this.parent = parent;
65 this.atomicName = atomicName;
66 this.absolutePath = parent == null ? "" : parent.getAbsolutePath() + ContextInterface.SEPARATOR + this.atomicName;
67 }
68
69 @Override
70 public String getAtomicName()
71 {
72 return this.atomicName;
73 }
74
75 @Override
76 public ContextInterface getParent()
77 {
78 return this.parent;
79 }
80
81 @Override
82 public ContextInterface getRootContext()
83 {
84 ContextInterface result = this;
85 while (result.getParent() != null)
86 {
87 result = result.getParent();
88 }
89 return result;
90 }
91
92 @Override
93 public String getAbsolutePath()
94 {
95 return this.absolutePath;
96 }
97
98 @Override
99 public Object getObject(final String key) throws NamingException
100 {
101 Throw.whenNull(key, "key cannot be null");
102 Throw.when(key.length() == 0 || key.contains(ContextInterface.SEPARATOR), NamingException.class,
103 "key [%s] is the empty string or key contains '/'", key);
104 if (!this.elements.containsKey(key))
105 {
106 throw new NameNotFoundException("key " + key + " does not exist in Context");
107 }
108
109 return this.elements.get(key);
110 }
111
112 @Override
113 public Object get(final String name) throws NamingException
114 {
115 ContextName contextName = lookup(name);
116 if (contextName.getName().length() == 0)
117 {
118 return contextName.getContext();
119 }
120 Object result = contextName.getContext().getObject(contextName.getName());
121 return result;
122 }
123
124 @Override
125 public boolean exists(final String name) throws NamingException
126 {
127 ContextName contextName = lookup(name);
128 if (contextName.getName().length() == 0)
129 {
130 return true;
131 }
132 return contextName.getContext().hasKey(contextName.getName());
133 }
134
135 @Override
136 public boolean hasKey(final String key) throws NamingException
137 {
138 Throw.whenNull(key, "key cannot be null");
139 Throw.when(key.length() == 0 || key.contains(ContextInterface.SEPARATOR), NamingException.class,
140 "key [%s] is the empty string or key contains '/'", key);
141 return this.elements.containsKey(key);
142 }
143
144
145
146
147
148
149 @Override
150 public boolean hasObject(final Object object)
151 {
152 return this.elements.containsValue(object);
153 }
154
155 @Override
156 public boolean isEmpty()
157 {
158 return this.elements.isEmpty();
159 }
160
161 @Override
162 public void bindObject(final String key, final Object object) throws NamingException
163 {
164 Throw.whenNull(key, "key cannot be null");
165 Throw.when(key.length() == 0 || key.contains(ContextInterface.SEPARATOR), NamingException.class,
166 "key [%s] is the empty string or key contains '/'", key);
167 if (this.elements.containsKey(key))
168 {
169 throw new NameAlreadyBoundException("key " + key + " already bound to object in Context");
170 }
171 checkCircular(object);
172 this.elements.put(key, object);
173 fireEvent(ContextInterface.OBJECT_ADDED_EVENT, new Object[] {getAbsolutePath(), key, object});
174 }
175
176 @Override
177 public void bindObject(final Object object) throws NamingException
178 {
179 Throw.whenNull(object, "object cannot be null");
180 bindObject(makeObjectKey(object), object);
181 }
182
183 @Override
184 public void bind(final String name, final Object object) throws NamingException
185 {
186 ContextName contextName = lookup(name);
187 contextName.getContext().bindObject(contextName.getName(), object);
188 }
189
190 @Override
191 public void unbindObject(final String key) throws NamingException
192 {
193 Throw.whenNull(key, "key cannot be null");
194 Throw.when(key.length() == 0 || key.contains(ContextInterface.SEPARATOR), NamingException.class,
195 "key [%s] is the empty string or key contains '/'", key);
196 if (this.elements.containsKey(key))
197 {
198 Object object = this.elements.remove(key);
199 fireEvent(ContextInterface.OBJECT_REMOVED_EVENT, new Object[] {getAbsolutePath(), key, object});
200 }
201 }
202
203 @Override
204 public void unbind(final String name) throws NamingException
205 {
206 ContextName contextName = lookup(name);
207 contextName.getContext().unbindObject(contextName.getName());
208 }
209
210 @Override
211 public void rebindObject(final String key, final Object object) throws NamingException
212 {
213 Throw.whenNull(key, "key cannot be null");
214 Throw.when(key.length() == 0 || key.contains(ContextInterface.SEPARATOR), NamingException.class,
215 "key [%s] is the empty string or key contains '/'", key);
216 checkCircular(object);
217 if (this.elements.containsKey(key))
218 {
219 this.elements.remove(key);
220 fireEvent(ContextInterface.OBJECT_REMOVED_EVENT, new Object[] {getAbsolutePath(), key, object});
221 }
222 this.elements.put(key, object);
223 fireEvent(ContextInterface.OBJECT_ADDED_EVENT, new Object[] {getAbsolutePath(), key, object});
224 }
225
226 @Override
227 public void rebind(final String name, final Object object) throws NamingException
228 {
229 ContextName contextName = lookup(name);
230 contextName.getContext().rebindObject(contextName.getName(), object);
231 }
232
233 @Override
234 public void rename(final String oldName, final String newName) throws NamingException
235 {
236 ContextName contextNameOld = lookup(oldName);
237 ContextName contextNameNew = lookup(newName);
238 if (contextNameNew.getContext().hasKey(contextNameNew.getName()))
239 {
240 throw new NameAlreadyBoundException("key " + newName + " already bound to object in Context");
241 }
242 Object object = contextNameOld.getContext().getObject(contextNameOld.getName());
243 contextNameNew.getContext().checkCircular(object);
244 contextNameOld.getContext().unbindObject(contextNameOld.getName());
245 contextNameNew.getContext().bindObject(contextNameNew.getName(), object);
246 }
247
248 @Override
249 public ContextInterface createSubcontext(final String name) throws NamingException
250 {
251 return lookupAndBuild(name);
252 }
253
254 @Override
255 public void destroySubcontext(final String name) throws NamingException
256 {
257 ContextName contextName = lookup(name);
258 Object object = contextName.getContext().getObject(contextName.getName());
259 if (!(object instanceof ContextInterface))
260 {
261 throw new NotContextException("name " + name + " is bound but does not name a context");
262 }
263 destroy((ContextInterface) object);
264 contextName.getContext().unbindObject(contextName.getName());
265 }
266
267
268
269
270
271
272
273
274
275
276 protected ContextInterface lookupAndBuild(final String name) throws NamingException
277 {
278 Throw.whenNull(name, "name cannot be null");
279
280
281 if (name.length() == 0 || name.equals(ContextInterface.SEPARATOR))
282 {
283 throw new NamingException("the terminal reference is '/' or empty");
284 }
285
286
287 String reference;
288 ContextInterface subContext;
289 if (name.startsWith(ContextInterface.ROOT))
290 {
291 reference = name.substring(ContextInterface.SEPARATOR.length());
292 subContext = getRootContext();
293 }
294 else
295 {
296 reference = name;
297 subContext = this;
298 }
299
300 while (true)
301 {
302 int index = reference.indexOf(ContextInterface.SEPARATOR);
303 if (index == -1)
304 {
305 ContextInterface newContext = new JvmContext(subContext, reference);
306 subContext.bind(reference, newContext);
307 subContext = newContext;
308 break;
309 }
310 String sub = reference.substring(0, index);
311 reference = reference.substring(index + ContextInterface.SEPARATOR.length());
312 if (!subContext.hasKey(sub))
313 {
314 ContextInterface newContext = new JvmContext(subContext, sub);
315 subContext.bind(sub, newContext);
316 subContext = newContext;
317 }
318 else
319 {
320 Object subObject = subContext.getObject(sub);
321 if (!(subObject instanceof ContextInterface))
322 {
323 throw new NameNotFoundException(
324 "parsing name " + name + " in context -- bound object " + sub + " is not a subcontext");
325 }
326 subContext = (ContextInterface) subObject;
327 }
328 }
329 return subContext;
330 }
331
332
333
334
335
336
337
338 protected void destroy(final ContextInterface context) throws NamingException
339 {
340
341 Set<String> copyKeySet = new LinkedHashSet<>(context.keySet());
342 for (String key : copyKeySet)
343 {
344 if (context.getObject(key) instanceof ContextInterface && context.getObject(key) != null)
345 {
346 destroy((ContextInterface) context.getObject(key));
347 context.unbindObject(key);
348 }
349 }
350
351
352 copyKeySet = new LinkedHashSet<>(context.keySet());
353 for (String key : copyKeySet)
354 {
355 if (context.getObject(key) instanceof ContextInterface)
356 {
357 throw new NamingException("Tree inconsistent -- Context not removed or added during destroy operation");
358 }
359 context.unbindObject(key);
360 }
361 }
362
363 @Override
364 public Set<String> keySet()
365 {
366 return this.elements.keySet();
367 }
368
369 @Override
370 public Collection<Object> values()
371 {
372 return this.elements.values();
373 }
374
375 @Override
376 public Map<String, Object> bindings()
377 {
378 return this.elements;
379 }
380
381 @Override
382 public void fireObjectChangedEventValue(final Object object)
383 throws NameNotFoundException, NullPointerException, NamingException
384 {
385 Throw.whenNull(object, "object cannot be null");
386 fireObjectChangedEventKey(makeObjectKey(object));
387 }
388
389 @Override
390 public void fireObjectChangedEventKey(final String key) throws NameNotFoundException, NullPointerException, NamingException
391 {
392 Throw.whenNull(key, "key cannot be null");
393 Throw.when(key.length() == 0 || key.contains(ContextInterface.SEPARATOR), NamingException.class,
394 "key [%s] is the empty string or key contains '/'", key);
395 if (!hasKey(key))
396 {
397 throw new NameNotFoundException("Could not find object with key " + key + " for fireObjectChangedEvent");
398 }
399 try
400 {
401 fireEvent(ContextInterface.OBJECT_CHANGED_EVENT, new Object[] {getAbsolutePath(), key, getObject(key)});
402 }
403 catch (Exception exception)
404 {
405 throw new NamingException(exception.getMessage());
406 }
407 }
408
409
410
411
412
413
414 private String makeObjectKey(final Object object)
415 {
416 return object.toString().replace(ContextInterface.SEPARATOR, ContextInterface.REPLACE_SEPARATOR);
417 }
418
419 @Override
420 public void checkCircular(final Object newObject) throws NamingException
421 {
422 if (!(newObject instanceof ContextInterface))
423 return;
424 for (ContextInterface parentContext = getParent(); parentContext != null; parentContext = parentContext.getParent())
425 {
426 if (parentContext.equals(newObject))
427 {
428 throw new NamingException(
429 "circular reference for object " + newObject + "; prevented insertion into " + toString());
430 }
431 }
432 }
433
434 @Override
435 public void close() throws NamingException
436 {
437 this.elements.clear();
438 this.atomicName = "";
439 this.parent = null;
440 }
441
442 @Override
443 public String toString()
444 {
445 String parentName;
446 parentName = this.parent == null ? "null" : this.parent.getAtomicName();
447 return "JvmContext[parent=" + parentName + ", atomicName=" + this.atomicName + "]";
448 }
449
450 @Override
451 public String toString(final boolean verbose)
452 {
453 if (!verbose)
454 {
455 return "JvmContext[" + getAtomicName() + "]";
456 }
457 return ContextUtil.toText(this);
458 }
459
460
461
462
463
464
465
466
467
468
469 protected ContextName lookup(final String name) throws NamingException
470 {
471 Throw.whenNull(name, "name cannot be null");
472
473
474 if (name.length() == 0)
475 {
476 return new ContextName(this, "");
477 }
478
479
480 String reference;
481 ContextInterface subContext;
482 if (name.startsWith(ContextInterface.ROOT))
483 {
484 reference = name.substring(ContextInterface.SEPARATOR.length());
485 subContext = getRootContext();
486 }
487 else
488 {
489 reference = name;
490 subContext = this;
491 }
492
493 while (true)
494 {
495 int index = reference.indexOf(ContextInterface.SEPARATOR);
496 if (index == -1)
497 {
498 break;
499 }
500 String sub = reference.substring(0, index);
501 reference = reference.substring(index + ContextInterface.SEPARATOR.length());
502 Object subObject = subContext.getObject(sub);
503 if (!(subObject instanceof ContextInterface))
504 {
505 throw new NameNotFoundException(
506 "parsing name " + name + " in context -- bound object " + sub + " is not a subcontext");
507 }
508 subContext = (ContextInterface) subObject;
509 }
510 return new ContextName(subContext, reference);
511 }
512
513
514
515
516
517
518
519
520
521
522
523
524 public static class ContextName
525 {
526
527 private final ContextInterface context;
528
529
530 private final String name;
531
532
533
534
535
536
537 public ContextName(final ContextInterface context, final String name)
538 {
539 this.context = context;
540 this.name = name;
541 }
542
543
544
545
546 public ContextInterface getContext()
547 {
548 return this.context;
549 }
550
551
552
553
554 public String getName()
555 {
556 return this.name;
557 }
558
559 @Override
560 public int hashCode()
561 {
562 final int prime = 31;
563 int result = 1;
564 result = prime * result + ((this.context == null) ? 0 : this.context.hashCode());
565 result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
566 return result;
567 }
568
569 @Override
570 public boolean equals(final Object obj)
571 {
572 if (this == obj)
573 return true;
574 if (obj == null)
575 return false;
576 if (getClass() != obj.getClass())
577 return false;
578 ContextName other = (ContextName) obj;
579 if (this.context == null)
580 {
581 if (other.context != null)
582 return false;
583 }
584 else if (!this.context.equals(other.context))
585 return false;
586 if (this.name == null)
587 {
588 if (other.name != null)
589 return false;
590 }
591 else if (!this.name.equals(other.name))
592 return false;
593 return true;
594 }
595
596 @Override
597 public String toString()
598 {
599 return "ContextName[context=" + this.context + ", name=" + this.name + "]";
600 }
601 }
602
603 }