001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall.xml;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.StringUtils.*;
021
022import java.util.*;
023import java.util.concurrent.*;
024
025import org.apache.juneau.marshall.*;
026
027/**
028 * Represents a simple namespace mapping between a simple name and URI.
029 *
030 * <p>
031 * In general, the simple name will be used as the XML prefix mapping unless there are conflicts or prefix re-mappings
032 * in the serializer.
033 *
034 * <h5 class='section'>See Also:</h5><ul>
035 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/XmlSupport">XML</a>
036 * </ul>
037 */
038@SuppressWarnings({
039   "java:S3740" // Raw Comparable type used intentionally for namespace ordering where the generic bound cannot be expressed
040})
041@Marshalled
042public class Namespace {
043
044   private static final ConcurrentHashMap<String,Namespace> CACHE = new ConcurrentHashMap<>();
045
046   /**
047    * Converts the specified object into a {@link Namespace} object.
048    *
049    * <p>
050    * Can be any of following types:
051    * <ul>
052    *    <li>A {@link Namespace} object
053    *    <li>A string containing a name/value pair of the form <js>"name:uri"</js>.
054    * </ul>
055    *
056    * @param o The input.
057    *    <br>Can be <jk>null</jk> (returns <jk>null</jk>).
058    * @return The namespace object, or <jk>null</jk> if the input was <jk>null</jk>.
059    */
060   public static Namespace create(Object o) {
061      if (o == null)
062         return null;
063      if (o instanceof Namespace o2)
064         return o2;
065      if (o instanceof CharSequence o3)
066         return of(o3.toString());
067      throw rex("Invalid object type passed to Namespace.create(Object):  '%s'", cn(o));
068   }
069
070   /**
071    * Converts the specified object into an array of {@link Namespace} object.
072    *
073    * <p>
074    * Can be any of following types:
075    * <ul>
076    *    <li>A {@link Namespace} array
077    *    <li>A comma-delimited string with key/value pairs of the form <js>"name:uri"</js>.
078    *    <li>A <c>Collection</c> containing any of object that can be passed to {@link #createArray(Object)}.
079    * </ul>
080    *
081    * @param o The input.
082    *    <br>Must not be <jk>null</jk>.
083    * @return The namespace objects.
084    */
085   public static Namespace[] createArray(Object o) {
086
087      if (o instanceof Namespace[] o2)
088         return o2;
089
090      if (o instanceof String[] o2) {
091         var n = new Namespace[o2.length];
092         for (var i = 0; i < o2.length; i++)
093            n[i] = create(o2[i]);
094         return n;
095      }
096
097      if (o instanceof CharSequence o2) {
098         var ss = splita(o2.toString());
099         var n = new Namespace[ss.length];
100         for (var i = 0; i < ss.length; i++)
101            n[i] = create(ss[i]);
102         return n;
103      }
104
105      if (o instanceof Collection o2) {
106         var n = new Namespace[o2.size()];
107         var i = 0;
108         for (var o3 : o2) {
109            if (o3 instanceof Namespace o4)
110               n[i++] = o4;
111            else if (o3 instanceof CharSequence o4)
112               n[i++] = create(o4.toString());
113            else
114               throw rex("Invalid type passed to NamespaceFactory.createArray: '%s'", cn(o));
115         }
116         return n;
117      }
118
119      throw rex("Invalid type passed to NamespaceFactory.createArray: '%s'", cn(o));
120   }
121
122   /**
123    * Create a {@link Namespace} from a <js>"name:uri"</js> string pair.
124    *
125    * @param key The key/pair string.
126    *    <br>Must not be <jk>null</jk>.
127    * @return The namespace object.
128    */
129   public static Namespace of(String key) {
130      var n = CACHE.get(key);
131      if (nn(n))
132         return n;
133      var i = key.indexOf(':');
134      if (i == -1)
135         return of(key, null);
136      if (key.startsWith("http://") || key.startsWith("https://"))
137         return of(null, key);
138      return of(key.substring(0, i).trim(), key.substring(i + 1).trim());
139   }
140
141   /**
142    * Create a {@link Namespace} with the specified name and URI.
143    *
144    * <p>
145    * Previously-encountered name/uri pairs return a cached copy.
146    *
147    * @param name The namespace name.  See {@link Namespace#getName()}.
148    *    <br>Can be <jk>null</jk> (the namespace has no name).
149    * @param uri The namespace URI.  See {@link Namespace#getUri()}.
150    *    <br>Can be <jk>null</jk> (the namespace has no URI).
151    * @return The namespace object.
152    */
153   public static Namespace of(String name, String uri) {
154      var key = name + ":" + uri;
155      var n = CACHE.get(key);
156      if (n == null) {
157         n = new Namespace(key, name, uri);
158         var n2 = CACHE.putIfAbsent(key, n);
159         return (n2 == null ? n : n2); // HTT: n2 != null branch requires concurrent first-load race
160      }
161      return n;
162   }
163
164   final String key;
165   final String name;
166   final String uri;
167
168   /**
169    * Constructor.
170    *
171    * @param name The short name of this schema.
172    * @param uri The URI of this schema.
173    */
174   private Namespace(String key, String name, String uri) {
175      this.key = key;
176      this.name = name;
177      this.uri = uri;
178   }
179
180   /**
181    * Returns the namespace name.
182    *
183    * @return The namespace name, or <jk>null</jk> if it was not specified.
184    */
185   public String getName() { return name; }
186
187   /**
188    * Returns the namespace URI.
189    *
190    * @return The namespace URI, or <jk>null</jk> if it was not specified.
191    */
192   public String getUri() { return uri; }
193
194   @Override /* Overridden from Object */
195   public String toString() {
196      return key;
197   }
198}