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