uk.co.jemos.protomak.engine.utils.ProtomakEngineHelper Maven / Gradle / Ivy
/**
*
*/
package uk.co.jemos.protomak.engine.utils;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import uk.co.jemos.xsds.protomak.proto.MessageAttributeOptionalType;
import uk.co.jemos.xsds.protomak.proto.MessageAttributeType;
import uk.co.jemos.xsds.protomak.proto.MessageRuntimeType;
import uk.co.jemos.xsds.protomak.proto.MessageType;
import uk.co.jemos.xsds.protomak.proto.ProtoRuntimeType;
import com.sun.xml.xsom.XSAttributeDecl;
import com.sun.xml.xsom.XSAttributeUse;
import com.sun.xml.xsom.XSElementDecl;
import com.sun.xml.xsom.XSSimpleType;
import com.sun.xml.xsom.XSType;
/**
* Helper class for Protomak Engine.
*
* @author mtedone
*
*/
public class ProtomakEngineHelper {
//------------------->> Constants
/** The application logger. */
public static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
.getLogger(ProtomakEngineHelper.class);
//------------------->> Instance / Static variables
/** A repository of anonymous type names bound to the XSD input file name */
private static final ConcurrentMap ANONYMOUS_TYPES_CACHE = new ConcurrentHashMap();
/** Mapping between XSD and Proto types */
public static final Map XSD_TO_PROTO_TYPE_MAPPING = new HashMap();
static {
XSD_TO_PROTO_TYPE_MAPPING.put("boolean", ProtoRuntimeType.BOOL);
XSD_TO_PROTO_TYPE_MAPPING.put("float", ProtoRuntimeType.FLOAT);
XSD_TO_PROTO_TYPE_MAPPING.put("double", ProtoRuntimeType.DOUBLE);
XSD_TO_PROTO_TYPE_MAPPING.put("integer", ProtoRuntimeType.SFIXED_32);
XSD_TO_PROTO_TYPE_MAPPING.put("int", ProtoRuntimeType.SFIXED_32);
XSD_TO_PROTO_TYPE_MAPPING.put("long", ProtoRuntimeType.SFIXED_64);
XSD_TO_PROTO_TYPE_MAPPING.put("short", ProtoRuntimeType.SFIXED_32);
XSD_TO_PROTO_TYPE_MAPPING.put("byte", ProtoRuntimeType.SFIXED_32);
XSD_TO_PROTO_TYPE_MAPPING.put("string", ProtoRuntimeType.STRING);
XSD_TO_PROTO_TYPE_MAPPING.put("base64Binary", ProtoRuntimeType.BYTES);
XSD_TO_PROTO_TYPE_MAPPING.put("hexBinary", ProtoRuntimeType.BYTES);
}
//------------------->> Constructors
/** Non instantiable constructor */
private ProtomakEngineHelper() {
throw new AssertionError();
}
//------------------->> Public methods
/**
* Given an XSAttributeUse it creates and returns a
* {@link MessageAttributeType}
*
* @param protoCounter
* The counter to associate with the given attribute.
* @param xsdAttribute
* The {@link XSAttributeUse} used to create a
* {@link MessageAttributeType}.
*
* @return A {@link MessageAttributeType}
*/
public static MessageAttributeType convertXsomAttributeToMessageAttributeType(int protoCounter,
XSAttributeUse xsdAttribute) {
MessageAttributeType retValue = new MessageAttributeType();
if (xsdAttribute.isRequired()) {
retValue.setOptionality(MessageAttributeOptionalType.REQUIRED);
} else {
retValue.setOptionality(MessageAttributeOptionalType.OPTIONAL);
}
XSAttributeDecl attributeDeclaration = xsdAttribute.getDecl();
retValue.setName(attributeDeclaration.getName());
MessageRuntimeType runtimeType = new MessageRuntimeType();
String attributeTypeName = attributeDeclaration.getType().getName();
ProtoRuntimeType protoRuntimeType = XSD_TO_PROTO_TYPE_MAPPING.get(attributeTypeName);
if (null != protoRuntimeType) {
runtimeType.setProtoType(protoRuntimeType);
} else {
runtimeType.setCustomType(attributeTypeName);
}
retValue.setRuntimeType(runtimeType);
retValue.setIndex(protoCounter);
return retValue;
}
/**
* Given an XSD element declaration it manufactures and returns a
* {@link MessageAttributeType}.
*
* @param element
* The XSD element
* @param messageAttributeOrdinal
* The proto index of this message attribute within a
* {@link MessageType}.
* @param attributeOptionality
* The optionality to assign to this message attribute.
* @return A {@link MessageType}
*/
public static MessageAttributeType getMessageAttribute(XSElementDecl element,
int messageAttributeOrdinal, MessageAttributeOptionalType attributeOptionality) {
MessageAttributeType msgAttrType = new MessageAttributeType();
msgAttrType.setName(element.getName());
msgAttrType.setIndex(messageAttributeOrdinal);
msgAttrType.setOptionality(attributeOptionality);
XSType elementType = element.getType();
MessageRuntimeType runtimeType = new MessageRuntimeType();
if (elementType.isComplexType()) {
runtimeType.setCustomType(elementType.getName());
} else {
XSSimpleType simpleType = elementType.asSimpleType();
if (simpleType.isRestriction() && simpleType.isLocal()) {
runtimeType
.setCustomType(ProtomakEngineConstants.ANONYMOUS_ENUM_DEFAULT_MESSAGE_TYPE_NAME
+ messageAttributeOrdinal);
} else {
ProtoRuntimeType protoRuntimeType = ProtomakEngineHelper.XSD_TO_PROTO_TYPE_MAPPING
.get(elementType.getName());
if (null == protoRuntimeType) {
LOG.debug("For element: " + element.getName() + " the SimpleType: "
+ elementType.getName() + " appears to be custom.");
//This is a custom SimpleType
runtimeType.setCustomType(elementType.getName());
} else {
runtimeType.setProtoType(protoRuntimeType);
}
}
}
msgAttrType.setRuntimeType(runtimeType);
return msgAttrType;
}
/**
* Given an input XSD file name it returns the same file with the proto
* extension.
*
* If the file name has got an invalid format (e.g. it does not end
* with a {@code .extension} this method returns a default value
*
* @param inputPath
* The XSD input file name
* @return The proto file name
*/
public static String extractProtoFileNameFromXsdName(String inputPath) {
String retValue = ProtomakEngineConstants.DEFAULT_PROTO_FILE_NAME;
int idx = inputPath.lastIndexOf(".");
if (idx >= 0) {
String fileName = inputPath.substring(0, idx);
retValue = fileName + ProtomakEngineConstants.PROTO_FILE_EXTENSION_NAME;
}
return retValue;
}
/**
* Given a target name space, it returns a proto package.
*
* This method converts an XSD target namespace to a proto package name,
* following the rules specified here
*
*
*
* Details of the proto language and the proto package format can be found
* online
*
*
*
*
*
*
* Generally:
*
* - Forward slashes will be replaced by dots
* - Hyphens will be replaced by underscores
* - The server part of a URI, when present, will be reversed. So
* www.jemos.eu will be written as eu.jemos.www
*
*
*
*
*
* @param targetNameSpace
* A target name space to convert into a proto package name.
* @return A package name in proto format.
*
* @throws IllegalArgumentException
* If the target name space is null or it violates naming
* standards
*/
public static String convertTargetNsToProtoPackageName(String targetNameSpace) {
URI uri = getNormalisedUri(targetNameSpace);
String authority = uri.getAuthority();
List packageTokens = new ArrayList();
if (authority != null) {
//Reverses the authority to form the package name
String[] authorityTokens = uri.getHost().split("\\.");
for (int i = authorityTokens.length - 1; i >= 0; i--) {
packageTokens.add(authorityTokens[i]);
}
}
String path = uri.getPath();
if (null != path) {
//It removes all ../
int idx = 0;
while ((idx = path.indexOf("../")) >= 0) {
path = path.substring(idx + 3);
}
String[] pathTokens = path.split("/");
if (pathTokens != null && pathTokens.length > 0) {
for (String pathToken : pathTokens) {
if (pathToken.equals("")) {
continue;
}
packageTokens.add(0, pathToken);
}
} else {
packageTokens.add(0, path);
}
}
StringBuilder buff = new StringBuilder();
for (int i = 0; i < packageTokens.size(); i++) {
buff.append(packageTokens.get(i));
if (i + 1 < packageTokens.size()) {
buff.append(".");
}
}
buff.append(";");
return buff.toString();
}
/**
* Given a min and max occurrence, it returns the appropriate
* {@link MessageAttributeOptionalType}.
*
* @param minOccurs
* The XSD element {@code minOccurs} element.
* @param maxOccurs
* The XSD element {@code maxOccurs} element.
* @return The appropriate {@link MessageAttributeOptionalType} for the
* given parameters.
*
* @throws IllegalArgumentException
* If min or max occurs have got invalid values.
*/
public static MessageAttributeOptionalType getMessageAttributeOptionality(int minOccurs,
int maxOccurs) {
if (minOccurs < 0 || minOccurs > 1) {
String errMsg = "minOccurs must either be 0 or 1 but it was: " + minOccurs;
LOG.error(errMsg);
throw new IllegalArgumentException(errMsg);
}
if (maxOccurs < -1 || maxOccurs > 1) {
String errMsg = "maxOccurs must either be -1, 0 or 1 but it was: " + maxOccurs;
LOG.error(errMsg);
throw new IllegalArgumentException(errMsg);
}
MessageAttributeOptionalType retValue = null;
if (minOccurs == 0) {
if (maxOccurs == 1) {
retValue = MessageAttributeOptionalType.OPTIONAL;
} else {
retValue = MessageAttributeOptionalType.REPEATED;
}
} else { //minOccurs = 1
if (maxOccurs == 1) {
retValue = MessageAttributeOptionalType.REQUIRED;
} else {
retValue = MessageAttributeOptionalType.REPEATED;
}
}
return retValue;
}
//------------------->> Private methods
// ------------------->> Getters / Setters
/**
* It creates, validates and returns the URI given as argument, throwing
* exception in case of any anomalies.
*
* @param targetNameSpace
* The package name to validate
* @return
*
* @throws IllegalArgumentException
*
* -
* If the package name does not adhere to standard naming
* conventions, as specified here
* - If the target NS is not a valid {@link URI}
*
*/
private static URI getNormalisedUri(String targetNameSpace) {
String errMsg = null;
if (null == targetNameSpace || "".equals(targetNameSpace)) {
errMsg = "Target name space cannot be null or empty";
LOG.error(errMsg);
throw new IllegalArgumentException(errMsg);
}
if (targetNameSpace.endsWith(".")) {
errMsg = "Target name space cannot end with a dot";
LOG.error(errMsg);
throw new IllegalArgumentException(errMsg);
}
//E.g. ".foo"
if (targetNameSpace.startsWith(".") && !targetNameSpace.startsWith("..")) {
targetNameSpace = targetNameSpace.substring(1);
}
if (Character.isDigit(targetNameSpace.charAt(0))) {
errMsg = "The target name space: " + targetNameSpace + " cannot start with a digit";
LOG.error(errMsg);
throw new IllegalArgumentException(errMsg);
}
targetNameSpace = targetNameSpace.replace('-', '_');
targetNameSpace = targetNameSpace.toLowerCase().trim();
URI uri = null;
try {
uri = new URI(targetNameSpace);
} catch (URISyntaxException e) {
errMsg = "The specified target namespace: " + targetNameSpace
+ " does not seem to be a valid URI";
LOG.error(errMsg);
throw new IllegalArgumentException(errMsg, e);
}
return uri.normalize();
}
/**
* It retrieves a name for an anonymous type, guaranteeing uniqueness for
* duplicates.
*
* @param candidateName
* A candidate name for a message type
*
* @param inputPath
* The full path to the XSD input file which originated the
* request
* @return A name for an anonymous type, guaranteeing that it is unique per
* proto file.
*/
public static String getMessageTypeName(String candidateName, String inputPath) {
//Guarantees multi-threading support
inputPath = inputPath.replace('\\', '/');//I prefer a normalised URL
String capitalisedName = capitaliseString(candidateName);
String key = inputPath + capitalisedName;
String retValue = ANONYMOUS_TYPES_CACHE.get(key);
if (retValue == null) {
String added = ANONYMOUS_TYPES_CACHE.putIfAbsent(key, capitalisedName);
if (added != null) {
retValue = added;
} else {
retValue = capitalisedName;
}
} else {
int idx = 0;
key = inputPath + capitalisedName + idx;
String value = capitalisedName + idx;
while (ANONYMOUS_TYPES_CACHE.containsKey(key)) {
idx++;
key = inputPath + capitalisedName + idx;
value = capitalisedName + idx;
}
String added = ANONYMOUS_TYPES_CACHE.putIfAbsent(key, value);
if (added != null) {
retValue = added;
} else {
retValue = value;
}
}
return retValue;
}
//------------------->> equals() / hashcode() / toString()
/**
* @param retValue
* @return
*/
public static String capitaliseString(String retValue) {
retValue = Character.toUpperCase(retValue.charAt(0)) + retValue.substring(1);
return retValue;
}
//------------------->> Inner classes
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy