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}