001package ca.uhn.fhir.context;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2016 University Health Network
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 java.lang.reflect.Constructor;
024import java.lang.reflect.InvocationTargetException;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030
031import org.apache.commons.lang3.StringUtils;
032import org.hl7.fhir.instance.model.api.IBase;
033
034public abstract class BaseRuntimeElementDefinition<T extends IBase> {
035
036        private static final Class<Void> VOID_CLASS = Void.class;
037        
038        private final String myName;
039        private final Class<? extends T> myImplementingClass;
040        private List<RuntimeChildDeclaredExtensionDefinition> myExtensions = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
041        private Map<String, RuntimeChildDeclaredExtensionDefinition> myUrlToExtension = new HashMap<String, RuntimeChildDeclaredExtensionDefinition>();
042        private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsModifier = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
043        private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsNonModifier = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
044        private final boolean myStandardType;
045        private Map<Class<?>, Constructor<T>> myConstructors = Collections.synchronizedMap(new HashMap<Class<?>, Constructor<T>>());
046
047        public BaseRuntimeElementDefinition(String theName, Class<? extends T> theImplementingClass, boolean theStandardType) {
048                assert StringUtils.isNotBlank(theName);
049                assert theImplementingClass != null;
050
051                String name = theName;
052                // TODO: remove this and fix for the model
053                if (name.endsWith("Dt")) {
054                        name = name.substring(0, name.length() - 2);
055                }
056                
057                
058                myName = name;
059                myStandardType = theStandardType;
060                myImplementingClass = theImplementingClass;
061        }
062
063        public boolean isStandardType() {
064                return myStandardType;
065        }
066
067        @Override
068        public String toString() {
069                return getClass().getSimpleName()+"[" + getName() + ", " + getImplementingClass().getSimpleName() + "]";
070        }
071
072        public void addExtension(RuntimeChildDeclaredExtensionDefinition theExtension) {
073                if (theExtension == null) {
074                        throw new NullPointerException();
075                }
076                myExtensions.add(theExtension);
077        }
078
079        public List<RuntimeChildDeclaredExtensionDefinition> getExtensions() {
080                return myExtensions;
081        }
082
083        public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsModifier() {
084                return myExtensionsModifier;
085        }
086
087        public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsNonModifier() {
088                return myExtensionsNonModifier;
089        }
090
091        /**
092         * @return Returns null if none
093         */
094        public RuntimeChildDeclaredExtensionDefinition getDeclaredExtension(String theExtensionUrl) {
095                return myUrlToExtension.get(theExtensionUrl);
096        }
097
098        /**
099         * @return Returns the runtime name for this resource (i.e. the name that
100         *         will be used in encoded messages)
101         */
102        public String getName() {
103                return myName;
104        }
105
106        public T newInstance() {
107                return newInstance(null);
108        }
109
110        public T newInstance(Object theArgument) {
111                try {
112                        if (theArgument == null) {
113                                return getConstructor(null).newInstance(null);
114                        } else {
115                                return getConstructor(theArgument).newInstance(theArgument);
116                        }
117                } catch (InstantiationException e) {
118                        throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
119                } catch (IllegalAccessException e) {
120                        throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
121                } catch (IllegalArgumentException e) {
122                        throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
123                } catch (InvocationTargetException e) {
124                        throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
125                } catch (SecurityException e) {
126                        throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
127                }
128        }
129
130        @SuppressWarnings("unchecked")
131        private Constructor<T> getConstructor(Object theArgument) {
132                
133                Class<? extends Object> argumentType;
134                if (theArgument == null) {
135                        argumentType = VOID_CLASS;
136                } else {
137                        argumentType = theArgument.getClass();
138                }
139                
140                Constructor<T> retVal = myConstructors.get(argumentType);
141                if (retVal == null) {
142                        for (Constructor<?> next : getImplementingClass().getConstructors()) {
143                                if (argumentType == VOID_CLASS) {
144                                        if (next.getParameterTypes().length == 0) {
145                                                retVal = (Constructor<T>) next;
146                                                break;
147                                        }
148                                } else if (next.getParameterTypes().length == 1) {
149                                        if (next.getParameterTypes()[0].isAssignableFrom(argumentType)) {
150                                                retVal = (Constructor<T>) next;
151                                                break;
152                                        }
153                                }
154                        }
155                        if (retVal == null) {
156                                throw new ConfigurationException("Class " + getImplementingClass() + " has no constructor with a single argument of type " + argumentType);
157                        }
158                        myConstructors.put(argumentType, retVal);
159                }
160                return retVal;
161        }
162
163        public Class<? extends T> getImplementingClass() {
164                return myImplementingClass;
165        }
166
167        /**
168         * Invoked prior to use to perform any initialization and make object
169         * mutable.
170         * @param theContext TODO
171         */
172        void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
173                for (BaseRuntimeChildDefinition next : myExtensions) {
174                        next.sealAndInitialize(theContext, theClassToElementDefinitions);
175                }
176
177                for (RuntimeChildDeclaredExtensionDefinition next : myExtensions) {
178                        String extUrl = next.getExtensionUrl();
179                        if (myUrlToExtension.containsKey(extUrl)) {
180                                throw new ConfigurationException("Duplicate extension URL[" + extUrl + "] in Element[" + getName() + "]");
181                        } else {
182                                myUrlToExtension.put(extUrl, next);
183                        }
184                        if (next.isModifier()) {
185                                myExtensionsModifier.add(next);
186                        } else {
187                                myExtensionsNonModifier.add(next);
188                        }
189
190                }
191
192                myExtensions = Collections.unmodifiableList(myExtensions);
193        }
194
195        public abstract ChildTypeEnum getChildType();
196
197        public enum ChildTypeEnum {
198                COMPOSITE_DATATYPE, PRIMITIVE_DATATYPE, RESOURCE, RESOURCE_REF, RESOURCE_BLOCK, 
199                /**
200                 * HAPI style.
201                 */
202                PRIMITIVE_XHTML, 
203                UNDECL_EXT, EXTENSION_DECLARED, 
204                /**
205                 * HAPI structure style.
206                 */
207                CONTAINED_RESOURCES, 
208                ID_DATATYPE, 
209                /**
210                 * HL7.org structure style.
211                 */
212                CONTAINED_RESOURCE_LIST, 
213                
214                /**
215                 * HL7.org style.
216                 */
217                PRIMITIVE_XHTML_HL7ORG, 
218                
219        }
220
221}