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