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 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("Specified base instance does not support property retrieval."); 057 } 058 myBase = theBase; 059 myFhirContext = theFhirContext; 060 } 061 062 /** 063 * Gets the method with the specified name and parameter types. 064 * 065 * @param theObject Non-null instance to get the method from 066 * @param theMethodName Name of the method to get 067 * @param theParamClasses Parameters types that method parameters should be assignable as 068 * @return Returns the method with the given name and parameters or null if it can't be found 069 */ 070 protected Method getMethod(Object theObject, String theMethodName, Class... theParamClasses) { 071 for (Method m : theObject.getClass().getDeclaredMethods()) { 072 if (m.getName().equals(theMethodName)) { 073 if (theParamClasses.length == 0) { 074 return m; 075 } 076 if (m.getParameterCount() != theParamClasses.length) { 077 continue; 078 } 079 for (int i = 0; i < theParamClasses.length; i++) { 080 if (!m.getParameterTypes()[i].isAssignableFrom(theParamClasses[i])) { 081 continue; 082 } 083 } 084 return m; 085 } 086 } 087 return null; 088 } 089 090 /** 091 * Gets all non-blank fields as a single string joined with the delimiter provided by {@link #getDelimiter()} 092 * 093 * @param theFiledNames Field names to retrieve values for 094 * @return Returns all specified non-blank fileds as a single string. 095 */ 096 public String getFields(String... theFiledNames) { 097 return Arrays.stream(theFiledNames) 098 .map(this::get) 099 .filter(s -> !StringUtils.isBlank(s)) 100 .collect(Collectors.joining(getDelimiter())); 101 } 102 103 /** 104 * Gets property with the specified name from the provided base class. 105 * 106 * @param thePropertyName Name of the property to get 107 * @return Returns property value converted to string. In case of multiple values, they are joined with the 108 * specified delimiter. 109 */ 110 public String get(String thePropertyName) { 111 return getMultiple(thePropertyName) 112 .stream() 113 .collect(Collectors.joining(getDelimiter())); 114 } 115 116 /** 117 * Sets property or adds to a collection of properties with the specified name from the provided base class. 118 * 119 * @param thePropertyName Name of the property to set or add element to in case property is a collection 120 */ 121 public void set(String thePropertyName, String theValue) { 122 if (theValue == null || theValue.isEmpty()) { 123 return; 124 } 125 126 try { 127 IBase value = myFhirContext.getElementDefinition("string").newInstance(theValue); 128 Method setPropertyMethod = findSetPropertyMethod(myBase, int.class, String.class, value.getClass()); 129 int hashCode = thePropertyName.hashCode(); 130 setPropertyMethod.invoke(myBase, hashCode, thePropertyName, value); 131 } catch (Exception e) { 132 throw new IllegalStateException(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(String.format("Instance %s does not supply property %s", myBase, thePropertyName), e); 149 } 150 151 return Arrays.stream(values) 152 .map(String::valueOf) 153 .filter(s -> !StringUtils.isEmpty(s)) 154 .collect(Collectors.toList()); 155 } 156 157 private Method findGetPropertyMethod(IBase theAddress) { 158 return getMethod(theAddress, GET_PROPERTY_METHOD_NAME); 159 } 160 161 private Method findSetPropertyMethod(IBase theAddress, Class... theParamClasses) { 162 return getMethod(theAddress, SET_PROPERTY_METHOD_NAME, theParamClasses); 163 } 164 165 /** 166 * Gets the delimiter used when concatenating multiple field values 167 * 168 * @return Returns the delimiter 169 */ 170 public String getDelimiter() { 171 return myDelimiter; 172 } 173 174 /** 175 * Sets the delimiter used when concatenating multiple field values 176 * 177 * @param theDelimiter The delimiter to set 178 */ 179 public void setDelimiter(String theDelimiter) { 180 this.myDelimiter = theDelimiter; 181 } 182 183 /** 184 * Gets the base instance that this helper operates on 185 * 186 * @return Returns the base instance 187 */ 188 public IBase getBase() { 189 return myBase; 190 } 191}