code: /*
   * Copyright (c) 2021 the Eclipse Milo Authors
   *
   * This program and the accompanying materials are made
   * available under the terms of the Eclipse Public License 2.0
   * which is available at https://www.eclipse.org/legal/epl-2.0/
   *
   * SPDX-License-Identifier: EPL-2.0
   */
  
  package org.eclipse.milo.opcua.stack.core.util;
  
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
  import org.jetbrains.annotations.NotNull;
  import org.jetbrains.annotations.Nullable;
  
  public class EndpointUtil {
  
      private static final Pattern ENDPOINT_URL_PATTERN =
          Pattern.compile("(opc.tcp|http|https|opc.http|opc.https|opc.ws|opc.wss)://([^:/]+|\\[.*])(:\\d+)?(/.*)?");
  
      @Nullable
      public static String getScheme(@NotNull String endpointUrl) {
          Matcher matcher = ENDPOINT_URL_PATTERN.matcher(endpointUrl);
  
          if (matcher.matches()) {
              return matcher.group(1);
          }
  
          return null;
      }
  
      @Nullable
      public static String getHost(@NotNull String endpointUrl) {
          Matcher matcher = ENDPOINT_URL_PATTERN.matcher(endpointUrl);
  
          if (matcher.matches()) {
              return matcher.group(2);
          }
  
          return null;
      }
  
      public static int getPort(@NotNull String endpointUrl) {
          Matcher matcher = ENDPOINT_URL_PATTERN.matcher(endpointUrl);
  
          if (matcher.matches()) {
              try {
                  String group = matcher.group(3);
                  if (group != null && group.startsWith(":")) {
                      group = group.substring(1);
                      return Integer.parseInt(group);
                  }
              } catch (NumberFormatException ignored) {
                  // ignored
              }
          }
  
          return 4840;
      }
  
      /**
       * Get the path component from an endpoint URL.
       * <p>
       * An empty path becomes "/" and trailing "/" are removed from non-empty paths.
       *
       * @param endpointUrl the endpoint URL.
       * @return the path component from the endpoint URL.
       */
      @NotNull
      public static String getPath(@NotNull String endpointUrl) {
          Matcher matcher = ENDPOINT_URL_PATTERN.matcher(endpointUrl);
  
          if (matcher.matches()) {
              String path = matcher.group(4);
  
              if (path == null || path.isEmpty()) {
                  path = "/";
              } else if (path.length() > 1 && path.endsWith("/")) {
                  path = path.substring(0, path.length() - 1);
              }
  
              return path;
          } else {
              return "/";
          }
      }
  
      /**
       * Replace the hostname in the endpoint URL field of {@code endpoint} with a new one.
       * <p>
       * All other fields of the original {@link EndpointDescription} remain equal.
       * <p>
       * If the endpoint URL does not match the expected format the original endpoint is returned.
       *
       * @param endpoint the {@link EndpointDescription} to modify.
       * @param hostname the new hostname to use. A null value will result in the original hostname being used.
       * @return an updated {@link EndpointDescription} in which the hostname of the endpoint URL has been replaced with
       * {@code hostname}.
       */
      public static EndpointDescription updateUrl(
          @NotNull EndpointDescription endpoint, @Nullable String hostname) {
  
          return updateUrl(endpoint, hostname, -1);
      }
  
      /**
       * Replace the hostname and port in the endpoint URL field of {@code endpoint} with new ones.
       * <p>
       * All other fields of the original {@link EndpointDescription} remain equal.
       * <p>
       * If the endpoint URL does not match the expected format the original endpoint is returned.
       *
       * @param endpoint the {@link EndpointDescription} to modify.
       * @param hostname the new hostname to use. A null value will result in the original hostname being used.
       * @param port     the new port to use. Any value <= 0 will result in the original port being used.
       * @return an updated {@link EndpointDescription} in which the hostname of the endpoint URL has been replaced with
       * {@code hostname}.
       */
      public static EndpointDescription updateUrl(
          @NotNull EndpointDescription endpoint, @Nullable String hostname, int port) {
  
          return new EndpointDescription(
              updateUrl(endpoint.getEndpointUrl(), hostname, port),
              endpoint.getServer(),
              endpoint.getServerCertificate(),
              endpoint.getSecurityMode(),
              endpoint.getSecurityPolicyUri(),
              endpoint.getUserIdentityTokens(),
              endpoint.getTransportProfileUri(),
              endpoint.getSecurityLevel()
          );
      }
  
      static String updateUrl(String endpointUrl, @Nullable String hostname) {
          return updateUrl(endpointUrl, hostname, -1);
      }
  
      static String updateUrl(String endpointUrl, @Nullable String hostname, int port) {
          Matcher matcher = ENDPOINT_URL_PATTERN.matcher(endpointUrl);
  
          if (matcher.matches()) {
              String scheme = matcher.group(1);
  
              String newHostname = matcher.group(2);
              if (hostname != null) {
                  newHostname = hostname;
              }
  
              String newPort = matcher.group(3); // e.g. ":4840"
              if (port >= 0) {
                  newPort = ":" + port;
              } else if (newPort == null) {
                  newPort = "";
              }
  
              String path = matcher.group(4); // e.g. "/" or "/foo" or "/foo/bar"
              if (path == null) path = "";
  
              return scheme + "://" + newHostname + newPort + path;
          } else {
              return endpointUrl;
          }
      }
  
  }
  