/**
   * Load saved server configuration.
   *
   * <p>Currently when MyVLe object is initialized the ServerInfoRegistry will be loaded.
   */
  protected void load() throws VrsException {
    logger.debugPrintf(">>> load() <<<\n");

    //
    // use serverInfo as mutex !
    //
    synchronized (this.serverInfos) {
      // Use new Configuration Manager
      ConfigManager confMan = this.context.getConfigManager();
      VRL loc = confMan.getServerRegistryLocation();

      // is synchronized will return consistant list of configs

      ArrayList<AttributeSet> sets = this.getInfoAttrSets();
      // No Context!
      ResourceLoader loader = new ResourceLoader();

      XMLData xmlifier = new XMLData();
      xmlifier.setVAttributeElementName("vlet:ServerInfoProperty");
      xmlifier.setVAttributeSetElementName("vlet:ServerInfo");

      InputStream inps;
      try {
        inps = loader.createInputStream(loc.toURL());
      } catch (IOException e) {
        throw new NestedIOException(e);
      } catch (Exception e) {
        throw new VRLSyntaxException(e);
      }

      sets = xmlifier.parseVAttributeSets(inps, XML_SERVER_CONFIG_HEADER_TAG);
      // for (VAttributeSet set:sets)
      // {
      // logger.debugPrintln(this,"Adding ServerInfo Set:"+set);
      // }

      // ===
      // Do not clear: just merge current with save ones !
      // serverInfos.clear();
      // ===
      for (AttributeSet set : sets) {
        ServerInfo info = new ServerInfo(context, set);
        logger.debugPrintf("Adding Server Config:%s\n", info);
        // actual store method:
        put(info);
      }

      this.isLoaded = true;
    }
  }
  /**
   * Saves the ServerInfo Registry if the current configuration allows a persistant Registry !
   *
   * <p>By default this is false, but when MyVle is initialized by the VBrowser the persistant
   * ServerInfo Registry will be enabled.
   */
  public void save() {
    // Check new Configuration Manager !
    ConfigManager confMan = this.context.getConfigManager();

    if (confMan.getUsePersistantUserConfiguration() == false) return;

    VRL loc = confMan.getServerRegistryLocation();

    // is synchronized will return consistant list of configs

    ArrayList<AttributeSet> sets = this.getInfoAttrSets();
    // ResourceLoader loader=new ResourceLoader(context);

    XMLData xmlifier = new XMLData();
    xmlifier.setVAttributeElementName("vlet:ServerInfoProperty");
    xmlifier.setVAttributeSetElementName("vlet:ServerInfo");
    // xmlifier.setPersistanteNodeElementName("vlet:ServerInfo2"); // No
    // Nodes!

    try {
      // bootstrap warning: use default ResourceLoader here: VRS might not be initialized!
      ResourceLoader writer = new ResourceLoader();
      String xmlString =
          xmlifier.createXMLString(XML_SERVER_CONFIG_HEADER_TAG, sets, XML_SERVER_CONFIG_HEADER);
      writer.writeTextTo(loc.toURI(), xmlString);
      this.isSaved = true;
    } catch (Exception e) {
      // check persistant user configuration:
      logger.logException(ClassLogger.ERROR, e, "Couldn't save ServerInfo registry to:%s\n", loc);
      // but continue!
    }
  }
  public void removeAll() {
    synchronized (this.serverInfos) {
      this.isSaved = false;
      this.isLoaded = false;

      logger.debugPrintf(">>> ServerInfos.clear() ! <<<\n");
      serverInfos.clear();
    }
  }
  /** Put ServerInfo into registry */
  private void put(ServerInfo info) {
    synchronized (serverInfos) {
      // store private copy !
      info = info.duplicate();

      updateServerInfoID(info);

      logger.debugPrintf("+++ Storing info:%s\n", info);

      ServerInfo prev = this.serverInfos.get(info.getID());

      if (prev == info) logger.infoPrintf(">>> updating ServerInfo:%s\n", info);
      else if (prev != null)
        logger.infoPrintf(">>> WARNING: Overwriting previous object with:%s\n", info);
      else logger.infoPrintf(">>> storing new ServerInfo:%s\n", info);

      this.serverInfos.put(info.getID(), info);
      // mark dirty:
      this.isSaved = false;
    }
  }
  /**
   * Return 1st ServerInfo object for the specified Scheme. Is used to find *any* server of the
   * specified protocol.
   *
   * @param scheme
   * @return ServerInfo object
   */
  public ServerInfo getServerInfoForScheme(String scheme) {
    if (scheme == null) return null;

    ServerInfo infos[] = this.getServerInfos(scheme, null);

    if ((infos == null) || (infos.length <= 0)) {
      logger.warnPrintf("Couldn't find server info for scheme:%s\n", scheme);
      return null;
    }

    return infos[0];
  }
  public ServerInfo remove(ServerInfo info) {
    if (info == null) return null;

    logger.infoPrintf("--- Removing ServerInfo:%s\n", info);

    ServerInfo prev = null;

    synchronized (this.serverInfos) {
      String key = info.getID(); // return NULL if key hasn't been
      // set/info hasn't been stored !
      if (key != null) prev = this.getServerInfo(key);
      removeByKey(key);
      return prev;
    }
  }
  /**
   * Main method to search for a Server Info object. When a search field is null it means "don't
   * care". Use port number less then 0 for a don't care. Use port number EQUAL to 0 for default
   * port ! Returns multiple matching ServerInfo descriptions or NULL when it can't find any
   * matching server descriptions.
   *
   * @param scheme if NULL then don't care (match any)
   * @param hostname if NULL then don't care (match any)
   * @param port if port==-1 => don't care, if port==0 => default and if port>0 match explicit port
   *     number
   * @param userInfo if NULL then don't care (match any)
   */
  public ServerInfo[] getServerInfos(String scheme, String host, int port, String optUserInfo) {

    logger.debugPrintf(
        ">>> find {scheme,host,port,user}=%s,%s,%s,%s\n", scheme, host, port, optUserInfo);
    // if (scheme.compareTo("srb")==0)
    // {
    // logger.debugPrintln(this,"SRB"); // breakpoint
    // }
    ServerInfo infoArr[] = new ServerInfo[serverInfos.size()];
    infoArr = this.serverInfos.values().toArray(infoArr);

    Vector<ServerInfo> result = new Vector<ServerInfo>();

    for (ServerInfo info : infoArr) {
      logger.debugPrintf(" - comparing:%s", info);

      if (info.matches(scheme, host, port, optUserInfo)) {
        logger.debugPrintf(" - adding:%s\n", info);
        // =========================
        // Add Duplicate !
        // ==========================
        result.add(info.duplicate());
      }
    }

    if (result.size() <= 0) {
      logger.debugPrintf("<<< Returning NULL ServerInfos\n");
      return null;
    }

    ServerInfo arr[] = new ServerInfo[result.size()];
    arr = result.toArray(arr);
    logger.debugPrintf("<<< Returning #%d ServerInfos\n", arr.length);

    return arr;
  }
  /** Checks whether the persistent Server Info Registry is loaded and load it if necessary. */
  private void checkIsLoaded() {
    // use serverInfos as Mutex

    synchronized (this.serverInfos) {
      // Check new Configuration Manager !
      ConfigManager confMan = this.context.getConfigManager();

      if (confMan.getUsePersistantUserConfiguration() == false) return;

      if (this.isLoaded == true) return;

      try {
        load();
      } catch (VrsException e) {
        logger.logException(ClassLogger.WARN, e, "Couldn't load ServerInfo Registry!\n");
      }
    }
  }
  /** Find ServerInfos for specified location, this can be 0 or more matches ! */
  public ServerInfo[] getServerInfosFor(VRL loc) {
    String host = loc.getHostname();
    int port = loc.getPort();
    String scheme = loc.getScheme();
    String userInfo = loc.getUserinfo(); // use FULL userinformation

    // no port in VRL mean => use DEFAULT, not DON'T CARE. (-1= DON'T CARE)
    if (port < 0) port = 0;

    ServerInfo[] infos = this.getServerInfos(scheme, host, port, userInfo);

    if (infos != null)
      if (infos.length > 1)
        logger.warnPrintf("Warning: matched more then 1 ServerInfo for location:%s\n", loc);
    // else
    // logger.warnPrintf(">>> returning 1 info:"+infos[0]);

    return infos;
  }
  @Override
  public VNode createNode(VNode parent, String type, AttributeSet attrSet) throws VrsException {
    if (type == null) throw new NullPointerException("Type can not be null");

    if (type.compareTo(VRS.RESOURCEFOLDER_TYPE) == 0) {
      ResourceFolder groupNode = null;

      if (attrSet == null) groupNode = new ResourceFolder(getContext(), (VRL) null);
      else groupNode = new ResourceFolder(getContext(), attrSet, (VRL) null);

      return groupNode;
    } else if (type.compareTo(LogicalResourceNode.PERSISTANT_TYPE) == 0) {
      LogicalResourceNode node = new LogicalResourceNode(getContext(), attrSet, null);
      return node;
    } else {
      ClassLogger.getLogger(this.getClass()).errorPrintf("***Error: unknown type:%s\n", type);
    }

    return null;
  }
  /** Stored new ServerInfo and writes to file (if persistant) Returnes updated serverInfo */
  public ServerInfo store(ServerInfo info) {
    logger.debugPrintf("store(): attrs=%s\n", info.getAttributeSet());

    synchronized (this.serverInfos) {
      // ===
      // Before adding new Info: check whether persistant database is
      // loaded !
      // ===
      checkIsLoaded();
      // remove before put!
      remove(info);
      // synchronized put: UPDATES SERVERINFO KEY!
      put(info);
      // enters mutex again
      save();

      // do NOT return reference to object into ServerRegistry
      return info.duplicate();
    }
  }
 private static void info(String msg) {
   ClassLogger.getLogger(LFCOutputStream.class).infoPrintf("%s\n", msg);
 }
 private static void error(String msg) {
   ClassLogger.getLogger(LFCOutputStream.class).errorPrintf("%s\n", msg);
 }
 static {
   logger = ClassLogger.getLogger(ServerInfoRegistry.class);
 }