View Javadoc
1   package nl.tudelft.simulation.naming.context.util;
2   
3   import java.rmi.RemoteException;
4   import java.util.ArrayList;
5   import java.util.Collections;
6   import java.util.List;
7   import java.util.Map.Entry;
8   
9   import javax.naming.NameNotFoundException;
10  import javax.naming.NamingException;
11  
12  import nl.tudelft.simulation.naming.context.ContextInterface;
13  
14  /**
15   * ContextUtil contains a few helper methods to deal with an InitialEventContext.
16   * <p>
17   * Copyright (c) 2020-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
18   * for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>. The DSOL
19   * project is distributed under a three-clause BSD-style license, which can be found at
20   * <a href="https://https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">
21   * https://https://simulation.tudelft.nl/dsol/docs/latest/license.html</a>.
22   * </p>
23   * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank">Alexander Verbraeck</a>
24   */
25  public class ContextUtil
26  {
27      /**
28       * Lookup or create a sub-context in the parentContext with the name as its path. The path can be absolute or relative. The
29       * terminating part of the name will be used as the key under which the created subcontext will be registered.
30       * @param parentContext ContextInterface; the parent context
31       * @param name String; the name to register the new subcontext
32       * @return ContextInterface; the newly created subcontext
33       * @throws NamingException when terminating key in the name is blank or contains "/" character(s)
34       * @throws RemoteException on a network error when the Context is used over RMI
35       */
36      public static ContextInterface lookupOrCreateSubContext(final ContextInterface parentContext, final String name)
37              throws NamingException, RemoteException
38      {
39          try
40          {
41              if (parentContext.exists(name))
42              {
43                  Object result = parentContext.get(name);
44                  if (result instanceof ContextInterface)
45                      return (ContextInterface) result;
46                  throw new NamingException(
47                          "lookup for " + name + " in context " + parentContext + " returned object that is not a Context");
48              }
49          }
50          catch (NameNotFoundException nnfe)
51          {
52              // ignore -- create the context path
53          }
54          return parentContext.createSubcontext(name);
55      }
56  
57      /**
58       * Lookup a sub-context in the parentContext with the name as its path. The path can be absolute or relative. The
59       * terminating part of the name will be used as the key under which the created subcontext will be registered.
60       * @param parentContext ContextInterface; the parent context
61       * @param name String; the name to register the new subcontext
62       * @return ContextInterface; the newly created subcontext
63       * @throws NamingException when terminating key in the name is blank or contains "/" character(s)
64       * @throws RemoteException on a network error when the Context is used over RMI
65       */
66      public static ContextInterface lookupSubContext(final ContextInterface parentContext, final String name)
67              throws NamingException, RemoteException
68      {
69          if (parentContext.exists(name))
70          {
71              Object result = parentContext.get(name);
72              if (result instanceof ContextInterface)
73                  return (ContextInterface) result;
74              throw new NamingException(
75                      "lookup for " + name + " in context " + parentContext + " returned object that is not a Context");
76          }
77          throw new NamingException("Context " + name + " not found in parentContext " + parentContext);
78      }
79  
80      /**
81       * Destroy a sub-context in the parentContext with the name as its path. The path can be absolute or relative. The
82       * terminating part of the name will be used as the key under for the subcontext to be removed.
83       * @param parentContext ContextInterface; the parent context
84       * @param name String; the name to use to find the subcontext to remove
85       * @throws NamingException when terminating key in the name is blank or contains "/" character(s)
86       * @throws RemoteException on a network error when the Context is used over RMI
87       */
88      public static void destroySubContext(final ContextInterface parentContext, final String name)
89              throws NamingException, RemoteException
90      {
91          parentContext.destroySubcontext(name);
92      }
93  
94      /**
95       * Resolve the key(s) for an object for a given context. This can be an expensive operation if the context is large. An
96       * object can be registered zero or more times in the context, so a List of keys under which the object is registered will
97       * be returned. The keys are relative to the startContext. The method starts with the given context. It is possible to look
98       * up null objects in the Context.
99       * @param startContext ContextInterface; the context to start the search
100      * @param object Object; the object to look up in the tree under the startContext
101      * @return List&lt;String&gt;; the list of keys that are bound to the object, or an empty list if no bindings for the object
102      *         were found
103      * @throws NamingException when an error occurs during searching
104      * @throws RemoteException on a network error when the Context is used over RMI
105      */
106     public static List<String> resolveKeys(final ContextInterface startContext, final Object object)
107             throws NamingException, RemoteException
108     {
109         List<String> result = new ArrayList<>();
110         resolveKeys(startContext, object, result, "");
111         return result;
112     }
113 
114     /**
115      * Resolve the key(s) for an object for a given context. This can be an expensive operation if the context is large. An
116      * object can be registered zero or more times in the context, so a List of keys under which the object is registered will
117      * be returned. The keys are relative to the startContext. The method starts with the given context. It is possible to look
118      * up null objects in the Context.
119      * @param context ContextInterface; the context to start the search
120      * @param object Object; the object to look up in the tree under the startContext
121      * @param result List&lt;String&gt;; the current list of keys that are bound to the object, or an empty list if no bindings
122      *            for the object were found yet
123      * @param partialKey the key of the current level in the tree, relative to the original start context of the search
124      * @throws NamingException when an error occurs during searching
125      * @throws RemoteException on a network error when the Context is used over RMI
126      */
127     private static void resolveKeys(final ContextInterface context, final Object object, final List<String> result,
128             final String partialKey) throws NamingException, RemoteException
129     {
130         for (Entry<String, Object> binding : context.bindings().entrySet())
131         {
132             Object value = binding.getValue();
133             if (value instanceof ContextInterface)
134             {
135                 resolveKeys((ContextInterface) value, object, result,
136                         partialKey + ContextInterface.SEPARATOR + binding.getKey());
137             }
138             // no else; we might be looking for an object that implements ContextInterface
139             if ((value == null && object == null) || (value != null && value.equals(object)))
140             {
141                 result.add(partialKey + ContextInterface.SEPARATOR + binding.getKey());
142             }
143         }
144     }
145 
146     /**
147      * recursively print the context in human-readable format to a String.
148      * @param ctx the context to print
149      * @return a human-readable String with the context tree
150      */
151     public static String toText(final ContextInterface ctx)
152     {
153         return toText(ctx, new StringBuffer(), 0);
154     }
155 
156     /**
157      * recursively print the context in human-readable format to a String.
158      * @param ctx the context to print
159      * @param sb the StringBuffer to add the information to
160      * @param indent the indentation on the screen
161      * @return a human-readable String with the context tree
162      */
163     private static String toText(final ContextInterface ctx, final StringBuffer sb, final int indent)
164     {
165         try
166         {
167             if (indent > 0)
168             {
169                 sb.append(String.join("", Collections.nCopies(indent - 1, "| ")));
170                 sb.append("+ ");
171             }
172             sb.append("CTX ");
173             sb.append(ctx.getAtomicName());
174             sb.append("\n");
175             for (String key : ctx.keySet())
176             {
177                 Object obj = ctx.getObject(key);
178                 if (obj instanceof ContextInterface)
179                 {
180                     toText((ContextInterface) obj, sb, indent + 1);
181                 }
182                 else
183                 {
184                     sb.append(String.join("", Collections.nCopies(indent, "| ")));
185                     sb.append("+ ");
186                     sb.append(key);
187                     sb.append("=");
188                     sb.append(obj);
189                     sb.append("\n");
190                 }
191             }
192         }
193         catch (NamingException | RemoteException exception)
194         {
195             sb.append("ERR " + exception.getMessage() + "\n");
196         }
197         return sb.toString();
198     }
199 
200 }