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  /**
499   * Set the value
500   * <p>
501   * <p>
502   * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally
503   * represented in hex), a uuid, an oid, or any other combination of lowercase
504   * letters, numerals, "-" and ".", with a length limit of 36 characters.
505   * </p>
506   * <p>
507   * regex: [a-z0-9\-\.]{1,36}
508   * </p>
509   */
510  @Override
511  public void setValueAsString(String theValue) {
512    setValue(theValue);
513  }
514
515  @Override
516  public String getVersionIdPart() {
517    return myUnqualifiedVersionId;
518  }
519
520  public Long getVersionIdPartAsLong() {
521    if (!hasVersionIdPart()) {
522      return null;
523    } else {
524      return Long.parseLong(getVersionIdPart());
525    }
526  }
527
528  /**
529   * Returns true if this ID has a base url
530   *
531   * @see #getBaseUrl()
532   */
533  public boolean hasBaseUrl() {
534    return isNotBlank(myBaseUrl);
535  }
536
537  @Override
538  public boolean hasIdPart() {
539    return isNotBlank(getIdPart());
540  }
541
542  @Override
543  public boolean hasResourceType() {
544    return isNotBlank(myResourceType);
545  }
546
547  @Override
548  public boolean hasVersionIdPart() {
549    return isNotBlank(getVersionIdPart());
550  }
551
552  @Override
553  public int hashCode() {
554    HashCodeBuilder b = new HashCodeBuilder();
555    b.append(getValueAsString());
556    return b.toHashCode();
557  }
558
559  /**
560   * Returns <code>true</code> if this ID contains an absolute URL (in other
561   * words, a URL starting with "http://" or "https://"
562   */
563  @Override
564  public boolean isAbsolute() {
565    if (StringUtils.isBlank(getValue())) {
566      return false;
567    }
568    return isUrlAbsolute(getValue());
569  }
570
571  @Override
572  public boolean isEmpty() {
573    return isBlank(getValue());
574  }
575
576  @Override
577  public boolean isIdPartValid() {
578    String id = getIdPart();
579    if (StringUtils.isBlank(id)) {
580      return false;
581    }
582    if (id.length() > 64) {
583      return false;
584    }
585    for (int i = 0; i < id.length(); i++) {
586      char nextChar = id.charAt(i);
587      if (nextChar >= 'a' && nextChar <= 'z') {
588        continue;
589      }
590      if (nextChar >= 'A' && nextChar <= 'Z') {
591        continue;
592      }
593      if (nextChar >= '0' && nextChar <= '9') {
594        continue;
595      }
596      if (nextChar == '-' || nextChar == '.') {
597        continue;
598      }
599      return false;
600    }
601    return true;
602  }
603
604  /**
605   * Returns <code>true</code> if the unqualified ID is a valid {@link Long}
606   * value (in other words, it consists only of digits)
607   */
608  @Override
609  public boolean isIdPartValidLong() {
610    return isValidLong(getIdPart());
611  }
612
613  /**
614   * Returns <code>true</code> if the ID is a local reference (in other words,
615   * it begins with the '#' character)
616   */
617  @Override
618  public boolean isLocal() {
619    return defaultString(myUnqualifiedId).startsWith("#");
620  }
621
622  public boolean isUrn() {
623    return defaultString(myUnqualifiedId).startsWith(URN_PREFIX);
624  }
625
626  @Override
627  public boolean isVersionIdPartValidLong() {
628    return isValidLong(getVersionIdPart());
629  }
630
631  @Override
632  public IIdType setParts(String theBaseUrl, String theResourceType, String theIdPart, String theVersionIdPart) {
633    if (isNotBlank(theVersionIdPart)) {
634      Validate.notBlank(theResourceType, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated");
635      Validate.notBlank(theIdPart, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated");
636    }
637    if (isNotBlank(theBaseUrl) && isNotBlank(theIdPart)) {
638      Validate.notBlank(theResourceType, "If theBaseUrl is populated and theIdPart is populated, theResourceType must be populated");
639    }
640
641    setValue(null);
642
643    myBaseUrl = theBaseUrl;
644    myResourceType = theResourceType;
645    myUnqualifiedId = theIdPart;
646    myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionIdPart, null);
647    myHaveComponentParts = true;
648
649    return this;
650  }
651
652  @Override
653  public String toString() {
654    return getValue();
655  }
656
657  /**
658   * Returns a new IdType containing this IdType's values but with no server
659   * base URL if one is present in this IdType. For example, if this IdType
660   * contains the ID "http://foo/Patient/1", this method will return a new
661   * IdType containing ID "Patient/1".
662   */
663  @Override
664  public IdType toUnqualified() {
665    if (isLocal() || isUrn()) {
666      return new IdType(getValueAsString());
667    }
668    return new IdType(getResourceType(), getIdPart(), getVersionIdPart());
669  }
670
671  @Override
672  public IdType toUnqualifiedVersionless() {
673    if (isLocal() || isUrn()) {
674      return new IdType(getValueAsString());
675    }
676    return new IdType(getResourceType(), getIdPart());
677  }
678
679  @Override
680  public IdType toVersionless() {
681    if (isLocal() || isUrn()) {
682      return new IdType(getValueAsString());
683    }
684    return new IdType(getBaseUrl(), getResourceType(), getIdPart(), null);
685  }
686
687  @Override
688  public IdType withResourceType(String theResourceName) {
689    if (isLocal() || isUrn()) {
690      return new IdType(getValueAsString());
691    }
692    return new IdType(theResourceName, getIdPart(), getVersionIdPart());
693  }
694
695  /**
696   * Returns a view of this ID as a fully qualified URL, given a server base and
697   * resource name (which will only be used if the ID does not already contain
698   * those respective parts). Essentially, because IdType can contain either a
699   * complete URL or a partial one (or even jut a simple ID), this method may be
700   * used to translate into a complete URL.
701   *
702   * @param theServerBase   The server base (e.g. "http://example.com/fhir")
703   * @param theResourceType The resource name (e.g. "Patient")
704   * @return A fully qualified URL for this ID (e.g.
705   * "http://example.com/fhir/Patient/1")
706   */
707  @Override
708  public IdType withServerBase(String theServerBase, String theResourceType) {
709    if (isLocal() || isUrn()) {
710      return new IdType(getValueAsString());
711    }
712    return new IdType(theServerBase, theResourceType, getIdPart(), getVersionIdPart());
713  }
714
715  /**
716   * Creates a new instance of this ID which is identical, but refers to the
717   * specific version of this resource ID noted by theVersion.
718   *
719   * @param theVersion The actual version string, e.g. "1". If theVersion is blank or null, returns the same as {@link #toVersionless()}}
720   * @return A new instance of IdType which is identical, but refers to the
721   * specific version of this resource ID noted by theVersion.
722   */
723  @Override
724  public IdType withVersion(String theVersion) {
725    if (isBlank(theVersion)) {
726      return toVersionless();
727    }
728
729    if (isLocal() || isUrn()) {
730      return new IdType(getValueAsString());
731    }
732
733    String existingValue = getValue();
734
735    int i = existingValue.indexOf("_history");
736    String value;
737    if (i > 1) {
738      value = existingValue.substring(0, i - 1);
739    } else {
740      value = existingValue;
741    }
742
743    return new IdType(value + '/' + "_history" + '/' + theVersion);
744  }
745
746  private static boolean isUrlAbsolute(String theValue) {
747    String value = theValue.toLowerCase();
748    return value.startsWith("http://") || value.startsWith("https://");
749  }
750
751  private static boolean isValidLong(String id) {
752    if (StringUtils.isBlank(id)) {
753      return false;
754    }
755    for (int i = 0; i < id.length(); i++) {
756      if (Character.isDigit(id.charAt(i)) == false) {
757        return false;
758      }
759    }
760    return true;
761  }
762
763  /**
764   * Construct a new ID with with form "urn:uuid:[UUID]" where [UUID] is a new,
765   * randomly created UUID generated by {@link UUID#randomUUID()}
766   */
767  public static IdType newRandomUuid() {
768    return new IdType("urn:uuid:" + UUID.randomUUID().toString());
769  }
770
771  /**
772   * Retrieves the ID from the given resource instance
773   */
774  public static IdType of(IBaseResource theResouce) {
775    if (theResouce == null) {
776      throw new NullPointerException("theResource can not be null");
777    } else {
778      IIdType retVal = theResouce.getIdElement();
779      if (retVal == null) {
780        return null;
781      } else if (retVal instanceof IdType) {
782        return (IdType) retVal;
783      } else {
784        return new IdType(retVal.getValue());
785      }
786    }
787  }
788
789  private static String toPlainStringWithNpeThrowIfNeeded(BigDecimal theIdPart) {
790    if (theIdPart == null) {
791      throw new NullPointerException("BigDecimal ID can not be null");
792    }
793    return theIdPart.toPlainString();
794  }
795
796  private static String toPlainStringWithNpeThrowIfNeeded(Long theIdPart) {
797    if (theIdPart == null) {
798      throw new NullPointerException("Long ID can not be null");
799    }
800    return theIdPart.toString();
801  }
802
803}