001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2023 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
153        public String getValue() {
154                return myValue;
155        }
156
157        /**
158         * See the note on equality on the {@link Include class documentation}
159         */
160        @Override
161        public int hashCode() {
162                final int prime = 31;
163                int result = 1;
164                result = prime * result + (myIterate ? 1231 : 1237);
165                result = prime * result + ((myValue == null) ? 0 : myValue.hashCode());
166                return result;
167        }
168
169        /**
170         * Is this object {@link #toLocked() locked}?
171         */
172        public boolean isLocked() {
173                return myImmutable;
174        }
175
176        public boolean isRecurse() {
177                return myIterate;
178        }
179
180        /**
181         * Should this include recurse
182         *
183         * @return  Returns a reference to <code>this</code> for easy method chaining
184         */
185        public Include setRecurse(boolean theRecurse) {
186                myIterate = theRecurse;
187                return this;
188        }
189
190        public void setValue(String theValue) {
191                if (myImmutable) {
192                        throw new IllegalStateException(Msg.code(1888) + "Can not change the value of this include");
193                }
194
195                String value = defaultString(theValue);
196
197                int firstColon = value.indexOf(':');
198                String paramType;
199                String paramName;
200                String paramTargetType;
201                if (firstColon == -1 || firstColon == value.length() - 1) {
202                        paramType = null;
203                        paramName = null;
204                        paramTargetType = null;
205                } else {
206                        paramType = value.substring(0, firstColon);
207                        int secondColon = value.indexOf(':', firstColon + 1);
208                        if (secondColon == -1) {
209                                paramName = value.substring(firstColon + 1);
210                                paramTargetType = null;
211                        } else {
212                                paramName =  value.substring(firstColon + 1, secondColon);
213                                paramTargetType = value.substring(secondColon + 1);
214                        }
215                }
216
217                myParamType = paramType;
218                myParamName = paramName;
219                myParamTargetType = paramTargetType;
220                myValue = theValue;
221
222        }
223
224        /**
225         * Return a new
226         */
227        public Include toLocked() {
228                Include retVal = new Include(myValue, myIterate, true);
229                return retVal;
230        }
231
232        @Override
233        public String toString() {
234                ToStringBuilder builder = new ToStringBuilder(this);
235                builder.append("value", myValue);
236                builder.append("iterate", myIterate);
237                return builder.toString();
238        }
239
240        /**
241         * Creates and returns a new copy of this Include with the given type. The following table shows what will be
242         * returned:
243         * <table>
244         * <tr>
245         * <th>Initial Contents</th>
246         * <th>theResourceType</th>
247         * <th>Output</th>
248         * </tr>
249         * <tr>
250         * <td>Patient:careProvider</th>
251         * <th>Organization</th>
252         * <th>Patient:careProvider:Organization</th>
253         * </tr>
254         * <tr>
255         * <td>Patient:careProvider:Practitioner</th>
256         * <th>Organization</th>
257         * <th>Patient:careProvider:Organization</th>
258         * </tr>
259         * <tr>
260         * <td>Patient</th>
261         * <th>(any)</th>
262         * <th>{@link IllegalStateException}</th>
263         * </tr>
264         * </table>
265         * 
266         * @param theResourceType
267         *           The resource type (e.g. "Organization")
268         * @return A new copy of the include. Note that if this include is {@link #toLocked() locked}, the returned include
269         *         will be too
270         */
271        public Include withType(String theResourceType) {
272                StringBuilder b = new StringBuilder();
273                
274                String paramType = getParamType();
275                String paramName = getParamName();
276                if (isBlank(paramType) || isBlank(paramName)) {
277                        throw new IllegalStateException(Msg.code(1889) + "This include does not contain a value in the format [ResourceType]:[paramName]");
278                }
279                b.append(paramType);
280                b.append(":");
281                b.append(paramName);
282                
283                if (isNotBlank(theResourceType)) {
284                        b.append(':');
285                        b.append(theResourceType);
286                }
287                Include retVal = new Include(b.toString(), myIterate, myImmutable);
288                return retVal;
289        }
290
291}