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.context;
021
022import ca.uhn.fhir.i18n.Msg;
023import ca.uhn.fhir.model.api.annotation.Child;
024import ca.uhn.fhir.model.api.annotation.Description;
025import ca.uhn.fhir.util.ParametersUtil;
026import ca.uhn.fhir.util.ValidateUtil;
027import org.apache.commons.lang3.Validate;
028import org.hl7.fhir.instance.model.api.IBase;
029
030import java.lang.reflect.Field;
031import java.util.ArrayList;
032import java.util.Collections;
033import java.util.List;
034import java.util.Optional;
035
036public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChildDefinition {
037        private final IAccessor myAccessor;
038        private final String myElementName;
039        private final Field myField;
040        private final String myFormalDefinition;
041        private final int myMax;
042        private final int myMin;
043        private final IMutator myMutator;
044        private final String myShortDefinition;
045        private String myBindingValueSet;
046        private boolean myModifier;
047        private boolean mySummary;
048
049        BaseRuntimeDeclaredChildDefinition(
050                        Field theField, Child theChildAnnotation, Description theDescriptionAnnotation, String theElementName)
051                        throws ConfigurationException {
052                super();
053                Validate.notNull(theField, "No field specified");
054                ValidateUtil.isGreaterThanOrEqualTo(theChildAnnotation.min(), 0, "Min must be >= 0");
055                Validate.isTrue(
056                                theChildAnnotation.max() == -1 || theChildAnnotation.max() >= theChildAnnotation.min(),
057                                "Max must be >= Min (unless it is -1 / unlimited)");
058                Validate.notBlank(theElementName, "Element name must not be blank");
059
060                myField = theField;
061                myMin = theChildAnnotation.min();
062                myMax = theChildAnnotation.max();
063                mySummary = theChildAnnotation.summary();
064                myModifier = theChildAnnotation.modifier();
065                myElementName = theElementName;
066                if (theDescriptionAnnotation != null) {
067                        myShortDefinition = theDescriptionAnnotation.shortDefinition();
068                        myFormalDefinition = ParametersUtil.extractDescription(theDescriptionAnnotation);
069                } else {
070                        myShortDefinition = null;
071                        myFormalDefinition = null;
072                }
073
074                myField.setAccessible(true);
075                if (List.class.equals(myField.getType())) {
076                        // TODO: verify that generic type is IElement
077                        myAccessor = new FieldListAccessor();
078                        myMutator = new FieldListMutator();
079                } else {
080                        myAccessor = new FieldPlainAccessor();
081                        myMutator = new FieldPlainMutator();
082                }
083        }
084
085        @Override
086        public IAccessor getAccessor() {
087                return myAccessor;
088        }
089
090        public String getBindingValueSet() {
091                return myBindingValueSet;
092        }
093
094        void setBindingValueSet(String theBindingValueSet) {
095                myBindingValueSet = theBindingValueSet;
096        }
097
098        @Override
099        public String getElementName() {
100                return myElementName;
101        }
102
103        public Field getField() {
104                return myField;
105        }
106
107        public String getFormalDefinition() {
108                return myFormalDefinition;
109        }
110
111        @Override
112        public int getMax() {
113                return myMax;
114        }
115
116        @Override
117        public int getMin() {
118                return myMin;
119        }
120
121        @Override
122        public IMutator getMutator() {
123                return myMutator;
124        }
125
126        public String getShortDefinition() {
127                return myShortDefinition;
128        }
129
130        public boolean isModifier() {
131                return myModifier;
132        }
133
134        protected void setModifier(boolean theModifier) {
135                myModifier = theModifier;
136        }
137
138        @Override
139        public boolean isSummary() {
140                return mySummary;
141        }
142
143        private final class FieldListAccessor implements IAccessor {
144                @SuppressWarnings("unchecked")
145                @Override
146                public List<IBase> getValues(IBase theTarget) {
147                        List<IBase> retVal = (List<IBase>) getFieldValue(theTarget, myField);
148                        if (retVal == null) {
149                                retVal = Collections.emptyList();
150                        }
151                        return retVal;
152                }
153        }
154
155        protected final class FieldListMutator implements IMutator {
156                @Override
157                public void addValue(IBase theTarget, IBase theValue) {
158                        addValue(theTarget, theValue, false);
159                }
160
161                private void addValue(IBase theTarget, IBase theValue, boolean theClear) {
162                        @SuppressWarnings("unchecked")
163                        List<IBase> existingList = (List<IBase>) getFieldValue(theTarget, myField);
164                        if (existingList == null) {
165                                existingList = new ArrayList<>(2);
166                                setFieldValue(theTarget, existingList, myField);
167                        }
168                        if (theClear) {
169                                existingList.clear();
170                                if (theValue == null) {
171                                        return;
172                                }
173                        }
174                        existingList.add(theValue);
175                }
176
177                @Override
178                public void setValue(IBase theTarget, IBase theValue) {
179                        addValue(theTarget, theValue, true);
180                }
181
182                @Override
183                public void remove(IBase theTarget, int theIndex) {
184                        List<IBase> existingList = (List<IBase>) getFieldValue(theTarget, myField);
185                        if (existingList == null) {
186                                throw new IndexOutOfBoundsException(
187                                                Msg.code(2143) + "Can not remove element at index " + theIndex + " from list - List is null");
188                        }
189                        if (theIndex >= existingList.size()) {
190                                throw new IndexOutOfBoundsException(Msg.code(2144) + "Can not remove element at index " + theIndex
191                                                + " from list - List size is " + existingList.size());
192                        }
193                        existingList.remove(theIndex);
194                }
195        }
196
197        private final class FieldPlainAccessor implements IAccessor {
198                @Override
199                public List<IBase> getValues(IBase theTarget) {
200                        Object values = getFieldValue(theTarget, myField);
201                        if (values == null) {
202                                return Collections.emptyList();
203                        }
204                        return Collections.singletonList((IBase) values);
205                }
206
207                @Override
208                public <T extends IBase> Optional<T> getFirstValueOrNull(IBase theTarget) {
209                        return Optional.ofNullable(((T) getFieldValue(theTarget, myField)));
210                }
211        }
212
213        protected final class FieldPlainMutator implements IMutator {
214                @Override
215                public void addValue(IBase theTarget, IBase theValue) {
216                        setFieldValue(theTarget, theValue, myField);
217                }
218
219                @Override
220                public void setValue(IBase theTarget, IBase theValue) {
221                        addValue(theTarget, theValue);
222                }
223
224                @Override
225                public void remove(IBase theTarget, int theIndex) {
226                        throw new UnsupportedOperationException(
227                                        Msg.code(2142) + "Remove by index can only be called on a list-valued field.  '" + myField.getName()
228                                                        + "' is a single-valued field.");
229                }
230        }
231
232        private static void setFieldValue(IBase theTarget, Object theValue, Field theField) {
233                try {
234                        theField.set(theTarget, theValue);
235                } catch (IllegalAccessException e) {
236                        throw new ConfigurationException(Msg.code(1736) + "Failed to set value", e);
237                }
238        }
239
240        private static Object getFieldValue(IBase theTarget, Field theField) {
241                try {
242                        return theField.get(theTarget);
243                } catch (IllegalAccessException e) {
244                        throw new ConfigurationException(Msg.code(1737) + "Failed to get value", e);
245                }
246        }
247}