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