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