docs: private JSONObject getConfigInfo(String uid, Dictionary<String, Object> config, Set<String> processed) throws IOException {
          String configDisplayId = (String) config.get("config.displayId");
  
          boolean isFactoryPid;
          String configElementName;
          if (isFactoryPid = configDisplayId.endsWith("]")) { // factory pid
              int end = configDisplayId.lastIndexOf('[');
              int begin = configDisplayId.lastIndexOf('/', end) + 1;
              configElementName = configDisplayId.substring(begin, end);
          } else
              configElementName = configDisplayId; // singleton pid
  
          if (configElementName.indexOf('.') >= 0 && !configElementName.startsWith("properties."))
              return null;
  
          //Get pid to use with config service
          String servicePid = isFactoryPid ? (String) config.get("service.factoryPid") : (String) config.get("service.pid");
          String extendsSourcePid = isFactoryPid ? (String) config.get("ibm.extends.source.factoryPid") : (String) config.get("ibm.extends.source.pid");
  
          String metaTypeElementName = configHelper.getMetaTypeElementName(extendsSourcePid == null ? servicePid : extendsSourcePid);
          // if the element's name is internal, no config should be added for that element
          if (metaTypeElementName != null && metaTypeElementName.equalsIgnoreCase("internal"))
              return null;
  
          JSONObject json = new OrderedJSONObject();
          json.put("configElementName", configElementName);
          if (isFactoryPid)
              json.put("uid", uid);
  
          if (!processed.add(configDisplayId)) {
              json.put("error", "Circular dependency in configuration.");
              return json;
          }
  
          boolean registryEntryExists = configHelper.registryEntryExists(servicePid);
  
          // Mapping of flat config prefix (like properties.0) to map of flattened config prop names/values
          SortedMap<String, SortedMap<String, Object>> flattened = new TreeMap<String, SortedMap<String, Object>>();
  
          // Mapping of pid to list of flat config prefixes which are of that pid type
          SortedMap<String, SortedSet<String>> flattenedPids = new TreeMap<String, SortedSet<String>>();
  
          // TODO app defined resources
          SortedSet<String> keys = new TreeSet<String>();
          for (java.util.Enumeration<String> en = config.keys(); en.hasMoreElements();) {
              String key = en.nextElement();
              //don't display items starting with config. or service. or ibm.extends (added by config service)
              if (key.startsWith("config.") || key.startsWith("service.") || key.startsWith("ibm.extends")) {
                  continue;
              }
  
              String metaTypeName = configHelper.getMetaTypeAttributeName(extendsSourcePid == null ? servicePid : extendsSourcePid, key);
              if ("id".equals(key) && "library".equals(configElementName)) {
                  // Work around the <library> element marking its id attribute as internal when its
                  // id is actually a configurable external.
                  keys.add(key);
              } else if ((metaTypeName != null && !metaTypeName.equalsIgnoreCase("internal")) || !registryEntryExists) {
                  // add attributes with a name that is not internal or any attributes if there is an error in the config
                  keys.add(key);
              } else {
                  int prefixEnd = -1;
                  StringBuilder prefix = new StringBuilder();
                  String suffix = key;
                  // flat config prefixes should match [<anything>.<numbers>.]+
                  // For example:  child.0.grandchild.0.value   prefix="child.0.grandchild.0." suffix="value"
                  while ((prefixEnd = nthIndexOf(suffix, ".", 2) + 1) > 0 && suffix.length() >= prefixEnd) {
                      String possiblePrefix = suffix.substring(0, prefixEnd);
                      if (!possiblePrefix.matches(".*\\.\\d+\\."))
                          break;
                      prefix.append(possiblePrefix);
                      suffix = suffix.substring(prefixEnd);
                  }
                  if (prefix.length() > 0) {
                      // It is probably a flattened config attribute
                      if ("config.referenceType".equals(suffix)) {
                          String flattenedPid = (String) config.get(key);
                          SortedSet<String> f = flattenedPids.get(flattenedPid);
                          if (f == null)
                              flattenedPids.put(flattenedPid, f = new TreeSet<String>());
                          f.add(prefix.toString());
                      } else {
                          // It is probably a flattened config attribute.
                          SortedMap<String, Object> f = flattened.get(prefix.toString());
                          if (f == null)
                              flattened.put(prefix.toString(), f = new TreeMap<String, Object>());
                          f.put(suffix, config.get(key));
                      }
                  } else {
                      // Add MetaTypes with null name.  This includes child first config and invalid config.
                      if (metaTypeName == null) {
                          keys.add(key);
                      }
                  }
              }
          }
  
          // Look for child-first nested config elements.
          // This style of config has been discontinued but is still in use by some existing config elements,
          // which need to be handled specially here.
          if ("resourceAdapter".equals(configElementName)) {
              String childFirstFilter = "(config.parentPID=" + config.get("service.pid") + ')';
              Configuration[] childFirstConfigs;
              try {
                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled())
                      Tr.debug(this, tc, "child first pid filter", childFirstFilter);
                  childFirstConfigs = configAdmin.listConfigurations(childFirstFilter);
              } catch (InvalidSyntaxException x) {
                  throw new RuntimeException(x);
              }
              if (childFirstConfigs != null)
                  for (Configuration c : childFirstConfigs) {
                      Dictionary<String, Object> props = c.getProperties();
                      String childConfigDisplayId = (String) props.get("config.displayId");
                      int start = configDisplayId.length() + 1;
                      String childElementName = childConfigDisplayId.substring(start, childConfigDisplayId.indexOf('[', start));
                      keys.add(childElementName);
                      config.put(childElementName, Collections.singleton(props.get("service.pid")));
                  }
          }
  
          // These properties intentionally placed first
          if (keys.remove("id")) {
              String id = (String) config.get("id");
              if (!isGenerated(id))
                  json.put("id", id);
          }
          if (keys.remove("jndiName"))
              json.put("jndiName", config.get("jndiName"));
  
          if (!registryEntryExists) { //registry entry doesn't exist - config service can't find the specified pid
              json.put("error", "Check that the spelling is correct and that the right features are enabled for this configuration.");
          }
  
          for (String key : keys)
              json.put(key, getJSONValue(config.get(key), processed));
  
          for (Map.Entry<String, SortedSet<String>> entry : flattenedPids.entrySet()) {
              String pid = entry.getKey();
              boolean registryEntryExistsForFlattenedConfig = configHelper.registryEntryExists(pid);
              JSONArray list = new JSONArray();
              String prefix = null;
              for (String flatConfigPrefix : entry.getValue()) {
                  JSONObject j = new OrderedJSONObject();
                  SortedMap<String, Object> flattenedConfigProps = flattened.get(prefix = flatConfigPrefix);
                  if (flattenedConfigProps != null)
                      for (Map.Entry<String, Object> prop : flattenedConfigProps.entrySet()) {
                          String key = prop.getKey();
                          String metaTypeName = configHelper.getMetaTypeAttributeName(pid, key);
                          if (metaTypeName == null // add unknown attributes added by the user
                              || !metaTypeName.equalsIgnoreCase("internal") // add externalized attributes
                              || !registryEntryExistsForFlattenedConfig) { // or all attributes if there is an error in the config
                              j.put(key, getJSONValue(prop.getValue(), processed));
                          }
                      }
                  list.add(j);
              }
              // TODO would be better to get the flattened config element name from config internals rather than hardcoding/approximating it
              String name = (String) config.get(prefix + ".resourceAdapterConfig.id");
              if (name == null) {
                  String baseAlias = prefix.replaceAll("\\.\\d+\\.", "");
                  name = configHelper.aliasFor(pid, baseAlias);
              }
              json.put(name, list);
          }
  
          // API for this configuration element instance
          if (servicePid != null) {
              ServiceReference<?>[] refs;
              String filter = FilterUtils.createPropertyFilter("com.ibm.wsspi.rest.handler.config.pid", servicePid);
              try {
                  refs = context.getBundleContext().getServiceReferences((String) null, filter);
              } catch (InvalidSyntaxException x) {
                  refs = null;
              }
              if (refs != null) {
                  SortedSet<String> apiRoots = new TreeSet<String>();
                  for (ServiceReference<?> ref : refs) {
                      String root = (String) ref.getProperty(RESTHandler.PROPERTY_REST_HANDLER_ROOT);
                      if (root != null)
                          apiRoots.add(root);
                  }
                  JSONArray api = new JSONArray();
                  for (String root : apiRoots) {
                      StringBuilder r = new StringBuilder("/ibm/api");
                      if (root.charAt(0) != '/')
                          r.append('/');
                      r.append(root);
                      if (root.charAt(root.length() - 1) != '/')
                          r.append('/');
                      r.append(configElementName);
                      if (isFactoryPid)
                          r.append('/').append(URLEncoder.encode(uid, "UTF-8"));
                      api.add(r.toString());
                  }
                  if (!api.isEmpty())
                      json.put("api", api);
              }
          }
  
          return json;
      }