content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.activemq.transport.amqp.client.util;
  
  import java.io.IOException;
  import java.net.InetAddress;
  import java.net.ServerSocket;
  import java.net.UnknownHostException;
  import java.util.concurrent.atomic.AtomicLong;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import java.lang.invoke.MethodHandles;
  
  /**
   * Generator for Globally unique Strings.
   */
  public class IdGenerator {
  
     private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
     private static final String UNIQUE_STUB;
     private static int instanceCount;
     private static String hostName;
     private String seed;
     private final AtomicLong sequence = new AtomicLong(1);
     private int length;
     public static final String PROPERTY_IDGENERATOR_PORT = "activemq.idgenerator.port";
  
     static {
        String stub = "";
        boolean canAccessSystemProps = true;
        try {
           SecurityManager sm = System.getSecurityManager();
           if (sm != null) {
              sm.checkPropertiesAccess();
           }
        } catch (SecurityException se) {
           canAccessSystemProps = false;
        }
  
        if (canAccessSystemProps) {
           int idGeneratorPort = 0;
           ServerSocket ss = null;
           try {
              idGeneratorPort = Integer.parseInt(System.getProperty(PROPERTY_IDGENERATOR_PORT, "0"));
              logger.trace("Using port {}", idGeneratorPort);
              hostName = getLocalHostName();
              ss = new ServerSocket(idGeneratorPort);
              stub = "-" + ss.getLocalPort() + "-" + System.currentTimeMillis() + "-";
              Thread.sleep(100);
           } catch (Exception e) {
              if (logger.isTraceEnabled()) {
                 logger.trace("could not generate unique stub by using DNS and binding to local port", e);
              } else {
                 logger.warn("could not generate unique stub by using DNS and binding to local port: {} {}", e.getClass().getCanonicalName(), e.getMessage());
              }
  
              // Restore interrupted state so higher level code can deal with it.
              if (e instanceof InterruptedException) {
                 Thread.currentThread().interrupt();
              }
           } finally {
              if (ss != null) {
                 try {
                    ss.close();
                 } catch (IOException ioe) {
                    if (logger.isTraceEnabled()) {
                       logger.trace("Closing the server socket failed", ioe);
                    } else {
                       logger.warn("Closing the server socket failed due to {}", ioe.getMessage());
                    }
                 }
              }
           }
        }
  
        if (hostName == null) {
           hostName = "localhost";
        }
        hostName = sanitizeHostName(hostName);
  
        if (stub.length() == 0) {
           stub = "-1-" + System.currentTimeMillis() + "-";
        }
        UNIQUE_STUB = stub;
     }
  
     /**
      * Construct an IdGenerator
      *
      * @param prefix The prefix value that is applied to all generated IDs.
      */
     public IdGenerator(String prefix) {
        synchronized (UNIQUE_STUB) {
           this.seed = prefix + UNIQUE_STUB + (instanceCount++) + ":";
           this.length = this.seed.length() + ("" + Long.MAX_VALUE).length();
        }
     }
  
     public IdGenerator() {
        this("ID:" + hostName);
     }
  
     /**
      * As we have to find the host name as a side-affect of generating a unique stub, we allow it's easy retrieval here
      *
      * @return the local host name
      */
     public static String getHostName() {
        return hostName;
     }
  
     /**
      * {@return a unique id}
      */
     public synchronized String generateId() {
        StringBuilder sb = new StringBuilder(length);
        sb.append(seed);
        sb.append(sequence.getAndIncrement());
        return sb.toString();
     }
  
     public static String sanitizeHostName(String hostName) {
        boolean changed = false;
  
        StringBuilder sb = new StringBuilder();
        for (char ch : hostName.toCharArray()) {
           // only include ASCII chars
           if (ch < 127) {
              sb.append(ch);
           } else {
              changed = true;
           }
        }
  
        if (changed) {
           String newHost = sb.toString();
           logger.info("Sanitized hostname from: {} to: {}", hostName, newHost);
           return newHost;
        } else {
           return hostName;
        }
     }
  
     /**
      * {@return a unique ID - that is friendly for a URL or file system}
      */
     public String generateSanitizedId() {
        String result = generateId();
        result = result.replace(':', '-');
        result = result.replace('_', '-');
        result = result.replace('.', '-');
        return result;
     }
  
     /**
      * From a generated id - return the seed (i.e. minus the count)
      *
      * @param id the generated identifier
      * @return the seed
      */
     public static String getSeedFromId(String id) {
        String result = id;
        if (id != null) {
           int index = id.lastIndexOf(':');
           if (index > 0 && (index + 1) < id.length()) {
              result = id.substring(0, index);
           }
        }
        return result;
     }
  
     /**
      * From a generated id - return the generator count
      *
      * @param id The ID that will be parsed for a sequence number.
      * @return the sequence value parsed from the given ID
      */
     public static long getSequenceFromId(String id) {
        long result = -1;
        if (id != null) {
           int index = id.lastIndexOf(':');
  
           if (index > 0 && (index + 1) < id.length()) {
              String numStr = id.substring(index + 1, id.length());
              result = Long.parseLong(numStr);
           }
        }
        return result;
     }
  
     /**
      * Does a proper compare on the Id's
      *
      * @param id1 the lhs of the comparison.
      * @param id2 the rhs of the comparison.
      * @return 0 if equal else a positive if {@literal id1 > id2} ..
      */
     public static int compare(String id1, String id2) {
        int result = -1;
        String seed1 = IdGenerator.getSeedFromId(id1);
        String seed2 = IdGenerator.getSeedFromId(id2);
        if (seed1 != null && seed2 != null) {
           result = seed1.compareTo(seed2);
           if (result == 0) {
              long count1 = IdGenerator.getSequenceFromId(id1);
              long count2 = IdGenerator.getSequenceFromId(id2);
              result = (int) (count1 - count2);
           }
        }
        return result;
     }
  
     /**
      * When using the {@link java.net.InetAddress#getHostName()} method in an environment where neither a proper DNS
      * lookup nor an {@code /etc/hosts} entry exists for a given host, the following exception will be thrown:
      * <pre>{@code
      * java.net.UnknownHostException: <hostname>: <hostname> at
      * java.net.InetAddress.getLocalHost(InetAddress.java:1425) ...
      * }</pre>
      * Instead of just throwing an UnknownHostException and giving up, this method grabs a suitable hostname from the
      * exception and prevents the exception from being thrown. If a suitable hostname cannot be acquired from the
      * exception, only then is the {@code UnknownHostException} thrown.
      *
      * @return The hostname
      * @throws UnknownHostException if the given host cannot be looked up.
      * @see java.net.InetAddress#getLocalHost()
      * @see java.net.InetAddress#getHostName()
      */
     protected static String getLocalHostName() throws UnknownHostException {
        try {
           return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException uhe) {
           String host = uhe.getMessage(); // host = "hostname: hostname"
           if (host != null) {
              int colon = host.indexOf(':');
              if (colon > 0) {
                 return host.substring(0, colon);
              }
           }
           throw uhe;
        }
     }
  }
  