All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openehr.rm.binding.RMInspector Maven / Gradle / Ivy

package org.openehr.rm.binding;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.*;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.openehr.build.RMObjectBuildingException;
import org.openehr.rm.Attribute;
import org.openehr.rm.FullConstructor;
import org.openehr.rm.common.archetyped.Archetyped;
import org.openehr.rm.common.changecontrol.Contribution;
import org.openehr.rm.common.changecontrol.OriginalVersion;
import org.openehr.rm.common.generic.Attestation;
import org.openehr.rm.common.generic.AuditDetails;
import org.openehr.rm.common.generic.Participation;
import org.openehr.rm.common.generic.PartyIdentified;
import org.openehr.rm.common.generic.PartyRelated;
import org.openehr.rm.common.generic.PartySelf;
import org.openehr.rm.composition.Composition;
import org.openehr.rm.composition.EventContext;
import org.openehr.rm.composition.content.entry.Action;
import org.openehr.rm.composition.content.entry.Activity;
import org.openehr.rm.composition.content.entry.AdminEntry;
import org.openehr.rm.composition.content.entry.Evaluation;
import org.openehr.rm.composition.content.entry.ISMTransition;
import org.openehr.rm.composition.content.entry.Instruction;
import org.openehr.rm.composition.content.entry.InstructionDetails;
import org.openehr.rm.composition.content.entry.Observation;
import org.openehr.rm.composition.content.navigation.Section;
import org.openehr.rm.datastructure.history.Event;
import org.openehr.rm.datastructure.history.History;
import org.openehr.rm.datastructure.history.IntervalEvent;
import org.openehr.rm.datastructure.history.PointEvent;
import org.openehr.rm.datastructure.itemstructure.ItemList;
import org.openehr.rm.datastructure.itemstructure.ItemSingle;
import org.openehr.rm.datastructure.itemstructure.ItemTable;
import org.openehr.rm.datastructure.itemstructure.ItemTree;
import org.openehr.rm.datastructure.itemstructure.representation.Cluster;
import org.openehr.rm.datastructure.itemstructure.representation.Element;
import org.openehr.rm.datatypes.basic.DvBoolean;
import org.openehr.rm.datatypes.basic.DvIdentifier;
import org.openehr.rm.datatypes.basic.DvState;
import org.openehr.rm.datatypes.encapsulated.DvMultimedia;
import org.openehr.rm.datatypes.encapsulated.DvParsable;
import org.openehr.rm.datatypes.quantity.DvCount;
import org.openehr.rm.datatypes.quantity.DvInterval;
import org.openehr.rm.datatypes.quantity.DvOrdinal;
import org.openehr.rm.datatypes.quantity.DvProportion;
import org.openehr.rm.datatypes.quantity.DvQuantity;
import org.openehr.rm.datatypes.quantity.ProportionKind;
import org.openehr.rm.datatypes.quantity.datetime.DvDate;
import org.openehr.rm.datatypes.quantity.datetime.DvDateTime;
import org.openehr.rm.datatypes.quantity.datetime.DvDuration;
import org.openehr.rm.datatypes.quantity.datetime.DvTime;
import org.openehr.rm.datatypes.text.CodePhrase;
import org.openehr.rm.datatypes.text.DvCodedText;
import org.openehr.rm.datatypes.text.DvParagraph;
import org.openehr.rm.datatypes.text.DvText;
import org.openehr.rm.datatypes.uri.DvURI;
import org.openehr.rm.demographic.Address;
import org.openehr.rm.demographic.Agent;
import org.openehr.rm.demographic.Capability;
import org.openehr.rm.demographic.Contact;
import org.openehr.rm.demographic.Group;
import org.openehr.rm.demographic.Organisation;
import org.openehr.rm.demographic.PartyIdentity;
import org.openehr.rm.demographic.PartyRelationship;
import org.openehr.rm.demographic.Person;
import org.openehr.rm.demographic.Role;
import org.openehr.rm.support.identification.AccessGroupRef;
import org.openehr.rm.support.identification.ArchetypeID;
import org.openehr.rm.support.identification.GenericID;
import org.openehr.rm.support.identification.HierObjectID;
import org.openehr.rm.support.identification.ISO_OID;
import org.openehr.rm.support.identification.InternetID;
import org.openehr.rm.support.identification.LocatableRef;
import org.openehr.rm.support.identification.ObjectRef;
import org.openehr.rm.support.identification.ObjectVersionID;
import org.openehr.rm.support.identification.PartyRef;
import org.openehr.rm.support.identification.TemplateID;
import org.openehr.rm.support.identification.TerminologyID;
import org.openehr.rm.support.identification.UUID;
import org.openehr.rm.support.identification.VersionTreeID;

