blob: ad35fb2d967a04eb7dd5d285dc4e99440d948f84 [file] [log] [blame]
/** Copyright (C) Conformiq Software Ltd.
* All rights reserved.
*
* Created Wed Aug 27 16:49:21 2008.
*
* @file TTCNScripter.java
*
* @author Conformiq Software Ltd.
*
*
*/
package com.conformiq.adaptation.ttcn;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;
import com.conformiq.adaptation.ttcn.Utils.Banner;
import com.conformiq.adaptation.ttcn.Utils.CheckPointInfo;
import com.conformiq.adaptation.ttcn.Utils.TemplateDefinition;
import com.conformiq.qtronic2.Checkpoint;
import com.conformiq.qtronic2.MetaDataDictionary;
import com.conformiq.qtronic2.NotificationSink;
import com.conformiq.qtronic2.QMLArray;
import com.conformiq.qtronic2.QMLBoolean;
import com.conformiq.qtronic2.QMLNumber;
import com.conformiq.qtronic2.QMLOptional;
import com.conformiq.qtronic2.QMLRecord;
import com.conformiq.qtronic2.QMLRecordType;
import com.conformiq.qtronic2.QMLRecordTypeField;
import com.conformiq.qtronic2.QMLString;
import com.conformiq.qtronic2.QMLUnionType;
import com.conformiq.qtronic2.QMLValue;
import com.conformiq.qtronic2.QMLValueVisitor;
import com.conformiq.qtronic2.ScriptBackend;
import com.conformiq.qtronic2.TimeStamp;
import com.conformiq.qtronic2.Checkpoint.CheckpointStatus;
public class TTCNScripter extends ScriptBackend
{
public TTCNScripter()
{
this.mTestCaseIndex = 0;
this.mTemplateIndex = 0;
this.mTimeStamp = new TimeStamp();
this.mTimeStamp.seconds = 0;
this.mTimeStamp.nanoseconds = 0;
this.mIsFirstStep = false;
this.mGeneratedTemplates = new HashMap<QMLRecord, Integer>();
this.mPostponedTemplates = new Vector<TemplateDefinition>();
this.mTestCaseNames = new Vector<String>();
this.collectedCheckpoints = null;
}
@Override
public void setNotificationSink(NotificationSink sink)
{
mNotifications = sink;
}
@Override
public boolean setMetaData(MetaDataDictionary dict)
{
mMetadata = dict;
extractPortsMetaData();
return true;
}
@Override
public boolean setConfigurationOption(java.lang.String property,
java.lang.String value)
{
return mConfiguration.setConfigurationOption(property, value);
}
@Override
public boolean beginScript(String testsuiteName)
{
this.mTestsuiteName = testsuiteName;
if (mNotifications != null)
{
mNotifications.notify("info", mScripterName
+ ": Export test cases for test design configuration "
+ testsuiteName);
}
String msg = null;
if (mConfiguration.getSuiteFile().getAbsolutePath().equals(""))
{
msg = "TTCN-3 backend must be configured with a file name.";
}
else
{
try
{
mOut = new PrettyPrinter(new BufferedWriter(new FileWriter(
mConfiguration.getSuiteFile())));
} catch (IOException e)
{
StringBuffer msgbuf = new StringBuffer();
msgbuf.append("Failed to open file '");
msgbuf.append(mConfiguration.getSuiteFile());
msgbuf.append("' for writing TTCN-3 script.");
msg = msgbuf.toString();
}
}
if (msg != null)
{
if (mNotifications != null)
{
mNotifications.notify("error", mScripterName + ": " + msg);
}
else
{
System.err.println(msg);
}
return false;
}
{
String[] desc = {
"This file contains all test cases generated from the "
+ "Conformiq '" + mConfiguration.getProjectName()
+ "' project",
"with '" + testsuiteName + "' design configuration." };
String[] remark = {
"WARNING! This file has been automatically generated using the",
"Conformiq TTCN-3 scripting backend. DO NOT EDIT." };
printHeader(mConfiguration.getSuiteFile(), desc, remark);
}
mOut.print("module ");
mOut.print(mConfiguration.getSuiteModuleName());
mOut.endl();
mOut.print("{");
mOut.endl();
mOut.print(mIndent);
mOut.block();
if (mConfiguration.getTestSystemModuleName() != null)
{
mOut.print("import from ");
mOut.print(mConfiguration.getTestSystemModuleName());
mOut.print(" all;");
mOut.endl();
}
if (mConfiguration.getDataTypesModuleName() != null)
{
mOut.print("import from ");
mOut.print(mConfiguration.getDataTypesModuleName());
mOut.print(" all;");
mOut.endl();
}
mOut.print("/* Customized imports begin */");
mOut.endl();
mOut.print(mConfiguration.getExtraImports());
mOut.endl();
mOut.println("/* Customized imports end */");
mOut.emptyline();
mOut.print("modulepar float " + mSlackVarName + " := ");
mOut.print(mConfiguration.getCommSlack());
mOut.println(";");
mOut.emptyline();
printDebugModulepars();
mOut.emptyline();
// print out the test harness template
try
{
// should the harness template be written
if (mConfiguration.isGenerateTestHarness())
{
PrettyPrinter harness = buildPrettyPrinter(mConfiguration
.getTestHarnessFile(), "harness template", false);
if (harness != null)
{
dumpTestHarness(harness);
if (mNotifications != null)
{
mNotifications.notify("info", mScripterName
+ ": Generated TTCN-3 test harness template to "
+ "<hyperlink=\""
+ mConfiguration.getTestHarnessFile()
.getAbsolutePath()
+ "\">"
+ mConfiguration.getTestHarnessFile()
.getAbsolutePath() + "</hyperlink>.");
}
}
}
} catch (Exception e)
{
mOut.print("/* EXCEPTION CAUGHT */");
mOut.endl();
if (mNotifications != null)
{
mNotifications.notify("error", mScripterName
+ ": Caught exception while rendering TTCN test harness"
+ " template. Please reload model and try again ("
+ e.getMessage() + ")");
}
return false;
}
if (mConfiguration.isGenerateDataTypes())
{
/* First dump all the types used in the module. */
try
{
dumpDataTypes();
} catch (Exception e)
{
mOut.print("/* EXCEPTION CAUGHT */");
mOut.endl();
if (mNotifications != null)
{
mNotifications.notify("error", mScripterName
+ ": Caught exception while "
+ "rendering TTCN type definitions. Please reload "
+ "model and try again (" + e.getMessage() + ")");
}
return false;
}
}
if (mConfiguration.isGenerateTestSystemFile())
{
/* First dump all the types used in the module. */
try
{
dumpTestComponentFile();
} catch (Exception e)
{
mOut.print("/* EXCEPTION CAUGHT */");
mOut.endl();
if (mNotifications != null)
{
mNotifications.notify("error", mScripterName
+ ": Caught exception while "
+ "rendering TTCN type definitions. Please reload "
+ "model and try again (" + e.getMessage() + ")");
}
return false;
}
}
deleteCopies();
mTemplateIndex = 0;
mTestCaseIndex = 0;
mIsFirstStep = false;
return true;
}
@Override
public boolean beginCase(String testcaseName)
{
collectedCheckpoints = new LinkedList<CheckPointInfo>();
mCaseStepIndex = 0;
mIsFirstStep = true;
mTestCaseIndex++;
Banner banner = new Banner();
banner.addTag("desc", "Generated test case #" + mTestCaseIndex);
banner.print(mOut);
mOut.print("testcase ");
mCrrentTestCaseName = recordAndRenderTestCaseName(testcaseName);
mOut.print(mCrrentTestCaseName);
mOut.println("()");
mOut.print("runs on ");
mOut.print(mConfiguration.getRunsOnName());
if (mConfiguration.getSystemTypeName() != null
&& !mConfiguration.getSystemTypeName().equals(""))
{
mOut.print(" system ");
mOut.print(mConfiguration.getSystemTypeName());
}
mOut.endl();
mOut.print("{");
mOut.endl();
mOut.print(mIndent);
mOut.block();
printLog(LogMessageType.DEBUG, "Starting execution of test case: '"
+ mCrrentTestCaseName + "'");
mOut.print("var float " + mOldTimerName + " := "
+ Utils.TimeStampToString(mTimeStamp) + ";");
mOut.endl();
if (!mConfiguration.getDefaultName().equals(""))
{
mOut.print("var default " + mConfiguration.getDefaultRefName()
+ ";");
mOut.endl();
}
if (!mConfiguration.getStartHook().equals(""))
{
mOut.emptyline();
mOut.print("/***** set up test configuration, TTCN-3 harness, "
+ "and adapter *****/");
mOut.endl();
mOut.print(mConfiguration.getFunctionsPrefix()
+ mConfiguration.getStartHook());
mOut.print(";");
mOut.endl();
}
if (!mConfiguration.getDefaultName().equals(""))
{
mOut.println("// default handles waiting beyond maximum response "
+ "time and reception of any");
mOut.println("// other than the expected message with setting a"
+ " fail verdict and stopping the test");
mOut.print(mConfiguration.getDefaultRefName() + " := activate(");
mOut.print(mConfiguration.getDefaultName());
mOut.print(");");
mOut.endl();
}
return true;
}
// @Override
public void caseProbability(double probability)
{
}
@Override
public boolean checkpointInfo(Checkpoint checkpoint, int status,
TimeStamp ts)
{
if (!mIsFirstStep || status == CheckpointStatus.UNCOVERED
|| status == CheckpointStatus.MAYBE_COVERED)
{
return true;
}
collectedCheckpoints.offer(new CheckPointInfo(checkpoint, status, ts));
return true;
}
@Override
public boolean endCase()
{
printCheckpoints();
mOut.emptyline();
mOut.println("setverdict(pass);");
/*
* Default is used only if the provided name is different from "", so if
* the default is not used let's not deactivate it either.
*/
if (!mConfiguration.getDefaultName().equals(""))
{
mOut.print("deactivate(" + mConfiguration.getDefaultRefName()
+ ");");
mOut.endl();
}
if (!mConfiguration.getEndHook().equals(""))
{
mOut.emptyline();
mOut.print("/***** "
+ "tear down test configuration, TTCN-3 harness, and adapter"
+ " *****/");
mOut.endl();
mOut.print(mConfiguration.getFunctionsPrefix()
+ mConfiguration.getEndHook());
mOut.print(";");
mOut.endl();
}
mOut.emptyline();
printLog(LogMessageType.DEBUG, "Ending execution of "
+ mCrrentTestCaseName);
mOut.resume();
mOut.print("}");
mOut.endl();
/* Generate templates out of the datums stored in the TestStep. */
printTemplates();
mPostponedTemplates.clear();
return true;
}
@Override
public boolean endScript()
{
prindAltstepDefaultDecl();
printSleepFunction();
mOut.emptyline();
printDebugMessageTypeEnum();
mOut.emptyline();
printDebugFunctionDecl();
printControl();
mOut.resume();
mOut.print("}");
mOut.endl();
try
{
mOut.flush();
mOut.close();
} catch (Exception e)
{
if (mNotifications != null)
{
mNotifications.notify("error", mScripterName + ": "
+ e.toString());
}
}
mOut = null;
StringBuffer ss = new StringBuffer();
ss.append("Generated TTCN-3 script to <hyperlink=\"");
ss.append(mConfiguration.getSuiteFile().getAbsolutePath());
ss.append("\">");
ss.append(mConfiguration.getSuiteFile().getAbsolutePath());
ss.append("</hyperlink>.");
if (mNotifications != null)
{
mNotifications.notify("info", mScripterName + ": " + ss.toString());
}
else
{
System.out.println(ss.toString());
}
mTestCaseNames.clear();
return true;
}
@Override
public boolean testStep(QMLRecord r, String thread, String port,
boolean isFromTester, TimeStamp ts)
{
/*
* We have an altstep defined:
*
* altstep CQDefaultAlt() runs on SystemType { [] any port.receive {
* mytimer.stop; setverdict(fail); stop; } [] mytimer.timeout {
* setverdict(fail); stop; } }
*
* which removes the need for "[] any port.receive" and timeout from alt
* statements.
*/
mCaseStepIndex++;
mOut.endl();
mOut.emptyline();
mOut.println("/***** Step " + mCaseStepIndex + "; t = "
+ Utils.TimeStampToString(ts) + " *****/");
mOut.endl();
printLog(LogMessageType.DEBUG, mCrrentTestCaseName + ": Step "
+ mCaseStepIndex);
printCheckpoints();
// Remember the datum so that we can generate a template out of it.
// Generate a new template only if it has not been generated before.
String name;
Integer idx = mGeneratedTemplates.get(r);
if (idx != null)
{
StringBuffer ss = new StringBuffer();
ss.append(r.getName());
ss.append("Template");
ss.append(idx);
name = ss.toString();
}
else
{
++mTemplateIndex;
StringBuffer ss = new StringBuffer();
ss.append(r.getName());
ss.append("Template");
ss.append(mTemplateIndex);
name = ss.toString();
if (!isFromTester)
{
name = "expected" + name;
}
mPostponedTemplates.add(
new TemplateDefinition(r.getName(), name, r));
mGeneratedTemplates.put(r, mTemplateIndex);
}
if (isFromTester)
{
/*
* Send datum to SUT. Generate:
*
* mytimer.start(<send time>); alt { [] mytimer.timeout { // OK } }
* <port>.send(template_<index>);
*/
if (ts.seconds > mTimeStamp.seconds
|| ts.nanoseconds > mTimeStamp.nanoseconds)
{
mOut.print(mConfiguration.getTimerName());
mOut.print(".start(");
mOut.print(ts.seconds);
mOut.print(".");
mOut.print(ts.nanoseconds);
mOut.print(" - " + mOldTimerName + ");");
mOut.endl();
mOut.print("alt");
mOut.endl();
mOut.print("{");
mOut.endl();
mOut.print(mIndent);
mOut.block();
mOut.print("[] ");
mOut.print(mConfiguration.getTimerName());
mOut.print(".timeout");
mOut.endl();
mOut.print("{");
mOut.endl();
mOut.print("}");
mOut.endl();
mOut.resume();
mOut.print("}");
mOut.endl();
mOut.print(mConfiguration.getTimerName());
mOut.print(".stop;");
mOut.endl();
}
mOut.endl();
mOut.print(mConfiguration.getFunctionsPrefix() + "send_");
mOut.print(r.getName());
mOut.print("_to_");
mOut.print(Utils.makeValidTTCN3Identifier(port));
mOut.print("(");
mOut.print("m_" + name);
mOut.print(");");
mOut.endl();
}
else
{
final String timeStamp = "" + ts.seconds + "." + ts.nanoseconds;
if (!Utils.EqualTimeStamps(this.mTimeStamp, ts))
{
mOut.println(mConfiguration.getFunctionsPrefix()
+ mSleepFunctionName + "(" + timeStamp + " - "
+ mOldTimerName + ");");
}
mOut.println(mConfiguration.getTimerName() + ".start("
+ mSlackVarName + ");");
mOut.println("// Note: In below receive "
+ mConfiguration.getDefaultRefName() + "() is active!");
mOut.print(mConfiguration.getFunctionsPrefix() + "receive_");
mOut.print(r.getName());
mOut.print("_from_");
mOut.print(Utils.makeValidTTCN3Identifier(port));
mOut.print("(");
mOut.print("m_" + name);
mOut.print(");");
mOut.endl();
mOut.print(mConfiguration.getTimerName());
mOut.print(".stop;");
mOut.endl();
}
if (!Utils.EqualTimeStamps(this.mTimeStamp, ts))
{
mOut.print(mOldTimerName + " := ");
mOut.print(ts.seconds);
mOut.print(".");
mOut.print(ts.nanoseconds);
mOut.println(";");
}
this.mTimeStamp.seconds = ts.seconds;
this.mTimeStamp.nanoseconds = ts.nanoseconds;
this.collectedCheckpoints = new LinkedList<CheckPointInfo>();
return true;
}
@Override
public boolean internalCommunicationsInfo(QMLRecord datum, String sender,
String receiver, String port, TimeStamp time)
{
return true;
}
@Override
public boolean trace(String message, TimeStamp time)
{
return true;
}
private PrettyPrinter buildPrettyPrinter(final File f, final String desc,
final boolean overWrite)
{
String name = f.getAbsolutePath();
if (f.exists() && !f.isDirectory() && !overWrite)
{
if (mNotifications != null)
{
mNotifications.notify("info", mScripterName + ": File "
+ "<hyperlink=\"" + name + "\">" + name + "</hyperlink>."
+ " exists, NOT overwriting it.");
}
return null;
}
if (name.equals("") || f.isDirectory())
{
mNotifications.notify("error",
"TTCN-3 backend must be configured with a valid " + desc
+ " file name.");
return null;
}
try
{
return new PrettyPrinter(new BufferedWriter(new FileWriter(f)));
} catch (IOException e)
{
mNotifications.notify("error", "Failed to open file '" + name
+ "' for writing" + desc + ".");
return null;
}
}
private void dumpTestComponentFile() throws Exception
{
String msg = null;
Exception rethrowMe = null;
PrettyPrinter oldOut = mOut;
if (mConfiguration.getTestSystemFile().getAbsolutePath().equals(""))
{
msg = "TTCN-3 backend must be configured with a test system file "
+ "name.";
}
else
{
try
{
mOut = new PrettyPrinter(new BufferedWriter(new FileWriter(
mConfiguration.getTestSystemFile())));
} catch (IOException e)
{
StringBuffer msgbuf = new StringBuffer();
msgbuf.append("Failed to open file '");
msgbuf.append(
mConfiguration.getTestSystemFile().getAbsolutePath());
msgbuf.append("' for writing test system information.");
msg = msgbuf.toString();
rethrowMe = e;
}
}
if (msg != null)
{
if (mNotifications != null)
{
mNotifications.notify("error", msg);
}
else
{
System.err.println(msg);
}
mOut = oldOut;
// Pass the exception forward for proper error handling further
// up the call stack.
throw (rethrowMe);
}
{
String[] desc = {
"This file contains port type definitions and test component "
+ "generated by Conformiq",
"with '" + mTestsuiteName + "' design configuration for '"
+ mConfiguration.getProjectName() + "' project." };
String[] remark = {
"WARNING! This file has been automatically generated using the",
"Conformiq TTCN-3 scripting backend. DO NOT EDIT." };
printHeader(mConfiguration.getDataTypesFile(), desc, remark);
}
mOut.print("module ");
mOut.print(mConfiguration.getTestSystemModuleName());
mOut.endl();
mOut.print("{");
mOut.endl();
mOut.print(mIndent);
mOut.block();
if (mConfiguration.getDataTypesModuleName() != null)
{
mOut.println("import from "
+ mConfiguration.getDataTypesModuleName() + " all;");
mOut.emptyline();
}
// system mapping
if (!mConfiguration.getSystemTypeName().equals("") && !mConfiguration.isEptfEnable())
{
mOut.emptyline();
Banner banner = new Banner();
banner.addTag("desc", new String[] {
"This TTCN-3 component type specifies the interface of the",
"test cases towards the SUT more specifically the SUT "
+ "adapter." });
banner.print(mOut);
mOut.print("type component ");
mOut.print(mConfiguration.getSystemTypeName());
mOut.endl();
mOut.println("{");
mOut.print(mIndent);
mOut.block();
mOut.println("// Add all port instances here");
mOut.resume();
mOut.println("}");
}
try
{
if (mConfiguration.isGenerateTestSystemFile())
{
for (Port p : externalPorts)
{
if (mConfiguration.isEptfEnable() &&
(p.name.equals(mConfiguration.getEptfInboundPort()) ||
p.name.equals(mConfiguration.getEptfOutboundPort()))
)
{
}
else
{
mOut.print("type port ");
mOut.print(p.name);
mOut.print("Port message");
mOut.endl();
mOut.print("{");
mOut.endl();
mOut.print(mIndent);
mOut.block();
for (QMLRecordType record : p.records)
{
String dir = p.isInbound ? "in" : "out";
if (mConfiguration.isEptfBidirectionalPort())
dir = "inout";
mOut.println(dir + " " + record.getTypeName() + ";");
}
mOut.resume();
mOut.print("}");
if (!mConfiguration.getPortExtensions().equals(""))
{
mOut.print(" with {extension \"");
mOut.print(mConfiguration.getPortExtensions());
mOut.print("\"}");
}
mOut.emptyline();
}
}
}
mOut.emptyline();
Banner banner = new Banner();
banner.addTag("desc", "The test component (MTC) on which all "
+ "generated test cases run on");
banner.print(mOut);
mOut.print("type component ");
mOut.print(mConfiguration.getRunsOnName());
if (!mConfiguration.getExtendsComponent().equals(""))
{
mOut.print(" extends ");
mOut.print(mConfiguration.getExtendsComponent());
}
mOut.endl();
mOut.print("{");
mOut.endl();
mOut.print(mIndent);
mOut.block();
for (Port p : externalPorts)
{
mOut.print("port ");
mOut.print(p.name);
mOut.print("Port ");
mOut.print(p.name);
mOut.print(";");
mOut.endl();
}
mOut.print("timer ");
mOut.print(mConfiguration.getTimerName());
mOut.print(" := 0.0;");
mOut.endl();
mOut.resume();
mOut.print("}");
mOut.endl();
mOut.resume();
mOut.print("}");
mOut.endl();
mOut.flush();
mOut.close();
mOut = oldOut;
} catch (Exception e)
{
mOut.flush();
mOut.close();
mOut = oldOut;
}
mNotifications.notify("info", mScripterName
+ ": Generated TTCN-3 port and component types file "
+ "<hyperlink=\""
+ mConfiguration.getTestSystemFile().getAbsolutePath() + "\">"
+ mConfiguration.getTestSystemFile().getAbsolutePath()
+ "</hyperlink>.");
}
/**
* prints out the test harness template, that contains:
*
* - import from CQTypes;
*
* - type component for the harness system
*
* - MTC component the tests should run on
*
* - harness alt step
*
* - start and end test hook calls
*
* - port and message mapping functions
*/
private void dumpTestHarness(final PrettyPrinter pp) throws Exception
{
assert (mMetadata != null);
PrettyPrinter oldOut = this.mOut;
this.mOut = pp;
{
String[] desc = {
"This is a template file with TTCN-3 function stubs for the "
+ "implementation ",
"of test harness code for the test suites generated from the "
+ "Conformiq '",
mConfiguration.getProjectName() + "' project." };
String[] remark = {
"All function definitions must be edited as instructed in "
+ "their comments in",
" order for the test suite to execute properly." };
printHeader(mConfiguration.getTestHarnessFile(), desc, remark);
}
mOut.print("module ");
mOut.print(Utils.buildModuleNameFromFileName(
mConfiguration.getTestHarnessFile()));
mOut.endl();
mOut.print("{");
mOut.endl();
// import all data types
if (mConfiguration.getDataTypesModuleName() != null)
{
mOut.print(mIndent);
mOut.print("import from ");
mOut.print(mConfiguration.getDataTypesModuleName());
mOut.print(" all;");
mOut.endl();
}
if (mConfiguration.getTestSystemModuleName() != null)
{
mOut.print(mIndent);
mOut.print("import from ");
mOut.print(mConfiguration.getTestSystemModuleName());
mOut.print(" all;");
mOut.endl();
}
mOut.emptyline();
// debug function
mOut.print(mIndent);
mOut.block();
// MTC port definitions
if (!mConfiguration.getRunsOnName().equals(""))
{
mOut.resume();
// default start and stop functions
mOut.print(mIndent);
mOut.block();
if (!mConfiguration.getStartHook().equals("") && !mConfiguration.isEptfEnable())
{
mOut.emptyline();
Banner banner = new Banner();
banner.addTag("desc", new String[] {
"This function sets up the test configuration, maps all "
+ "mtc",
"to system component ports, and configures (if needed)",
"TTCN-3 harness and test system adapter" });
banner.print(mOut);
mOut.print("function ");
mOut.print(mConfiguration.getFunctionsPrefix()
+ mConfiguration.getStartHook());
mOut.print(" runs on ");
mOut.print(mConfiguration.getRunsOnName());
mOut.endl();
mOut.print("{");
mOut.endl();
mOut.print(mIndent);
mOut.block();
mOut.println("// Specify here map operations between MTC and "
+ "abstract test");
mOut.println("// system interface or update and uncomment "
+ "generated code below");
for (Port p : externalPorts)
{
mOut.println("// map(" + mConfiguration.getRunsOnName()
+ ": " + p.name + ", system: <some port>);");
}
if (mConfiguration.isDoLogCQInfoMessages())
{
mOut.println("// Remove or comment the following generated "
+ "code");
}
printLog(LogMessageType.INFO, "Warning: "
+ mConfiguration.getFunctionsPrefix()
+ mConfiguration.getStartHook() + ": "
+ "function is not implemented");
mOut.resume();
mOut.println("}");
}
if (!mConfiguration.getEndHook().equals("") && !mConfiguration.isEptfEnable())
{
Banner banner = new Banner();
banner.addTag("desc", new String[] {
"This function tears down the test configuration",
"and unmaps all mtc to system component ports" });
banner.print(mOut);
mOut.print("function ");
mOut.print(mConfiguration.getFunctionsPrefix()
+ mConfiguration.getEndHook());
mOut.print(" runs on ");
mOut.println(mConfiguration.getRunsOnName());
mOut.println("{");
mOut.print(mIndent);
mOut.block();
mOut.println("//Specify here unmap operations between MTC and"
+ " abstract test");
mOut.println("// system interface or update and uncomment"
+ " generated code below");
for (Port p : externalPorts)
{
mOut.println("// unmap(" + mConfiguration.getRunsOnName()
+ ": " + p.name + ", system: <some port>);");
}
if (mConfiguration.isDoLogCQInfoMessages())
{
mOut.println("// Remove or comment the following generated "
+ "code");
}
printLog(LogMessageType.INFO, "Warning: "
+ mConfiguration.getFunctionsPrefix()
+ mConfiguration.getEndHook() + ": "
+ "function is not implemented");
mOut.resume();
mOut.println("}");
}
mOut.resume();
// port mapping
try
{
mOut.print(mIndent);
mOut.block();
for (Port p : externalPorts)
{
for (QMLRecordType type : p.records)
{
String record = type.getTypeName();
String argName = null;
if (p.isInbound)
{
argName = "p_" + "expected" + record;
}
else
{
argName = "p_" + record;
}
// (p.isInbound ? "tmplToMatch" : "msgToSend");
String functionName =
mConfiguration.getFunctionsPrefix()
+ (p.isInbound ? "receive_" : "send_")
+ record
+ (p.isInbound ? "_from_" : "_to_") + p.name;
String functionDecl = "function " + functionName
+ "(template " + record + " " + argName + ")";
mOut.emptyline();
Banner banner = new Banner();
if (p.isInbound)
{
banner.addTag("desc", new String[] {
"This function receives a TTCN-3 value "
+ "corresponding to a " + record,
"via the abstract test system interface from "
+ "the SUT, performs any",
"manipulation and transformation needed to "
+ "convert it to a " + record,
"value, and then attempts to match it to the "
+ argName + " generated by CQ Designer" });
banner.addTag("param", new String[] { argName
+ "Expected data generated by CQ Designer" });
// * from the SUT to */
}
else
{
banner.addTag("desc", new String[] {
"This function performs manipulation needed"
+ " and" + " sends a " + argName,
"via the abstract test interface to the"
+ " SUT." });
banner.addTag("param", argName
+ " Message data generated by CQ Designer"
+ "to be sent to the SUT");
}
banner.print(mOut);
mOut.println(functionDecl + " runs on "
+ mConfiguration.getRunsOnName());
mOut.println("{");
mOut.print(mIndent);
mOut.block();
if (p.isInbound)
{
mOut.println("//Steps that need to be implemented"
+ " here are:");
mOut.println("//1. receive and store (any) TTCN-3"
+ " data value via TTCN-3 port which "
+ "corresponds to the " + p.name + " model "
+ "port");
mOut.println("// var <T3_" + record
+ "Type> v_recvT3" + record + ";");
mOut.println("// " + p.name + ".receive(<T3_"
+ record + "Type>:?) -> value v_recvT3"
+ record + ";");
mOut.println("// 2. replace real with symbolic "
+ "values (if any) in the received TTCN-3 data"
+ " value");
mOut.println("// 3. transform data from a TTCN-3 "
+ "to " + record + " data value (if needed)");
mOut.println("// var" + record + "v_recv" + record
+ " = " + "f_transform" + record
+ "T3toCQ(v_recvT3" + record + ");");
mOut.println("// 4. set the verdict to fail if if"
+ " there is a mismatch of the "
+ "transformed value and " + argName);
mOut.println("// if ( !match( v_recv" + record
+ " " + argName + ") {");
mOut.println("// log(CQ_INFO: " + functionName
+ ": FAIL: Mismatch in received and expected "
+ record + " values. Stopping test case.�);");
mOut.println("// setverdict(fail);");
mOut.println("// "
+ mConfiguration.getFunctionsPrefix()
+ mConfiguration.getEndHook()
+ ";");
mOut.println("// stop; }");
if (mConfiguration.isEptfEnable())
{
if (p.name.equals(mConfiguration.getEptfInboundPort())
|| p.name.equals(mConfiguration.getEptfOutboundPort()))
{
mOut.println(" EPTF_MBT_TESTER_PCO.receive("+argName+") from vc_lgen;");
}
else
{
mOut.println(" " + p.name + ".receive("+argName+");");
}
}
}
else
{
mOut.println("// Steps that need to be implemented"
+ " here are:");
mOut.println("// (modify and uncomment example "
+ "code as needed)");
mOut.println("// 1. transform data from a "
+ record + " to the TTCN-3 data value"
+ " used by the test harness (if needed)");
mOut.println("// var <T3" + record + "Type> v_T3"
+ record + " := " + "f_transform" + record
+ "CQtoT3(" + argName + ");");
mOut.println("// 2. replace symbolic values "
+ "(if any) with real values in TTCN-3"
+ " data value");
mOut.println("// 3. send TTCN-3 data value via "
+ "TTCN-3 port which corresponds to" + p.name
+ "model port");
mOut.println("// " + p.name + ".send(v_T3" + record
+ ");");
if (mConfiguration.isEptfEnable())
{
if (p.name.equals(mConfiguration.getEptfInboundPort())
|| p.name.equals(mConfiguration.getEptfOutboundPort()))
{
mOut.println(" EPTF_MBT_TESTER_PCO.send("+argName+") to vc_lgen;");
}
else
{
mOut.println(" " + p.name + ".send("+argName+");");
}
}
}
if (mConfiguration.isDoLogCQInfoMessages())
{
mOut.println("// Remove or comment the following "
+ "generated code");
}
printLog(LogMessageType.INFO, "Warning: "
+ functionName + ": "
+ "function is not implemented)");
mOut.resume();
mOut.println("}");
}
}
mOut.resume();
} catch (Exception e)
{
mOut.print("/* EXCEPTION CAUGHT in dumpTestHarness */");
mOut.flush();
mOut.close();
this.mOut = oldOut;
throw e;
}
}
// Must close the module block.
mOut.print("}");
mOut.endl();
mOut.flush();
mOut.close();
this.mOut = oldOut;
}
private void dumpDataTypes() throws Exception
{
assert (mMetadata != null);
PrettyPrinter oldOut = mOut;
String msg = null;
Exception rethrowMe = null;
if (mConfiguration.getDataTypesFile().getAbsolutePath().equals(""))
{
msg = "TTCN-3 backend must be configured with a data type file "
+ "name.";
}
else
{
try
{
mOut = new PrettyPrinter(new BufferedWriter(new FileWriter(
mConfiguration.getDataTypesFile())));
} catch (IOException e)
{
StringBuffer msgbuf = new StringBuffer();
msgbuf.append("Failed to open file '");
msgbuf.append(mConfiguration.getDataTypesFile()
.getAbsolutePath());
msgbuf.append("' for writing data types.");
msg = msgbuf.toString();
rethrowMe = e;
}
}
if (msg != null)
{
if (mNotifications != null)
{
mNotifications.notify("error", msg);
}
else
{
System.err.println(msg);
}
mOut = oldOut;
// Pass the exception forward for proper error handling further
// up the call stack.
throw (rethrowMe);
}
{
String[] desc = {
"This file contains all data type definitions exported from "
+ "the Conformiq " + mConfiguration.getProjectName()
+ "' project.",
"with '" + mTestsuiteName + "' design configuration.'"};
String[] remark = {
"WARNING! This file has been automatically generated using the",
"Conformiq TTCN-3 scripting backend. DO NOT EDIT." };
printHeader(mConfiguration.getDataTypesFile(), desc, remark);
}
mOut.print("module ");
mOut.print(Utils.buildModuleNameFromFileName(mConfiguration
.getDataTypesFile()));
mOut.endl();
mOut.print("{");
mOut.endl();
mOut.print(mIndent);
mOut.block();
try
{
if (mConfiguration.isGenerateDataTypes())
{
final Set<QMLRecordType> alreadyDumped =
new HashSet<QMLRecordType>();
final Queue<QMLRecordType> workList =
new LinkedList<QMLRecordType>();
final Set<String> arrays = new HashSet<String>();
for (Port p : externalPorts)
{
for (QMLRecordType t : p.records)
{
workList.offer(t);
}
}
while (!workList.isEmpty())
{
QMLRecordType type = workList.poll();
if (alreadyDumped.contains(type) ||
(
mConfiguration.isEptfEnable() &&
type.getTypeName().length() >= 5 &&
type.getTypeName().substring(0, 5).equals("EPTF_")
)
)
{
// Skipping type generation
}
else
{
alreadyDumped.add(type);
if (type instanceof QMLUnionType)
{
mOut.print("type union ");
}
else
{
mOut.print("type record ");
}
mOut.println(type.getTypeName());
mOut.println("{");
mOut.print(mIndent);
mOut.block();
for (int i = 0; i < type.getNumberOfFields(); i++)
{
QMLRecordTypeField field = type.getField(i);
TTCNTypeName v = new TTCNTypeName(workList,
alreadyDumped, arrays);
field.getType().accept(v);
if (field.getType() instanceof QMLRecordType)
{
String fieldType =
Utils.makeValidTTCN3Identifier(
field.getType().getTypeName());
if (!alreadyDumped.contains(fieldType))
{
workList.offer(
(QMLRecordType)field.getType());
}
}
assert (!v.getName().equals(""));
if (i != 0)
{
mOut.print(",");
mOut.endl();
}
mOut.print(v.getName());
mOut.print(" ");
mOut.print(Utils.makeValidTTCN3Identifier(
field.getFieldName()));
if (v.getOptional())
{
mOut.print(" optional");
}
}
mOut.resume();
mOut.endl();
mOut.print("}");
mOut.endl();
}
}
Iterator<String> it = arrays.iterator();
while (it.hasNext())
{
String x = it.next();
// TODO: reconsider changing this because now both
// TTCNTypeName class and this piece of code here has
// to know how array names are constructed.
mOut.print("type record of ");
mOut.print(x);
mOut.print(" ");
mOut.print(x);
mOut.print("Array;");
mOut.endl();
}
}
/*
* altstep CQDefaultAlt() runs on SystemType { [] any port.receive {
* mytimer.stop; setverdict(fail); stop; } [] mytimer.timeout {
* setverdict(fail); stop; } }
*/
// Must close the module block.
mOut.resume();
mOut.print("}");
mOut.endl();
mOut.flush();
mOut.close();
mOut = oldOut;
} catch (Exception e)
{
mOut.flush();
mOut.close();
mOut = oldOut;
}
mNotifications.notify("info", mScripterName
+ ": Generated TTCN-3 type definitions " + "<hyperlink=\""
+ mConfiguration.getDataTypesFile() + "\">"
+ mConfiguration.getDataTypesFile() + "</hyperlink>.");
}
private void printControl()
{
mOut.print("control");
mOut.endl();
mOut.print("{");
mOut.endl();
mOut.print(mIndent);
mOut.block();
for (int i = 1; i <= mTestCaseIndex; i++)
{
mOut.print("execute(");
mOut.print(getTestCaseName(i));
mOut.print("());");
mOut.endl();
}
mOut.resume();
mOut.print("}");
mOut.endl();
mOut.emptyline();
}
public void reportError(String msg)
{
mNotifications.notify("error", mScripterName + ": " + msg);
}
public void reportInfo(String msg)
{
mNotifications.notify("info", mScripterName + ": " + msg);
}
private void extractPortsMetaData()
{
final String portinfo = "portinfo:";
String key = mMetadata.getNextKey(portinfo);
externalPorts = new LinkedList<Port>();
while (key != null)
{
String data = key;
if (!data.startsWith(portinfo))
{
break;
}
final QMLValue definition = mMetadata.get(key);
assert (definition != null);
QMLArray tuple = (QMLArray) definition;
if (tuple != null)
{
Port port = new Port(key, tuple);
externalPorts.add(port);
}
key = mMetadata.getNextKey(key);
}
}
private void printSleepFunction()
{
Banner banner = new Banner();
banner.addTag("desc",
"This function blocks the execution for the specified time");
banner.addTag("param", "p_duration The specified time in seconds");
banner.print(mOut);
mOut.println("function " + mConfiguration.getFunctionsPrefix()
+ mSleepFunctionName + "(float p_duration)");
mOut.println("{");
mOut.print(mIndent);
mOut.block();
mOut.println("timer t;");
mOut.println("t.start(p_duration);");
mOut.println("t.timeout; // Note that any active default behavior "
+ "may interrupt this waiting!");
mOut.endl();
mOut.resume();
mOut.print("}");
mOut.endl();
}
private void printAltStepBody()
{
mOut.print(mIndent);
mOut.block();
if (!mConfiguration.isEptfEnable())
{
mOut.print("[] any port.receive");
mOut.endl();
mOut.print("{");
mOut.endl();
mOut.print(mIndent);
mOut.block();
mOut.print(mConfiguration.getTimerName());
mOut.print(".stop;");
mOut.endl();
mOut.print("setverdict(fail);");
mOut.endl();
printLog(LogMessageType.DEBUG, mConfiguration.getDefaultName()
+ ": FAIL: Stopping test case after receiving unexpected "
+ "message in default: " + mConfiguration.getDefaultName() + "!");
if (!mConfiguration.getEndHook().equals(""))
{
mOut.print(mConfiguration.getFunctionsPrefix()
+ mConfiguration.getEndHook());
mOut.print(";");
mOut.endl();
}
mOut.print("stop;");
mOut.endl();
mOut.resume();
mOut.print("}");
mOut.endl();
}
mOut.print("[] ");
mOut.print(mConfiguration.getTimerName());
mOut.print(".timeout");
mOut.endl();
mOut.print("{");
mOut.endl();
mOut.print(mIndent);
mOut.block();
mOut.print("setverdict(fail);");
mOut.endl();
printLog(LogMessageType.DEBUG, mConfiguration.getDefaultName()
+ ": FAIL: Stopping test case after " + "time out of timer: "
+ mConfiguration.getTimerName() + "!");
if (!mConfiguration.getEndHook().equals(""))
{
mOut.print(mConfiguration.getFunctionsPrefix()
+ mConfiguration.getEndHook());
mOut.print(";");
mOut.endl();
}
mOut.print("stop;");
mOut.endl();
mOut.resume();
mOut.print("}");
mOut.endl();
mOut.resume();
mOut.endl();
}
private void printTemplates()
{
if (!mPostponedTemplates.isEmpty())
{
mOut.println("/***** Template definitions generated for "
+ mCrrentTestCaseName + "*****/");
Iterator<TemplateDefinition> it = mPostponedTemplates.iterator();
while (it.hasNext())
{
final TemplateDefinition def = it.next();
mOut.print("template ");
mOut.print(Utils.makeValidTTCN3Identifier(def.getTypeName()));
mOut.print(" ");
mOut.print("m_" + def.getTemplateName());
mOut.ws();
mOut.print(":=");
mOut.endl();
QMLRecord r = def.getRecord();
TemplateDumper t = new TemplateDumper(mOut, mConfiguration
.isUseFractions());
r.accept(t);
mOut.endl();
mOut.emptyline();
}
}
}
private void prindAltstepDefaultDecl()
{
mOut.emptyline();
Banner banner = new Banner();
banner.addTag("desc", new String[] {
"This altstep handles terminating after waiting up to",
"the maximum response time and receiving any other",
"than the expected message with setting a fail ",
"verdict and stopping the test" });
banner.print(mOut);
mOut.print("altstep ");
mOut.print(mConfiguration.getDefaultName());
mOut.print(" runs on ");
mOut.print(mConfiguration.getRunsOnName());
mOut.endl();
mOut.print("{");
mOut.endl();
mOut.print(mIndent);
mOut.block();
printAltStepBody();
mOut.resume();
mOut.println("}");
}
private void printDebugModulepars()
{
mOut.println("/***** Logging verbosity flags *****/");
for (DebugMessageType t : DebugMessageType.enabledValues())
{
Banner banner = new Banner();
banner.addTag("desc", t.modeuleparComment);
banner.print(mOut);
printModulepar("boolean", t.enablingFlag, "" + t.defaultFlagState);
}
}
private void printModulepar(final String type, final String name,
final String init)
{
mOut.println("modulepar " + type + " " + name + " := " + init + ";");
}
private void printDebugMessageTypeEnum()
{
mOut.println("type enumerated " + mDebugMessageTypeEnumName);
mOut.println("{");
mOut.print(mIndent);
mOut.block();
DebugMessageType[] debugMessageTypes = DebugMessageType.enabledValues();
for (int i = 0; i < debugMessageTypes.length; i++)
{
mOut.print(debugMessageTypes[i].dispatchingEnumItemName);
if (i != debugMessageTypes.length - 1)
{
mOut.print(",");
}
mOut.endl();
}
mOut.resume();
mOut.endl();
mOut.print("}");
mOut.endl();
}
private void printDebugFunctionDecl()
{
String firstArg = "p_description";
String secondArg = "p_" + mDebugMessageTypeEnumName;
Banner banner = new Banner();
List<String> descBody = new LinkedList<String>();
descBody.add("This function logs target description if the module"
+ " parameter");
descBody.add("related to the target type is set to true.");
descBody.add("Module parameters and target type dependencies are"
+ " as follows:");
for (DebugMessageType t : DebugMessageType.enabledValues())
{
descBody.add(mIndent + t.enablingFlag
+ " controls logging of targets type "
+ t.dispatchingEnumItemName);
}
banner.addTag("desc", descBody.toArray(new String[0]));
banner.addTag("param", new String[] { firstArg
+ " The textual target description to be logged" });
banner.addTag("param ", new String[] { secondArg
+ " The type of target covered by a test" });
banner.print(mOut);
mOut.print("function " + mConfiguration.getFunctionsPrefix()
+ mSmartLogCommand + " (");
mOut.print("charstring " + firstArg + ", " + mDebugMessageTypeEnumName
+ " " + secondArg);
mOut.print(") runs on " + mConfiguration.getRunsOnName());
mOut.endl();
mOut.println("{");
mOut.print(mIndent);
mOut.block();
DebugMessageType types[] = DebugMessageType.enabledValues();
for (int i = 0; i < types.length; i++)
{
DebugMessageType t = types[i];
mOut.print("if ((" + secondArg + " == " + t.dispatchingEnumItemName
+ ")");
mOut.print(" and ");
mOut.print(t.enablingFlag + ")");
mOut.endl();
mOut.println("{");
mOut.print(mIndent);
mOut.block();
printLogVar(firstArg);
mOut.resume();
mOut.println("}");
if (i != types.length - 1)
{
mOut.println("else");
}
}
mOut.resume();
mOut.print("}");
mOut.endl();
}
private void printLog(LogMessageType type, String msg)
{
String messageHeader;
switch (type) {
case DEBUG:
if (!mConfiguration.isDoLogCQDEbugMessages())
{
return;
}
messageHeader = "DEBUG";
break;
case INFO:
if (!mConfiguration.isDoLogCQInfoMessages())
{
return;
}
messageHeader = "INFO";
break;
default:
return;
}
mOut.print("log");
mOut.print("(\"CQ_" + messageHeader + ": " + msg + "\");");
mOut.endl();
}
private void printLogVar(String varName)
{
mOut.print("log");
mOut.print("(\"CQ_INFO: \" & " + varName + ");");
mOut.endl();
}
private void printHeader(File file, String[] desc, String[] remark)
{
assert (mOut != null);
mOut.print("/* -*- ttcn-3 -*- */");
mOut.endl();
mOut.emptyline();
Banner banner = new Banner();
banner.addTag("file", file.getAbsolutePath());
banner.addTag("author", "Conformiq TTCN-3 scripting backend");
banner.addTag("version", new Date().toString());
if (desc != null)
{
banner.addTag("desc", desc);
}
if (remark != null)
{
banner.addTag("remark", remark);
}
banner.print(mOut);
}
private void deleteCopies()
{
mGeneratedTemplates.clear();
}
private void printCheckpoints()
{
for (CheckPointInfo cp : collectedCheckpoints)
{
printCheckPoint(cp);
}
}
private void printCheckPoint(CheckPointInfo cp)
{
String name = cp.checkpoint.getName();
for (DebugMessageType t : DebugMessageType.values())
{
for (String p : t.checkpointPrefixes)
{
if (name.startsWith(p) && t.enabled)
{
mOut.print(mConfiguration.getFunctionsPrefix() +
mSmartLogCommand);
mOut.print("(\"Covered ");
mOut.print(Utils.escapeString(name));
mOut.print("\"");
mOut.print(", ");
mOut.print(t.dispatchingEnumItemName);
mOut.print(");");
mOut.endl();
return;
}
}
}
}
private String recordAndRenderTestCaseName(String testCaseName)
{
// Remove whitespace
StringBuffer rendered = new StringBuffer("tc_");
for (int i = 0; i < testCaseName.length(); i++)
{
// Inverted the logic here to be safer by replacing everything
// but valid identifier chars with an underscore. According to the
// spec: "TTCN-3 identifiers are case sensitive and may
// only contain lowercase letters (a-z) uppercase letters
// (A-Z) and numeric digits (0-9). Use of the underscore (
// _ ) symbol is also allowed. An identifier shall begin
// with a letter (i.e., not a number and not an
// underscore)."
char c = testCaseName.substring(i, i + 1).charAt(0);
// If the original test case name starts with a digit, we want
// to preserve that but need to prefix the test case name with
// an underscore.
if (c >= '0' && c <= '9' && i == 0)
rendered.append('_');
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|| (c >= '0' && c <= '9') || c == '_')
rendered.append(c);
else
rendered.append('_');
}
// Put into test case name list
mTestCaseNames.add(rendered.toString());
return rendered.toString();
}
private String getTestCaseName(int i)
{
// Get test case name at index i of the test case list
return mTestCaseNames.get(i - 1);
}
private static final String mIndent = " ";
private static final String mDebugMessageTypeEnumName = "target_type";
private static final String mSmartLogCommand = "log_target";
static public final String mScripterName = "TTCN-3 scripter";
static private final String mSlackVarName = "mp_max_response_time";
static private final String mOldTimerName = "v_last_wait_timeout";
static private final String mSleepFunctionName = "sleep";
private PrettyPrinter mOut;
private NotificationSink mNotifications;
private MetaDataDictionary mMetadata;
private int mTestCaseIndex;
private int mTemplateIndex;
private String mCrrentTestCaseName;
private int mCaseStepIndex;
private TimeStamp mTimeStamp;
private boolean mIsFirstStep;
private Map<QMLRecord, Integer> mGeneratedTemplates;
private Vector<TemplateDefinition> mPostponedTemplates;
private Vector<String> mTestCaseNames;
private Configuration mConfiguration = new Configuration();
private String mTestsuiteName;
private Queue<Utils.CheckPointInfo> collectedCheckpoints;
enum LogMessageType {
DEBUG, INFO
}
public class Port
{
public Port(final String key, final QMLArray tuple)
{
final StringTokenizer tokenizer = new StringTokenizer(key, ":");
final String potinfo = tokenizer.nextToken();
assert potinfo == "portinfo";
final String dir = tokenizer.nextToken();
isInbound = !dir.equals("inbound");
final boolean isOutbound = !dir.equals("outbound");
assert isInbound != isOutbound;
final String origname = tokenizer.nextToken();
name = Utils.makeValidTTCN3Identifier(origname);
assert !tokenizer.hasMoreElements();
this.records = new LinkedList<QMLRecordType>();
tuple.accept(new PortDataExtractor());
}
final List<QMLRecordType> records;
final boolean isInbound;
final String name;
private class PortDataExtractor implements QMLValueVisitor
{
public PortDataExtractor()
{
}
@Override
public void visit(QMLArray x)
{
final int size = x.getNumberOfElements();
for (int i = 0; i < size; i++)
{
QMLString portname = (QMLString) x.getValue(i);
if (portname != null)
{
final String origname = portname.getValue();
QMLRecordType type = mMetadata.getType(origname);
if (type == null)
{
mNotifications.notify("info", "Warning: Nested r" +
"records in port declarations types are " +
"not yet supported by the scripter");
continue;
}
records.add(type);
}
}
}
@Override
public void visit(QMLBoolean b)
{
}
@Override
public void visit(QMLNumber n)
{
}
@Override
public void visit(QMLRecord r)
{
}
@Override
public void visit(QMLString s)
{
}
@Override
public void visit(QMLOptional p)
{
}
}
}
private List<Port> externalPorts;
}