/*
* Copyright (c) 2013-2016 Chris Newland.
* Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD
* Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki
*/
package org.adoptopenjdk.jitwatch.loader;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_COLON;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_HASH;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_NEWLINE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_OPEN_ANGLE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_SEMICOLON;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.DEBUG_LOGGING_BYTECODE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_BYTECODE_CODE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_BYTECODE_CONSTANT_POOL;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_BYTECODE_EXCEPTION_TABLE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_BYTECODE_INNERCLASSES;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_BYTECODE_LINENUMBERTABLE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_BYTECODE_LOCALVARIABLETABLE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_BYTECODE_MAJOR_VERSION;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_BYTECODE_MINOR_VERSION;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_BYTECODE_RUNTIMEVISIBLEANNOTATIONS;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_BYTECODE_SIGNATURE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_BYTECODE_SOURCE_FILE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_BYTECODE_STACKMAPTABLE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_BYTECODE_STATIC_INITIALISER_SIGNATURE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_CLOSE_BRACE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_COLON;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_COMMA;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_DEFAULT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_DOT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_DOUBLE_QUOTE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_EMPTY;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_HASH;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_NEWLINE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_SEMICOLON;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_SLASH;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_SPACE;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.adoptopenjdk.jitwatch.model.MemberSignatureParts;
import org.adoptopenjdk.jitwatch.model.MetaClass;
import org.adoptopenjdk.jitwatch.model.bytecode.BCParamConstant;
import org.adoptopenjdk.jitwatch.model.bytecode.BCParamNumeric;
import org.adoptopenjdk.jitwatch.model.bytecode.BCParamString;
import org.adoptopenjdk.jitwatch.model.bytecode.BCParamSwitch;
import org.adoptopenjdk.jitwatch.model.bytecode.BytecodeInstruction;
import org.adoptopenjdk.jitwatch.model.bytecode.ClassBC;
import org.adoptopenjdk.jitwatch.model.bytecode.ExceptionTableEntry;
import org.adoptopenjdk.jitwatch.model.bytecode.IBytecodeParam;
import org.adoptopenjdk.jitwatch.model.bytecode.InnerClassRelationship;
import org.adoptopenjdk.jitwatch.model.bytecode.LineTableEntry;
import org.adoptopenjdk.jitwatch.model.bytecode.MemberBytecode;
import org.adoptopenjdk.jitwatch.model.bytecode.Opcode;
import org.adoptopenjdk.jitwatch.model.bytecode.SourceMapper;
import org.adoptopenjdk.jitwatch.process.javap.JavapProcess;
import org.adoptopenjdk.jitwatch.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class BytecodeLoader
{
private static final Logger logger = LoggerFactory.getLogger(BytecodeLoader.class);
private static final Pattern PATTERN_BYTECODE_INSTRUCTION = Pattern
.compile("^([0-9]+):\\s([0-9a-z_]+)\\s?([#0-9a-z,\\- ]+)?\\s?\\{?\\s?(//.*)?");
enum BytecodeSection
{
NONE, CONSTANT_POOL, CODE, EXCEPTIONTABLE, LINETABLE, RUNTIMEVISIBLEANNOTATIONS, LOCALVARIABLETABLE, STACKMAPTABLE, INNERCLASSES
}
private static final Map<String, BytecodeSection> sectionLabelMap = new HashMap<>();
static
{
sectionLabelMap.put(S_BYTECODE_CONSTANT_POOL, BytecodeSection.CONSTANT_POOL);
sectionLabelMap.put(S_BYTECODE_CODE, BytecodeSection.CODE);
sectionLabelMap.put(S_BYTECODE_LINENUMBERTABLE, BytecodeSection.LINETABLE);
sectionLabelMap.put(S_BYTECODE_LOCALVARIABLETABLE, BytecodeSection.LOCALVARIABLETABLE);
sectionLabelMap.put(S_BYTECODE_RUNTIMEVISIBLEANNOTATIONS, BytecodeSection.RUNTIMEVISIBLEANNOTATIONS);
sectionLabelMap.put(S_BYTECODE_EXCEPTION_TABLE, BytecodeSection.EXCEPTIONTABLE);
sectionLabelMap.put(S_BYTECODE_STACKMAPTABLE, BytecodeSection.STACKMAPTABLE);
sectionLabelMap.put(S_BYTECODE_INNERCLASSES, BytecodeSection.INNERCLASSES);
}
private BytecodeLoader()
{
}
/*
* Builds a meta class from bytecode where JITDataModel.buildAndGetMetaClass
* fails due to NoClassDefFoundError
*/
public static MetaClass buildMetaClassFromClass(String fqClassName)
{
//TODO implement
return null;
}
public static ClassBC fetchBytecodeForClass(List<String> classLocations, String fqClassName, boolean cacheBytecode)
{
return fetchBytecodeForClass(classLocations, fqClassName, null, cacheBytecode);
}
public static ClassBC fetchBytecodeForClass(List<String> classLocations, String fqClassName, Path javapPath,
boolean cacheBytecode)
{
if (DEBUG_LOGGING_BYTECODE)
{
logger.debug("fetchBytecodeForClass: {}", fqClassName);
logger.info("Class locations: {}", StringUtil.listToString(classLocations));
}
try
{
JavapProcess javapProcess;
if (javapPath != null)
{
javapProcess = new JavapProcess(javapPath);
}
else
{
javapProcess = new JavapProcess();
}
javapProcess.execute(classLocations, fqClassName);
String byteCodeString = javapProcess.getOutputStream();
return parseByteCodeFromString(fqClassName, byteCodeString, cacheBytecode);
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
private static ClassBC parseByteCodeFromString(String fqClassName, String byteCodeString, boolean cacheBytecode)
{
ClassBC result = null;
if (byteCodeString != null)
{
String[] bytecodeLines = byteCodeString.split(S_NEWLINE);
try
{
result = parse(fqClassName, bytecodeLines, cacheBytecode);
}
catch (Throwable t)
{
logger.error("Exception parsing bytecode", t);
}
}
return result;
}
// TODO refactor this class - better stateful than all statics
public static ClassBC parse(String fqClassName, String[] bytecodeLines, boolean cacheBytecode)
{
ClassBC classBytecode = new ClassBC(fqClassName);
int pos = 0;
StringBuilder builder = new StringBuilder();
BytecodeSection section = BytecodeSection.NONE;
MemberSignatureParts msp = null;
MemberBytecode memberBytecode = null;
while (pos < bytecodeLines.length)
{
String line = bytecodeLines[pos].trim();
if (DEBUG_LOGGING_BYTECODE)
{
logger.debug("Line: '{}'", line);
}
BytecodeSection nextSection = getNextSection(line);
if (nextSection != null)
{
sectionFinished(fqClassName, section, msp, builder, memberBytecode, classBytecode);
section = changeSection(nextSection);
pos++;
if (pos < bytecodeLines.length)
{
line = bytecodeLines[pos].trim();
}
}
if (DEBUG_LOGGING_BYTECODE)
{
logger.debug("{} Line: {}", section, line);
}
switch (section)
{
case NONE:
if (couldBeMemberSignature(line))
{
msp = MemberSignatureParts.fromBytecodeSignature(fqClassName, line);
msp.setClassBC(classBytecode);
if (DEBUG_LOGGING_BYTECODE)
{
logger.debug("New signature: {}", msp);
}
memberBytecode = new MemberBytecode(classBytecode, msp);
if (DEBUG_LOGGING_BYTECODE)
{
logger.debug("Initialised new MemberBytecode");
}
}
else if (line.startsWith(S_BYTECODE_SIGNATURE))
{
buildClassGenerics(line, classBytecode);
}
else if (line.startsWith(S_BYTECODE_MINOR_VERSION))
{
int minorVersion = getVersionPart(line);
if (minorVersion != -1)
{
classBytecode.setMinorVersion(minorVersion);
}
}
else if (line.startsWith(S_BYTECODE_MAJOR_VERSION))
{
int majorVersion = getVersionPart(line);
if (majorVersion != -1)
{
classBytecode.setMajorVersion(majorVersion);
}
}
else if (line.startsWith(S_BYTECODE_SOURCE_FILE))
{
String sourceFilename = getSourceFile(line);
if (sourceFilename != null)
{
classBytecode.setSourceFile(sourceFilename);
if (cacheBytecode)
{
SourceMapper.addSourceClassMapping(classBytecode);
}
}
else
{
logger.warn("Couldn't parse source file from line {}", line);
}
}
break;
case INNERCLASSES:
InnerClassRelationship icr = InnerClassRelationship.parse(line);
if (icr != null)
{
if (fqClassName.equals(icr.getParentClass()))
{
classBytecode.addInnerClassName(icr.getChildClass());
}
}
else
{
section = changeSection(BytecodeSection.NONE);
pos--;
}
break;
case CODE:
section = performCODE(fqClassName, classBytecode, builder, section, msp, memberBytecode, line);
break;
case CONSTANT_POOL:
section = performConstantPool(fqClassName, classBytecode, builder, section, msp, memberBytecode, line);
break;
case LINETABLE:
section = performLINETABLE(fqClassName, classBytecode, builder, section, msp, memberBytecode, line);
break;
case RUNTIMEVISIBLEANNOTATIONS:
if (!isRunTimeVisibleAnnotation(line))
{
section = changeSection(BytecodeSection.NONE);
pos--;
}
break;
case LOCALVARIABLETABLE:
if (!isLocalVariableLine(line))
{
section = changeSection(BytecodeSection.NONE);
pos--;
}
break;
case STACKMAPTABLE:
if (!isStackMapTable(line))
{
section = changeSection(BytecodeSection.NONE);
pos--;
}
break;
case EXCEPTIONTABLE:
section = performEXCEPTIONTABLE(fqClassName, classBytecode, builder, section, msp, memberBytecode, line);
break;
}
pos++;
}
return classBytecode;
}
public static void buildClassGenerics(String line, ClassBC classBytecode)
{
StringBuilder keyBuilder = new StringBuilder();
StringBuilder valBuilder = new StringBuilder();
boolean inKey = false;
boolean inVal = false;
for (int i = 0; i < line.length(); i++)
{
char c = line.charAt(i);
if (c == C_OPEN_ANGLE)
{
inKey = true;
inVal = false;
}
else if (c == C_COLON)
{
if (inKey && !inVal)
{
inKey = false;
inVal = true;
}
}
else if (c == C_SEMICOLON)
{
if (!inKey && inVal)
{
String key = keyBuilder.toString();
String val = valBuilder.toString();
if (val.length() > 0)
{
val = val.substring(1); // string leading 'L'
val = val.replace(S_SLASH, S_DOT);
}
classBytecode.addGenericsMapping(key, val);
keyBuilder.setLength(0);
valBuilder.setLength(0);
inKey = true;
inVal = false;
}
}
else if (inKey)
{
keyBuilder.append(c);
}
else if (inVal)
{
valBuilder.append(c);
}
}
if (!inKey && inVal)
{
String key = keyBuilder.toString();
String val = valBuilder.toString();
if (val.length() > 0)
{
val = val.substring(1); // string leading 'L'
val = val.replace(S_SLASH, S_DOT);
}
classBytecode.addGenericsMapping(key, val);
keyBuilder.setLength(0);
valBuilder.setLength(0);
inKey = false;
inVal = false;
}
}
private static BytecodeSection performLINETABLE(String fqClassName, ClassBC classBytecode, StringBuilder builder,
BytecodeSection section, MemberSignatureParts msp, MemberBytecode memberBytecode, String line)
{
if (line.startsWith("line "))
{
builder.append(line).append(C_NEWLINE);
}
else
{
sectionFinished(fqClassName, BytecodeSection.LINETABLE, msp, builder, memberBytecode, classBytecode);
section = changeSection(BytecodeSection.NONE);
}
return section;
}
private static BytecodeSection performEXCEPTIONTABLE(String fqClassName, ClassBC classBytecode, StringBuilder builder,
BytecodeSection section, MemberSignatureParts msp, MemberBytecode memberBytecode, String line)
{
if (line.contains(" Class "))
{
builder.append(line).append(C_NEWLINE);
}
else if (line.replace(S_SPACE, S_EMPTY).equals("fromtotargettype"))
{
}
else
{
sectionFinished(fqClassName, BytecodeSection.EXCEPTIONTABLE, msp, builder, memberBytecode, classBytecode);
section = changeSection(BytecodeSection.NONE);
}
return section;
}
private static BytecodeSection performConstantPool(String fqClassName, ClassBC classBytecode, StringBuilder builder,
BytecodeSection section, MemberSignatureParts msp, MemberBytecode memberBytecode, String line)
{
if (!line.startsWith(S_HASH))
{
sectionFinished(fqClassName, BytecodeSection.CONSTANT_POOL, msp, builder, memberBytecode, classBytecode);
section = changeSection(BytecodeSection.NONE);
}
return section;
}
private static BytecodeSection performCODE(String fqClassName, ClassBC classBytecode, StringBuilder builder,
BytecodeSection section, MemberSignatureParts msp, MemberBytecode memberBytecode, final String line)
{
int firstColonIndex = line.indexOf(C_COLON);
if (firstColonIndex != -1)
{
String beforeColon = line.substring(0, firstColonIndex);
try
{
// line number ?
Integer.parseInt(beforeColon);
builder.append(line).append(C_NEWLINE);
}
catch (NumberFormatException nfe)
{
if (S_DEFAULT.equals(beforeColon))
{
// possibly inside a tableswitch or lookupswitch
builder.append(line).append(C_NEWLINE);
}
else
{
sectionFinished(fqClassName, BytecodeSection.CODE, msp, builder, memberBytecode, classBytecode);
section = changeSection(BytecodeSection.NONE);
}
}
}
else if (S_CLOSE_BRACE.equals(line.trim()))
{
// end of a tableswitch or lookupswitch
builder.append(line).append(C_NEWLINE);
}
return section;
}
private static boolean isRunTimeVisibleAnnotation(final String line)
{
return line.contains(": #");
}
private static boolean isLocalVariableLine(final String line)
{
return line.startsWith("Start") || (line.length() > 0 && Character.isDigit(line.charAt(0)));
}
private static boolean isStackMapTable(final String line)
{
String trimmedLine = line.trim();
return trimmedLine.startsWith("frame_type") || trimmedLine.startsWith("offset_delta") || trimmedLine.startsWith("locals")
|| trimmedLine.startsWith("stack");
}
private static boolean couldBeMemberSignature(String line)
{
return line.endsWith(");") || line.contains(" throws ") && line.endsWith(S_SEMICOLON)
|| line.startsWith(S_BYTECODE_STATIC_INITIALISER_SIGNATURE);
}
private static void sectionFinished(String fqClassName, BytecodeSection lastSection, MemberSignatureParts msp,
StringBuilder builder, MemberBytecode memberBytecode, ClassBC classBytecode)
{
if (DEBUG_LOGGING_BYTECODE)
{
logger.debug("sectionFinished: {}", lastSection);
}
if (lastSection == BytecodeSection.CODE)
{
List<BytecodeInstruction> instructions = parseInstructions(builder.toString());
if (memberBytecode != null)
{
memberBytecode.setInstructions(instructions);
classBytecode.addMemberBytecode(memberBytecode);
if (DEBUG_LOGGING_BYTECODE)
{
logger.debug("stored bytecode for:\n{}", msp);
}
}
else
{
logger.error("No member for these instructions");
for (BytecodeInstruction instr : instructions)
{
logger.error("{}", instr);
}
}
}
else if (lastSection == BytecodeSection.LINETABLE)
{
storeLineNumberTable(fqClassName, memberBytecode, builder.toString(), msp);
if (DEBUG_LOGGING_BYTECODE)
{
logger.debug("stored line number table for : {}", msp);
}
}
else if (lastSection == BytecodeSection.EXCEPTIONTABLE)
{
storeExceptionTable(fqClassName, memberBytecode, builder.toString(), msp);
if (DEBUG_LOGGING_BYTECODE)
{
logger.debug("stored exception table for : {}", msp);
}
}
builder.delete(0, builder.length());
}
private static BytecodeSection changeSection(BytecodeSection nextSection)
{
if (DEBUG_LOGGING_BYTECODE)
{
logger.debug("Changing section to: {}", nextSection);
}
return nextSection;
}
private static BytecodeSection getNextSection(final String line)
{
BytecodeSection nextSection = null;
if (line != null)
{
if (line.length() == 0)
{
nextSection = BytecodeSection.NONE;
}
for (Map.Entry<String, BytecodeSection> entry : sectionLabelMap.entrySet())
{
if (line.trim().startsWith(entry.getKey()))
{
nextSection = entry.getValue();
break;
}
}
}
return nextSection;
}
private static int getVersionPart(final String line)
{
int version = -1;
int colonPos = line.indexOf(C_COLON);
if (colonPos != -1 && colonPos != line.length() - 1)
{
String versionPart = line.substring(colonPos + 1).trim();
try
{
version = Integer.parseInt(versionPart);
}
catch (NumberFormatException nfe)
{
}
}
return version;
}
private static String getSourceFile(final String line)
{
String result = null;
int colonPos = line.indexOf(C_COLON);
if (colonPos != -1 && colonPos != line.length() - 1)
{
result = line.substring(colonPos + 1);
result = result.replace(S_DOUBLE_QUOTE, S_EMPTY).trim();
}
return result;
}
public static List<BytecodeInstruction> parseInstructions(final String bytecode)
{
List<BytecodeInstruction> bytecodeInstructions = new ArrayList<>();
if (DEBUG_LOGGING_BYTECODE)
{
logger.debug("Raw bytecode: '{}'", bytecode);
}
String[] lines = bytecode.split(S_NEWLINE);
boolean inSwitch = false;
BCParamSwitch table = new BCParamSwitch();
BytecodeInstruction instruction = null;
for (String line : lines)
{
line = line.trim();
if (DEBUG_LOGGING_BYTECODE)
{
logger.debug("parsing bytecode line: '{}' inSwitch: {}", line, inSwitch);
}
if (inSwitch)
{
if (S_CLOSE_BRACE.equals(line))
{
instruction.addParameter(table);
bytecodeInstructions.add(instruction);
inSwitch = false;
if (DEBUG_LOGGING_BYTECODE)
{
logger.debug("finished switch");
}
}
else
{
String[] parts = line.split(S_COLON);
if (parts.length == 2)
{
table.put(parts[0].trim(), parts[1].trim());
}
else
{
logger.error("Unexpected tableswitch entry: " + line);
}
}
}
else
{
try
{
Matcher matcher = PATTERN_BYTECODE_INSTRUCTION.matcher(line);
if (matcher.find())
{
instruction = new BytecodeInstruction();
String offset = matcher.group(1);
String mnemonic = matcher.group(2);
String paramString = matcher.group(3);
String comment = matcher.group(4);
if (mnemonic.endsWith("_w") && !Opcode.GOTO_W.equals(mnemonic) && !Opcode.JSR_W.equals(mnemonic)
&& !Opcode.LDC_W.equals(mnemonic) && !Opcode.LDC2_W.equals(mnemonic))
{
mnemonic = mnemonic.substring(0, mnemonic.length() - "_w".length());
}
instruction.setOffset(Integer.parseInt(offset));
instruction.setOpcode(Opcode.getByMnemonic(mnemonic));
if (comment != null && comment.trim().length() > 0)
{
instruction.setComment(comment.trim());
}
if (instruction.getOpcode() == Opcode.TABLESWITCH || instruction.getOpcode() == Opcode.LOOKUPSWITCH)
{
if (DEBUG_LOGGING_BYTECODE)
{
logger.debug("Found a table or lookup switch");
}
inSwitch = true;
}
else
{
if (paramString != null && paramString.trim().length() > 0)
{
processParameters(paramString.trim(), instruction);
}
bytecodeInstructions.add(instruction);
}
}
else
{
logger.error("could not parse bytecode: '" + line + "'");
}
}
catch (Exception e)
{
logger.error("Error parsing bytecode line: '" + line + "'", e);
}
}
}
return bytecodeInstructions;
}
private static void storeLineNumberTable(String fqClassName, MemberBytecode memberBytecode, String tableLines,
MemberSignatureParts msp)
{
String[] lines = tableLines.split(S_NEWLINE);
for (String line : lines)
{
// strip off 'line '
line = line.trim().substring(5);
String[] parts = line.split(S_COLON);
if (parts.length == 2)
{
String source = parts[0].trim();
String offset = parts[1].trim();
try
{
LineTableEntry entry = new LineTableEntry(Integer.parseInt(source), Integer.parseInt(offset));
memberBytecode.addLineTableEntry(entry);
}
catch (NumberFormatException nfe)
{
logger.error("Could not parse LineTableEntry {}", line, nfe);
}
}
else
{
logger.error("Could not split LineTableEntry line: {}", line);
}
}
}
private static void storeExceptionTable(String fqClassName, MemberBytecode memberBytecode, String exceptionLines,
MemberSignatureParts msp)
{
String[] lines = exceptionLines.split(S_NEWLINE);
for (String line : lines)
{
line = line.trim();
if (line.length() > 0)
{
String[] parts = line.split("\\s+");
if (parts.length == 5)
{
try
{
int from = Integer.parseInt(parts[0].trim());
int to = Integer.parseInt(parts[1].trim());
int target = Integer.parseInt(parts[2].trim());
String type = parts[4].trim();
ExceptionTableEntry entry = new ExceptionTableEntry(from, to, target, type);
memberBytecode.addExceptionTableEntry(entry);
}
catch (NumberFormatException nfe)
{
logger.error("Could not parse ExceptionTableEntry {}", line, nfe);
}
}
else
{
logger.error("Could not split ExceptionTableEntry line: '{}' got parts {}", line, parts.length);
}
}
}
}
private static void processParameters(String paramString, BytecodeInstruction instruction)
{
String[] parts = paramString.split(S_COMMA);
for (String part : parts)
{
IBytecodeParam parameter;
part = part.trim();
if (part.charAt(0) == C_HASH)
{
parameter = new BCParamConstant(part);
}
else
{
try
{
int value = Integer.parseInt(part);
parameter = new BCParamNumeric(value);
}
catch (NumberFormatException nfe)
{
parameter = new BCParamString(part);
}
}
instruction.addParameter(parameter);
}
}
}