001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.model.api;
021
022import ca.uhn.fhir.i18n.Msg;
023import org.apache.commons.lang3.builder.ToStringBuilder;
024
025import java.io.Serializable;
026
027import static org.apache.commons.lang3.StringUtils.defaultString;
028import static org.apache.commons.lang3.StringUtils.isBlank;
029import static org.apache.commons.lang3.StringUtils.isNotBlank;
030
031/**
032 * Represents a FHIR resource path specification, e.g. <code>Patient:name</code>
033 * <p>
034 * Note on equality: This class uses {@link #getValue() value} and the {@link #isRecurse() recurse} properties to test
035 * equality. Prior to HAPI 1.2 (and FHIR DSTU2) the recurse property did not exist, so this may merit consideration when
036 * upgrading servers.
037 * </p>
038 * <p>
039 * Note on thread safety: This class is not thread safe.
040 * </p>
041 */
042public class Include implements Serializable {
043
044        private static final long serialVersionUID = 1L;
045
046        private final boolean myImmutable;
047        private boolean myIterate;
048        private String myValue;
049        private String myParamType;
050        private String myParamName;
051        private String myParamTargetType;
052
053        /**
054         * Constructor for <b>non-recursive</b> include
055         *
056         * @param theValue
057         *           The <code>_include</code> value, e.g. "Patient:name"
058         */
059        public Include(String theValue) {
060                this(theValue, false);
061        }
062
063        /**
064         * Constructor for an include
065         *
066         * @param theValue
067         *           The <code>_include</code> value, e.g. "Patient:name"
068         * @param theIterate
069         *           Should the include recurse
070         */
071        public Include(String theValue, boolean theIterate) {
072                this(theValue, theIterate, false);
073        }
074
075        /**
076         * Constructor for an include
077         *
078         * @param theValue
079         *           The <code>_include</code> value, e.g. "Patient:name"
080         * @param theIterate
081         *           Should the include recurse
082         */
083        public Include(String theValue, boolean theIterate, boolean theImmutable) {
084                setValue(theValue);
085                myIterate = theIterate;
086                myImmutable = theImmutable;
087        }
088
089        /**
090         * Creates a copy of this include with non-recurse behaviour
091         */
092        public Include asNonRecursive() {
093                return new Include(myValue, false);
094        }
095
096        /**
097         * Creates a copy of this include with recurse behaviour
098         */
099        public Include asRecursive() {
100                return new Include(myValue, true);
101        }
102
103        /**
104         * See the note on equality on the {@link Include class documentation}
105         */
106        @Override
107        public boolean equals(Object obj) {
108                if (this == obj) {
109                        return true;
110                }
111                if (obj == null) {
112                        return false;
113                }
114                if (getClass() != obj.getClass()) {
115                        return false;
116                }
117                Include other = (Include) obj;
118                if (myIterate != other.myIterate) {
119                        return false;
120                }
121                if (myValue == null) {
122                        if (other.myValue != null) {
123                                return false;
124                        }
125                } else if (!myValue.equals(other.myValue)) {
126                        return false;
127                }
128                return true;
129        }
130
131        /**
132         * Returns the portion of the value before the first colon
133         */
134        public String getParamType() {
135                return myParamType;
136        }
137
138        /**
139         * Returns the portion of the value after the first colon but before the second colon
140         */
141        public String getParamName() {
142                return myParamName;
143        }
144
145        /**
146         * Returns the portion of the string after the second colon, or null if there are not two colons in the value.
147         */
148        public String getParamTargetType() {
149                return myParamTargetType;
150        }
151
152        public String getValue() {
153                return myValue;
154        }
155
156        /**
157         * See the note on equality on the {@link Include class documentation}
158         */
159        @Override
160        public int hashCode() {
161                final int prime = 31;
162                int result = 1;
163                result = prime * result + (myIterate ? 1231 : 1237);
164                result = prime * result + ((myValue == null) ? 0 : myValue.hashCode());
165                return result;
166        }
167
168        /**
169         * Is this object {@link #toLocked() locked}?
170         */
171        public boolean isLocked() {
172                return myImmutable;
173        }
174
175        public boolean isRecurse() {
176                return myIterate;
177        }
178
179        /**
180         * Should this include recurse
181         *
182         * @return  Returns a reference to <code>this</code> for easy method chaining
183         */
184        public Include setRecurse(boolean theRecurse) {
185                myIterate = theRecurse;
186                return this;
187        }
188
189        public void setValue(String theValue) {
190                if (myImmutable) {
191                        throw new IllegalStateException(Msg.code(1888) + "Can not change the value of this include");
192                }
193
194                String value = defaultString(theValue);
195
196                int firstColon = value.indexOf(':');
197                String paramType;
198                String paramName;
199                String paramTargetType;
200                if (firstColon == -1 || firstColon == value.length() - 1) {
201                        paramType = null;
202                        paramName = null;
203                        paramTargetType = null;
204                } else {
205                        paramType = value.substring(0, firstColon);
206                        int secondColon = value.indexOf(':', firstColon + 1);
207                        if (secondColon == -1) {
208                                paramName = value.substring(firstColon + 1);
209                                paramTargetType = null;
210                        } else {
211                                paramName = value.substring(firstColon + 1, secondColon);
212                                paramTargetType = value.substring(secondColon + 1);
213                        }
214                }
215
216                myParamType = paramType;
217                myParamName = paramName;
218                myParamTargetType = paramTargetType;
219                myValue = theValue;
220        }
221
222        /**
223         * Return a new
224         */
225        public Include toLocked() {
226                Include retVal = new Include(myValue, myIterate, true);
227                return retVal;
228        }
229
230        @Override
231        public String toString() {
232                ToStringBuilder builder = new ToStringBuilder(this);
233                builder.append("value", myValue);
234                builder.append("iterate", myIterate);
235                return builder.toString();
236        }
237
238        /**
239         * Creates and returns a new copy of this Include with the given type. The following table shows what will be
240         * returned:
241         * <table>
242         * <tr>
243         * <th>Initial Contents</th>
244         * <th>theResourceType</th>
245         * <th>Output</th>
246         * </tr>
247         * <tr>
248         * <td>Patient:careProvider</th>
249         * <th>Organization</th>
250         * <th>Patient:careProvider:Organization</th>
251         * </tr>
252         * <tr>
253         * <td>Patient:careProvider:Practitioner</th>
254         * <th>Organization</th>
255         * <th>Patient:careProvider:Organization</th>
256         * </tr>
257         * <tr>
258         * <td>Patient</th>
259         * <th>(any)</th>
260         * <th>{@link IllegalStateException}</th>
261         * </tr>
262         * </table>
263         *
264         * @param theResourceType
265         *           The resource type (e.g. "Organization")
266         * @return A new copy of the include. Note that if this include is {@link #toLocked() locked}, the returned include
267         *         will be too
268         */
269        public Include withType(String theResourceType) {
270                StringBuilder b = new StringBuilder();
271
272                String paramType = getParamType();
273                String paramName = getParamName();
274                if (isBlank(paramType) || isBlank(paramName)) {
275                        throw new IllegalStateException(
276                                        Msg.code(1889) + "This include does not contain a value in the format [ResourceType]:[paramName]");
277                }
278                b.append(paramType);
279                b.append(":");
280                b.append(paramName);
281
282                if (isNotBlank(theResourceType)) {
283                        b.append(':');
284                        b.append(theResourceType);
285                }
286                Include retVal = new Include(b.toString(), myIterate, myImmutable);
287                return retVal;
288        }
289}