private static Constructor fullConstructor(Class klass) { Constructor[] array = klass.getConstructors(); for (Constructor constructor : array) { if (constructor.isAnnotationPresent(FullConstructor.class)) { return constructor; } } return null; }
// load all reference types that are possible to instantiate // using FullConstructor annotation private Map<String, Class> loadTypeMap() throws ClassNotFoundException { Class[] classes = { // common classes PartySelf.class, Archetyped.class, Attestation.class, AuditDetails.class, Participation.class, PartyIdentified.class, PartyRelated.class, PartySelf.class, OriginalVersion.class, Contribution.class, // support classes TerminologyID.class, ArchetypeID.class, HierObjectID.class, AccessGroupRef.class, GenericID.class, InternetID.class, ISO_OID.class, LocatableRef.class, ObjectVersionID.class, ObjectRef.class, PartyRef.class, TemplateID.class, TerminologyID.class, org.openehr.rm.support.identification.UUID.class, // VersionTreeID.class, // datatypes classes DvBoolean.class, DvState.class, DvIdentifier.class, DvText.class, DvCodedText.class, DvParagraph.class, CodePhrase.class, DvCount.class, DvOrdinal.class, DvQuantity.class, DvInterval.class, DvProportion.class, DvDate.class, DvDateTime.class, DvTime.class, DvDuration.class, DvParsable.class, DvURI.class, DvEHRURI.class, DvMultimedia.class, // datastructure classes Element.class, Cluster.class, ItemSingle.class, ItemList.class, ItemTable.class, ItemTree.class, // ItemStructure.class, History.class, IntervalEvent.class, PointEvent.class, // ehr classes Action.class, Activity.class, Evaluation.class, ISMTransition.class, Instruction.class, InstructionDetails.class, Observation.class, AdminEntry.class, Section.class, Composition.class, EventContext.class, ISMTransition.class, // demographic classes Address.class, PartyIdentity.class, Agent.class, Group.class, Organisation.class, Person.class, Contact.class, PartyRelationship.class, Role.class, Capability.class }; typeMap = new HashMap<String, Class>(); upperCaseMap = new HashMap<String, Class>(); for (Class klass : classes) { String name = klass.getSimpleName(); typeMap.put(name, klass); upperCaseMap.put(name.toUpperCase(), klass); } return typeMap; }
/** * Finds the matching RM class that can be used to create RM object for given value map * * @param valueMap * @return null if no match RM class is found */ public String findMatchingRMClass(Map<String, Object> valueMap) { List simpleTypes = Arrays.asList(SKIPPED_TYPES_IN_MATCHING); for (Class rmClass : typeMap.values()) { log.debug("matching rmClass: " + rmClass.getName()); if (simpleTypes.contains(rmClass.getSimpleName())) { continue; // skip simple value types } // replace underscore separated names with camel case Map<String, Object> filteredMap = new HashMap<String, Object>(); for (String name : valueMap.keySet()) { filteredMap.put(toCamelCase(name), valueMap.get(name)); } Constructor constructor = fullConstructor(rmClass); if (constructor == null) { throw new RuntimeException("annotated constructor missing for " + rmClass); } Annotation[][] annotations = constructor.getParameterAnnotations(); if (annotations == null || annotations.length == 0) { throw new RuntimeException("attribute annotations missing for " + rmClass); } Class[] types = constructor.getParameterTypes(); boolean matched = true; Set<String> attributes = new HashSet<String>(); for (int i = 0; i < types.length; i++) { if (annotations[i].length == 0) { throw new RuntimeException("attribute annotation missing for" + rmClass); } Attribute attribute = (Attribute) annotations[i][0]; attributes.add(attribute.name()); log.debug("checking attribute: " + attribute.name()); String attrName = attribute.name(); Object attrValue = filteredMap.get(attrName); if (attribute.required() && attrValue == null) { log.debug("missing required attribute.."); matched = false; break; } else if (attrValue != null) { if (((attrValue instanceof Boolean) && types[i] != boolean.class) || ((attrValue instanceof Integer) && types[i] != Integer.class) || ((attrValue instanceof Double) && types[i] != double.class)) { log.debug("wrong primitive value type for attribute.."); matched = false; break; } else if (!types[i].isPrimitive() && !types[i].isInstance(attrValue)) { log.debug("wrong value type for attribute.."); matched = false; break; } } } for (String attr : filteredMap.keySet()) { if (!attributes.contains(attr)) { log.debug("unknown attribute: " + attr); matched = false; } } // matching found if (matched) { String className = rmClass.getSimpleName(); log.debug(">>> MATCHING FOUND: " + className); return className; } } return null; }
/** * Construct an instance of RM class of given name and values. * * <p>If the input is a string, and the required attribute is some other types (integer, double * etc), it will be converted into right type. if there is any error during conversion, * AttributeFormatException will be thrown. * * @param rmClassName * @param valueMap * @return created instance * @throws RMObjectBuildingException */ public RMObject construct(String rmClassName, Map<String, Object> valueMap) throws RMObjectBuildingException { Class rmClass = retrieveRMType(rmClassName); // replace underscore separated names with camel case Map<String, Object> filteredMap = new HashMap<String, Object>(); for (String name : valueMap.keySet()) { filteredMap.put(toCamelCase(name), valueMap.get(name)); } Constructor constructor = fullConstructor(rmClass); Map<String, Class> typeMap = attributeType(rmClass); Map<String, Integer> indexMap = attributeIndex(rmClass); Map<String, Attribute> attributeMap = attributeMap(rmClass); Object[] valueArray = new Object[indexMap.size()]; for (String name : typeMap.keySet()) { Object value = filteredMap.get(name); if (!typeMap.containsKey(name) || !attributeMap.containsKey(name)) { throw new RMObjectBuildingException("unknown attribute " + name); } Class type = typeMap.get(name); Integer index = indexMap.get(name); Attribute attribute = attributeMap.get(name); if (index == null || type == null) { throw new RMObjectBuildingException("unknown attribute \"" + name + "\""); } // system supplied value if (attribute.system()) { SystemValue sysvalue = SystemValue.fromId(name); if (sysvalue == null) { throw new RMObjectBuildingException("unknonw system value" + "\"" + name + "\""); } value = systemValues.get(sysvalue); if (value == null) { throw new AttributeMissingException( "missing value for " + "system attribute \"" + name + "\" in class: " + rmClass + ", with valueMap: " + valueMap); } } // check required attributes if (value == null && attribute.required()) { log.info(attribute); throw new AttributeMissingException( "missing value for " + "required attribute \"" + name + "\" of type " + type + " while constructing " + rmClass + " with valueMap: " + valueMap); } // enum else if (type.isEnum() && !value.getClass().isEnum()) { // OG added if (type.equals(ProportionKind.class)) value = ProportionKind.fromValue(Integer.parseInt(value.toString())); else value = Enum.valueOf(type, value.toString()); } // in case of null, create a default value else if (value == null) { value = defaultValue(type); } // in case of string value, convert to right type if necessary else if (value instanceof String) { String str = (String) value; try { // for DvCount if (type.equals(int.class)) { value = Integer.parseInt(str); // for DvQuantity } else if (type.equals(double.class)) { value = Double.parseDouble(str); // for DvProportion.precision } else if (type.equals(Integer.class)) { value = new Integer(str); } } catch (NumberFormatException e) { throw new AttributeFormatException( "wrong format of " + "attribute " + name + ", expect " + type); } // deal with mismatch between array and list } else if (type.isAssignableFrom(List.class) && value.getClass().isArray()) { Object[] array = (Object[]) value; List list = new ArrayList(); for (Object o : array) { list.add(o); } value = list; // deal with mismatch between array and set } else if (type.isAssignableFrom(Set.class) && value.getClass().isArray()) { Object[] array = (Object[]) value; Set set = new HashSet(); for (Object o : array) { set.add(o); } value = set; } // check type else if (value != null && !type.isPrimitive()) { try { type.cast(value); } catch (ClassCastException e) { throw new RMObjectBuildingException( "Failed to construct: " + rmClassName + ", value for attribute '" + name + "' has wrong type, expected \"" + type + "\", but got \"" + value.getClass() + "\""); } } valueArray[index] = value; } Object ret = null; try { // OG added hack if (rmClassName.equalsIgnoreCase("DVCOUNT")) { log.debug("Fixing DVCOUNT..."); for (int i = 0; i < valueArray.length; i++) { Object value = valueArray[i]; if (value != null && value.getClass().equals(Float.class)) valueArray[i] = Double.parseDouble(value.toString()); else if (value != null && value.getClass().equals(Long.class)) valueArray[i] = Integer.parseInt(value.toString()); } } ret = constructor.newInstance(valueArray); } catch (Exception e) { if (log.isDebugEnabled()) { e.printStackTrace(); } log.debug("failed in constructor.newInstance()", e); if (stringParsingTypes.contains(rmClassName)) { throw new AttributeFormatException("wrong format for type " + rmClassName); } throw new RMObjectBuildingException( "failed to create new instance of " + rmClassName + " with valueMap: " + toString(valueMap) + ", cause: " + e.getMessage()); } return (RMObject) ret; }