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.util; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.i18n.Msg; 024import org.apache.commons.lang3.StringUtils; 025import org.hl7.fhir.instance.model.api.IBase; 026 027import java.lang.reflect.Method; 028import java.util.Arrays; 029import java.util.List; 030import java.util.stream.Collectors; 031 032/** 033 * Helper class for handling updates of the instances that support property modification via <code>setProperty</code> 034 * and <code>getProperty</code> methods. 035 */ 036public class PropertyModifyingHelper { 037 038 public static final String GET_PROPERTY_METHOD_NAME = "getProperty"; 039 public static final String SET_PROPERTY_METHOD_NAME = "setProperty"; 040 public static final String DEFAULT_DELIMITER = ", "; 041 042 private IBase myBase; 043 044 private String myDelimiter = DEFAULT_DELIMITER; 045 046 private FhirContext myFhirContext; 047 048 /** 049 * Creates a new instance initializing the dependencies. 050 * 051 * @param theFhirContext FHIR context holding the resource definitions 052 * @param theBase The base class to set properties on 053 */ 054 public PropertyModifyingHelper(FhirContext theFhirContext, IBase theBase) { 055 if (findGetPropertyMethod(theBase) == null) { 056 throw new IllegalArgumentException( 057 Msg.code(1771) + "Specified base instance does not support property retrieval."); 058 } 059 myBase = theBase; 060 myFhirContext = theFhirContext; 061 } 062 063 /** 064 * Gets the method with the specified name and parameter types. 065 * 066 * @param theObject Non-null instance to get the method from 067 * @param theMethodName Name of the method to get 068 * @param theParamClasses Parameters types that method parameters should be assignable as 069 * @return Returns the method with the given name and parameters or null if it can't be found 070 */ 071 protected Method getMethod(Object theObject, String theMethodName, Class... theParamClasses) { 072 for (Method m : theObject.getClass().getDeclaredMethods()) { 073 if (m.getName().equals(theMethodName)) { 074 if (theParamClasses.length == 0) { 075 return m; 076 } 077 if (m.getParameterCount() != theParamClasses.length) { 078 continue; 079 } 080 for (int i = 0; i < theParamClasses.length; i++) { 081 if (!m.getParameterTypes()[i].isAssignableFrom(theParamClasses[i])) { 082 continue; 083 } 084 } 085 return m; 086 } 087 } 088 return null; 089 } 090 091 /** 092 * Gets all non-blank fields as a single string joined with the delimiter provided by {@link #getDelimiter()} 093 * 094 * @param theFiledNames Field names to retrieve values for 095 * @return Returns all specified non-blank fileds as a single string. 096 */ 097 public String getFields(String... theFiledNames) { 098 return Arrays.stream(theFiledNames) 099 .map(this::get) 100 .filter(s -> !StringUtils.isBlank(s)) 101 .collect(Collectors.joining(getDelimiter())); 102 } 103 104 /** 105 * Gets property with the specified name from the provided base class. 106 * 107 * @param thePropertyName Name of the property to get 108 * @return Returns property value converted to string. In case of multiple values, they are joined with the 109 * specified delimiter. 110 */ 111 public String get(String thePropertyName) { 112 return getMultiple(thePropertyName).stream().collect(Collectors.joining(getDelimiter())); 113 } 114 115 /** 116 * Sets property or adds to a collection of properties with the specified name from the provided base class. 117 * 118 * @param thePropertyName Name of the property to set or add element to in case property is a collection 119 */ 120 public void set(String thePropertyName, String theValue) { 121 if (theValue == null || theValue.isEmpty()) { 122 return; 123 } 124 125 try { 126 IBase value = myFhirContext.getElementDefinition("string").newInstance(theValue); 127 Method setPropertyMethod = findSetPropertyMethod(myBase, int.class, String.class, value.getClass()); 128 int hashCode = thePropertyName.hashCode(); 129 setPropertyMethod.invoke(myBase, hashCode, thePropertyName, value); 130 } catch (Exception e) { 131 throw new IllegalStateException( 132 Msg.code(1772) + String.format("Unable to set property %s on %s", thePropertyName, myBase), e); 133 } 134 } 135 136 /** 137 * Gets property values with the specified name from the provided base class. 138 * 139 * @param thePropertyName Name of the property to get 140 * @return Returns property values converted to string. 141 */ 142 public List<String> getMultiple(String thePropertyName) { 143 Method getPropertyMethod = findGetPropertyMethod(myBase); 144 Object[] values; 145 try { 146 values = (Object[]) getPropertyMethod.invoke(myBase, thePropertyName.hashCode(), thePropertyName, true); 147 } catch (Exception e) { 148 throw new IllegalStateException( 149 Msg.code(1773) + String.format("Instance %s does not supply property %s", myBase, thePropertyName), 150 e); 151 } 152 153 return Arrays.stream(values) 154 .map(String::valueOf) 155 .filter(s -> !StringUtils.isEmpty(s)) 156 .collect(Collectors.toList()); 157 } 158 159 private Method findGetPropertyMethod(IBase theAddress) { 160 return getMethod(theAddress, GET_PROPERTY_METHOD_NAME); 161 } 162 163 private Method findSetPropertyMethod(IBase theAddress, Class... theParamClasses) { 164 return getMethod(theAddress, SET_PROPERTY_METHOD_NAME, theParamClasses); 165 } 166 167 /** 168 * Gets the delimiter used when concatenating multiple field values 169 * 170 * @return Returns the delimiter 171 */ 172 public String getDelimiter() { 173 return myDelimiter; 174 } 175 176 /** 177 * Sets the delimiter used when concatenating multiple field values 178 * 179 * @param theDelimiter The delimiter to set 180 */ 181 public void setDelimiter(String theDelimiter) { 182 this.myDelimiter = theDelimiter; 183 } 184 185 /** 186 * Gets the base instance that this helper operates on 187 * 188 * @return Returns the base instance 189 */ 190 public IBase getBase() { 191 return myBase; 192 } 193}