package js.tinyvm;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.HashSet;
import js.common.ToolProgressMonitor;
import js.tinyvm.io.IByteWriter;
import js.tinyvm.util.HashVector;
/**
* Abstraction for dumped binary.
*/
public class Binary
{
// State that is written to the binary:
final RecordTable<WritableData> iEntireBinary = new RecordTable<WritableData>("binary", true, true);
final RecordTable<WritableData> iStaticStorage = new RecordTable<WritableData>("binary", true, true);
// Contents of binary:
final MasterRecord iMasterRecord = new MasterRecord(this);
RecordTable<ClassRecord> iClassTable = new RecordTable<ClassRecord>("class table", false, true);
RecordTable<StaticValue> iStaticState = new RecordTable<StaticValue>("static state", true, true);
RecordTable<StaticFieldRecord> iStaticFields = new RecordTable<StaticFieldRecord>("static fields", true, true);
RecordTable<ConstantRecord> iConstantTable = new RecordTable<ConstantRecord>("constants", false, true);
RecordTable<RecordTable<MethodRecord>> iMethodTables = new RecordTable<RecordTable<MethodRecord>>("methods", true, true);
RecordTable<RecordTable<ExceptionRecord>> iExceptionTables = new RecordTable<RecordTable<ExceptionRecord>>("exceptions", false, true);
RecordTable<RecordTable<InstanceFieldRecord>> iInstanceFieldTables = new RecordTable<RecordTable<InstanceFieldRecord>>("instance fields",
true, true);
final RecordTable<CodeSequence> iCodeSequences = new RecordTable<CodeSequence>("code", true, true);
RecordTable<ConstantValue> iConstantValues = new RecordTable<ConstantValue>("constant values", true,
true);
final RecordTable<EntryClassIndex> iEntryClassIndices = new RecordTable<EntryClassIndex>(
"entry class indices", true, true);
final RecordTable<InterfaceMap> iInterfaceMaps = new RecordTable<InterfaceMap>("interface", true, true);
// Other state:
final HashSet<Signature> iSpecialSignatures = new HashSet<Signature>();
final HashMap<String, ClassRecord> iClasses = new HashMap<String, ClassRecord>();
final HashVector<Signature> iSignatures = new HashVector<Signature>();
int usedClassCount = 0;
int markGeneration = 0;
boolean useAll = false;
// Optimal order for storing constants/statics etc. Note we store 4 byte
// items first to maximize the chance of using optimized load/store operations
// on them.
final int[] alignments = {4, 8, 2, 1};
int constOpLoads = 0;
int constNormLoads = 0;
int constWideLoads = 0;
int constString = 0;
int staticOpLoads = 0;
int staticNormLoads = 0;
int fieldOpOp = 0;
int fieldNormOp = 0;
int interfaceClasses = 0;
int usedInterfaceClasses = 0;
int implementedInterfaces = 0;
int usedImplementedInterfaces = 0;
/**
* Constructor.
* @param useAll true if all classes/methods etc. should be included
*/
public Binary (boolean useAll)
{
this.useAll = useAll;
}
/**
* Dump.
*
* @param writer
* @throws TinyVMException
*/
public void dump (IByteWriter writer) throws TinyVMException
{
iEntireBinary.dump(writer);
}
//
// TODO public interface
//
//
// TODO protected interface
//
//
// classes
//
/**
* Add a class.
*
* @param className class name with '/'
* @param classRecord
*/
protected void addClassRecord (String className, ClassRecord classRecord)
{
assert className != null: "Precondition: className != null";
assert classRecord != null: "Precondition: classRecord != null";
assert className.indexOf('.') == -1: "Precondition: className is in correct form";
iClasses.put(className, classRecord);
iClassTable.add(classRecord);
}
/**
* Has class in binary a public static void main (String[] args) method?
*
* @param className class name with '/'
* @return
*/
public boolean hasMain (String className)
{
assert className != null: "Precondition: className != null";
assert className.indexOf('.') == -1: "Precondition: className is in correct form";
ClassRecord pRec = getClassRecord(className);
return pRec.hasMethod(new Signature("main", "([Ljava/lang/String;)V"),
true);
}
/**
* Get class record with given signature.
*
* @param className class name with '/'
* @return class record or null if not found
*/
public ClassRecord getClassRecord (String className)
{
assert className != null: "Precondition: className != null";
assert className.indexOf('.') == -1: "Precondition: className is in correct form";
return iClasses.get(className);
}
/**
* Return the class the represents an array of the given type and dimension.
*
*
* @param elementClass
* @return class record or null if not found or the array is a primitive array.
* @throws TinyVMException
*/
public ClassRecord getClassRecordForArray (ClassRecord elementClass) throws TinyVMException
{
int dims = 1;
if (elementClass.isArray())
{
dims += elementClass.getArrayDimension();
}
int pSize = iClassTable.size();
for (int pIndex = 0; pIndex < pSize; pIndex++)
{
ClassRecord pRec = iClassTable.get(pIndex);
if (pRec.getArrayDimension() == dims && pRec.getArrayElementClass() == elementClass)
return pRec;
}
// Not found so we will create one...
String sig = "";
for(int i = 0; i < dims; i++)
sig += "[";
sig += elementClass.signature();
return ClassRecord.storeArrayClass(sig, iClasses, iClassTable, null, this);
}
/**
* Get index of class in binary by its signature.
*
* @param className class name with '/'
* @return index of class in binary or -1 if not found
*/
public int getClassIndex (String className)
{
assert className != null: "Precondition: className != null";
assert className.indexOf('.') == -1: "Precondition: className is in correct form";
return getClassIndex(getClassRecord(className));
}
/**
* Get index of class in binary by its class record.
*
* @param classRecord
* @return index of class in binary or -1 if not found
*/
public int getClassIndex (ClassRecord classRecord)
{
if (classRecord == null)
{
return -1;
}
return iClassTable.indexOf(classRecord);
}
/**
* Mark the given class as actually used.
*
* @param classRecord the class to be marked
* @param instance
*/
public void markClassUsed(ClassRecord classRecord, boolean instance)
{
if (instance && !classRecord.instanceUsed())
{
classRecord.markInstanceUsed();
usedClassCount++;
}
if (!classRecord.used())
{
classRecord.markUsed();
usedClassCount++;
}
}
/**
* Return the current marking generation. This is used to ensure that for
* each new iteration (or generation) of the recursive mark we will
* walk all of the code at least once.
* @return current generation
*/
public int getGeneration()
{
return markGeneration;
}
//
// constants
//
/**
* Get constant record with given index.
*
* @param index
* @return constant record or null if not found
*/
public ConstantRecord getConstantRecord (int index)
{
assert index >= 0: "Precondition: index >= 0";
return iConstantTable.get(index);
}
/**
* Get index of constant in binary by its constant record.
*
* @param constantRecord
* @return index of constant in binary or -1 if not found
*/
public int getConstantIndex (ConstantRecord constantRecord)
{
if (constantRecord == null)
{
return -1;
}
return iConstantTable.indexOf(constantRecord);
}
/**
* Return true if unused methods/classes etc. should still be included in
* the output file.
* @return
*/
public boolean useAll()
{
return useAll;
}
//
// processing
//
/**
* Create closure.
*
* @param entryClassNames names of entry class with '/'
* @param classPath class path
* @param all do not filter classes?
* @return
* @throws TinyVMException
*/
public static Binary createFromClosureOf (String[] entryClassNames,
ClassPath classPath, boolean all) throws TinyVMException
{
Binary result = new Binary(all);
// From special classes and entry class, store closure
result.processClasses(entryClassNames, classPath);
// Store special signatures
result.processSpecialSignatures();
result.processConstants();
result.processMethods();
result.processFields();
// Remove unused methods/classes/fields/constants.
result.markUsed(entryClassNames);
result.processOptimizedClasses();
result.processOptimizedConstants();
result.processOptimizedMethods();
result.processOptimizedFields();
// Copy code as is (first pass)
result.processCode(false);
result.storeComponents();
result.initOffsets();
// Post-process code after offsets are set (second pass)
result.processCode(true);
assert result != null: "Postconditon: result != null";
return result;
}
public void processClasses (String[] entryClassNames, ClassPath classPath)
throws TinyVMException
{
assert entryClassNames != null: "Precondition: entryClassNames != null";
assert classPath != null: "Precondition: classPath != null";
ArrayList<String> pInterfaceMethods = new ArrayList<String>();
// Add special all classes first
String[] specialClasses = SpecialClassConstants.CLASSES;
//_logger.log(Level.INFO, "Starting with " + specialClasses.length
// + " special classes.");
for (int i = 0; i < specialClasses.length; i++)
{
String className = specialClasses[i];
if (className.charAt(0) == '[')
ClassRecord.storeArrayClass(className, iClasses, iClassTable, classPath, this);
else if (className.indexOf('/') != -1)
addClassRecord(className, ClassRecord.getClassRecord(className, classPath, this));
else
addClassRecord(className, PrimitiveClassRecord.getClassRecord(className, this, (byte)i));
}
// Now add entry classes
// _logger.log(Level.INFO, "Starting with " + entryClassNames.length
// + " entry classes.");
for (int i = 0; i < entryClassNames.length; i++)
{
String className = entryClassNames[i];
ClassRecord classRecord = ClassRecord.getClassRecord(className,
classPath, this);
// Convert name into standard form.
className = classRecord.getName().replace('.', '/');
classRecord = ClassRecord.getClassRecord(className, classPath, this);
entryClassNames[i] = className;
addClassRecord(className, classRecord);
classRecord.useAllMethods();
// Update table of indices to entry classes
iEntryClassIndices.add(new EntryClassIndex(this, className));
}
// Now add the closure.
// _logger.log(Level.INFO, "Starting with " + iClassTable.size()
// + " classes.");
// Yes, call iClassTable.size() in every pass of the loop.
for (int pIndex = 0; pIndex < iClassTable.size(); pIndex++)
{
ClassRecord classRecord = iClassTable.get(pIndex);
classRecord.storeReferredClasses(iClasses, iClassTable, classPath,
pInterfaceMethods);
}
// Initialize indices and flags
int pSize = iClassTable.size();
for (int pIndex = 0; pIndex < pSize; pIndex++)
{
ClassRecord classRecord = iClassTable.get(pIndex);
for (int i = 0; i < pInterfaceMethods.size(); i++)
{
classRecord.addUsedMethod(pInterfaceMethods.get(i));
}
classRecord.iIndex = pIndex;
classRecord.initFlags();
classRecord.initParent();
}
}
/**
* Optimize the number and order of classes.
* We make a pass of all of the classes and select only those that are
* actually used. We also optimize the order of the classes in an attempt
* to minimize the size of the interface maps.
* @throws TinyVMException
*/
public void processOptimizedClasses () throws TinyVMException
{
RecordTable<ClassRecord> iNewClassTable = new RecordTable<ClassRecord>("class table", false, true);
int pSize = iClassTable.size();
// First copy over the special classes
for (int pIndex = 0; pIndex < SpecialClassConstants.CLASSES.length; pIndex++)
{
ClassRecord classRecord = iClassTable.get(pIndex);
iNewClassTable.add(classRecord);
}
// Now we add in any classes that have interfaces. This keeps them all
// together and keeps the interface map small. Note duplicates are
// not allowed so we can add the same entry multiple times.
for (int pIndex = SpecialClassConstants.CLASSES.length; pIndex < pSize; pIndex++)
{
ClassRecord classRecord = iClassTable.get(pIndex);
if (classRecord.isInterface() && (useAll() || classRecord.used()))
classRecord.storeOptimizedImplementingClasses(iNewClassTable);
}
// now add in the rest of the used classes
for (int pIndex = SpecialClassConstants.CLASSES.length; pIndex < pSize; pIndex++)
{
ClassRecord classRecord = iClassTable.get(pIndex);
if (useAll() || classRecord.used())
iNewClassTable.add(classRecord);
}
iClassTable = iNewClassTable;
pSize = iClassTable.size();
// Now we selected all of the classes we can fix up any linkages (which
// may use the class index) and create the interface maps.
for (int pIndex = 0; pIndex < pSize; pIndex++)
{
ClassRecord classRecord = iClassTable.get(pIndex);
classRecord.initParent();
if (classRecord.isInterface())
classRecord.storeInterfaceMap(iInterfaceMaps);
}
}
public void markUsed (String[] entryClassNames)
throws TinyVMException
{
/* First stage of unused method/class/field elimination.
* Starting with the callable root methods we need to mark all callable
* methods. We recursively walk the code for each method marking and
* walking new methods as we come to them.
* As we walk the code we also mark all used classes and fields.
* We need to take particular care with interfaces and with
* over-ridden methods to ensure that all possible destinations are
* included.
*/
/* For interfaces we need to ensure that for every method in an interface
* that ends up being marked we locate all possible implementations
* of that method. To do that we search all classes for those that
* implement an interface and associate those classes with the interface.
* Then when we mark a method in the interface we can mark all possible
* implementations.
*
* We also need to handle marking methods that may be over-ridden by a
* method in a sub class. We locate all such methods and link them to the
* "super-method" if this gets marked we also mark the sub-methods.
*/
int pSize = iClassTable.size();
// Mark special classes as being used (they may be generated by the vm
String[] specialClasses = SpecialClassConstants.CLASSES;
for (int i = 0; i < specialClasses.length; i++)
{
String className = specialClasses[i];
ClassRecord classRecord = getClassRecord(className);
classRecord.markUsed();
classRecord.markInstanceUsed();
}
// Add the run method that is called directly from the vm
Signature staticInit = new Signature("<clinit>()V");
Signature runMethod = new Signature("run()V");
Signature mainMethod = new Signature("main", "([Ljava/lang/String;)V");
// Now add entry classes
for (int i = 0; i < entryClassNames.length; i++)
{
ClassRecord classRecord = getClassRecord(entryClassNames[i]);
classRecord.markUsed();
classRecord.markInstanceUsed();
//classRecord.markMethods();
}
// We now add in the static initializers of all marked classes.
// We also add in the special entry points that may be called
// directly from the VM.
// Note: The set of used classes may increase as a result of marking.
// in which case we do the whole thing over again.
int classCount;
do {
classCount = usedClassCount;
markGeneration++;
// First make sure all interfaces implementors and hidden
// methods are exposed
for (int pIndex = 0; pIndex < pSize; pIndex++)
{
ClassRecord classRecord = iClassTable.get(pIndex);
if (classRecord.used())
{
if (classRecord.instanceUsed())
{
classRecord.addInterfaces(classRecord);
classRecord.findHiddenMethods();
}
}
}
// Now recursively mark any classes that can be called directly by
// the VM
for (int pIndex = 0; pIndex < pSize; pIndex++)
{
ClassRecord classRecord = iClassTable.get(pIndex);
if (classRecord.used())
{
if (classRecord.hasMethod(runMethod, false))
{
MethodRecord pRec = classRecord.getMethodRecord(runMethod);
classRecord.markMethod(pRec, true);
}
if (classRecord.hasStaticInitializer())
{
MethodRecord pRec = classRecord.getMethodRecord(staticInit);
classRecord.markMethod(pRec, true);
}
}
}
// Finally mark starting from all of the entry classes
for (int i = 0; i < entryClassNames.length; i++)
{
ClassRecord classRecord = getClassRecord(entryClassNames[i]);
if (classRecord.hasMethod(mainMethod, true))
{
MethodRecord pRec = classRecord.getMethodRecord(mainMethod);
classRecord.markMethod(pRec, true);
}
}
} while (classCount != usedClassCount);
}
public void processSpecialSignatures ()
{
for (int i = 0; i < SpecialSignatureConstants.SIGNATURES.length; i++)
{
Signature pSig = new Signature(SpecialSignatureConstants.SIGNATURES[i]);
iSignatures.addElement(pSig);
iSpecialSignatures.add(pSig);
}
}
public boolean isSpecialSignature (Signature aSig)
{
return iSpecialSignatures.contains(aSig);
}
public void processConstants () throws TinyVMException
{
int pSize = iClassTable.size();
for (int pIndex = 0; pIndex < pSize; pIndex++)
{
ClassRecord pRec = iClassTable.get(pIndex);
pRec.storeConstants(iConstantTable, iConstantValues);
}
}
/**
* Store constant values in an optimal fashion.
* We only include constants that are actually used. We also arrange to store
* constant values correctly aligned so that they can be accessed directly
* by the VM. We order the constants to allow fast access to the commanly used
* int/float types.
* @throws TinyVMException
*/
public void processOptimizedConstants () throws TinyVMException
{
int pSize = iConstantTable.size();
RecordTable<ConstantRecord> iOptConstantTable = new RecordTable<ConstantRecord>("constants", false, true);
RecordTable<ConstantValue> iOptConstantValues = new RecordTable<ConstantValue>("constant values", true, true);
for(int align : alignments)
{
for (int pIndex = 0; pIndex < pSize; pIndex++)
{
ConstantRecord pRec = iConstantTable.get(pIndex);
if (pRec.constantValue().getAlignment() == align && (useAll() || pRec.used()))
{
iOptConstantTable.add(pRec);
iOptConstantValues.add(pRec.constantValue());
}
}
}
iConstantTable = iOptConstantTable;
iConstantValues = iOptConstantValues;
}
/**
* Calls storeMethods on all the classes of the closure previously computed
* with processClasses.
*
* @throws TinyVMException
*/
public void processMethods () throws TinyVMException
{
int pSize = iClassTable.size();
for (int pIndex = 0; pIndex < pSize; pIndex++)
{
ClassRecord classRecord = iClassTable.get(pIndex);
classRecord.storeMethods(iMethodTables, iExceptionTables, iSignatures,
useAll());
}
}
public void processOptimizedMethods () throws TinyVMException
{
/* This is the second stage of the unused methods elimination code.
* We need to re-create the method and exception tables so that they
* only contain methods that are actually called.
*/
int pSize = iClassTable.size();
// We need an optimized version of the method and exception tables
// so create new ones and repopulate.
iMethodTables = new RecordTable<RecordTable<MethodRecord>>("methods", true, true);
iExceptionTables = new RecordTable<RecordTable<ExceptionRecord>>("exceptions", false, true);
for (int pIndex = 0; pIndex < pSize; pIndex++)
{
ClassRecord classRecord = iClassTable.get(pIndex);
classRecord.storeOptimizedMethods(iMethodTables, iExceptionTables, iSignatures);
}
}
public void processFields () throws TinyVMException
{
int pSize = iClassTable.size();
for (int pIndex = 0; pIndex < pSize; pIndex++)
{
ClassRecord pRec = iClassTable.get(pIndex);
pRec.storeFields(iInstanceFieldTables, iStaticFields, iStaticState);
}
}
public void printInterfaces () throws TinyVMException
{
int pSize = iClassTable.size();
for (int pIndex = 0; pIndex < pSize; pIndex++)
{
ClassRecord pRec = iClassTable.get(pIndex);
if (pRec.isInterface() && pRec.used())
{
System.out.println("Interface: " + pRec.iName + " is implemented by:");
for(ClassRecord cr : pRec.iImplementedBy)
{
System.out.println("Active class: " + cr.iName + " id " + this.getClassIndex(cr));
}
}
}
}
public void processOptimizedFields () throws TinyVMException
{
int pSize = iClassTable.size();
// We need an optimized version of the static tables
// so create new ones and repopulate.
iStaticState = new RecordTable<StaticValue>("static state", true, true);
iStaticFields = new RecordTable<StaticFieldRecord>("static fields", true, true);
iInstanceFieldTables = new RecordTable<RecordTable<InstanceFieldRecord>>("instance fields", true, true);
for(int align : alignments)
{
for (int pIndex = 0; pIndex < pSize; pIndex++)
{
ClassRecord pRec = iClassTable.get(pIndex);
pRec.storeOptimizedStaticFields(iStaticFields, iStaticState, align);
}
}
for (int pIndex = 0; pIndex < pSize; pIndex++)
{
ClassRecord pRec = iClassTable.get(pIndex);
pRec.storeOptimizedFields(iInstanceFieldTables);
}
}
public void processCode (boolean aPostProcess) throws TinyVMException
{
int pSize = iClassTable.size();
for (int pIndex = 0; pIndex < pSize; pIndex++)
{
ClassRecord pRec = iClassTable.get(pIndex);
pRec.storeCode(iCodeSequences, aPostProcess);
}
}
//
// storing
//
public void storeComponents ()
{
// Master record and class table are always the first two, all
// tables are aligned on 4 byte boundaries.
iEntireBinary.add(iMasterRecord);
iEntireBinary.add(iClassTable);
// We do not need to store the static fields, just calculate layout
iStaticStorage.add(iStaticState);
iEntireBinary.add(iStaticFields);
iEntireBinary.add(iConstantTable);
iEntireBinary.add(iMethodTables);
iEntireBinary.add(iExceptionTables);
iEntireBinary.add(iInstanceFieldTables);
iEntireBinary.add(iConstantValues);
iEntireBinary.add(iInterfaceMaps);
iEntireBinary.add(iEntryClassIndices);
iEntireBinary.add(iCodeSequences);
}
public void initOffsets () throws TinyVMException
{
iEntireBinary.initOffset(0);
iStaticStorage.initOffset(0);
}
public void setRunTimeOptions(int opt)
{
iMasterRecord.setRunTimeOptions(opt);
}
public int getTotalNumMethods ()
{
int pTotal = 0;
int pSize = iMethodTables.size();
for (int i = 0; i < pSize; i++)
{
pTotal += iMethodTables.get(i).size();
}
return pTotal;
}
public int getTotalNumInstanceFields ()
{
int pTotal = 0;
int pSize = iInstanceFieldTables.size();
for (int i = 0; i < pSize; i++)
{
pTotal += iInstanceFieldTables.get(i).size();
}
return pTotal;
}
public int getTotalNumExceptionRecords()
{
int pTotal = 0;
int pSize = iExceptionTables.size();
for (int i = 0; i < pSize; i++)
{
pTotal += iExceptionTables.get(i).size();
}
return pTotal;
}
// private static final Logger _logger = Logger.getLogger("TinyVM");
public void log(ToolProgressMonitor monitor) throws TinyVMException {
// all classes
for (int pIndex = 0; pIndex < iClassTable.size(); pIndex++)
{
ClassRecord pRec = iClassTable.get(pIndex);
monitor.log("Class " + pIndex + ": " + pRec.getName());
}
int pSize = iMethodTables.size();
int methodNo = 0;
for (int i = 0; i < pSize; i++)
{
RecordTable<MethodRecord> rt = iMethodTables.get(i);
int cnt = rt.size();
for(int j = 0; j < cnt; j++)
{
MethodRecord mr = rt.get(j);
if ((mr.iFlags & TinyVMConstants.M_NATIVE) == 0)
monitor.log("Method " + methodNo + ": Class: " + mr.iClassRecord.getName() + " Signature: " +
(iSignatures.elementAt(mr.iSignatureId)).getImage() + " PC " + mr.getCodeStart() + " Signature id " + mr.iSignatureId);
else
monitor.log("Method " + methodNo + ": Class: " + mr.iClassRecord.getName() + " Signature: " +
(iSignatures.elementAt(mr.iSignatureId)).getImage() + " Native id " + mr.iSignatureId);
methodNo++;
}
}
monitor.log("Master record : " + iMasterRecord.getLength() + " bytes.");
monitor.log("Class records : " + iClassTable.size() + " (" + iClassTable.getLength() + " bytes).");
monitor.log("Field records : " + getTotalNumInstanceFields() + " (" + iInstanceFieldTables.getLength() + " bytes).");
monitor.log("Static fields : " + iStaticFields.size() + " (" + iStaticFields.getLength() + " bytes).");
monitor.log("Static state : " + iStaticState.size() + " (" + iStaticState.getLength() + " bytes).");
monitor.log("Constant records : " + iConstantTable.size() + " (" + iConstantTable.getLength() + " bytes).");
monitor.log("Constant values : " + iConstantValues.size() + " (" + iConstantValues.getLength() + " bytes).");
monitor.log("Method records : " + getTotalNumMethods() + " (" + iMethodTables.getLength() + " bytes).");
monitor.log("Exception records: " + getTotalNumExceptionRecords() + " (" + iExceptionTables.getLength() + " bytes).");
monitor.log("Interface maps : " + iInterfaceMaps.size() + " (" + iInterfaceMaps.getLength() + " bytes).");
monitor.log("Code : " + iCodeSequences.size() + " (" + iCodeSequences.getLength() + " bytes).");
monitor.log("Total : " + iEntireBinary.getLength() + " bytes.");
monitor.log("Run time options : " + iMasterRecord.getRunTimeOptions());
monitor.log("Constant loads : " + this.constNormLoads + "N " + this.constOpLoads + "O " + this.constWideLoads + "W " + this.constString + "S");
monitor.log("Static load/store: " + this.staticNormLoads + "N " + this.staticOpLoads + "O");
monitor.log("Field load/store: " + this.fieldNormOp + "N " + this.fieldOpOp + "O");
//printInterfaces();
}
}