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