001package ca.uhn.fhir.util; 002 003/*- 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.i18n.Msg; 025import org.apache.commons.lang3.StringUtils; 026import org.hl7.fhir.instance.model.api.IBase; 027 028import java.lang.reflect.Method; 029import java.util.Arrays; 030import java.util.List; 031import java.util.stream.Collectors; 032 033/** 034 * Helper class for handling updates of the instances that support property modification via <code>setProperty</code> 035 * and <code>getProperty</code> methods. 036 */ 037public class PropertyModifyingHelper { 038 039 public static final String GET_PROPERTY_METHOD_NAME = "getProperty"; 040 public static final String SET_PROPERTY_METHOD_NAME = "setProperty"; 041 public static final String DEFAULT_DELIMITER = ", "; 042 043 private IBase myBase; 044 045 private String myDelimiter = DEFAULT_DELIMITER; 046 047 private FhirContext myFhirContext; 048 049 /** 050 * Creates a new instance initializing the dependencies. 051 * 052 * @param theFhirContext FHIR context holding the resource definitions 053 * @param theBase The base class to set properties on 054 */ 055 public PropertyModifyingHelper(FhirContext theFhirContext, IBase theBase) { 056 if (findGetPropertyMethod(theBase) == null) { 057 throw new IllegalArgumentException(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) 113 .stream() 114 .collect(Collectors.joining(getDelimiter())); 115 } 116 117 /** 118 * Sets property or adds to a collection of properties with the specified name from the provided base class. 119 * 120 * @param thePropertyName Name of the property to set or add element to in case property is a collection 121 */ 122 public void set(String thePropertyName, String theValue) { 123 if (theValue == null || theValue.isEmpty()) { 124 return; 125 } 126 127 try { 128 IBase value = myFhirContext.getElementDefinition("string").newInstance(theValue); 129 Method setPropertyMethod = findSetPropertyMethod(myBase, int.class, String.class, value.getClass()); 130 int hashCode = thePropertyName.hashCode(); 131 setPropertyMethod.invoke(myBase, hashCode, thePropertyName, value); 132 } catch (Exception e) { 133 throw new IllegalStateException(Msg.code(1772) + String.format("Unable to set property %s on %s", thePropertyName, myBase), e); 134 } 135 } 136 137 /** 138 * Gets property values with the specified name from the provided base class. 139 * 140 * @param thePropertyName Name of the property to get 141 * @return Returns property values converted to string. 142 */ 143 public List<String> getMultiple(String thePropertyName) { 144 Method getPropertyMethod = findGetPropertyMethod(myBase); 145 Object[] values; 146 try { 147 values = (Object[]) getPropertyMethod.invoke(myBase, thePropertyName.hashCode(), thePropertyName, true); 148 } catch (Exception e) { 149 throw new IllegalStateException(Msg.code(1773) + String.format("Instance %s does not supply property %s", myBase, thePropertyName), e); 150 } 151 152 return Arrays.stream(values) 153 .map(String::valueOf) 154 .filter(s -> !StringUtils.isEmpty(s)) 155 .collect(Collectors.toList()); 156 } 157 158 private Method findGetPropertyMethod(IBase theAddress) { 159 return getMethod(theAddress, GET_PROPERTY_METHOD_NAME); 160 } 161 162 private Method findSetPropertyMethod(IBase theAddress, Class... theParamClasses) { 163 return getMethod(theAddress, SET_PROPERTY_METHOD_NAME, theParamClasses); 164 } 165 166 /** 167 * Gets the delimiter used when concatenating multiple field values 168 * 169 * @return Returns the delimiter 170 */ 171 public String getDelimiter() { 172 return myDelimiter; 173 } 174 175 /** 176 * Sets the delimiter used when concatenating multiple field values 177 * 178 * @param theDelimiter The delimiter to set 179 */ 180 public void setDelimiter(String theDelimiter) { 181 this.myDelimiter = theDelimiter; 182 } 183 184 /** 185 * Gets the base instance that this helper operates on 186 * 187 * @return Returns the base instance 188 */ 189 public IBase getBase() { 190 return myBase; 191 } 192}