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