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