001package org.hl7.fhir.r4.model;
002
003import static org.apache.commons.lang3.StringUtils.defaultString;
004import static org.apache.commons.lang3.StringUtils.isBlank;
005import static org.apache.commons.lang3.StringUtils.isNotBlank;
006
007import java.math.BigDecimal;
008import java.util.UUID;
009
010import org.apache.commons.lang3.ObjectUtils;
011import org.apache.commons.lang3.StringUtils;
012import org.apache.commons.lang3.Validate;
013import org.apache.commons.lang3.builder.HashCodeBuilder;
014import org.hl7.fhir.instance.model.api.IBaseResource;
015import org.hl7.fhir.instance.model.api.IIdType;
016import org.hl7.fhir.instance.model.api.IPrimitiveType;
017
018/*
019  Copyright (c) 2011+, HL7, Inc.
020  All rights reserved.
021
022  Redistribution and use in source and binary forms, with or without modification,
023  are permitted provided that the following conditions are met:
024
025   * Redistributions of source code must retain the above copyright notice, this
026     list of conditions and the following disclaimer.
027   * Redistributions in binary form must reproduce the above copyright notice,
028     this list of conditions and the following disclaimer in the documentation
029     and/or other materials provided with the distribution.
030   * Neither the name of HL7 nor the names of its contributors may be used to
031     endorse or promote products derived from this software without specific
032     prior written permission.
033
034  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
035  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
036  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
037  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
038  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
039  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
040  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
041  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
043  POSSIBILITY OF SUCH DAMAGE.
044
045*/
046
047/*
048Copyright (c) 2011+, HL7, Inc.
049All rights reserved.
050
051Redistribution and use in source and binary forms, with or without modification,
052are permitted provided that the following conditions are met:
053
054 * Redistributions of source code must retain the above copyright notice, this
055   list of conditions and the following disclaimer.
056 * Redistributions in binary form must reproduce the above copyright notice,
057   this list of conditions and the following disclaimer in the documentation
058   and/or other materials provided with the distribution.
059 * Neither the name of HL7 nor the names of its contributors may be used to
060   endorse or promote products derived from this software without specific
061   prior written permission.
062
063THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
064ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
065WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
066IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
067INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
068NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
069PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
070WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
071ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
072POSSIBILITY OF SUCH DAMAGE.
073
074*/
075
076import ca.uhn.fhir.model.api.annotation.DatatypeDef;
077
078/**
079 * This class represents the logical identity for a resource, or as much of that
080 * identity is known. In FHIR, every resource must have a "logical ID" which is
081 * defined by the FHIR specification as:
082 * <p>
083 * <code>
084 * Any combination of upper or lower case ASCII letters ('A'..'Z', and 'a'..'z', numerals ('0'..'9'), '-' and '.', with a length limit of 64 characters. (This might be an integer, an un-prefixed OID, UUID or any other identifier pattern that meets these constraints.)
085 * </code>
086 * </p>
087 * <p>
088 * This class contains that logical ID, and can optionally also contain a
089 * relative or absolute URL representing the resource identity. For example, the
090 * following are all valid values for IdType, and all might represent the same
091 * resource:
092 * </p>
093 * <ul>
094 * <li><code>123</code> (just a resource's ID)</li>
095 * <li><code>Patient/123</code> (a relative identity)</li>
096 * <li><code>http://example.com/Patient/123 (an absolute identity)</code></li>
097 * <li>
098 * <code>http://example.com/Patient/123/_history/1 (an absolute identity with a version id)</code>
099 * </li>
100 * <li>
101 * <code>Patient/123/_history/1 (a relative identity with a version id)</code>
102 * </li>
103 * </ul>
104 * <p>
105 * Note that the 64 character limit applies only to the ID portion ("123" in the
106 * examples above).
107 * </p>
108 * <p>
109 * In most situations, you only need to populate the resource's ID (e.g.
110 * <code>123</code>) in resources you are constructing and the encoder will
111 * infer the rest from the context in which the object is being used. On the
112 * other hand, the parser will always try to populate the complete absolute
113 * identity on objects it creates as a convenience.
114 * </p>
115 * <p>
116 * Regex for ID: [a-z0-9\-\.]{1,36}
117 * </p>
118 */
119@DatatypeDef(name = "id", profileOf = StringType.class)
120public final class IdType extends UriType implements IPrimitiveType<String>, IIdType {
121  public static final String URN_PREFIX = "urn:";
122
123  /**
124   * This is the maximum length for the ID
125   */
126  public static final int MAX_LENGTH = 64; // maximum length
127
128  private static final long serialVersionUID = 2L;
129  private String myBaseUrl;
130  private boolean myHaveComponentParts;
131  private String myResourceType;
132  private String myUnqualifiedId;
133  private String myUnqualifiedVersionId;
134
135  /**
136   * Create a new empty ID
137   */
138  public IdType() {
139    super();
140  }
141
142  /**
143   * Create a new ID, using a BigDecimal input. Uses
144   * {@link BigDecimal#toPlainString()} to generate the string representation.
145   */
146  public IdType(BigDecimal thePid) {
147    if (thePid != null) {
148      setValue(toPlainStringWithNpeThrowIfNeeded(thePid));
149    } else {
150      setValue(null);
151    }
152  }
153
154  /**
155   * Create a new ID using a long
156   */
157  public IdType(long theId) {
158    setValue(Long.toString(theId));
159  }
160
161  /**
162   * Create a new ID using a string. This String may contain a simple ID (e.g.
163   * "1234") or it may contain a complete URL
164   * (http://example.com/fhir/Patient/1234).
165   * <p>
166   * <p>
167   * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally
168   * represented in hex), a uuid, an oid, or any other combination of lowercase
169   * letters, numerals, "-" and ".", with a length limit of 36 characters.
170   * </p>
171   * <p>
172   * regex: [a-z0-9\-\.]{1,36}
173   * </p>
174   */
175  public IdType(String theValue) {
176    setValue(theValue);
177  }
178
179  /**
180   * Constructor
181   *
182   * @param theResourceType The resource type (e.g. "Patient")
183   * @param theIdPart       The ID (e.g. "123")
184   */
185  public IdType(String theResourceType, BigDecimal theIdPart) {
186    this(theResourceType, toPlainStringWithNpeThrowIfNeeded(theIdPart));
187  }
188
189  /**
190   * Constructor
191   *
192   * @param theResourceType The resource type (e.g. "Patient")
193   * @param theIdPart       The ID (e.g. "123")
194   */
195  public IdType(String theResourceType, Long theIdPart) {
196    this(theResourceType, toPlainStringWithNpeThrowIfNeeded(theIdPart));
197  }
198
199  /**
200   * Constructor
201   *
202   * @param theResourceType The resource type (e.g. "Patient")
203   * @param theId           The ID (e.g. "123")
204   */
205  public IdType(String theResourceType, String theId) {
206    this(theResourceType, theId, null);
207  }
208
209  /**
210   * Constructor
211   *
212   * @param theResourceType The resource type (e.g. "Patient")
213   * @param theId           The ID (e.g. "123")
214   * @param theVersionId    The version ID ("e.g. "456")
215   */
216  public IdType(String theResourceType, String theId, String theVersionId) {
217    this(null, theResourceType, theId, theVersionId);
218  }
219
220  /**
221   * Constructor
222   *
223   * @param theBaseUrl      The server base URL (e.g. "http://example.com/fhir")
224   * @param theResourceType The resource type (e.g. "Patient")
225   * @param theId           The ID (e.g. "123")
226   * @param theVersionId    The version ID ("e.g. "456")
227   */
228  public IdType(String theBaseUrl, String theResourceType, String theId, String theVersionId) {
229    myBaseUrl = theBaseUrl;
230    myResourceType = theResourceType;
231    myUnqualifiedId = theId;
232    myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionId, null);
233    myHaveComponentParts = true;
234    if (isBlank(myBaseUrl) && isBlank(myResourceType) && isBlank(myUnqualifiedId) && isBlank(myUnqualifiedVersionId)) {
235      myHaveComponentParts = false;
236    }
237  }
238
239  /**
240   * Creates an ID based on a given URL
241   */
242  public IdType(UriType theUrl) {
243    setValue(theUrl.getValueAsString());
244  }
245
246  public void applyTo(IBaseResource theResouce) {
247    if (theResouce == null) {
248      throw new NullPointerException("theResource can not be null");
249    } else {
250      theResouce.setId(new IdType(getValue()));
251    }
252  }
253
254  /**
255   * @deprecated Use {@link #getIdPartAsBigDecimal()} instead (this method was
256   *             deprocated because its name is ambiguous)
257   */
258  @Deprecated
259  public BigDecimal asBigDecimal() {
260    return getIdPartAsBigDecimal();
261  }
262
263  @Override
264  public IdType copy() {
265    IdType ret = new IdType(getValue());
266    copyValues(ret);
267    return ret;
268  }
269
270  @Override
271  public boolean equals(Object theArg0) {
272    if (!(theArg0 instanceof IdType)) {
273      return false;
274    }
275    return StringUtils.equals(getValueAsString(), ((IdType) theArg0).getValueAsString());
276  }
277
278  /**
279   * Returns true if this IdType matches the given IdType in terms of resource
280   * type and ID, but ignores the URL base
281   */
282  @SuppressWarnings("deprecation")
283  public boolean equalsIgnoreBase(IdType theId) {
284    if (theId == null) {
285      return false;
286    }
287    if (theId.isEmpty()) {
288      return isEmpty();
289    }
290    return ObjectUtils.equals(getResourceType(), theId.getResourceType())
291        && ObjectUtils.equals(getIdPart(), theId.getIdPart())
292        && ObjectUtils.equals(getVersionIdPart(), theId.getVersionIdPart());
293  }
294
295  public String fhirType() {
296    return "id";
297  }
298
299  /**
300   * Returns the portion of this resource ID which corresponds to the server base
301   * URL. For example given the resource ID
302   * <code>http://example.com/fhir/Patient/123</code> the base URL would be
303   * <code>http://example.com/fhir</code>.
304   * <p>
305   * This method may return null if the ID contains no base (e.g. "Patient/123")
306   * </p>
307   */
308  @Override
309  public String getBaseUrl() {
310    return myBaseUrl;
311  }
312
313  /**
314   * Returns only the logical ID part of this ID. For example, given the ID
315   * "http://example,.com/fhir/Patient/123/_history/456", this method would return
316   * "123".
317   */
318  @Override
319  public String getIdPart() {
320    return myUnqualifiedId;
321  }
322
323  /**
324   * Returns the unqualified portion of this ID as a big decimal, or
325   * <code>null</code> if the value is null
326   *
327   * @throws NumberFormatException If the value is not a valid BigDecimal
328   */
329  public BigDecimal getIdPartAsBigDecimal() {
330    String val = getIdPart();
331    if (isBlank(val)) {
332      return null;
333    }
334    return new BigDecimal(val);
335  }
336
337  /**
338   * Returns the unqualified portion of this ID as a {@link Long}, or
339   * <code>null</code> if the value is null
340   *
341   * @throws NumberFormatException If the value is not a valid Long
342   */
343  @Override
344  public Long getIdPartAsLong() {
345    String val = getIdPart();
346    if (isBlank(val)) {
347      return null;
348    }
349    return Long.parseLong(val);
350  }
351
352  @Override
353  public String getResourceType() {
354    return myResourceType;
355  }
356
357  /**
358   * Returns the value of this ID. Note that this value may be a fully qualified
359   * URL, a relative/partial URL, or a simple ID. Use {@link #getIdPart()} to get
360   * just the ID portion.
361   *
362   * @see #getIdPart()
363   */
364  @Override
365  public String getValue() {
366    String retVal = super.getValue();
367    if (retVal == null && myHaveComponentParts) {
368
369      if (isLocal() || isUrn()) {
370        return myUnqualifiedId;
371      }
372
373      StringBuilder b = new StringBuilder();
374      if (isNotBlank(myBaseUrl)) {
375        b.append(myBaseUrl);
376        if (myBaseUrl.charAt(myBaseUrl.length() - 1) != '/') {
377          b.append('/');
378        }
379      }
380
381      if (isNotBlank(myResourceType)) {
382        b.append(myResourceType);
383      }
384
385      if (b.length() > 0 && isNotBlank(myUnqualifiedId)) {
386        b.append('/');
387      }
388
389      if (isNotBlank(myUnqualifiedId)) {
390        b.append(myUnqualifiedId);
391      } else if (isNotBlank(myUnqualifiedVersionId)) {
392        b.append('/');
393      }
394
395      if (isNotBlank(myUnqualifiedVersionId)) {
396        b.append('/');
397        b.append("_history");
398        b.append('/');
399        b.append(myUnqualifiedVersionId);
400      }
401      retVal = b.toString();
402      super.setValue(retVal);
403    }
404    return retVal;
405  }
406
407  /**
408   * Set the value
409   * <p>
410   * <p>
411   * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally
412   * represented in hex), a uuid, an oid, or any other combination of lowercase
413   * letters, numerals, "-" and ".", with a length limit of 36 characters.
414   * </p>
415   * <p>
416   * regex: [a-z0-9\-\.]{1,36}
417   * </p>
418   */
419  @Override
420  public IdType setValue(String theValue) {
421    // TODO: add validation
422    super.setValue(theValue);
423    myHaveComponentParts = false;
424
425    if (StringUtils.isBlank(theValue)) {
426      myBaseUrl = null;
427      super.setValue(null);
428      myUnqualifiedId = null;
429      myUnqualifiedVersionId = null;
430      myResourceType = null;
431    } else if (theValue.charAt(0) == '#' && theValue.length() > 1) {
432      super.setValue(theValue);
433      myBaseUrl = null;
434      myUnqualifiedId = theValue;
435      myUnqualifiedVersionId = null;
436      myResourceType = null;
437      myHaveComponentParts = true;
438    } else if (theValue.startsWith(URN_PREFIX)) {
439      myBaseUrl = null;
440      myUnqualifiedId = theValue;
441      myUnqualifiedVersionId = null;
442      myResourceType = null;
443      myHaveComponentParts = true;
444    } else {
445      int vidIndex = theValue.indexOf("/_history/");
446      int idIndex;
447      if (vidIndex != -1) {
448        myUnqualifiedVersionId = theValue.substring(vidIndex + "/_history/".length());
449        idIndex = theValue.lastIndexOf('/', vidIndex - 1);
450        myUnqualifiedId = theValue.substring(idIndex + 1, vidIndex);
451      } else {
452        idIndex = theValue.lastIndexOf('/');
453        myUnqualifiedId = theValue.substring(idIndex + 1);
454        myUnqualifiedVersionId = null;
455      }
456
457      myBaseUrl = null;
458      if (idIndex <= 0) {
459        myResourceType = null;
460      } else {
461        int typeIndex = theValue.lastIndexOf('/', idIndex - 1);
462        if (typeIndex == -1) {
463          myResourceType = theValue.substring(0, idIndex);
464        } else {
465          if (typeIndex > 0 && '/' == theValue.charAt(typeIndex - 1)) {
466            typeIndex = theValue.indexOf('/', typeIndex + 1);
467          }
468          if (typeIndex >= idIndex) {
469            // e.g. http://example.org/foo
470            // 'foo' was the id but we're making that the resource type. Nullify the id part
471            // because we don't have an id.
472            // Also set null value to the super.setValue() and enable myHaveComponentParts
473            // so it forces getValue() to properly
474            // recreate the url
475            myResourceType = myUnqualifiedId;
476            myUnqualifiedId = null;
477            super.setValue(null);
478            myHaveComponentParts = true;
479          } else {
480            myResourceType = theValue.substring(typeIndex + 1, idIndex);
481          }
482
483          if (typeIndex > 4) {
484            myBaseUrl = theValue.substring(0, typeIndex);
485          }
486
487        }
488      }
489
490    }
491    return this;
492  }
493
494  @Override
495  public String getValueAsString() {
496    return getValue();
497  }
498
499  @Override
500  public String asStringValue() {
501    return getValue();
502  }
503
504  /**
505   * Set the value
506   * <p>
507   * <p>
508   * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally
509   * represented in hex), a uuid, an oid, or any other combination of lowercase
510   * letters, numerals, "-" and ".", with a length limit of 36 characters.
511   * </p>
512   * <p>
513   * regex: [a-z0-9\-\.]{1,36}
514   * </p>
515   */
516  @Override
517  public void setValueAsString(String theValue) {
518    setValue(theValue);
519  }
520
521  @Override
522  public String getVersionIdPart() {
523    return myUnqualifiedVersionId;
524  }
525
526  public Long getVersionIdPartAsLong() {
527    if (!hasVersionIdPart()) {
528      return null;
529    } else {
530      return Long.parseLong(getVersionIdPart());
531    }
532  }
533
534  /**
535   * Returns true if this ID has a base url
536   *
537   * @see #getBaseUrl()
538   */
539  public boolean hasBaseUrl() {
540    return isNotBlank(myBaseUrl);
541  }
542
543  @Override
544  public boolean hasIdPart() {
545    return isNotBlank(getIdPart());
546  }
547
548  @Override
549  public boolean hasResourceType() {
550    return isNotBlank(myResourceType);
551  }
552
553  @Override
554  public boolean hasVersionIdPart() {
555    return isNotBlank(getVersionIdPart());
556  }
557
558  @Override
559  public int hashCode() {
560    HashCodeBuilder b = new HashCodeBuilder();
561    b.append(getValueAsString());
562    return b.toHashCode();
563  }
564
565  /**
566   * Returns <code>true</code> if this ID contains an absolute URL (in other
567   * words, a URL starting with "http://" or "https://"
568   */
569  @Override
570  public boolean isAbsolute() {
571    if (StringUtils.isBlank(getValue())) {
572      return false;
573    }
574    return isUrlAbsolute(getValue());
575  }
576
577  @Override
578  public boolean isEmpty() {
579    return isBlank(getValue());
580  }
581
582  @Override
583  public boolean isIdPartValid() {
584    String id = getIdPart();
585    if (StringUtils.isBlank(id)) {
586      return false;
587    }
588    if (id.length() > 64) {
589      return false;
590    }
591    for (int i = 0; i < id.length(); i++) {
592      char nextChar = id.charAt(i);
593      if (nextChar >= 'a' && nextChar <= 'z') {
594        continue;
595      }
596      if (nextChar >= 'A' && nextChar <= 'Z') {
597        continue;
598      }
599      if (nextChar >= '0' && nextChar <= '9') {
600        continue;
601      }
602      if (nextChar == '-' || nextChar == '.') {
603        continue;
604      }
605      return false;
606    }
607    return true;
608  }
609
610  /**
611   * Returns <code>true</code> if the unqualified ID is a valid {@link Long} value
612   * (in other words, it consists only of digits)
613   */
614  @Override
615  public boolean isIdPartValidLong() {
616    return isValidLong(getIdPart());
617  }
618
619  /**
620   * Returns <code>true</code> if the ID is a local reference (in other words, it
621   * begins with the '#' character)
622   */
623  @Override
624  public boolean isLocal() {
625    return defaultString(myUnqualifiedId).startsWith("#");
626  }
627
628  public boolean isUrn() {
629    return defaultString(myUnqualifiedId).startsWith(URN_PREFIX);
630  }
631
632  @Override
633  public boolean isVersionIdPartValidLong() {
634    return isValidLong(getVersionIdPart());
635  }
636
637  @Override
638  public IIdType setParts(String theBaseUrl, String theResourceType, String theIdPart, String theVersionIdPart) {
639    if (isNotBlank(theVersionIdPart)) {
640      Validate.notBlank(theResourceType,
641          "If theVersionIdPart is populated, theResourceType and theIdPart must be populated");
642      Validate.notBlank(theIdPart, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated");
643    }
644    if (isNotBlank(theBaseUrl) && isNotBlank(theIdPart)) {
645      Validate.notBlank(theResourceType,
646          "If theBaseUrl is populated and theIdPart is populated, theResourceType must be populated");
647    }
648
649    setValue(null);
650
651    myBaseUrl = theBaseUrl;
652    myResourceType = theResourceType;
653    myUnqualifiedId = theIdPart;
654    myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionIdPart, null);
655    myHaveComponentParts = true;
656
657    return this;
658  }
659
660  @Override
661  public String toString() {
662    return getValue();
663  }
664
665  /**
666   * Returns a new IdType containing this IdType's values but with no server base
667   * URL if one is present in this IdType. For example, if this IdType contains
668   * the ID "http://foo/Patient/1", this method will return a new IdType
669   * containing ID "Patient/1".
670   */
671  @Override
672  public IdType toUnqualified() {
673    if (isLocal() || isUrn()) {
674      return new IdType(getValueAsString());
675    }
676    return new IdType(getResourceType(), getIdPart(), getVersionIdPart());
677  }
678
679  @Override
680  public IdType toUnqualifiedVersionless() {
681    if (isLocal() || isUrn()) {
682      return new IdType(getValueAsString());
683    }
684    return new IdType(getResourceType(), getIdPart());
685  }
686
687  @Override
688  public IdType toVersionless() {
689    if (isLocal() || isUrn()) {
690      return new IdType(getValueAsString());
691    }
692    return new IdType(getBaseUrl(), getResourceType(), getIdPart(), null);
693  }
694
695  @Override
696  public IdType withResourceType(String theResourceName) {
697    if (isLocal() || isUrn()) {
698      return new IdType(getValueAsString());
699    }
700    return new IdType(theResourceName, getIdPart(), getVersionIdPart());
701  }
702
703  /**
704   * Returns a view of this ID as a fully qualified URL, given a server base and
705   * resource name (which will only be used if the ID does not already contain
706   * those respective parts). Essentially, because IdType can contain either a
707   * complete URL or a partial one (or even jut a simple ID), this method may be
708   * used to translate into a complete URL.
709   *
710   * @param theServerBase   The server base (e.g. "http://example.com/fhir")
711   * @param theResourceType The resource name (e.g. "Patient")
712   * @return A fully qualified URL for this ID (e.g.
713   *         "http://example.com/fhir/Patient/1")
714   */
715  @Override
716  public IdType withServerBase(String theServerBase, String theResourceType) {
717    if (isLocal() || isUrn()) {
718      return new IdType(getValueAsString());
719    }
720    return new IdType(theServerBase, theResourceType, getIdPart(), getVersionIdPart());
721  }
722
723  /**
724   * Creates a new instance of this ID which is identical, but refers to the
725   * specific version of this resource ID noted by theVersion.
726   *
727   * @param theVersion The actual version string, e.g. "1". If theVersion is blank
728   *                   or null, returns the same as {@link #toVersionless()}}
729   * @return A new instance of IdType which is identical, but refers to the
730   *         specific version of this resource ID noted by theVersion.
731   */
732  @Override
733  public IdType withVersion(String theVersion) {
734    if (isBlank(theVersion)) {
735      return toVersionless();
736    }
737
738    if (isLocal() || isUrn()) {
739      return new IdType(getValueAsString());
740    }
741
742    String existingValue = getValue();
743
744    int i = existingValue.indexOf("_history");
745    String value;
746    if (i > 1) {
747      value = existingValue.substring(0, i - 1);
748    } else {
749      value = existingValue;
750    }
751
752    return new IdType(value + '/' + "_history" + '/' + theVersion);
753  }
754
755  private static boolean isUrlAbsolute(String theValue) {
756    String value = theValue.toLowerCase();
757    return value.startsWith("http://") || value.startsWith("https://");
758  }
759
760  private static boolean isValidLong(String id) {
761    if (StringUtils.isBlank(id)) {
762      return false;
763    }
764    for (int i = 0; i < id.length(); i++) {
765      if (Character.isDigit(id.charAt(i)) == false) {
766        return false;
767      }
768    }
769    return true;
770  }
771
772  /**
773   * Construct a new ID with with form "urn:uuid:[UUID]" where [UUID] is a new,
774   * randomly created UUID generated by {@link UUID#randomUUID()}
775   */
776  public static IdType newRandomUuid() {
777    return new IdType("urn:uuid:" + UUID.randomUUID().toString());
778  }
779
780  /**
781   * Retrieves the ID from the given resource instance
782   */
783  public static IdType of(IBaseResource theResouce) {
784    if (theResouce == null) {
785      throw new NullPointerException("theResource can not be null");
786    } else {
787      IIdType retVal = theResouce.getIdElement();
788      if (retVal == null) {
789        return null;
790      } else if (retVal instanceof IdType) {
791        return (IdType) retVal;
792      } else {
793        return new IdType(retVal.getValue());
794      }
795    }
796  }
797
798  private static String toPlainStringWithNpeThrowIfNeeded(BigDecimal theIdPart) {
799    if (theIdPart == null) {
800      throw new NullPointerException("BigDecimal ID can not be null");
801    }
802    return theIdPart.toPlainString();
803  }
804
805  private static String toPlainStringWithNpeThrowIfNeeded(Long theIdPart) {
806    if (theIdPart == null) {
807      throw new NullPointerException("Long ID can not be null");
808    }
809    return theIdPart.toString();
810  }
811
812}