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