Commit e6db5878 authored by Martti Käärik's avatar Martti Käärik
Browse files

Rendering of Template structures ¤114

parent 951cfc51
Loading
Loading
Loading
Loading
+247 −17
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ import java.util.stream.Collectors;

import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.resource.XtextResource;
@@ -95,6 +96,7 @@ import org.etsi.mts.tdl.Time;
import org.etsi.mts.tdl.TimeConstraint;
import org.etsi.mts.tdl.TimeLabel;
import org.etsi.mts.tdl.TimeLabelUse;
import org.etsi.mts.tdl.UnassignedMemberTreatment;
import org.etsi.mts.tdl.TimeOperation;
import org.etsi.mts.tdl.TimeOut;
import org.etsi.mts.tdl.Timer;
@@ -107,6 +109,7 @@ import org.etsi.mts.tdl.Variable;
import org.etsi.mts.tdl.VariableUse;
import org.etsi.mts.tdl.VerdictAssignment;
import org.etsi.mts.tdl.Wait;
import org.etsi.mts.tdl.tdlPackage;

public class JUnitTestGenerator extends Renderer {

@@ -147,6 +150,12 @@ public class JUnitTestGenerator extends Renderer {

	private Map<DataType, String> declaredTypes = new Hashtable<DataType, String>();

	/**
	 * Suffix of the variable holding the {@code TemplateImpl} that accompanies a
	 * data use variable (see {@link #getTemplateVariable(DataUse, Map)}).
	 */
	private static final String TEMPLATE_VARIABLE_SUFFIX = "_t";

	public JUnitTestGenerator(Package model, Settings settings) {
		super(settings.outputFile, settings.outPackage);
		this.model = model;
@@ -2047,6 +2056,9 @@ public class JUnitTestGenerator extends Renderer {
				}
			}

			if (needsTemplate(d))
				line("TemplateImpl " + varName + TEMPLATE_VARIABLE_SUFFIX + ";");

			dataUseVariables.put(d, varName);

			if (inst instanceof StructuredDataInstance || inst instanceof CollectionDataInstance) {
@@ -2109,6 +2121,7 @@ public class JUnitTestGenerator extends Renderer {
		DataType castTo = null;

		String ref = dataUseVariables.get(d);
		String templateRef = getTemplateVariable(d, dataUseVariables);
		List<ParameterBinding> arguments = getDataUseArguments(d);

		if (d instanceof CastDataUse) {
@@ -2234,21 +2247,17 @@ public class JUnitTestGenerator extends Renderer {
			}

		} else if (d instanceof SpecialValueUse) {
			String anyKind = "AnyValue";
			anyKind = null; // Defaults to "AnyValue";
			if (d instanceof AnyValueOrOmit)
				anyKind = "AnyValueOrOmit";
			else if (d instanceof OmitValue)
				anyKind = "OmitValue";
			// A special value has no value of its own, it is marked in the template
			// (see writeTemplate() below)
			if (isUnmapped()) {
				if (anyKind != null)
					append(ref + " = new " + CORE_PACKAGE + ".ValueImpl(" + CORE_PACKAGE + ".SpecialValue.AnyValue)");
				if (typeName != null)
					append(unmappedDataInitializer);
				else
					append("null");
				line(";");
					append(" = new " + CORE_PACKAGE + ".ValueImpl()");
			} else {
				// XXX
				append("null");
				// The variable may be of a Java primitive type: assign its default,
				// the template marking makes the actual value irrelevant
				append(javaDefaultLiteral(d.resolveDataType()));
			}

		} else if (d instanceof LiteralValueUse) {
@@ -2352,6 +2361,202 @@ public class JUnitTestGenerator extends Renderer {

			}
		}

		if (templateRef != null) {
			// Members of a referenced structured data instance are part of the
			// template even when the instance value is pre-built (mapped)
			List<MemberAssignment> templateMembers = memberAssignments;
			if (templateMembers == null && dataInstance instanceof StructuredDataInstance)
				templateMembers = ((StructuredDataInstance) dataInstance).getMemberAssignment();
			writeTemplate(d, ref, templateRef, dataInstance, templateMembers, arguments, collectionItems,
					dataUseVariables);
		}
	}

	/**
	 * Emits the initialization of the template variable of a data use: the special
	 * value, the treatment of unassigned members and the templates of members and items that need one. In
	 * unmapped mode the template is also attached to the value.
	 */
	private void writeTemplate(DataUse d, String ref, String templateRef, DataInstance dataInstance,
			List<MemberAssignment> memberAssignments, List<ParameterBinding> arguments, List<DataUse> collectionItems,
			Map<DataUse, String> dataUseVariables) {

		append(templateRef + " = new TemplateImpl(");
		if (d instanceof SpecialValueUse)
			append("SpecialValue." + getSpecialValueName((SpecialValueUse) d));
		append(")");

		UnassignedMemberTreatment treatment = getUnassignedMemberTreatment(d, dataInstance);
		if (treatment != null)
			append(".setUnassignedMemberTreatment(SpecialValue." + treatment.getName() + ")");

		if (memberAssignments != null)
			for (MemberAssignment ma : memberAssignments)
				writeTemplateMember(ma.getMember(), ma.getMemberSpec(), dataUseVariables);
		if (arguments != null)
			for (ParameterBinding arg : arguments)
				writeTemplateMember(arg.getParameter(), arg.getDataUse(), dataUseVariables);

		if (collectionItems != null && collectionItems.stream().anyMatch(item -> needsTemplate(item))) {
			// Items are positional, so every item gets an entry
			for (DataUse item : collectionItems) {
				String itemTemplate = getTemplateVariable(item, dataUseVariables);
				append(".item(" + (itemTemplate != null ? itemTemplate : "TemplateImpl.NONE") + ")");
			}
		}
		line(";");

		if (isUnmapped())
			line(ref + ".setTemplate(" + templateRef + ");");
	}

	private void writeTemplateMember(Parameter p, DataUse memberSpec, Map<DataUse, String> dataUseVariables) {
		String memberTemplate = getTemplateVariable(memberSpec, dataUseVariables);
		if (memberTemplate != null)
			append(".member(\"" + getTemplateMemberName(p) + "\", " + memberTemplate + ")");
	}

	/**
	 * The name under which a member is registered in a template: the same name a
	 * consumer navigates the value with, i.e. the mapped Java field name
	 * (parameter URI of the mapping) in mapped mode and the TDL member name in
	 * unmapped mode.
	 */
	private String getTemplateMemberName(Parameter p) {
		if (!isUnmapped()) {
			String uri = getParameterMappingUri(p);
			if (uri != null)
				return uri;
		}
		return p.getName();
	}

	/**
	 * The Java default value literal for the mapped type of a data type:
	 * <code>null</code> for reference types, the zero value for primitives.
	 */
	private String javaDefaultLiteral(DataType type) {
		if (type == null || isUnmapped())
			return "null";
		String javaType = getElementName(type);
		if (javaType == null)
			return "null";
		switch (javaType) {
		case "int":
		case "long":
		case "short":
		case "byte":
			return "0";
		case "char":
			return "'\\0'";
		case "boolean":
			return "false";
		case "float":
			return "0f";
		case "double":
			return "0d";
		default:
			return "null";
		}
	}

	private String getSpecialValueName(SpecialValueUse d) {
		if (d instanceof AnyValueOrOmit)
			return "AnyValueOrOmit";
		if (d instanceof OmitValue)
			return "OmitValue";
		return "AnyValue";
	}

	/**
	 * The treatment of unassigned members defined for a data use: the
	 * <i>unassignedMember</i> property of the closest element that defines one,
	 * looking from the use outwards in: the use itself, the use wrapped by it
	 * (for a cast), then the referenced data instance. The property is read
	 * generically from whichever of these elements carries it in the meta-model.
	 *
	 * @param dataInstance The referenced data instance if already resolved by the
	 *                     caller, otherwise <b>null</b> (resolved here).
	 * @return The treatment, or <b>null</b> if undefined everywhere.
	 */
	private UnassignedMemberTreatment getUnassignedMemberTreatment(DataUse d, DataInstance dataInstance) {
		// Closest first: the use and, for a cast, the wrapped use(s)
		for (DataUse use = d; use != null; use = use instanceof CastDataUse ? ((CastDataUse) use).getDataUse() : null) {
			UnassignedMemberTreatment t = null;
			if (use instanceof DataInstanceUse)
				t = getUnassignedMember(use, tdlPackage.eINSTANCE.getDataInstanceUse_UnassignedMember());
			if (use instanceof DataElementUse)
				t = getUnassignedMember(use, tdlPackage.eINSTANCE.getDataElementUse_UnassignedMember());
			if (!isUndefined(t))
				return t;
			if (dataInstance == null)
				dataInstance = getReferencedDataInstance(use);
		}
		// Then the definition of the referenced data instance
		if (dataInstance instanceof StructuredDataInstance) {
			UnassignedMemberTreatment t = getUnassignedMember(dataInstance, tdlPackage.eINSTANCE.getStructuredDataInstance_UnassignedMember());
			return isUndefined(t) ? null : t;
		}
		return null;
	}

	/**
	 * The <i>unassignedMember</i> property of a model element, or <b>null</b> if
	 * the element's meta-class does not have it.
	 */
	private UnassignedMemberTreatment getUnassignedMember(EObject e, EStructuralFeature f) {
		if (e == null)
			return null;
		Object value = e.eGet(f);
		return value instanceof UnassignedMemberTreatment ? (UnassignedMemberTreatment) value : null;
	}

	private DataInstance getReferencedDataInstance(DataUse d) {
		if (d instanceof DataElementUse && ((DataElementUse) d).getDataElement() instanceof DataInstance)
			return (DataInstance) ((DataElementUse) d).getDataElement();
		if (d instanceof DataInstanceUse)
			return ((DataInstanceUse) d).getDataInstance();
		return null;
	}

	private boolean isUndefined(UnassignedMemberTreatment t) {
		return t == null || t == UnassignedMemberTreatment.UNDEFINED;
	}

	/**
	 * Whether a data use needs a template: it is a special value, defines a
	 * treatment of unassigned members, or contains (transitively) a member or item
	 * that does.
	 */
	private boolean needsTemplate(DataUse d) {
		if (d == null || d instanceof VariableUse || d instanceof TimeLabelUse)
			return false;
		if (d instanceof CastDataUse)
			return needsTemplate(((CastDataUse) d).getDataUse());
		if (d instanceof SpecialValueUse)
			return true;
		if (getUnassignedMemberTreatment(d, null) != null)
			return true;
		if (d instanceof DataElementUse)
			for (DataUse item : ((DataElementUse) d).getItem())
				if (needsTemplate(item))
					return true;
		for (DataUse arg : getDataUseArgumentValues(d))
			if (needsTemplate(arg))
				return true;
		return false;
	}

	/**
	 * The name of the template variable accompanying the variable of a data use,
	 * or <b>null</b> if the data use needs no template.
	 */
	private String getTemplateVariable(DataUse d, Map<DataUse, String> dataUseVariables) {
		if (!needsTemplate(d))
			return null;
		String ref = dataUseVariables.get(d);
		return ref != null ? ref + TEMPLATE_VARIABLE_SUFFIX : null;
	}

	private void writeMemberAssignment(Parameter p, DataUse v, Map<DataUse, String> dataUseVariables) {
@@ -2365,8 +2570,12 @@ public class JUnitTestGenerator extends Renderer {
		} else {
			ParameterMapping mapping = getParameterMapping(p);
			if (mapping != null) {
				if (hasAnnotation(mapping, MAPPING_ANNOTATION_SETTER)) {
					append(mapping.getParameterURI());
				Annotation setter = getAnnotation(mapping, MAPPING_ANNOTATION_SETTER);
				if (setter != null) {
					String setterName = setter.getValue();
					if (setterName == null || setterName.isEmpty())
						setterName = mapping.getParameterURI();
					append(setterName);
					append("(");
					write(v, dataUseVariables);
					append(")");
@@ -2474,12 +2683,33 @@ public class JUnitTestGenerator extends Renderer {
	}

	private void convertToData(DataUse d, Map<DataUse, String> dataUseVariables) {
		String templateRef = getTemplateVariable(d, dataUseVariables);

		DataUse inner = d instanceof CastDataUse ? ((CastDataUse) d).getDataUse() : d;
		if (inner instanceof SpecialValueUse) {
			// A special value as the whole expected data has no value to derive the
			// type from, so the type must be given explicitly (used for decoding)
			DataType type = d.resolveDataType();
			if (type == null)
				throw new RuntimeException("A special value used as expected data must have a data type ("
						+ getSpecialValueName((SpecialValueUse) inner) + " in " + dataUseVariables.get(d) + ")");
			if (!isUnmapped()) {
				String classLiteral = type instanceof CollectionDataType ? "java.util.List.class"
						: getElementName(type) + ".class";
				append("new PojoData(" + classLiteral + ", " + templateRef + ")");
				return;
			}
		}

		if (!isUnmapped())
			append("new PojoData(");
		write(d, dataUseVariables);
		if (!isUnmapped())
		if (!isUnmapped()) {
			if (templateRef != null)
				append(", " + templateRef);
			append(")");
		}
	}

	private void convertToArgument(String parameterName, DataUse d, Map<DataUse, String> dataUseVariables) {
		if (!isUnmapped())