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