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