
org.hl7.cql.model.GenericClassSignatureParser Maven / Gradle / Ivy
package org.hl7.cql.model;
import org.apache.commons.lang3.StringUtils;
import org.hl7.elm_modelinfo.r1.ParameterTypeSpecifier;
import java.util.List;
import java.util.Map;
/**
* The GenericClassSignatureParser is a convenience class for the parsing of generic signature
* and the creation of the corresponding CQL DataTypes, namely, GenericClassType and GenericClassProfileType.
* The former is used to capture the declaration of a GenericClass such as List<T>. The latter is used to capture a new type
* such as 'IntegerList' formed by binding types to generic parameters such as List<Integer>.
*/
public class GenericClassSignatureParser {
public static final CharSequence OPEN_BRACKET = "<";
public static final CharSequence CLOSE_BRACKET = ">";
public static final String EXTENDS = "extends";
private int startPos = 0;
private int endPos = 0;
private int bracketCount = 0;
private int currentBracketPosition = 0;
private Map resolvedTypes;
/**
* A generic signature such as List<T> or a bound signature
* such as List<Person>
*/
private String genericSignature;
/**
* The base type for the class type or the profile.
*/
private String baseType;
/**
* The name of a bound type such as PersonList = List<Person>
*/
private String boundGenericTypeName;
public GenericClassSignatureParser(String genericSignature, String baseType, String boundGenericTypeName, Map resolvedTypes) {
this.genericSignature = genericSignature;
this.resolvedTypes = resolvedTypes;
this.baseType = baseType;
this.boundGenericTypeName = boundGenericTypeName;
}
public GenericClassSignatureParser(String genericSignature, Map resolvedTypes) {
this(genericSignature, null, null, resolvedTypes);
}
public String getGenericSignature() {
return genericSignature;
}
public void setGenericSignature(String genericSignature) {
this.genericSignature = genericSignature;
}
public String getBaseType() {
return baseType;
}
public void setBaseType(String baseType) {
this.baseType = baseType;
}
public String getBoundGenericTypeName() {
return boundGenericTypeName;
}
public void setBoundGenericTypeName(String boundGenericTypeName) {
this.boundGenericTypeName = boundGenericTypeName;
}
/**
* Parses a generic type declaration such as Map<K,V>.
*
* @return Generic class constructed from this definition.
*/
public ClassType parseGenericSignature() {
String genericTypeName = genericSignature;
String[] params = new String[0];
if(isValidGenericSignature()) {
genericTypeName = genericSignature.substring(0, genericSignature.indexOf('<'));
String parameters = genericSignature.substring(genericSignature.indexOf('<') + 1, genericSignature.lastIndexOf('>'));
params = escapeNestedCommas(parameters).split(",");
}
String baseTypeName = baseType;
String[] baseTypeParameters = null;
if(baseType != null && baseType.contains("<")) {
baseTypeName = baseType.substring(0, baseType.indexOf('<'));
String baseTypeParameterString = baseType.substring(baseType.indexOf('<') + 1, baseType.lastIndexOf('>'));
baseTypeParameters = escapeNestedCommas(baseTypeParameterString).split(",");
}
DataType baseDataType = resolveTypeName(baseTypeName);
ClassType genericClassType = new ClassType(genericTypeName, baseDataType);
for(String param: params) {
TypeParameter paramType = handleParameterDeclaration(unescapeNestedCommas(param));
genericClassType.addGenericParameter(paramType);
}
if(baseTypeParameters != null) {
int index = 0;
for(String baseTypeParameter: baseTypeParameters) {
if(baseTypeParameter.length() == 1 && genericClassType.getGenericParameterByIdentifier(baseTypeParameter) == null) {
throw new RuntimeException("Cannot resolve symbol " + baseTypeParameter);
} else{
DataType boundType = resolveTypeName(unescapeNestedCommas(baseTypeParameter));
ClassType baseTypeClass = (ClassType)baseDataType;
List baseClassFields = baseTypeClass.getElements();
String myParam = baseTypeClass.getGenericParameters().get(index).getIdentifier();
System.out.println(boundType + " replaces param " + myParam);
for(ClassTypeElement baseClassField : baseClassFields) {
ClassTypeElement myElement = new ClassTypeElement(baseClassField.getName(), boundType);
genericClassType.addElement(myElement);
}
}
index++;
}
}
return genericClassType;
}
/**
* Method handles a generic parameter declaration such as T, or T extends MyType.
*
* @param parameterString
* @return Type parameter for this parameter for this string declaration.
*/
protected TypeParameter handleParameterDeclaration(String parameterString) {
String[] paramComponents = parameterString.split("\\s+");
if(paramComponents.length == 1) {
return new TypeParameter(StringUtils.trim(parameterString), TypeParameter.TypeParameterConstraint.NONE, null);
} else if(paramComponents.length == 3) {
if(paramComponents[1].equalsIgnoreCase(EXTENDS)) {
return new TypeParameter(paramComponents[0], TypeParameter.TypeParameterConstraint.TYPE, resolveTypeName(paramComponents[2]));
} else {
throw new RuntimeException("Invalid parameter syntax: " + parameterString);
}
} else {
throw new RuntimeException("Invalid parameter syntax: " + parameterString);
}
}
/**
* Identifies the data type for the named type. If the argument is null,
* the return type will be null.
*
* @param parameterType
* @return The parameter's type
*/
protected DataType resolveTypeName(String parameterType) {
if(isValidGenericSignature(parameterType)) {
return handleBoundType(parameterType);
} else {
if(parameterType == null) {
return null;
} else {
return resolveType(parameterType);
}
}
}
/**
* Method resolves bound type if it exists or else creates it and
* adds it to the resolved type index.
*
* @param boundGenericSignature
* @return The bound type created or resolved
*/
protected DataType handleBoundType(String boundGenericSignature) {
ClassType resolvedType = (ClassType)resolvedTypes.get(escapeNestedAngleBrackets(boundGenericSignature));
if(resolvedType != null) {
return resolvedType;
} else {
String genericTypeName = boundGenericSignature.substring(0, boundGenericSignature.indexOf('<'));
resolvedType = (ClassType)resolveType(genericTypeName);
if(resolvedType == null) {
throw new RuntimeException("Unknown type " + genericTypeName);
}
ClassType newType = new ClassType(escapeNestedAngleBrackets(boundGenericSignature), resolvedType);
String parameters = boundGenericSignature.substring(boundGenericSignature.indexOf('<') + 1, boundGenericSignature.lastIndexOf('>'));
String[] params = escapeNestedCommas(parameters).split(",");
int index = 0;
for(String param : params) {
DataType boundParam = null;
param = unescapeNestedCommas(param);
if(isValidGenericSignature(param)) {
boundParam = handleBoundType(param);
} else {
boundParam = resolveType(param);
}
TypeParameter typeParameter = resolvedType.getGenericParameters().get(index);
for(ClassTypeElement classTypeElement : resolvedType.getElements()) {
if(classTypeElement.getType() instanceof TypeParameter) {
if(((TypeParameter)classTypeElement.getType()).getIdentifier().equalsIgnoreCase(typeParameter.getIdentifier())) {
ClassTypeElement newElement = new ClassTypeElement(classTypeElement.getName(), boundParam);
newType.addElement(newElement);
}
}
}
index++;
}
resolvedTypes.put(newType.getName(), newType);
return newType;
}
}
/**
* Returns true if the generic signature assigned to this object is well-formed.
*
* @return True if the generic signature is valid
*/
public boolean isValidGenericSignature() {
return isValidGenericSignature(genericSignature);
}
/**
* Returns true if the generic signature passed as an argument is well-formed.
*
* @param genericSignature
* @return True if the generic signature is valid
*/
public boolean isValidGenericSignature(String genericSignature) {
return areBracketsPaired(genericSignature) && closingBracketsComeAfterOpeningBrackets(genericSignature);
}
/**
* Method sets the initial state of this parser.
*/
private void initializeParser() {
startPos = genericSignature.indexOf('<');
endPos = genericSignature.lastIndexOf('>');
bracketCount = openBracketCount();
}
/**
* Counts the number of < in this signature.
*
* @return
*/
private int openBracketCount() {
return openBracketCount(genericSignature);
}
/**
* Counts the number of < in this signature.
*
* @param signatureString
* @return
*/
private int openBracketCount(String signatureString) {
int matchCount = 0;
if(signatureString != null) {
matchCount = StringUtils.countMatches(signatureString, OPEN_BRACKET);
}
return matchCount;
}
/**
* Counts the number of > in this signature.
*
* @return
*/
private int closeBracketCount() {
return closeBracketCount(genericSignature);
}
/**
* Counts the number of > in this signature.
*
* @param signatureString
* @return
*/
private int closeBracketCount(String signatureString) {
int matchCount = 0;
if(signatureString != null) {
matchCount = StringUtils.countMatches(signatureString, CLOSE_BRACKET);
}
return matchCount;
}
/**
* Method returns if the number of < matches the number of >
*
* @return
*/
private boolean areBracketsPaired() {
return areBracketsPaired(genericSignature);
}
/**
* Method returns if the number of < matches the number of >
*
* @param signatureString
* @return
*/
private boolean areBracketsPaired(String signatureString) {
boolean paired = false;
if(signatureString != null) {
int openCount = openBracketCount(signatureString);
int closeCount = closeBracketCount(signatureString);
paired = (openCount == closeCount) && (openCount > 0);
}
return paired;
}
/**
* Simple check to make sure that closing brackets come after opening brackets.
*
* @return
*/
private boolean closingBracketsComeAfterOpeningBrackets() {
return closingBracketsComeAfterOpeningBrackets(genericSignature);
}
/**
* Simple check to make sure that closing brackets come after opening brackets.
*
* @param signatureString
* @return
*/
private boolean closingBracketsComeAfterOpeningBrackets(String signatureString) {
return signatureString != null && signatureString.lastIndexOf('<') < signatureString.indexOf('>');
}
/**
* Convenience method for the parsing of nested comma-separated parameters.
* Call before unescapeNestedCommas when done processing the top level of nested parameters.
*
* @param signature
* @return
*/
private String escapeNestedCommas(String signature) {
char[] signatureCharArray = signature.toCharArray();
int openBracketCount = 0;
for(int index = 0; index < signatureCharArray.length; index++) {
char c = signatureCharArray[index];
if(c == '<') {
openBracketCount++;
} else if(c == '>') {
openBracketCount--;
} else if(c == ',' && openBracketCount > 0) {
signatureCharArray[index] = '|';
}
}
return new String(signatureCharArray);
}
/**
* Convenience method for the parsing of nested comma-separated parameters.
* Call after escapeNestedCommas when handling the top level of nested parameters.
*
* @param escapedSignature
* @return
*/
private String unescapeNestedCommas(String escapedSignature) {
return escapedSignature.replaceAll("\\|", ",");
}
/**
* Method looks up data type of typeName is index.
*
* @param typeName
* @return
*/
private DataType resolveType(String typeName) {
DataType type = resolvedTypes.get(typeName);
if(type == null) {
throw new RuntimeException("Unable to resolve " + typeName);
}
return type;
}
private String escapeNestedAngleBrackets(String genericSignature) {
return genericSignature.replaceAll("<", "[").replaceAll(">", "]");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy