001package org.hl7.fhir.r5.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
106 * limit applies only to the ID portion ("123" in the 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  @Override
255  public IdType copy() {
256    IdType ret = new IdType(getValue());
257    copyValues(ret);
258    return ret;
259  }
260
261  @Override
262  public boolean equals(Object theArg0) {
263    if (!(theArg0 instanceof IdType)) {
264      return false;
265    }
266    return StringUtils.equals(getValueAsString(), ((IdType) theArg0).getValueAsString());
267  }
268
269  /**
270   * Returns true if this IdType matches the given IdType in terms of resource
271   * type and ID, but ignores the URL base
272   */
273  @SuppressWarnings("deprecation")
274  public boolean equalsIgnoreBase(IdType theId) {
275    if (theId == null) {
276      return false;
277    }
278    if (theId.isEmpty()) {
279      return isEmpty();
280    }
281    return ObjectUtils.equals(getResourceType(), theId.getResourceType())
282      && ObjectUtils.equals(getIdPart(), theId.getIdPart())
283      && ObjectUtils.equals(getVersionIdPart(), theId.getVersionIdPart());
284  }
285
286  public String fhirType() {
287    return "id";
288  }
289
290  /**
291   * Returns the portion of this resource ID which corresponds to the server
292   * base URL. For example given the resource ID
293   * <code>http://example.com/fhir/Patient/123</code> the base URL would be
294   * <code>http://example.com/fhir</code>.
295   * <p>
296   * This method may return null if the ID contains no base (e.g. "Patient/123")
297   * </p>
298   */
299  @Override
300  public String getBaseUrl() {
301    return myBaseUrl;
302  }
303
304  /**
305   * Returns only the logical ID part of this ID. For example, given the ID
306   * "http://example,.com/fhir/Patient/123/_history/456", this method would
307   * return "123".
308   */
309  @Override
310  public String getIdPart() {
311    return myUnqualifiedId;
312  }
313
314  /**
315   * Returns the unqualified portion of this ID as a big decimal, or
316   * <code>null</code> if the value is null
317   *
318   * @throws NumberFormatException If the value is not a valid BigDecimal
319   */
320  public BigDecimal getIdPartAsBigDecimal() {
321    String val = getIdPart();
322    if (isBlank(val)) {
323      return null;
324    }
325    return new BigDecimal(val);
326  }
327
328  /**
329   * Returns the unqualified portion of this ID as a {@link Long}, or
330   * <code>null</code> if the value is null
331   *
332   * @throws NumberFormatException If the value is not a valid Long
333   */
334  @Override
335  public Long getIdPartAsLong() {
336    String val = getIdPart();
337    if (isBlank(val)) {
338      return null;
339    }
340    return Long.parseLong(val);
341  }
342
343  @Override
344  public String getResourceType() {
345    return myResourceType;
346  }
347
348  /**
349   * Returns the value of this ID. Note that this value may be a fully qualified
350   * URL, a relative/partial URL, or a simple ID. Use {@link #getIdPart()} to
351   * get just the ID portion.
352   *
353   * @see #getIdPart()
354   */
355  @Override
356  public String getValue() {
357    String retVal = super.getValue();
358    if (retVal == null && myHaveComponentParts) {
359
360      if (isLocal() || isUrn()) {
361        return myUnqualifiedId;
362      }
363
364      StringBuilder b = new StringBuilder();
365      if (isNotBlank(myBaseUrl)) {
366        b.append(myBaseUrl);
367        if (myBaseUrl.charAt(myBaseUrl.length() - 1) != '/') {
368          b.append('/');
369        }
370      }
371
372      if (isNotBlank(myResourceType)) {
373        b.append(myResourceType);
374      }
375
376      if (b.length() > 0 && isNotBlank(myUnqualifiedId)) {
377        b.append('/');
378      }
379
380      if (isNotBlank(myUnqualifiedId)) {
381        b.append(myUnqualifiedId);
382      } else if (isNotBlank(myUnqualifiedVersionId)) {
383        b.append('/');
384      }
385
386      if (isNotBlank(myUnqualifiedVersionId)) {
387        b.append('/');
388        b.append("_history");
389        b.append('/');
390        b.append(myUnqualifiedVersionId);
391      }
392      retVal = b.toString();
393      super.setValue(retVal);
394    }
395    return retVal;
396  }
397
398  /**
399   * Set the value
400   * <p>
401   * <p>
402   * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally
403   * represented in hex), a uuid, an oid, or any other combination of lowercase
404   * letters, numerals, "-" and ".", with a length limit of 36 characters.
405   * </p>
406   * <p>
407   * regex: [a-z0-9\-\.]{1,36}
408   * </p>
409   */
410  @Override
411  public IdType setValue(String theValue) {
412    // TODO: add validation
413    super.setValue(theValue);
414    myHaveComponentParts = false;
415
416    if (StringUtils.isBlank(theValue)) {
417      myBaseUrl = null;
418      super.setValue(null);
419      myUnqualifiedId = null;
420      myUnqualifiedVersionId = null;
421      myResourceType = null;
422    } else if (theValue.charAt(0) == '#' && theValue.length() > 1) {
423      super.setValue(theValue);
424      myBaseUrl = null;
425      myUnqualifiedId = theValue;
426      myUnqualifiedVersionId = null;
427      myResourceType = null;
428      myHaveComponentParts = true;
429    } else if (theValue.startsWith(URN_PREFIX)) {
430      myBaseUrl = null;
431      myUnqualifiedId = theValue;
432      myUnqualifiedVersionId = null;
433      myResourceType = null;
434      myHaveComponentParts = true;
435    } else {
436      int vidIndex = theValue.indexOf("/_history/");
437      int idIndex;
438      if (vidIndex != -1) {
439        myUnqualifiedVersionId = theValue.substring(vidIndex + "/_history/".length());
440        idIndex = theValue.lastIndexOf('/', vidIndex - 1);
441        myUnqualifiedId = theValue.substring(idIndex + 1, vidIndex);
442      } else {
443        idIndex = theValue.lastIndexOf('/');
444        myUnqualifiedId = theValue.substring(idIndex + 1);
445        myUnqualifiedVersionId = null;
446      }
447
448      myBaseUrl = null;
449      if (idIndex <= 0) {
450        myResourceType = null;
451      } else {
452        int typeIndex = theValue.lastIndexOf('/', idIndex - 1);
453        if (typeIndex == -1) {
454          myResourceType = theValue.substring(0, idIndex);
455        } else {
456          if (typeIndex > 0 && '/' == theValue.charAt(typeIndex - 1)) {
457            typeIndex = theValue.indexOf('/', typeIndex + 1);
458          }
459          if (typeIndex >= idIndex) {
460            // e.g. http://example.org/foo
461            // 'foo' was the id but we're making that the resource type. Nullify the id part because we don't have an id.
462            // Also set null value to the super.setValue() and enable myHaveComponentParts so it forces getValue() to properly
463            // recreate the url
464            myResourceType = myUnqualifiedId;
465            myUnqualifiedId = null;
466            super.setValue(null);
467            myHaveComponentParts = true;
468          } else {
469            myResourceType = theValue.substring(typeIndex + 1, idIndex);
470          }
471
472          if (typeIndex > 4) {
473            myBaseUrl = theValue.substring(0, typeIndex);
474          }
475
476        }
477      }
478
479    }
480    return this;
481  }
482  @Override
483  public String getValueAsString() {
484    return getValue();
485  }
486
487  @Override
488  public String asStringValue() {
489    return getValue();
490  }
491
492  /**
493   * Set the value
494   * <p>
495   * <p>
496   * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally
497   * represented in hex), a uuid, an oid, or any other combination of lowercase
498   * letters, numerals, "-" and ".", with a length limit of 36 characters.
499   * </p>
500   * <p>
501   * regex: [a-z0-9\-\.]{1,36}
502   * </p>
503   */
504  @Override
505  public void setValueAsString(String theValue) {
506    setValue(theValue);
507  }
508
509  @Override
510  public String getVersionIdPart() {
511    return myUnqualifiedVersionId;
512  }
513
514  public Long getVersionIdPartAsLong() {
515    if (!hasVersionIdPart()) {
516      return null;
517    } else {
518      return Long.parseLong(getVersionIdPart());
519    }
520  }
521
522  /**
523   * Returns true if this ID has a base url
524   *
525   * @see #getBaseUrl()
526   */
527  public boolean hasBaseUrl() {
528    return isNotBlank(myBaseUrl);
529  }
530
531  @Override
532  public boolean hasIdPart() {
533    return isNotBlank(getIdPart());
534  }
535
536  @Override
537  public boolean hasResourceType() {
538    return isNotBlank(myResourceType);
539  }
540
541  @Override
542  public boolean hasVersionIdPart() {
543    return isNotBlank(getVersionIdPart());
544  }
545
546  @Override
547  public int hashCode() {
548    HashCodeBuilder b = new HashCodeBuilder();
549    b.append(getValueAsString());
550    return b.toHashCode();
551  }
552
553  /**
554   * Returns <code>true</code> if this ID contains an absolute URL (in other
555   * words, a URL starting with "http://" or "https://"
556   */
557  @Override
558  public boolean isAbsolute() {
559    if (StringUtils.isBlank(getValue())) {
560      return false;
561    }
562    return isUrlAbsolute(getValue());
563  }
564
565  @Override
566  public boolean isEmpty() {
567    return isBlank(getValue());
568  }
569
570  @Override
571  public boolean isIdPartValid() {
572    String id = getIdPart();
573    if (StringUtils.isBlank(id)) {
574      return false;
575    }
576    if (id.length() > 64) {
577      return false;
578    }
579    for (int i = 0; i < id.length(); i++) {
580      char nextChar = id.charAt(i);
581      if (nextChar >= 'a' && nextChar <= 'z') {
582        continue;
583      }
584      if (nextChar >= 'A' && nextChar <= 'Z') {
585        continue;
586      }
587      if (nextChar >= '0' && nextChar <= '9') {
588        continue;
589      }
590      if (nextChar == '-' || nextChar == '.') {
591        continue;
592      }
593      return false;
594    }
595    return true;
596  }
597
598  /**
599   * Returns <code>true</code> if the unqualified ID is a valid {@link Long}
600   * value (in other words, it consists only of digits)
601   */
602  @Override
603  public boolean isIdPartValidLong() {
604    return isValidLong(getIdPart());
605  }
606
607  /**
608   * Returns <code>true</code> if the ID is a local reference (in other words,
609   * it begins with the '#' character)
610   */
611  @Override
612  public boolean isLocal() {
613    return defaultString(myUnqualifiedId).startsWith("#");
614  }
615
616  public boolean isUrn() {
617    return defaultString(myUnqualifiedId).startsWith(URN_PREFIX);
618  }
619
620  @Override
621  public boolean isVersionIdPartValidLong() {
622    return isValidLong(getVersionIdPart());
623  }
624
625  @Override
626  public IIdType setParts(String theBaseUrl, String theResourceType, String theIdPart, String theVersionIdPart) {
627    if (isNotBlank(theVersionIdPart)) {
628      Validate.notBlank(theResourceType, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated");
629      Validate.notBlank(theIdPart, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated");
630    }
631    if (isNotBlank(theBaseUrl) && isNotBlank(theIdPart)) {
632      Validate.notBlank(theResourceType, "If theBaseUrl is populated and theIdPart is populated, theResourceType must be populated");
633    }
634
635    setValue(null);
636
637    myBaseUrl = theBaseUrl;
638    myResourceType = theResourceType;
639    myUnqualifiedId = theIdPart;
640    myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionIdPart, null);
641    myHaveComponentParts = true;
642
643    return this;
644  }
645
646  @Override
647  public String toString() {
648    return getValue();
649  }
650
651  /**
652   * Returns a new IdType containing this IdType's values but with no server
653   * base URL if one is present in this IdType. For example, if this IdType
654   * contains the ID "http://foo/Patient/1", this method will return a new
655   * IdType containing ID "Patient/1".
656   */
657  @Override
658  public IdType toUnqualified() {
659    if (isLocal() || isUrn()) {
660      return new IdType(getValueAsString());
661    }
662    return new IdType(getResourceType(), getIdPart(), getVersionIdPart());
663  }
664
665  @Override
666  public IdType toUnqualifiedVersionless() {
667    if (isLocal() || isUrn()) {
668      return new IdType(getValueAsString());
669    }
670    return new IdType(getResourceType(), getIdPart());
671  }
672
673  @Override
674  public IdType toVersionless() {
675    if (isLocal() || isUrn()) {
676      return new IdType(getValueAsString());
677    }
678    return new IdType(getBaseUrl(), getResourceType(), getIdPart(), null);
679  }
680
681  @Override
682  public IdType withResourceType(String theResourceName) {
683    if (isLocal() || isUrn()) {
684      return new IdType(getValueAsString());
685    }
686    return new IdType(theResourceName, getIdPart(), getVersionIdPart());
687  }
688
689  /**
690   * Returns a view of this ID as a fully qualified URL, given a server base and
691   * resource name (which will only be used if the ID does not already contain
692   * those respective parts). Essentially, because IdType can contain either a
693   * complete URL or a partial one (or even jut a simple ID), this method may be
694   * used to translate into a complete URL.
695   *
696   * @param theServerBase   The server base (e.g. "http://example.com/fhir")
697   * @param theResourceType The resource name (e.g. "Patient")
698   * @return A fully qualified URL for this ID (e.g.
699   * "http://example.com/fhir/Patient/1")
700   */
701  @Override
702  public IdType withServerBase(String theServerBase, String theResourceType) {
703    if (isLocal() || isUrn()) {
704      return new IdType(getValueAsString());
705    }
706    return new IdType(theServerBase, theResourceType, getIdPart(), getVersionIdPart());
707  }
708
709  /**
710   * Creates a new instance of this ID which is identical, but refers to the
711   * specific version of this resource ID noted by theVersion.
712   *
713   * @param theVersion The actual version string, e.g. "1". If theVersion is blank or null, returns the same as {@link #toVersionless()}}
714   * @return A new instance of IdType which is identical, but refers to the
715   * specific version of this resource ID noted by theVersion.
716   */
717  @Override
718  public IdType withVersion(String theVersion) {
719    if (isBlank(theVersion)) {
720      return toVersionless();
721    }
722
723    if (isLocal() || isUrn()) {
724      return new IdType(getValueAsString());
725    }
726
727    String existingValue = getValue();
728
729    int i = existingValue.indexOf("_history");
730    String value;
731    if (i > 1) {
732      value = existingValue.substring(0, i - 1);
733    } else {
734      value = existingValue;
735    }
736
737    return new IdType(value + '/' + "_history" + '/' + theVersion);
738  }
739
740  private static boolean isUrlAbsolute(String theValue) {
741    String value = theValue.toLowerCase();
742    return value.startsWith("http://") || value.startsWith("https://");
743  }
744
745  private static boolean isValidLong(String id) {
746    if (StringUtils.isBlank(id)) {
747      return false;
748    }
749    for (int i = 0; i < id.length(); i++) {
750      if (Character.isDigit(id.charAt(i)) == false) {
751        return false;
752      }
753    }
754    return true;
755  }
756
757  /**
758   * Construct a new ID with with form "urn:uuid:[UUID]" where [UUID] is a new,
759   * randomly created UUID generated by {@link UUID#randomUUID()}
760   */
761  public static IdType newRandomUuid() {
762    return new IdType("urn:uuid:" + UUID.randomUUID().toString());
763  }
764
765  /**
766   * Retrieves the ID from the given resource instance
767   */
768  public static IdType of(IBaseResource theResouce) {
769    if (theResouce == null) {
770      throw new NullPointerException("theResource can not be null");
771    } else {
772      IIdType retVal = theResouce.getIdElement();
773      if (retVal == null) {
774        return null;
775      } else if (retVal instanceof IdType) {
776        return (IdType) retVal;
777      } else {
778        return new IdType(retVal.getValue());
779      }
780    }
781  }
782
783  private static String toPlainStringWithNpeThrowIfNeeded(BigDecimal theIdPart) {
784    if (theIdPart == null) {
785      throw new NullPointerException("BigDecimal ID can not be null");
786    }
787    return theIdPart.toPlainString();
788  }
789
790  private static String toPlainStringWithNpeThrowIfNeeded(Long theIdPart) {
791    if (theIdPart == null) {
792      throw new NullPointerException("Long ID can not be null");
793    }
794    return theIdPart.toString();
795  }
796
797}