diff --git a/hapi-fhir-base/.classpath b/hapi-fhir-base/.classpath index e174050cd32..7f2fb7b4ce3 100644 --- a/hapi-fhir-base/.classpath +++ b/hapi-fhir-base/.classpath @@ -1,55 +1,59 @@ - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hapi-fhir-base/.project b/hapi-fhir-base/.project index a75380b92e5..22f076c3eef 100644 --- a/hapi-fhir-base/.project +++ b/hapi-fhir-base/.project @@ -1,4 +1,3 @@ - hapi-fhir-base NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse. diff --git a/hapi-fhir-base/.settings/org.eclipse.jdt.core.prefs b/hapi-fhir-base/.settings/org.eclipse.jdt.core.prefs index fac709e2f3f..b03bfe1c855 100644 --- a/hapi-fhir-base/.settings/org.eclipse.jdt.core.prefs +++ b/hapi-fhir-base/.settings/org.eclipse.jdt.core.prefs @@ -1,5 +1,92 @@ -#Sun Feb 16 17:15:18 EST 2014 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.source=1.6 +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=warning +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=warning +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=warning +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=error +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=warning +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index b6e16eb2b31..9fa5568d3b0 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -29,6 +29,14 @@ woodstox-core-asl 4.2.0 + + @@ -36,6 +44,11 @@ commons-lang3 3.2.1 + + commons-codec + commons-codec + 1.9 + @@ -63,7 +76,7 @@ 1.3.2 test - + diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDefinition.java index e4a4ae6e81f..9d22c6495d3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeChildDefinition.java @@ -1,19 +1,26 @@ package ca.uhn.fhir.context; -import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.*; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; import ca.uhn.fhir.model.api.IElement; +import ca.uhn.fhir.util.BeanUtils; public abstract class BaseRuntimeChildDefinition { - private final Field myField; - private final int myMin; - private final int myMax; + private final IAccessor myAccessor; private final String myElementName; + private final Field myField; + private final int myMax; + private final int myMin; + private final IMutator myMutator; BaseRuntimeChildDefinition(Field theField, int theMin, int theMax, String theElementName) throws ConfigurationException { super(); @@ -29,34 +36,96 @@ public abstract class BaseRuntimeChildDefinition { if (isBlank(theElementName)) { throw new ConfigurationException("Element name must not be blank"); } - - myField=theField; - myMin=theMin; - myMax=theMax; + + myField = theField; + myMin = theMin; + myMax = theMax; myElementName = theElementName; + + // TODO: handle lists (max>0), and maybe max=0? + + if (myMax == 1) { + Class declaringClass = myField.getDeclaringClass(); + Class targetReturnType = myField.getType(); + try { + final Method accessor = BeanUtils.findAccessor(declaringClass, targetReturnType, myElementName); + final Method mutator = BeanUtils.findMutator(declaringClass, targetReturnType, myElementName); + myAccessor = new IAccessor() { + @Override + public List getValues(Object theTarget) { + try { + return Collections.singletonList(accessor.invoke(theTarget)); + } catch (IllegalAccessException e) { + throw new ConfigurationException("Failed to get value", e); + } catch (IllegalArgumentException e) { + throw new ConfigurationException("Failed to get value", e); + } catch (InvocationTargetException e) { + throw new ConfigurationException("Failed to get value", e); + } + } + }; + myMutator = new IMutator() { + @Override + public void addValue(Object theTarget, Object theValue) { + try { + mutator.invoke(theTarget, theValue); + } catch (IllegalAccessException e) { + throw new ConfigurationException("Failed to get value", e); + } catch (IllegalArgumentException e) { + throw new ConfigurationException("Failed to get value", e); + } catch (InvocationTargetException e) { + throw new ConfigurationException("Failed to get value", e); + } + } + }; + } catch (NoSuchFieldException e) { + throw new ConfigurationException(e); + } + } else { + + // replace this with an implementation + myAccessor = null; + myMutator = null; + + } + } + public IAccessor getAccessor() { + return myAccessor; + } + + public abstract BaseRuntimeElementDefinition getChildByName(String theName); + public String getElementName() { return myElementName; } - public int getMin() { - return myMin; + public Field getField() { + return myField; } public int getMax() { return myMax; } - public Field getField() { - return myField; + public int getMin() { + return myMin; } - + + public IMutator getMutator() { + return myMutator; + } + public abstract Set getValidChildNames(); - - public abstract BaseRuntimeElementDefinition getChildByName(String theName); abstract void sealAndInitialize(Map, BaseRuntimeElementDefinition> theClassToElementDefinitions); - - + + public interface IMutator { + void addValue(Object theTarget, Object theValue); + } + + public interface IAccessor { + List getValues(Object theTarget); + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementCompositeDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementCompositeDefinition.java index 3662e96af54..b9e734ea757 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementCompositeDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementCompositeDefinition.java @@ -23,8 +23,8 @@ public abstract class BaseRuntimeElementCompositeDefinition getChildByNameOrThrowDataFormatException(String theName) throws DataFormatException { - BaseRuntimeElementDefinition retVal = myNameToChild.get(theName); + public BaseRuntimeChildDefinition getChildByNameOrThrowDataFormatException(String theName) throws DataFormatException { + BaseRuntimeChildDefinition retVal = myNameToChild.get(theName); if (retVal == null) { throw new DataFormatException("Unknown child name: " + theName); } @@ -37,7 +37,7 @@ public abstract class BaseRuntimeElementCompositeDefinition>(); + myNameToChild = new HashMap(); for (BaseRuntimeChildDefinition next : myChildren) { for (String nextName : next.getValidChildNames()) { if (myNameToChild.containsKey(nextName)) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java index e132549d35c..e7eb4331bc6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeElementDefinition.java @@ -22,6 +22,16 @@ public abstract class BaseRuntimeElementDefinition { return myName; } + public T newInstance() { + try { + return getImplementingClass().newInstance(); + } catch (InstantiationException e) { + throw new ConfigurationException("Failed to instantiate type:"+getImplementingClass().getName(), e); + } catch (IllegalAccessException e) { + throw new ConfigurationException("Failed to instantiate type:"+getImplementingClass().getName(), e); + } + } + public Class getImplementingClass() { return myImplementingClass; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java index 36b01f53a76..a9a98e46abc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.context; -import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.*; import java.lang.reflect.Field; import java.util.ArrayList; @@ -8,9 +8,12 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Stack; +import java.util.TreeMap; import ca.uhn.fhir.model.api.CodeableConceptElement; import ca.uhn.fhir.model.api.ICodeEnum; @@ -24,10 +27,12 @@ import ca.uhn.fhir.model.api.ResourceReference; import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.ChildResource; import ca.uhn.fhir.model.api.annotation.Choice; -import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.CodeTableDef; +import ca.uhn.fhir.model.api.annotation.DatatypeDef; +import ca.uhn.fhir.model.api.annotation.Narrative; import ca.uhn.fhir.model.api.annotation.ResourceDef; import ca.uhn.fhir.model.datatype.ICodedDatatype; +import ca.uhn.fhir.model.datatype.NarrativeDt; class ModelScanner { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class); @@ -35,6 +40,7 @@ class ModelScanner { private Map, BaseRuntimeElementDefinition> myClassToElementDefinitions = new HashMap, BaseRuntimeElementDefinition>(); private Map myNameToResourceDefinitions = new HashMap(); private Set> myScanAlso = new HashSet>(); + private Set> myScanAlsoCodeTable = new HashSet>(); // private Map // myNameToDatatypeDefinitions = new HashMap... theResourceTypes) throws ConfigurationException { Set> toScan = new HashSet>(Arrays.asList(theResourceTypes)); + toScan.add(NarrativeDt.class); + do { for (Class nextClass : toScan) { scan(nextClass); } - for (Iterator> iter = myScanAlso.iterator(); iter.hasNext(); ) { + for (Iterator> iter = myScanAlso.iterator(); iter.hasNext();) { if (myClassToElementDefinitions.containsKey(iter.next())) { iter.remove(); } @@ -60,11 +68,13 @@ class ModelScanner { toScan.addAll(myScanAlso); myScanAlso.clear(); } while (!myScanAlso.isEmpty()); - + for (BaseRuntimeElementDefinition next : myClassToElementDefinitions.values()) { next.sealAndInitialize(myClassToElementDefinitions); } - + + ourLog.info("Done scanning FHIR library, found {} model entries", myClassToElementDefinitions.size()); + } private String scan(Class theClass) throws ConfigurationException { @@ -76,8 +86,7 @@ class ModelScanner { ResourceDef resourceDefinition = theClass.getAnnotation(ResourceDef.class); if (resourceDefinition != null) { if (!IResource.class.isAssignableFrom(theClass)) { - throw new ConfigurationException("Resource type contains a @" + ResourceDef.class.getSimpleName() + " annotation but does not implement " + IResource.class.getCanonicalName() - + ": " + theClass.getCanonicalName()); + throw new ConfigurationException("Resource type contains a @" + ResourceDef.class.getSimpleName() + " annotation but does not implement " + IResource.class.getCanonicalName() + ": " + theClass.getCanonicalName()); } @SuppressWarnings("unchecked") Class resClass = (Class) theClass; @@ -91,12 +100,11 @@ class ModelScanner { Class resClass = (Class) theClass; return scanCompositeDatatype(resClass, datatypeDefinition); } else if (IPrimitiveDatatype.class.isAssignableFrom(theClass)) { - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) Class resClass = (Class) theClass; return scanPrimitiveDatatype(resClass, datatypeDefinition); } else { - throw new ConfigurationException("Resource type contains a @" + DatatypeDef.class.getSimpleName() + " annotation but does not implement " + IDatatype.class.getCanonicalName() + ": " - + theClass.getCanonicalName()); + throw new ConfigurationException("Resource type contains a @" + DatatypeDef.class.getSimpleName() + " annotation but does not implement " + IDatatype.class.getCanonicalName() + ": " + theClass.getCanonicalName()); } } @@ -107,13 +115,11 @@ class ModelScanner { Class resClass = (Class) theClass; return scanCodeTable(resClass, codeTableDefinition); } else { - throw new ConfigurationException("Resource type contains a @" + CodeTableDef.class.getSimpleName() + " annotation but does not implement " + ICodeEnum.class.getCanonicalName() + ": " - + theClass.getCanonicalName()); + throw new ConfigurationException("Resource type contains a @" + CodeTableDef.class.getSimpleName() + " annotation but does not implement " + ICodeEnum.class.getCanonicalName() + ": " + theClass.getCanonicalName()); } } - throw new ConfigurationException("Resource type does not contain a @" + ResourceDef.class.getSimpleName() + " annotation or a @" + DatatypeDef.class.getSimpleName() + " annotation: " - + theClass.getCanonicalName()); + throw new ConfigurationException("Resource type does not contain a @" + ResourceDef.class.getSimpleName() + " annotation or a @" + DatatypeDef.class.getSimpleName() + " annotation: " + theClass.getCanonicalName()); } private String scanCompositeDatatype(Class theClass, DatatypeDef theDatatypeDefinition) { @@ -126,8 +132,7 @@ class ModelScanner { if (myNameToResourceDefinitions.containsKey(resourceName)) { if (!myNameToResourceDefinitions.get(resourceName).getImplementingClass().equals(theClass)) { - throw new ConfigurationException("Detected duplicate element name '" + resourceName + "' in types '" + theClass.getCanonicalName() + "' and '" - + myNameToResourceDefinitions.get(resourceName).getImplementingClass() + "'"); + throw new ConfigurationException("Detected duplicate element name '" + resourceName + "' in types '" + theClass.getCanonicalName() + "' and '" + myNameToResourceDefinitions.get(resourceName).getImplementingClass() + "'"); } return resourceName; } @@ -150,8 +155,7 @@ class ModelScanner { if (myNameToResourceDefinitions.containsKey(resourceName)) { if (!myNameToResourceDefinitions.get(resourceName).getImplementingClass().equals(theClass)) { - throw new ConfigurationException("Detected duplicate element name '" + resourceName + "' in types '" + theClass.getCanonicalName() + "' and '" - + myNameToResourceDefinitions.get(resourceName).getImplementingClass() + "'"); + throw new ConfigurationException("Detected duplicate element name '" + resourceName + "' in types '" + theClass.getCanonicalName() + "' and '" + myNameToResourceDefinitions.get(resourceName).getImplementingClass() + "'"); } return resourceName; } @@ -172,8 +176,7 @@ class ModelScanner { if (myNameToResourceDefinitions.containsKey(resourceName)) { if (!myNameToResourceDefinitions.get(resourceName).getImplementingClass().equals(theClass)) { - throw new ConfigurationException("Detected duplicate element name '" + resourceName + "' in types '" + theClass.getCanonicalName() + "' and '" - + myNameToResourceDefinitions.get(resourceName).getImplementingClass() + "'"); + throw new ConfigurationException("Detected duplicate element name '" + resourceName + "' in types '" + theClass.getCanonicalName() + "' and '" + myNameToResourceDefinitions.get(resourceName).getImplementingClass() + "'"); } return resourceName; } @@ -187,10 +190,52 @@ class ModelScanner { return resourceName; } + @SuppressWarnings("unchecked") private void scanCompositeElementForChildren(Class theClass, BaseRuntimeElementCompositeDefinition theDefinition) { Set elementNames = new HashSet(); - Map orderToElementDef = new HashMap(); - for (Field next : theClass.getFields()) { + TreeMap orderToElementDef = new TreeMap(); + + LinkedList> classes = new LinkedList>(); + Class current = theClass; + do { + classes.push(current); + if (ICompositeElement.class.isAssignableFrom(current.getSuperclass())) { + current = (Class) current.getSuperclass(); + }else { + current = null; + } + }while (current != null); + + for (Class next : classes) { + scanCompositeElementForChildren(next, theDefinition, elementNames, orderToElementDef); + } + + dealwithnegative + + for (int i = 0; i < orderToElementDef.size(); i++) { + if (!orderToElementDef.containsKey(i)) { + throw new ConfigurationException("Type '" + theClass.getCanonicalName() + "' does not have a child with order " + i + " (in other words, there are gaps between specified child orders)"); + } + BaseRuntimeChildDefinition next = orderToElementDef.get(i); + theDefinition.addChild(next); + } + + } + + private void scanCompositeElementForChildren(Class theClass, BaseRuntimeElementCompositeDefinition theDefinition, Set elementNames, TreeMap orderToElementDef) { + for (Field next : theClass.getDeclaredFields()) { + + Narrative hasNarrative = next.getAnnotation(Narrative.class); + if (hasNarrative != null) { + RuntimeChildNarrativeDefinition def; + try { + def = new RuntimeChildNarrativeDefinition(next, hasNarrative.name()); + } catch (Exception e) { + throw new ConfigurationException("Failed to find narrative field", e); + } + theDefinition.addChild(def); + } + Child element = next.getAnnotation(Child.class); if (element == null) { ourLog.debug("Ignoring non-type field: " + next.getName()); @@ -202,6 +247,10 @@ class ModelScanner { int min = element.min(); int max = element.max(); + while (order == Child.ORDER_UNKNOWN && orderToElementDef.containsKey(order)) { + order--; + } + Choice choiceAttr = element.choice(); List> choiceTypes = new ArrayList>(); for (Class nextChoiceType : choiceAttr.types()) { @@ -230,8 +279,7 @@ class ModelScanner { * Child is a resource reference */ if (resRefAnnotation == null) { - throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is a resource reference but does not have a @" - + ChildResource.class.getSimpleName() + " annotation"); + throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is a resource reference but does not have a @" + ChildResource.class.getSimpleName() + " annotation"); } Class[] refType = resRefAnnotation.types(); @@ -242,12 +290,10 @@ class ModelScanner { } else { if (resRefAnnotation != null) { - throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is not a resource reference but has a @" - + ChildResource.class.getSimpleName() + " annotation"); + throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is not a resource reference but has a @" + ChildResource.class.getSimpleName() + " annotation"); } if (!IDatatype.class.isAssignableFrom(next.getType())) { - throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is not a resource reference and is not an instance of type " - + IDatatype.class.getName()); + throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is not a resource reference and is not an instance of type " + IDatatype.class.getName()); } @SuppressWarnings("unchecked") @@ -264,11 +310,10 @@ class ModelScanner { CodeableConceptElement concept = next.getAnnotation(CodeableConceptElement.class); if (concept != null) { if (!ICodedDatatype.class.isAssignableFrom(nextDatatype)) { - throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is marked as @" + CodeableConceptElement.class.getCanonicalName() - + " but type is not a subtype of " + ICodedDatatype.class.getName()); + throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is marked as @" + CodeableConceptElement.class.getCanonicalName() + " but type is not a subtype of " + ICodedDatatype.class.getName()); } else { Class type = concept.type(); - myScanAlso.add(type); + myScanAlsoCodeTable.add(type); def.setCodeType(type); } } @@ -280,8 +325,11 @@ class ModelScanner { // Class codeType = concept.type(); // String codeTableName = scanCodeTable(codeType); // @SuppressWarnings("unchecked") - // String datatypeName = scan((Class) next.getType()); - // RuntimeChildCompositeDatatypeDefinition def = new RuntimeChildCompositeDatatypeDefinition(next, elementName, datatypeName, min, max, codeTableName); + // String datatypeName = scan((Class) + // next.getType()); + // RuntimeChildCompositeDatatypeDefinition def = new + // RuntimeChildCompositeDatatypeDefinition(next, elementName, + // datatypeName, min, max, codeTableName); // orderToElementDef.put(order, def); // } else { // @SuppressWarnings("unchecked") @@ -291,16 +339,6 @@ class ModelScanner { elementNames.add(elementName); } - - for (int i = 0; i < orderToElementDef.size(); i++) { - if (!orderToElementDef.containsKey(i)) { - throw new ConfigurationException("Type '" + theClass.getCanonicalName() + "' does not have a child with order " + i - + " (in other words, there are gaps between specified child orders)"); - } - BaseRuntimeChildDefinition next = orderToElementDef.get(i); - theDefinition.addChild(next); - } - } private String scanCodeTable(Class theCodeType, CodeTableDef theCodeTableDefinition) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildNarrativeDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildNarrativeDefinition.java new file mode 100644 index 00000000000..abb0c77b168 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildNarrativeDefinition.java @@ -0,0 +1,13 @@ +package ca.uhn.fhir.context; + +import java.lang.reflect.Field; + +import ca.uhn.fhir.model.datatype.NarrativeDt; + +public class RuntimeChildNarrativeDefinition extends BaseRuntimeChildDatatypeDefinition { + + public RuntimeChildNarrativeDefinition(Field theField, String theElementName) { + super(theField, theElementName, 1, 1, NarrativeDt.class); + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java index bc93877dea4..19774d47baf 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java @@ -8,16 +8,6 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini super(theName, theClass); } - public IResource newInstance() { - try { - return getImplementingClass().newInstance(); - } catch (InstantiationException e) { - throw new ConfigurationException("Failed to instantiate type:"+getImplementingClass().getName(), e); - } catch (IllegalAccessException e) { - throw new ConfigurationException("Failed to instantiate type:"+getImplementingClass().getName(), e); - } - } - @Override public ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum getChildType() { return ChildTypeEnum.RESOURCE; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BasePrimitiveDatatype.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BasePrimitiveDatatype.java index 9a2f18f52ab..ccc3d84a297 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BasePrimitiveDatatype.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BasePrimitiveDatatype.java @@ -1,6 +1,8 @@ package ca.uhn.fhir.model.api; -public class BasePrimitiveDatatype extends BaseDatatype implements IPrimitiveDatatype { +public abstract class BasePrimitiveDatatype extends BaseDatatype implements IPrimitiveDatatype { + // nothing yet + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ICodeEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ICodeEnum.java index 2a7dbe6b569..7a33356f1ff 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ICodeEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ICodeEnum.java @@ -1,5 +1,5 @@ package ca.uhn.fhir.model.api; -public interface ICodeEnum extends IPrimitiveDatatype { - +public interface ICodeEnum { +// nothing yet } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IPrimitiveDatatype.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IPrimitiveDatatype.java index 5b3ceca722b..a2a4dc8db4c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IPrimitiveDatatype.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IPrimitiveDatatype.java @@ -1,5 +1,14 @@ package ca.uhn.fhir.model.api; -public interface IPrimitiveDatatype extends IDatatype { +import ca.uhn.fhir.parser.DataFormatException; +public interface IPrimitiveDatatype extends IDatatype { + + void setValueAsString(String theValue) throws DataFormatException; + + String getValueAsString(); + + T getValue(); + + void setValue(T theValue) throws DataFormatException; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/IsIdentifier.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/IsIdentifier.java new file mode 100644 index 00000000000..710c63a2321 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/IsIdentifier.java @@ -0,0 +1,14 @@ +package ca.uhn.fhir.model.api.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(value= {ElementType.FIELD}) +public @interface IsIdentifier { + + // nothing + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Narrative.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Narrative.java new file mode 100644 index 00000000000..cb9cf3a155f --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Narrative.java @@ -0,0 +1,14 @@ +package ca.uhn.fhir.model.api.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(value= {ElementType.FIELD}) +public @interface Narrative { + + String name(); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/Base64BinaryDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/Base64BinaryDt.java index 2f823650368..0ee44d41b46 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/Base64BinaryDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/Base64BinaryDt.java @@ -1,11 +1,41 @@ package ca.uhn.fhir.model.datatype; +import org.apache.commons.codec.binary.Base64; + import ca.uhn.fhir.model.api.BasePrimitiveDatatype; import ca.uhn.fhir.model.api.annotation.DatatypeDef; -@DatatypeDef(name="base64Binary") -public class Base64BinaryDt extends BasePrimitiveDatatype { +@DatatypeDef(name = "base64Binary") +public class Base64BinaryDt extends BasePrimitiveDatatype { private byte[] myValue; - + + @Override + public void setValueAsString(String theValue) { + if (theValue == null) { + myValue = null; + } else { + myValue = Base64.decodeBase64(theValue); + } + } + + @Override + public String getValueAsString() { + if (myValue == null) { + return null; + } else { + return Base64.encodeBase64String(myValue); + } + } + + @Override + public void setValue(byte[] theValue) { + myValue = theValue; + } + + @Override + public byte[] getValue() { + return myValue; + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/BaseDateTimeDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/BaseDateTimeDt.java new file mode 100644 index 00000000000..05b8a37e974 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/BaseDateTimeDt.java @@ -0,0 +1,162 @@ +package ca.uhn.fhir.model.datatype; + +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.apache.commons.lang3.time.FastDateFormat; + +import ca.uhn.fhir.model.api.BasePrimitiveDatatype; +import ca.uhn.fhir.parser.DataFormatException; + +public abstract class BaseDateTimeDt extends BasePrimitiveDatatype { + + private static final FastDateFormat ourYearFormat = FastDateFormat.getInstance("yyyy"); + private static final FastDateFormat ourYearMonthDayFormat = FastDateFormat.getInstance("yyyy-MM-dd"); + private static final FastDateFormat ourYearMonthFormat = FastDateFormat.getInstance("yyyy-MM"); + private static final FastDateFormat ourYearMonthDayTimeFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss"); + private static final FastDateFormat ourYearMonthDayTimeZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssX"); + + private int myPrecision = Calendar.SECOND; + private Date myValue; + private TimeZone myTimeZone; + private boolean myTimeZoneZulu = false; + + + /** + * Gets the precision for this datatype using field values from + * {@link Calendar}, such as {@link Calendar#MONTH}. Default is + * {@link Calendar#DAY_OF_MONTH} + * + * @see #setPrecision(int) + */ + public int getPrecision() { + return myPrecision; + } + + @Override + public Date getValue() { + return myValue; + } + + @Override + public String getValueAsString() { + if (myValue == null) { + return null; + } else { + switch (myPrecision) { + case Calendar.DAY_OF_MONTH: + return ourYearMonthDayFormat.format(myValue); + case Calendar.MONTH: + return ourYearMonthFormat.format(myValue); + case Calendar.YEAR: + return ourYearFormat.format(myValue); + case Calendar.SECOND: + if (myTimeZoneZulu) { + GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); + cal.setTime(myValue); + return ourYearMonthDayTimeZoneFormat.format(cal)+"Z"; + } else if (myTimeZone != null) { + GregorianCalendar cal = new GregorianCalendar(myTimeZone); + cal.setTime(myValue); + return ourYearMonthDayTimeZoneFormat.format(cal); + } else { + return ourYearMonthDayTimeFormat.format(myValue); + } + } + throw new IllegalStateException("Invalid precition (this is a HAPI bug, shouldn't happen): " + myPrecision); + } + } + + /** + * Sets the precision for this datatype using field values from + * {@link Calendar}. Valid values are: + *
    + *
  • {@link Calendar#SECOND} + *
  • {@link Calendar#DAY_OF_MONTH} + *
  • {@link Calendar#MONTH} + *
  • {@link Calendar#YEAR} + *
+ * + * @throws DataFormatException + */ + public void setPrecision(int thePrecision) throws DataFormatException { + switch (thePrecision) { + case Calendar.DAY_OF_MONTH: + case Calendar.MONTH: + case Calendar.YEAR: + case Calendar.SECOND: + myPrecision = thePrecision; + break; + default: + throw new DataFormatException("Invalid precision value"); + } + } + + @Override + public void setValue(Date theValue) throws DataFormatException { + myValue = theValue; + } + + @Override + public void setValueAsString(String theValue) throws DataFormatException { + try { + if (theValue == null) { + myValue = null; + clearTimeZone(); + } else if (theValue.length() == 4 && isPrecisionAllowed(Calendar.YEAR)) { + setValue(ourYearFormat.parse(theValue)); + setPrecision(Calendar.YEAR); + clearTimeZone(); + } else if (theValue.length() == 7 && isPrecisionAllowed(Calendar.MONTH)) { + setValue(ourYearMonthFormat.parse(theValue)); + setPrecision(Calendar.MONTH); + clearTimeZone(); + } else if (theValue.length() == 9 && isPrecisionAllowed(Calendar.DAY_OF_MONTH)) { + setValue(ourYearMonthDayFormat.parse(theValue)); + setPrecision(Calendar.DAY_OF_MONTH); + clearTimeZone(); + } else if ((theValue.length() == 18 || theValue.length() == 19 || theValue.length() == 24) && isPrecisionAllowed(Calendar.SECOND)) { + ourYearMonthDayTimeZoneFormat.parse(theValue); + setPrecision(Calendar.SECOND); + clearTimeZone(); + if (theValue.length() == 19 && theValue.charAt(theValue.length()-1) == 'Z') { + myTimeZoneZulu = true; + } + } else { + throw new DataFormatException("Invalid date string"); + } + } catch (ParseException e) { + throw new DataFormatException("Invalid date string"); + } + } + + public TimeZone getTimeZone() { + return myTimeZone; + } + + public void setTimeZone(TimeZone theTimeZone) { + myTimeZone = theTimeZone; + } + + public boolean isTimeZoneZulu() { + return myTimeZoneZulu; + } + + public void setTimeZoneZulu(boolean theTimeZoneZulu) { + myTimeZoneZulu = theTimeZoneZulu; + } + + private void clearTimeZone() { + myTimeZone=null; + myTimeZoneZulu=false; + } + + /** + * To be implemented by subclasses to indicate whether the given precision is allowed by this type + */ + abstract boolean isPrecisionAllowed(int thePrecision); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/BooleanDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/BooleanDt.java index 8f3d864fb64..5c23c887644 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/BooleanDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/BooleanDt.java @@ -2,10 +2,37 @@ package ca.uhn.fhir.model.datatype; import ca.uhn.fhir.model.api.BasePrimitiveDatatype; import ca.uhn.fhir.model.api.annotation.DatatypeDef; +import ca.uhn.fhir.parser.DataFormatException; @DatatypeDef(name="boolean") -public class BooleanDt extends BasePrimitiveDatatype { +public class BooleanDt extends BasePrimitiveDatatype { - private boolean myValue; + private Boolean myValue; + + @Override + public void setValueAsString(String theValue) throws DataFormatException { + if ("true".equals(theValue)) { + myValue = Boolean.TRUE; + }else if ("false".equals(theValue)) { + myValue=Boolean.FALSE; + }else { + throw new DataFormatException("Invalid boolean string: '" + theValue + "'"); + } + } + + @Override + public String getValueAsString() { + return null; + } + + @Override + public void setValue(Boolean theValue) { + myValue = theValue; + } + + @Override + public Boolean getValue() { + return myValue; + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/CodeDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/CodeDt.java index 04d2b5ef2e6..6ed4da709c3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/CodeDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/CodeDt.java @@ -3,8 +3,39 @@ package ca.uhn.fhir.model.datatype; import ca.uhn.fhir.model.api.BasePrimitiveDatatype; import ca.uhn.fhir.model.api.ICodeEnum; import ca.uhn.fhir.model.api.annotation.DatatypeDef; +import ca.uhn.fhir.parser.DataFormatException; -@DatatypeDef(name="code") -public class CodeDt extends BasePrimitiveDatatype implements ICodedDatatype { +@DatatypeDef(name = "code") +public class CodeDt extends BasePrimitiveDatatype implements ICodedDatatype { + + private String myValue; + + public String getValue() { + return myValue; + } + + public void setValue(String theValue) throws DataFormatException { + if (theValue == null) { + myValue = null; + } else { + if (theValue.length() == 0) { + throw new DataFormatException("Value can not be empty"); + } + if (Character.isWhitespace(theValue.charAt(0)) || Character.isWhitespace(theValue.charAt(theValue.length()-1))){ + throw new DataFormatException("Value must not contain trailing or leading whitespace"); + } + myValue = theValue; + } + } + + @Override + public void setValueAsString(String theValue) throws DataFormatException { + setValue(theValue); + } + + @Override + public String getValueAsString() { + return getValue(); + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/DateDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/DateDt.java index 5533502c036..185c41bffe4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/DateDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/DateDt.java @@ -1,15 +1,23 @@ package ca.uhn.fhir.model.datatype; -import java.util.GregorianCalendar; +import java.util.Calendar; -import ca.uhn.fhir.model.api.BaseDatatype; -import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.annotation.DatatypeDef; -@DatatypeDef(name="date") -public class DateDt extends BaseDatatype implements IPrimitiveDatatype { +@DatatypeDef(name = "date") +public class DateDt extends BaseDateTimeDt { + + @Override + boolean isPrecisionAllowed(int thePrecision) { + switch (thePrecision) { + case Calendar.YEAR: + case Calendar.MONTH: + case Calendar.DATE: + return true; + default: + return false; + } + } + - private GregorianCalendar myValue; - private int myPrecision; - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/DateTimeDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/DateTimeDt.java index ffde5292041..8a68551df75 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/DateTimeDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/DateTimeDt.java @@ -1,15 +1,23 @@ package ca.uhn.fhir.model.datatype; -import java.util.GregorianCalendar; +import java.util.Calendar; -import ca.uhn.fhir.model.api.BasePrimitiveDatatype; import ca.uhn.fhir.model.api.annotation.DatatypeDef; -@DatatypeDef(name="dateTime") -public class DateTimeDt extends BasePrimitiveDatatype { +@DatatypeDef(name = "dateTime") +public class DateTimeDt extends BaseDateTimeDt { + + @Override + boolean isPrecisionAllowed(int thePrecision) { + switch (thePrecision) { + case Calendar.YEAR: + case Calendar.MONTH: + case Calendar.DATE: + case Calendar.SECOND: + return true; + default: + return false; + } + } - private GregorianCalendar myValue; - - private int myPrecision; - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/DecimalDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/DecimalDt.java index e2641bd17cd..bb243557a74 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/DecimalDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/DecimalDt.java @@ -4,10 +4,46 @@ import java.math.BigDecimal; import ca.uhn.fhir.model.api.BasePrimitiveDatatype; import ca.uhn.fhir.model.api.annotation.DatatypeDef; +import ca.uhn.fhir.parser.DataFormatException; -@DatatypeDef(name="decimal") -public class DecimalDt extends BasePrimitiveDatatype { +@DatatypeDef(name = "decimal") +public class DecimalDt extends BasePrimitiveDatatype { private BigDecimal myValue; - + + @Override + public void setValueAsString(String theValue) throws DataFormatException { + if (theValue == null) { + myValue = null; + } else { + myValue = new BigDecimal(theValue); + } + } + + @Override + public String getValueAsString() { + if (myValue == null) { + return null; + } + return myValue.toPlainString(); + } + + @Override + public BigDecimal getValue() { + return myValue; + } + + @Override + public void setValue(BigDecimal theValue) throws DataFormatException { + myValue = theValue; + } + + public void setValueAsInteger(int theValue) { + myValue = new BigDecimal(theValue); + } + + public int getValueAsInteger() { + return myValue.intValue(); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/InstantDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/InstantDt.java index 21cdfdf9870..4d25a891b16 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/InstantDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/InstantDt.java @@ -1,13 +1,15 @@ package ca.uhn.fhir.model.datatype; -import java.util.Date; +import java.util.Calendar; -import ca.uhn.fhir.model.api.BasePrimitiveDatatype; import ca.uhn.fhir.model.api.annotation.DatatypeDef; @DatatypeDef(name="instant") -public class InstantDt extends BasePrimitiveDatatype { +public class InstantDt extends BaseDateTimeDt { - private Date myValue; + @Override + boolean isPrecisionAllowed(int thePrecision) { + return thePrecision == Calendar.SECOND; + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/IntegerDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/IntegerDt.java index 0282a05539a..0a64fa3534f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/IntegerDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/IntegerDt.java @@ -2,10 +2,36 @@ package ca.uhn.fhir.model.datatype; import ca.uhn.fhir.model.api.BasePrimitiveDatatype; import ca.uhn.fhir.model.api.annotation.DatatypeDef; +import ca.uhn.fhir.parser.DataFormatException; @DatatypeDef(name="integer") -public class IntegerDt extends BasePrimitiveDatatype { +public class IntegerDt extends BasePrimitiveDatatype { - private int myValue; + private Integer myValue; + + public Integer getValue() { + return myValue; + } + + public void setValue(Integer theValue) { + myValue = theValue; + } + + @Override + public void setValueAsString(String theValue) throws DataFormatException { + if (theValue == null) { + myValue = null; + }else { + myValue = Integer.parseInt(theValue); + } + } + + @Override + public String getValueAsString() { + if (myValue==null) { + return null; + } + return Integer.toString(myValue); + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/NarrativeDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/NarrativeDt.java new file mode 100644 index 00000000000..d7ccb38955a --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/NarrativeDt.java @@ -0,0 +1,37 @@ +package ca.uhn.fhir.model.datatype; + +import ca.uhn.fhir.model.api.BaseCompositeDatatype; +import ca.uhn.fhir.model.api.CodeableConceptElement; +import ca.uhn.fhir.model.api.annotation.Child; +import ca.uhn.fhir.model.api.annotation.DatatypeDef; +import ca.uhn.fhir.model.enm.NarrativeStatusEnum; + +@DatatypeDef(name="Narrative") +public class NarrativeDt extends BaseCompositeDatatype { + + @Child(name="status", order=0, min=1) + @CodeableConceptElement(type=NarrativeStatusEnum.class) + private CodeDt myStatus; + + @Child(name="div", order=1) + private StringDt myDiv; + + public StringDt getDiv() { + return myDiv; + } + + public CodeDt getStatus() { + return myStatus; + } + + + public void setDiv(StringDt theDiv) { + myDiv = theDiv; + } + + public void setStatus(CodeDt theStatus) { + myStatus = theStatus; + } + + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/StringDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/StringDt.java index 3b8c7fc6570..3085c0d6976 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/StringDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/StringDt.java @@ -2,10 +2,31 @@ package ca.uhn.fhir.model.datatype; import ca.uhn.fhir.model.api.BasePrimitiveDatatype; import ca.uhn.fhir.model.api.annotation.DatatypeDef; +import ca.uhn.fhir.parser.DataFormatException; -@DatatypeDef(name="string") -public class StringDt extends BasePrimitiveDatatype { +@DatatypeDef(name = "string") +public class StringDt extends BasePrimitiveDatatype { private String myValue; - + + @Override + public String getValue() { + return myValue; + } + + @Override + public String getValueAsString() { + return myValue; + } + + @Override + public void setValue(String theValue) throws DataFormatException { + myValue = theValue; + } + + @Override + public void setValueAsString(String theValue) throws DataFormatException { + myValue = theValue; + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/UriDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/UriDt.java index 16ffd3a4277..2996ccb2a3a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/UriDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/datatype/UriDt.java @@ -1,12 +1,46 @@ package ca.uhn.fhir.model.datatype; +import java.net.URI; +import java.net.URISyntaxException; + import ca.uhn.fhir.model.api.BaseDatatype; import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.annotation.DatatypeDef; +import ca.uhn.fhir.parser.DataFormatException; -@DatatypeDef(name="uri") -public class UriDt extends BaseDatatype implements IPrimitiveDatatype { +@DatatypeDef(name = "uri") +public class UriDt extends BaseDatatype implements IPrimitiveDatatype { + + private URI myValue; + + public URI getValue() { + return myValue; + } + + public void setValue(URI theValue) { + myValue = theValue; + } + + @Override + public void setValueAsString(String theValue) throws DataFormatException { + if (theValue==null) { + myValue=null; + }else { + try { + myValue = new URI(theValue); + } catch (URISyntaxException e) { + throw new DataFormatException("Unable to parse URI value", e); + } + } + } + + @Override + public String getValueAsString() { + if (myValue == null) { + return null; + } else { + return myValue.toString(); + } + } - private String myValue; - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/enm/NarrativeStatusEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/enm/NarrativeStatusEnum.java new file mode 100644 index 00000000000..603331f69b0 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/enm/NarrativeStatusEnum.java @@ -0,0 +1,26 @@ +package ca.uhn.fhir.model.enm; + +import ca.uhn.fhir.model.api.ICodeEnum; +import ca.uhn.fhir.model.api.annotation.Description; +import ca.uhn.fhir.model.api.annotation.EnumeratedCodeValue; +import ca.uhn.fhir.model.api.annotation.CodeTableDef; + +@CodeTableDef(tableId = 28, name = "narrative-status") +public enum NarrativeStatusEnum implements ICodeEnum { + + @EnumeratedCodeValue(value = "generated") + @Description("The contents of the narrative are entirely generated from the structured data in the resource.") + GENERATED, + + @EnumeratedCodeValue(value = "extensions") + @Description("The contents of the narrative are entirely generated from the structured data in the resource and some of the content is generated from extensions.") + EXTENSIONS, + + @EnumeratedCodeValue(value = "additional") + @Description("The contents of the narrative contain additional information not found in the structured data.") + ADDITIONAL, + + @EnumeratedCodeValue(value = "empty") + @Description("the contents of the narrative are some equivalent of \"No human-readable text provided for this resource\".") + EMPTY +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/BaseResource.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/BaseResource.java index 0697b00597c..2af2a6a8702 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/BaseResource.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/BaseResource.java @@ -2,9 +2,22 @@ package ca.uhn.fhir.model.resource; import ca.uhn.fhir.model.api.BaseElement; import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.annotation.Narrative; +import ca.uhn.fhir.model.datatype.NarrativeDt; public abstract class BaseResource extends BaseElement implements IResource { + @Narrative(name="text") + private NarrativeDt myText; + + public NarrativeDt getText() { + return myText; + } + + public void setText(NarrativeDt theText) { + myText = theText; + } + // public abstract void setAllChildValues(List theChildren); // // public abstract List getAllChildValues(); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/BaseResourceWithIdentifier.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/BaseResourceWithIdentifier.java index 059a74b788c..785088d6bd9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/BaseResourceWithIdentifier.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/BaseResourceWithIdentifier.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.model.resource; import ca.uhn.fhir.model.api.annotation.Child; +import ca.uhn.fhir.model.api.annotation.IsIdentifier; import ca.uhn.fhir.model.datatype.IdentifierDt; /** @@ -9,6 +10,15 @@ import ca.uhn.fhir.model.datatype.IdentifierDt; public abstract class BaseResourceWithIdentifier extends BaseResource { @Child(name="identifier", order=Child.ORDER_UNKNOWN) + @IsIdentifier private IdentifierDt myIdentifier; + + public IdentifierDt getIdentifier() { + return myIdentifier; + } + + public void setIdentifier(IdentifierDt theIdentifier) { + myIdentifier = theIdentifier; + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/Observation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/Observation.java index c29a6a3ea09..7aa4e152aa3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/Observation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/resource/Observation.java @@ -6,11 +6,13 @@ import ca.uhn.fhir.model.api.ResourceReference; import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.ChildResource; import ca.uhn.fhir.model.api.annotation.Choice; +import ca.uhn.fhir.model.api.annotation.Narrative; import ca.uhn.fhir.model.api.annotation.ResourceDef; import ca.uhn.fhir.model.datatype.AttachmentDt; import ca.uhn.fhir.model.datatype.CodeableConceptDt; import ca.uhn.fhir.model.datatype.DateTimeDt; import ca.uhn.fhir.model.datatype.InstantDt; +import ca.uhn.fhir.model.datatype.NarrativeDt; import ca.uhn.fhir.model.datatype.PeriodDt; import ca.uhn.fhir.model.datatype.QuantityDt; import ca.uhn.fhir.model.datatype.RatioDt; @@ -77,6 +79,96 @@ public class Observation extends BaseResourceWithIdentifier { Patient.class, Group.class // TODO: add device, location }) private ResourceReference mySubject; + + public CodeableConceptDt getName() { + return myName; + } + + public void setName(CodeableConceptDt theName) { + myName = theName; + } + + public IDatatype getValue() { + return myValue; + } + + public void setValue(IDatatype theValue) { + myValue = theValue; + } + + public CodeableConceptDt getInterpretation() { + return myInterpretation; + } + + public void setInterpretation(CodeableConceptDt theInterpretation) { + myInterpretation = theInterpretation; + } + + public StringDt getComments() { + return myComments; + } + + public void setComments(StringDt theComments) { + myComments = theComments; + } + + public IDatatype getApplies() { + return myApplies; + } + + public void setApplies(IDatatype theApplies) { + myApplies = theApplies; + } + + public InstantDt getIssued() { + return myIssued; + } + + public void setIssued(InstantDt theIssued) { + myIssued = theIssued; + } + + public CodeableConceptDt getStatus() { + return myStatus; + } + + public void setStatus(CodeableConceptDt theStatus) { + myStatus = theStatus; + } + + public CodeableConceptDt getReliability() { + return myReliability; + } + + public void setReliability(CodeableConceptDt theReliability) { + myReliability = theReliability; + } + + public CodeableConceptDt getBodySite() { + return myBodySite; + } + + public void setBodySite(CodeableConceptDt theBodySite) { + myBodySite = theBodySite; + } + + public CodeableConceptDt getMethod() { + return myMethod; + } + + public void setMethod(CodeableConceptDt theMethod) { + myMethod = theMethod; + } + + public ResourceReference getSubject() { + return mySubject; + } + + public void setSubject(ResourceReference theSubject) { + mySubject = theSubject; + } + + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java index 27651f4388c..7d0aaaaa5d3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java @@ -1,18 +1,44 @@ package ca.uhn.fhir.parser; import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.model.api.BaseCompositeDatatype; +import ca.uhn.fhir.model.api.ICompositeDatatype; +import ca.uhn.fhir.model.api.ICompositeElement; +import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IResource; class ParserState { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ParserState.class); private FhirContext myContext; + private BaseState myState; + private Object myObject; public ParserState(FhirContext theContext) { - myContext=theContext; + myContext = theContext; + } + + public void attributeValue(String theValue) throws DataFormatException { + myState.attributeValue(theValue); + } + + public void enteringNewElement(String theLocalPart) throws DataFormatException { + myState.enteringNewElement(theLocalPart); + } + + private void push(BaseState theState) { + theState.setStack(myState); + myState = theState; + } + + private void setState(BaseState theState) { + myState = theState; } public static ParserState getResourceInstance(FhirContext theContext, String theLocalPart) throws DataFormatException { @@ -20,58 +46,126 @@ class ParserState { if (!(definition instanceof RuntimeResourceDefinition)) { throw new DataFormatException("Element '" + theLocalPart + "' is not a resource, expected a resource at this position"); } - + RuntimeResourceDefinition def = (RuntimeResourceDefinition) definition; IResource instance = def.newInstance(); - + ParserState retVal = new ParserState(theContext); - retVal.setState(retVal.new ResourceParserState(def, instance)); - + retVal.setState(retVal.new ContainerState(def, instance)); + return retVal; } +private abstract class BaseState { + private BaseState myStack; - private void setState(BaseState theState) { - myState = theState; + public abstract void attributeValue(String theValue) throws DataFormatException; + + public abstract void enteringNewElement(String theLocalPart) throws DataFormatException; + + public void setStack(BaseState theState) { + myStack = theState; } - private abstract class BaseState - { - private BaseState myStack; - - public abstract void enteringNewElement(String theLocalPart) throws DataFormatException; - - } - - private class ResourceParserState extends BaseState - { + public abstract void endingElement(String theLocalPart); - private RuntimeResourceDefinition myResourceDefinition; - private IResource myInstance; +} + private class ContainerState extends BaseState { - public ResourceParserState(RuntimeResourceDefinition theDef, IResource theInstance) { - myResourceDefinition = theDef; + private BaseRuntimeElementCompositeDefinition myDefinition; + private ICompositeElement myInstance; + + public ContainerState(BaseRuntimeElementCompositeDefinition theDef, ICompositeElement theInstance) { + myDefinition = theDef; myInstance = theInstance; } @Override - public void enteringNewElement(String theChildName) throws DataFormatException { - BaseRuntimeElementDefinition child = myResourceDefinition.getChildByNameOrThrowDataFormatException(theChildName); - switch (child.getChildType()) { - case COMPOSITE_DATATYPE: - break; - case PRIMITIVE_DATATYPE: - break; - case RESOURCE: - default: - throw new DataFormatException("Illegal resource position"); - } - + public void attributeValue(String theValue) { + ourLog.debug("Ignoring attribute value: {}", theValue); } - + + @Override + public void enteringNewElement(String theChildName) throws DataFormatException { + BaseRuntimeChildDefinition child = myDefinition.getChildByNameOrThrowDataFormatException(theChildName); + BaseRuntimeElementDefinition target = child.getChildByName(theChildName); + + switch (target.getChildType()) { + case COMPOSITE_DATATYPE: { + BaseRuntimeElementCompositeDefinition compositeTarget = (BaseRuntimeElementCompositeDefinition) target; + ICompositeDatatype newChildInstance = (ICompositeDatatype) compositeTarget.newInstance(); + child.getMutator().addValue(myInstance, newChildInstance); + ContainerState newState = new ContainerState(compositeTarget, newChildInstance); + push(newState); + break; + } + case PRIMITIVE_DATATYPE: { + RuntimePrimitiveDatatypeDefinition primitiveTarget = (RuntimePrimitiveDatatypeDefinition) target; + IPrimitiveDatatype newChildInstance = primitiveTarget.newInstance(); + child.getMutator().addValue(myInstance, newChildInstance); + PrimitiveState newState = new PrimitiveState(primitiveTarget, newChildInstance); + push(newState); + break; + } + case RESOURCE: { + break; + } + default: + throw new DataFormatException("Illegal resource position"); + } + + } + + @Override + public void endingElement(String theLocalPart) { + pop(); + if (myState == null) { + myObject = myInstance; + } + } + } - public void enteringNewElement(String theLocalPart) throws DataFormatException { - myState.enteringNewElement(theLocalPart); + private class PrimitiveState extends BaseState { + private RuntimePrimitiveDatatypeDefinition myDefinition; + private IPrimitiveDatatype myInstance; + + public PrimitiveState(RuntimePrimitiveDatatypeDefinition theDefinition, IPrimitiveDatatype theInstance) { + super(); + myDefinition = theDefinition; + myInstance = theInstance; + } + + @Override + public void attributeValue(String theValue) throws DataFormatException { + myInstance.setValueAsString(theValue); + } + + @Override + public void enteringNewElement(String theLocalPart) throws DataFormatException { + throw new Error("?? can this happen?"); // TODO: can this happen? + } + + @Override + public void endingElement(String theLocalPart) { + pop(); + } + } - + + public Object getObject() { + return myObject; + } + + private void pop() { + myState = myState.myStack; + } + + public boolean isComplete() { + return myObject != null; + } + + public void endingElement(String theLocalPart) { + myState.endingElement(theLocalPart); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java index bbc545a0a78..c30ccf32c6f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java @@ -7,6 +7,8 @@ import javax.xml.stream.XMLEventFactory; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; @@ -52,6 +54,30 @@ public class XmlParser { } else { parserState.enteringNewElement(elem.getName().getLocalPart()); } + } else if (nextEvent.isAttribute()) { + Attribute elem = (Attribute) nextEvent; + if (!FHIR_NS.equals(elem.getName().getNamespaceURI())) { + continue; + } + if (!"value".equals(elem.getName().getLocalPart())) { + continue; + } + if (parserState == null) { + throw new DataFormatException("Detected attribute before element"); + } + parserState.attributeValue(elem.getValue()); + } else if (nextEvent.isEndElement()) { + EndElement elem = nextEvent.asEndElement(); + if (!FHIR_NS.equals(elem.getName().getNamespaceURI())) { + continue; + } + if (parserState == null) { + throw new DataFormatException("Detected unexpected end-element"); + } + parserState.endingElement(elem.getName().getLocalPart()); + if (parserState.isComplete()) { + return (IResource) parserState.getObject(); + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BeanUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BeanUtils.java new file mode 100644 index 00000000000..4b99e0d46c1 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BeanUtils.java @@ -0,0 +1,48 @@ +package ca.uhn.fhir.util; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; + +public class BeanUtils { + + public static Method findAccessor(Class theClassToIntrospect, Class theTargetReturnType, String thePropertyName) throws NoSuchFieldException { + BeanInfo info; + try { + info = Introspector.getBeanInfo(theClassToIntrospect); + } catch (IntrospectionException e) { + throw new NoSuchFieldException(e.getMessage()); + } + for (PropertyDescriptor pd : info.getPropertyDescriptors()) { + if (thePropertyName.equals(pd.getName())) { + if (theTargetReturnType.isAssignableFrom(pd.getPropertyType())) { + return pd.getReadMethod(); + }else { + throw new NoSuchFieldException(theClassToIntrospect + " has an accessor for field " + thePropertyName + " but it does not return type " + theTargetReturnType); + } + } + } + throw new NoSuchFieldException(theClassToIntrospect + " has no accessor for field " + thePropertyName); + } + + public static Method findMutator(Class theClassToIntrospect, Class theTargetReturnType, String thePropertyName) throws NoSuchFieldException { + BeanInfo info; + try { + info = Introspector.getBeanInfo(theClassToIntrospect); + } catch (IntrospectionException e) { + throw new NoSuchFieldException(e.getMessage()); + } + for (PropertyDescriptor pd : info.getPropertyDescriptors()) { + if (thePropertyName.equals(pd.getName())) { + if (theTargetReturnType.isAssignableFrom(pd.getPropertyType())) { + return pd.getWriteMethod(); + }else { + throw new NoSuchFieldException(theClassToIntrospect + " has an mutator for field " + thePropertyName + " but it does not return type " + theTargetReturnType); + } + } + } + throw new NoSuchFieldException(theClassToIntrospect + " has no mutator for field " + thePropertyName); + } +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/datatype/Base64BinaryDtTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/datatype/Base64BinaryDtTest.java new file mode 100644 index 00000000000..d2ede78d052 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/datatype/Base64BinaryDtTest.java @@ -0,0 +1,13 @@ +package ca.uhn.fhir.model.datatype; + +import org.junit.Test; + + +public class Base64BinaryDtTest { + + @Test + public void testDecodeNull() { + new Base64BinaryDt().setValueAsString(null); + } + +}