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.command;
  
  import java.io.Externalizable;
  import java.io.IOException;
  import java.io.ObjectInput;
  import java.io.ObjectOutput;
  import java.net.URISyntaxException;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Properties;
  import java.util.Set;
  import java.util.StringTokenizer;
  
  import jakarta.jms.Destination;
  import jakarta.jms.JMSException;
  import jakarta.jms.Queue;
  import jakarta.jms.TemporaryQueue;
  import jakarta.jms.TemporaryTopic;
  import jakarta.jms.Topic;
  
  import org.apache.activemq.filter.AnyDestination;
  import org.apache.activemq.filter.DestinationFilter;
  import org.apache.activemq.jndi.JNDIBaseStorable;
  import org.apache.activemq.util.IntrospectionSupport;
  import org.apache.activemq.util.URISupport;
  
  /**
   * @openwire:marshaller
   */
  public abstract class ActiveMQDestination extends JNDIBaseStorable implements DataStructure, Destination, Externalizable, Comparable<Object> {
  
      public static final String PATH_SEPERATOR = ".";
      public static final char COMPOSITE_SEPERATOR = ',';
  
      public static final byte QUEUE_TYPE = 0x01;
      public static final byte TOPIC_TYPE = 0x02;
      public static final byte TEMP_MASK = 0x04;
      public static final byte TEMP_TOPIC_TYPE = TOPIC_TYPE | TEMP_MASK;
      public static final byte TEMP_QUEUE_TYPE = QUEUE_TYPE | TEMP_MASK;
  
      public static final String QUEUE_QUALIFIED_PREFIX = "queue://";
      public static final String TOPIC_QUALIFIED_PREFIX = "topic://";
      public static final String TEMP_QUEUE_QUALIFED_PREFIX = "temp-queue://";
      public static final String TEMP_TOPIC_QUALIFED_PREFIX = "temp-topic://";
      public static final String IS_DLQ = "isDLQ";
  
      public static final String TEMP_DESTINATION_NAME_PREFIX = "ID:";
  
      private static final long serialVersionUID = -3885260014960795889L;
  
      protected String physicalName;
  
      protected transient ActiveMQDestination[] compositeDestinations;
      protected transient String[] destinationPaths;
      protected transient boolean isPattern;
      protected transient int hashValue;
      protected Map<String, String> options;
  
      protected static UnresolvedDestinationTransformer unresolvableDestinationTransformer = new DefaultUnresolvedDestinationTransformer();
  
      public ActiveMQDestination() {
      }
  
      protected ActiveMQDestination(String name) {
          setPhysicalName(name);
      }
  
      public ActiveMQDestination(ActiveMQDestination composites[]) {
          setCompositeDestinations(composites);
      }
  
      // static helper methods for working with destinations
      // -------------------------------------------------------------------------
      public static ActiveMQDestination createDestination(String name, byte defaultType) {
          if (name.startsWith(QUEUE_QUALIFIED_PREFIX)) {
              return new ActiveMQQueue(name.substring(QUEUE_QUALIFIED_PREFIX.length()));
          } else if (name.startsWith(TOPIC_QUALIFIED_PREFIX)) {
              return new ActiveMQTopic(name.substring(TOPIC_QUALIFIED_PREFIX.length()));
          } else if (name.startsWith(TEMP_QUEUE_QUALIFED_PREFIX)) {
              return new ActiveMQTempQueue(name.substring(TEMP_QUEUE_QUALIFED_PREFIX.length()));
          } else if (name.startsWith(TEMP_TOPIC_QUALIFED_PREFIX)) {
              return new ActiveMQTempTopic(name.substring(TEMP_TOPIC_QUALIFED_PREFIX.length()));
          }
  
          switch (defaultType) {
              case QUEUE_TYPE:
                  return new ActiveMQQueue(name);
              case TOPIC_TYPE:
                  return new ActiveMQTopic(name);
              case TEMP_QUEUE_TYPE:
                  return new ActiveMQTempQueue(name);
              case TEMP_TOPIC_TYPE:
                  return new ActiveMQTempTopic(name);
              default:
                  throw new IllegalArgumentException("Invalid default destination type: " + defaultType);
          }
      }
  
      public static ActiveMQDestination transform(Destination dest) throws JMSException {
          if (dest == null) {
              return null;
          }
          if (dest instanceof ActiveMQDestination) {
              return (ActiveMQDestination) dest;
          }
  
          if (dest instanceof Queue && dest instanceof Topic) {
              String queueName = ((Queue) dest).getQueueName();
              String topicName = ((Topic) dest).getTopicName();
              if (queueName != null && topicName == null) {
                  return new ActiveMQQueue(queueName);
              } else if (queueName == null && topicName != null) {
                  return new ActiveMQTopic(topicName);
              } else {
                  return unresolvableDestinationTransformer.transform(dest);
              }
          }
          if (dest instanceof TemporaryQueue) {
              return new ActiveMQTempQueue(((TemporaryQueue) dest).getQueueName());
          }
          if (dest instanceof TemporaryTopic) {
              return new ActiveMQTempTopic(((TemporaryTopic) dest).getTopicName());
          }
          if (dest instanceof Queue) {
              return new ActiveMQQueue(((Queue) dest).getQueueName());
          }
          if (dest instanceof Topic) {
              return new ActiveMQTopic(((Topic) dest).getTopicName());
          }
          throw new JMSException("Could not transform the destination into a ActiveMQ destination: " + dest);
      }
  
      public static int compare(ActiveMQDestination destination, ActiveMQDestination destination2) {
          if (destination == destination2) {
              return 0;
          }
          if (destination == null || destination2 instanceof AnyDestination) {
              return -1;
          } else if (destination2 == null || destination instanceof AnyDestination) {
              return 1;
          } else {
              if (destination.getDestinationType() == destination2.getDestinationType()) {
  
                  if (destination.isPattern() && destination2.isPattern() ) {
                      if (destination.getPhysicalName().compareTo(destination2.getPhysicalName()) == 0) {
                          return 0;
                      }
                  }
                  if (destination.isPattern()) {
                      DestinationFilter filter = DestinationFilter.parseFilter(destination);
                      if (filter.matches(destination2)) {
                          return 1;
                      }
                  }
                  if (destination2.isPattern()) {
                      DestinationFilter filter = DestinationFilter.parseFilter(destination2);
                      if (filter.matches(destination)) {
                          return -1;
                      }
                  }
                  return destination.getPhysicalName().compareTo(destination2.getPhysicalName());
  
              } else {
                  return destination.isQueue() ? -1 : 1;
              }
          }
      }
  
      @Override
      public int compareTo(Object that) {
          if (that instanceof ActiveMQDestination) {
              return compare(this, (ActiveMQDestination) that);
          }
          if (that == null) {
              return 1;
          } else {
              return getClass().getName().compareTo(that.getClass().getName());
          }
      }
  
      public boolean isComposite() {
          return compositeDestinations != null;
      }
  
      public ActiveMQDestination[] getCompositeDestinations() {
          return compositeDestinations;
      }
  
      public void setCompositeDestinations(ActiveMQDestination[] destinations) {
          this.compositeDestinations = destinations;
          this.destinationPaths = null;
          this.hashValue = 0;
          this.isPattern = false;
  
          StringBuilder sb = new StringBuilder();
          for (int i = 0; i < destinations.length; i++) {
              if (i != 0) {
                  sb.append(COMPOSITE_SEPERATOR);
              }
              if (getDestinationType() == destinations[i].getDestinationType()) {
                  sb.append(destinations[i].getPhysicalName());
              } else {
                  sb.append(destinations[i].getQualifiedName());
              }
          }
          physicalName = sb.toString();
      }
  
      public String getQualifiedName() {
          if (isComposite()) {
              return physicalName;
          }
          return getQualifiedPrefix() + physicalName;
      }
  
      protected abstract String getQualifiedPrefix();
  
      /**
       * @openwire:property version=1
       */
      public String getPhysicalName() {
          return physicalName;
      }
  
      public void setPhysicalName(String physicalName) {
          physicalName = physicalName.trim();
          final int length = physicalName.length();
  
          if (physicalName.isEmpty()) {
              throw new IllegalArgumentException("Invalid destination name: a non-empty name is required");
          }
  
          // options offset
          int p = -1;
          boolean composite = false;
          for (int i = 0; i < length; i++) {
              char c = physicalName.charAt(i);
              if (c == '?') {
                  p = i;
                  break;
              }
              if (c == COMPOSITE_SEPERATOR) {
                  // won't be wild card
                  isPattern = false;
                  composite = true;
              } else if (!composite && (c == '*' || c == '>')) {
                  isPattern = true;
              }
          }
          // Strip off any options
          if (p >= 0) {
              String optstring = physicalName.substring(p + 1);
              physicalName = physicalName.substring(0, p);
              try {
                  options = URISupport.parseQuery(optstring);
              } catch (URISyntaxException e) {
                  throw new IllegalArgumentException("Invalid destination name: " + physicalName + ", it's options are not encoded properly: " + e);
              }
          }
          this.physicalName = physicalName;
          this.destinationPaths = null;
          this.hashValue = 0;
          if (composite) {
              // Check to see if it is a composite.
              Set<String> l = new HashSet<String>();
              StringTokenizer iter = new StringTokenizer(physicalName, "" + COMPOSITE_SEPERATOR);
              while (iter.hasMoreTokens()) {
                  String name = iter.nextToken().trim();
                  if (name.length() == 0) {
                      continue;
                  }
                  l.add(name);
              }
              compositeDestinations = new ActiveMQDestination[l.size()];
              int counter = 0;
              for (String dest : l) {
                  compositeDestinations[counter++] = createDestination(dest);
              }
          }
      }
  
      public ActiveMQDestination createDestination(String name) {
          return createDestination(name, getDestinationType());
      }
  
      public String[] getDestinationPaths() {
  
          if (destinationPaths != null) {
              return destinationPaths;
          }
  
          List<String> l = new ArrayList<String>();
          StringBuilder level = new StringBuilder();
          final char separator = PATH_SEPERATOR.charAt(0);
          for (char c : physicalName.toCharArray()) {
              if (c == separator) {
                  l.add(level.toString());
                  level.delete(0, level.length());
              } else {
                  level.append(c);
              }
          }
          l.add(level.toString());
  
          destinationPaths = new String[l.size()];
          l.toArray(destinationPaths);
          return destinationPaths;
      }
  
      public abstract byte getDestinationType();
  
      public boolean isQueue() {
          return false;
      }
  
      public boolean isTopic() {
          return false;
      }
  
      public boolean isTemporary() {
          return false;
      }
  
      @Override
      public boolean equals(Object o) {
          if (this == o) {
              return true;
          }
          if (o == null || getClass() != o.getClass()) {
              return false;
          }
  
          ActiveMQDestination d = (ActiveMQDestination) o;
          return physicalName.equals(d.physicalName);
      }
  
      @Override
      public int hashCode() {
          if (hashValue == 0) {
              hashValue = physicalName.hashCode();
          }
          return hashValue;
      }
  
      @Override
      public String toString() {
          return getQualifiedName();
      }
  
      @Override
      public void writeExternal(ObjectOutput out) throws IOException {
          out.writeUTF(this.getPhysicalName());
          out.writeObject(options);
      }
  
      @Override
      @SuppressWarnings("unchecked")
      public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
          this.setPhysicalName(in.readUTF());
          this.options = (Map<String, String>) in.readObject();
      }
  
      public String getDestinationTypeAsString() {
          switch (getDestinationType()) {
              case QUEUE_TYPE:
                  return "Queue";
              case TOPIC_TYPE:
                  return "Topic";
              case TEMP_QUEUE_TYPE:
                  return "TempQueue";
              case TEMP_TOPIC_TYPE:
                  return "TempTopic";
              default:
                  throw new IllegalArgumentException("Invalid destination type: " + getDestinationType());
          }
      }
  
      public Map<String, String> getOptions() {
          return options;
      }
  
      @Override
      public boolean isMarshallAware() {
          return false;
      }
  
      @Override
      public void buildFromProperties(Properties properties) {
          if (properties == null) {
              properties = new Properties();
          }
  
          IntrospectionSupport.setProperties(this, properties);
      }
  
      @Override
      public void populateProperties(Properties props) {
          props.setProperty("physicalName", getPhysicalName());
      }
  
      public boolean isPattern() {
          return isPattern;
      }
  
      public boolean isDLQ() {
          return options != null && options.containsKey(IS_DLQ);
      }
  
      public void setDLQ(boolean val) {
          if (options == null) {
              options = new HashMap<String, String>();
          }
          options.put(IS_DLQ, String.valueOf(val));
      }
  
      public static UnresolvedDestinationTransformer getUnresolvableDestinationTransformer() {
          return unresolvableDestinationTransformer;
      }
  
      public static void setUnresolvableDestinationTransformer(UnresolvedDestinationTransformer unresolvableDestinationTransformer) {
          ActiveMQDestination.unresolvableDestinationTransformer = unresolvableDestinationTransformer;
      }
  }
  