001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2025 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.BaseRuntimeChildDefinition;
023import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
024import ca.uhn.fhir.i18n.Msg;
025import ca.uhn.fhir.model.api.ICompositeElement;
026import ca.uhn.fhir.model.api.IElement;
027import jakarta.annotation.Nullable;
028import org.hl7.fhir.instance.model.api.IBase;
029import org.hl7.fhir.instance.model.api.IBaseResource;
030import org.hl7.fhir.instance.model.api.IPrimitiveType;
031
032import java.util.ArrayList;
033import java.util.Date;
034import java.util.List;
035import java.util.function.Function;
036
037public class ElementUtil {
038
039        public static final Function<IBase, String> CONVERT_PRIMITIVE_TO_STRING =
040                        e -> ((IPrimitiveType<?>) e).getValueAsString();
041        public static final Function<IBase, IBaseResource> CAST_BASE_TO_RESOURCE = e -> (IBaseResource) e;
042
043        @SuppressWarnings("unchecked")
044        public static final Function<IBase, IPrimitiveType<Date>> CAST_TO_PRIMITIVE_DATE = e -> ((IPrimitiveType<Date>) e);
045
046        @SuppressWarnings("unchecked")
047        public static boolean isEmpty(Object... theElements) {
048                if (theElements == null) {
049                        return true;
050                }
051                for (int i = 0; i < theElements.length; i++) {
052                        Object next = theElements[i];
053                        if (next instanceof List) {
054                                if (!isEmpty((List<? extends IBase>) next)) {
055                                        return false;
056                                }
057                        } else if (next instanceof String && (!((String) next).isEmpty())) {
058                                return false;
059                        } else if (next != null && !((IBase) next).isEmpty()) {
060                                return false;
061                        }
062                }
063                return true;
064        }
065
066        public static boolean isEmpty(IBase... theElements) {
067                if (theElements == null) {
068                        return true;
069                }
070                for (int i = 0; i < theElements.length; i++) {
071                        IBase next = theElements[i];
072                        if (next != null && !next.isEmpty()) {
073                                return false;
074                        }
075                }
076                return true;
077        }
078
079        public static boolean isEmpty(IElement... theElements) {
080                if (theElements == null) {
081                        return true;
082                }
083                for (int i = 0; i < theElements.length; i++) {
084                        IBase next = theElements[i];
085                        if (next != null && !next.isEmpty()) {
086                                return false;
087                        }
088                }
089                return true;
090        }
091
092        public static boolean isEmpty(List<? extends IBase> theElements) {
093                if (theElements == null) {
094                        return true;
095                }
096                for (int i = 0; i < theElements.size(); i++) {
097                        IBase next;
098                        try {
099                                next = theElements.get(i);
100                        } catch (ClassCastException e) {
101                                List<?> elements = theElements;
102                                String s = "Found instance of " + elements.get(i).getClass()
103                                                + " - Did you set a field value to the incorrect type? Expected " + IBase.class.getName();
104                                throw new ClassCastException(Msg.code(1748) + s);
105                        }
106                        if (next != null && !next.isEmpty()) {
107                                return false;
108                        }
109                }
110                return true;
111        }
112
113        /**
114         * Note that this method does not work on HL7.org structures
115         */
116        public static <T extends IElement> List<T> allPopulatedChildElements(Class<T> theType, Object... theElements) {
117                ArrayList<T> retVal = new ArrayList<>();
118                for (Object next : theElements) {
119                        if (next == null) {
120                                continue;
121                        } else if (next instanceof IElement) {
122                                addElement(retVal, (IElement) next, theType);
123                        } else if (next instanceof List) {
124                                for (Object nextElement : ((List<?>) next)) {
125                                        if (!(nextElement instanceof IBase)) {
126                                                throw new IllegalArgumentException(
127                                                                Msg.code(1749) + "Found element of " + nextElement.getClass());
128                                        }
129                                        addElement(retVal, (IElement) nextElement, theType);
130                                }
131                        } else {
132                                throw new IllegalArgumentException(Msg.code(1750) + "Found element of " + next.getClass());
133                        }
134                }
135                return retVal;
136        }
137
138        // @SuppressWarnings("unchecked")
139        private static <T extends IElement> void addElement(ArrayList<T> retVal, IElement next, Class<T> theType) {
140                if (theType != null && theType.isAssignableFrom(next.getClass())) {
141                        retVal.add(theType.cast(next));
142                }
143                if (next instanceof ICompositeElement) {
144                        ICompositeElement iCompositeElement = (ICompositeElement) next;
145                        // TODO: Use of a deprecated method should be resolved.
146                        retVal.addAll(iCompositeElement.getAllPopulatedChildElementsOfType(theType));
147                }
148        }
149
150        public static IBase setValue(IBase theTarget, BaseRuntimeChildDefinition theChildDef, Object theValue) {
151                BaseRuntimeElementDefinition<?> elementDefinition = theChildDef.getChildByName(theChildDef.getElementName());
152                IBase value;
153                if (theValue == null || elementDefinition.getImplementingClass().isInstance(theValue)) {
154                        value = (IBase) theValue;
155                } else {
156                        value = elementDefinition.newInstance(theValue);
157                }
158                theChildDef.getMutator().setValue(theTarget, value);
159                return value;
160        }
161
162        @Nullable
163        public static <T> T getSingleValueOrNull(
164                        IBase theParentElement, BaseRuntimeChildDefinition theChildDefinition, Function<IBase, T> theConverter) {
165                if (theParentElement == null) {
166                        return null;
167                }
168                return theChildDefinition
169                                .getAccessor()
170                                .getFirstValueOrNull(theParentElement)
171                                .map(theConverter)
172                                .orElse(null);
173        }
174}