package ca.uhn.fhir.context;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.lang.reflect.Field;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Description;
public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefinition {
private List<Class<? extends IBase>> myChoiceTypes;
private Map<String, BaseRuntimeElementDefinition<?>> myNameToChildDefinition;
private Map<Class<? extends IBase>, String> myDatatypeToElementName;
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myDatatypeToElementDefinition;
private String myReferenceSuffix;
private List<Class<? extends IBaseResource>> myResourceTypes;
/**
* Constructor
*/
public RuntimeChildChoiceDefinition(Field theField, String theElementName, Child theChildAnnotation, Description theDescriptionAnnotation, List<Class<? extends IBase>> theChoiceTypes) {
super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName);
myChoiceTypes = Collections.unmodifiableList(theChoiceTypes);
}
/**
* Constructor
*
* For extension, if myChoiceTypes will be set some other way
*/
RuntimeChildChoiceDefinition(Field theField, String theElementName, Child theChildAnnotation, Description theDescriptionAnnotation) {
super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName);
}
void setChoiceTypes(List<Class<? extends IBase>> theChoiceTypes) {
myChoiceTypes = Collections.unmodifiableList(theChoiceTypes);
}
public List<Class<? extends IBase>> getChoices() {
return myChoiceTypes;
}
@Override
public Set<String> getValidChildNames() {
return myNameToChildDefinition.keySet();
}
@Override
public BaseRuntimeElementDefinition<?> getChildByName(String theName) {
assert myNameToChildDefinition.containsKey(theName);
return myNameToChildDefinition.get(theName);
}
@SuppressWarnings("unchecked")
@Override
void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
myNameToChildDefinition = new HashMap<String, BaseRuntimeElementDefinition<?>>();
myDatatypeToElementName = new HashMap<Class<? extends IBase>, String>();
myDatatypeToElementDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>();
myResourceTypes = new ArrayList<Class<? extends IBaseResource>>();
if (theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) {
myReferenceSuffix = "Resource";
} else {
myReferenceSuffix = "Reference";
}
for (Class<? extends IBase> next : myChoiceTypes) {
String elementName = null;
BaseRuntimeElementDefinition<?> nextDef;
boolean nonPreferred = false;
if (IBaseResource.class.isAssignableFrom(next)) {
elementName = getElementName() + StringUtils.capitalize(next.getSimpleName());
List<Class<? extends IBaseResource>> types = new ArrayList<Class<? extends IBaseResource>>();
types.add((Class<? extends IBaseResource>) next);
nextDef = findResourceReferenceDefinition(theClassToElementDefinitions);
myNameToChildDefinition.put(getElementName() + "Reference", nextDef);
myNameToChildDefinition.put(getElementName() + "Resource", nextDef);
myResourceTypes.add((Class<? extends IBaseResource>) next);
} else {
nextDef = theClassToElementDefinitions.get(next);
BaseRuntimeElementDefinition<?> nextDefForChoice = nextDef;
/*
* In HAPI 1.3 the following applied:
* Elements which are called foo[x] and have a choice which is a profiled datatype must use the
* unprofiled datatype as the element name. E.g. if foo[x] allows markdown as a datatype, it calls the
* element fooString when encoded, because markdown is a profile of string. This is according to the
* FHIR spec
*
* Note that as of HAPI 1.4 this applies only to non-primitive datatypes after discussion
* with Grahame.
*/
if (nextDef instanceof IRuntimeDatatypeDefinition) {
IRuntimeDatatypeDefinition nextDefDatatype = (IRuntimeDatatypeDefinition) nextDef;
if (nextDefDatatype.getProfileOf() != null && !IPrimitiveType.class.isAssignableFrom(next)) {
nextDefForChoice = null;
nonPreferred = true;
Class<? extends IBaseDatatype> profileType = nextDefDatatype.getProfileOf();
BaseRuntimeElementDefinition<?> elementDef = theClassToElementDefinitions.get(profileType);
elementName = getElementName() + StringUtils.capitalize(elementDef.getName());
}
}
if (nextDefForChoice != null) {
elementName = getElementName() + StringUtils.capitalize(nextDefForChoice.getName());
}
}
// I don't see how elementName could be null here, but eclipse complains..
if (elementName != null) {
if (myNameToChildDefinition.containsKey(elementName) == false || !nonPreferred) {
myNameToChildDefinition.put(elementName, nextDef);
}
}
/*
* If this is a resource reference, the element name is "fooNameReference"
*/
if (IBaseResource.class.isAssignableFrom(next) || IBaseReference.class.isAssignableFrom(next)) {
next = theContext.getVersion().getResourceReferenceType();
elementName = getElementName() + myReferenceSuffix;
myNameToChildDefinition.put(elementName, nextDef);
}
myDatatypeToElementDefinition.put(next, nextDef);
if (myDatatypeToElementName.containsKey(next)) {
String existing = myDatatypeToElementName.get(next);
if (!existing.equals(elementName)) {
throw new ConfigurationException("Already have element name " + existing + " for datatype " + next.getSimpleName() + " in " + getElementName() + ", cannot add " + elementName);
}
} else {
myDatatypeToElementName.put(next, elementName);
}
}
myNameToChildDefinition = Collections.unmodifiableMap(myNameToChildDefinition);
myDatatypeToElementName = Collections.unmodifiableMap(myDatatypeToElementName);
myDatatypeToElementDefinition = Collections.unmodifiableMap(myDatatypeToElementDefinition);
myResourceTypes = Collections.unmodifiableList(myResourceTypes);
}
public List<Class<? extends IBaseResource>> getResourceTypes() {
return myResourceTypes;
}
@Override
public String getChildNameByDatatype(Class<? extends IBase> theDatatype) {
String retVal = myDatatypeToElementName.get(theDatatype);
return retVal;
}
@Override
public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IBase> theDatatype) {
return myDatatypeToElementDefinition.get(theDatatype);
}
public Set<Class<? extends IBase>> getValidChildTypes() {
return Collections.unmodifiableSet((myDatatypeToElementDefinition.keySet()));
}
}