public class RMInspector {
	
	public static RMInspector getInstance() {
		return soleInstance;
	}
	
	/**
	 * Create a RMInspector
	 */
	private RMInspector() {
		try {
			loadTypeMap();
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("failed to load class, " + e
					+ " when starting RMInspector..");
		}
	}

	// load all reference types that are possible to instantiate
	// using FullConstructor annotation
	private Map loadTypeMap() throws ClassNotFoundException {
		Class[] classes = {
				
				// implied types
				Integer.class,
				String.class,
				Boolean.class,
				Double.class,

				// 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,
				UUID.class,
				VersionTreeID.class,

				// datatypes classes
				DvBoolean.class,
				DvURI.class,
				DvState.class,
				DvIdentifier.class,
				DvText.class,
				DvCodedText.class,
				DvParagraph.class,
				CodePhrase.class,
				DvCount.class,
				DvOrdinal.class,
				DvQuantity.class,
				DvInterval.class,
				DvProportion.class,
				ProportionKind.class,
				DvDate.class,
				DvDateTime.class,
				DvTime.class,
				DvDuration.class,
				DvParsable.class, 
				DvMultimedia.class,

				// datastructure classes
				Element.class, Cluster.class, ItemSingle.class, ItemList.class,
				ItemTable.class,
				ItemTree.class,
				History.class,
				Event.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 LinkedHashMap();
		upperCaseMap = new LinkedHashMap();
		for (Class klass : classes) {
			String name = klass.getSimpleName();
			typeMap.put(name, klass);
			upperCaseMap.put(name.toUpperCase(), klass);
		}
		return typeMap;
	}

	/*
	 * Return a map with name as the key and class as the value for
	 * required parameters of the full constructor in the RMObject
	 * 
	 * @param rmClass 
	 * @return empty map if not found rm class
	 */
	private Map attributeType(Class rmClass) {

		Map map = new HashMap();
		Constructor constructor = fullConstructor(rmClass);
		if (constructor == null) {
			return map;
		}
		Annotation[][] annotations = constructor.getParameterAnnotations();
		Class[] types = constructor.getParameterTypes();

		if (annotations.length != types.length) {
			throw new IllegalArgumentException("less annotations");
		}
		for (int i = 0; i < types.length; i++) {
			if (annotations[i].length == 0) {
				throw new IllegalArgumentException(
						"missing annotations of attribute " + i);
			}
			Attribute attribute = (Attribute) annotations[i][0];
			map.put(attribute.name(), types[i]);
		}
		return map;
	}

	/**
	 * Return a map with name as the key and index of position as the value for
	 * all parameters of the full constructor in the RMObject
	 * 
	 * @param rmClass
	 * @return
	 */
	private Map attributeIndex(Class rmClass) {
		Map map = new HashMap();
		Constructor constructor = fullConstructor(rmClass);
		Annotation[][] annotations = constructor.getParameterAnnotations();

		for (int i = 0; i < annotations.length; i++) {
			if (annotations[i].length == 0) {
				throw new IllegalArgumentException(
						"missing annotation at position " + i);
			}
			Attribute attribute = (Attribute) annotations[i][0];
			map.put(attribute.name(), i);
		}
		return map;
	}

	/**
	 * Return a map with name as the key and attribute as the value for
	 * all parameters of the full constructor in the RMObject
	 * 
	 * @param rmClass
	 * @return
	 */
	public Map attributeMap(Class rmClass) {
		Map map = new HashMap();
		Constructor constructor = fullConstructor(rmClass);
		Annotation[][] annotations = constructor.getParameterAnnotations();

		for (int i = 0; i < annotations.length; i++) {
			if (annotations[i].length == 0) {
				throw new IllegalArgumentException(
						"missing annotation at position " + i);
			}
			Attribute attribute = (Attribute) annotations[i][0];
			map.put(attribute.name(), attribute);
		}
		return map;
	}

	private static Constructor fullConstructor(Class klass) {
		if(klass == null) {
			return null;
		}
		Constructor[] array = klass.getConstructors();
		for (Constructor constructor : array) {
			if (constructor.isAnnotationPresent(FullConstructor.class)) {
				return constructor;
			}
		}
		return null;
	}	

	/**
	 * Retrieves RM type using given name try both the CamelCase and
	 * Underscore-separated ways
	 * 
	 * @param rmClassName
	 * @return null if not found
	 */
	public Class retrieveRMType(String rmClassName) {
		Class rmClass = typeMap.get(rmClassName);
		if (rmClass == null) {
			rmClass = upperCaseMap.get(rmClassName.replace("_", ""));
		}
		return rmClass;
	}
	
	/**
	 * Retrieves Map of attribute classes indexed by names of given class
	 * 
	 * @param rmClassName
	 * @return
	 * @throws RMObjectBuildingException
	 */
	public Map retrieveRMAttributes(String rmClassName) {
		Class rmClass = retrieveRMType(rmClassName);
	
		log.debug("----- rmClassName: "+ rmClassName);
		log.debug("rmClass: "+ rmClass.getSimpleName());
		
		
		Map map = attributeType(rmClass);
		Map ret = new HashMap();
		for(Map.Entry entry : map.entrySet()) {
			String name = entry.getKey();
			ret.put(toUnderscoreSeparated(name), entry.getValue());
			
			log.debug("rmattribute: " +name +": "+ entry.getValue());
		}
		return ret;
	}
	
	/**
	 * Retrieves list of attribute names of given class; each name is converted
	 * from camel case to underscore delimited form
	 * 
	 * @param rmClassName
	 * @return
	 */
	public Set retrieveRMAttributeNames(String rmClassName) {
		Class rmClass = retrieveRMType(rmClassName);
		
		log.debug("----- rmClassName: "+ rmClassName);
		log.debug("rmClass: "+ rmClass.getSimpleName());
		
		Map map = attributeType(rmClass);
		Set names = new LinkedHashSet();
		for(String name : map.keySet()) {
			names.add(toUnderscoreSeparated(name));
			
			log.debug("name: " +name);
		}
		return names;
	}

	public String toCamelCase(String underscoreSeparated) {
		StringTokenizer tokens = new StringTokenizer(underscoreSeparated, "_");
		StringBuffer buf = new StringBuffer();
		while (tokens.hasMoreTokens()) {
			String word = tokens.nextToken();
			if (buf.length() == 0) {
				buf.append(word);
			} else {
				buf.append(word.substring(0, 1).toUpperCase());
				buf.append(word.substring(1));
			}
		}
		return buf.toString();
	}

	public String toUnderscoreSeparated(String camelCase) {
		String[] array = StringUtils.splitByCharacterTypeCamelCase(camelCase);
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < array.length; i++) {
			String s = array[i];
			buf.append(s.substring(0, 1).toLowerCase());
			buf.append(s.substring(1));
			if (i != array.length - 1) {
				buf.append("_");
			}
		}
		return buf.toString();
	}

	/**
	 * 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 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 filteredMap = new HashMap();
			for (Map.Entry entry : valueMap.entrySet()) {
				filteredMap.put(toCamelCase(entry.getKey()), entry.getValue());
			}

			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 attributes = new HashSet();

			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;
	}

	/*
	 * Skipped types during matching: 1. Simple value types in DADL 2. Cluster
	 * due to clash with ItemList
	 */
	private static final String[] SKIPPED_TYPES_IN_MATCHING = { "DvDateTime",
			"DvDate", "DvTime", "DvDuration", "Cluster",
			// due to clash with DvText
			"TerminologyID", "ArchetypeID", "TemplateID", "ISO_OID",
			"HierObjectID", "DvBoolean", "InternetID", "UUID",
			"ObjectVersionID"
	};

	
	/* logger */
	private static final Logger log = Logger.getLogger(RMInspector.class);

	// loaded rm type map
	private Map typeMap;
	private Map upperCaseMap;
	private static final Set stringParsingTypes;
	private static final RMInspector soleInstance = new RMInspector();

	static {
		// so far only types from quantity.datetime
		stringParsingTypes = new HashSet();
		String[] types = { "DvDate", "DvDateTime", "DvTime", "DvDuration",
				"DvPartialDate", "DvPartialTime" };
		stringParsingTypes.addAll(Arrays.asList(types));
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy