package com.openMap1.mapper.actions;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.List;
import org.w3c.dom.Element;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import com.openMap1.mapper.query.DataSource;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.core.NamespaceSet;
import com.openMap1.mapper.core.StructureMismatch;
import com.openMap1.mapper.core.SchemaMismatch;
import com.openMap1.mapper.core.SemanticMismatch;
import com.openMap1.mapper.core.TranslationIssue;
import com.openMap1.mapper.core.ValidationIssue;
import com.openMap1.mapper.core.RunIssue;
import com.openMap1.mapper.core.TranslationSummaryItem;
import com.openMap1.mapper.core.CompilationIssue;
import com.openMap1.mapper.core.Xpth;
import com.openMap1.mapper.core.XpthException;
import com.openMap1.mapper.util.EclipseFileUtil;
import com.openMap1.mapper.util.FileUtil;
import com.openMap1.mapper.util.MapperValidator;
import com.openMap1.mapper.util.EcoreMatcher;
import com.openMap1.mapper.util.SystemMessageChannel;
import com.openMap1.mapper.util.Timer;
import com.openMap1.mapper.util.XMLUtil;
import com.openMap1.mapper.reader.MDLXOReader;
import com.openMap1.mapper.reader.GenericEMFInstanceFactoryImpl;
import com.openMap1.mapper.reader.EMFInstanceFactory;
import com.openMap1.mapper.reader.XOReader;
import com.openMap1.mapper.views.DataSourceView;
import com.openMap1.mapper.views.WorkBenchUtil;
import com.openMap1.mapper.writer.ProcedureCompiler;
import com.openMap1.mapper.writer.XMLWriter;
import com.openMap1.mapper.writer.XMLObjectGetter;
import com.openMap1.mapper.writer.objectGetter;
import com.openMap1.mapper.MappedStructure;
/**
* This class makes a comprehensive test of translations between the N active data
* sources (with codes A,B, C..). It tests all self-translations (A=>A via the class model),
* all pairwise translations A=>B, all simple round-trips A=>B=>A,
* and one 'grand round trip' A=>B=>C=>A.
*
* It does this by going through the following steps:
*
* (0) Check that there are some active data sources, and choose the first active data source
* for each mapping set to use in the tests
*
* (1) Clear all files (resulting from previous tests) from the 'Tests' folder. Clear
* the Translation Test Summary and Translation Issue views.
*
* (2) Validate each mapping set and prepare write procedure files (*.wproc) for all mapping sets involved,
* in the 'Translators' folder.
*
* (3) Do all translations, putting the results in the 'Tests' folder
*
* (4) Test that each translation source or result conforms to the structure recorded in
* its mapping set, or record discrepancies
* (done twice - once against the schema, once against the mapped structure)
*
* (5) Make Ecore model instances from all translation sources and targets,
* putting them in the 'Tests' folder
*
* (6) For every translation, test that the result Ecore model instance
* is a subset of the source Ecore instance, or record discrepancies
*
* (7) For every translation, check that the gaps in the result ECore instance
* (with respect to the source Ecore instance) are all expected because of gaps
* in the mappings (eg for a self-translation A=>A, there should be no gaps);
* or record discrepancies
*
* (8) Summarise results for each tested translation in the Translation Summary view,
* and show all discrepancies in the Test Issues view. Save these views in the Tests folder.
*
* @author robert
*
*/
public class TranslationTestAction extends Action{
private boolean validateMappingSets = true;
private boolean tracing = false; // for a trace of general progress
private boolean doRunTracing = false; // for a trace of the XML Writer
private Timer timer;// for performance testing
private Shell shell; // for error messages
private DataSourceView dataSourceView;
private Vector<DataSource> chosenSources = new Vector<DataSource>();
private IProject theProject;
private IFolder testFolder;
// all the chains of translation that need to be done to complete the tests.
private Vector<TranslationChain> translationChains = new Vector<TranslationChain>();
// all mismatches of structure or semantics between translation result file (or source files)
private Vector<TranslationIssue> allMismatches = new Vector<TranslationIssue>();
// for each source or translation result, keep a summary of all translation issues
private Hashtable<String,TranslationSummaryItem> translationSummaryItems;
// all Ecore model instances, keyed by the name root, eg 'ABC'
private Hashtable<String,Resource> ecoreInstances = new Hashtable<String,Resource>();
//-----------------------------------------------------------------------------------------
// constructor
//-----------------------------------------------------------------------------------------
public TranslationTestAction(Shell shell, DataSourceView dataSourceView)
{
this.shell = shell;
this.dataSourceView = dataSourceView;
setText("Translation Test");
setToolTipText("Test generated translations by traverses and round-trips between all active data sources");
setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().
getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK));
// setActionDefinitionId("TranslationTest");
timer = new Timer("Translation Test");
}
public void run() {
String failureMessage = "";
try{
// initialise
trace("Start translation test");
timer.start(Timer.TRANSLATION_TEST);
/* (0) Check that there are some active data sources, and choose the first active data source
* for each mapping set to use in the tests. No chance to reset the password for an RDB source */
dataSourceView.refreshAllActiveSources(false);
if (dataSourceView.getActiveDataSources().size() == 0)
{showMessage("There are no active data sources with which to test translations");return;}
failureMessage = "Failed to choose data sources for test: ";
chooseSourcesForTest();
trace("Refreshed data sources");
/* (1) Clear out the 'Tests' folder of the appropriate project (the project
* which contains the common class model of all the data sources),
* and do other initialisation */
failureMessage = "Failed to clear out test folder: ";
clearTestFolder();
allMismatches = new Vector<TranslationIssue>();
translationSummaryItems = new Hashtable<String,TranslationSummaryItem>();
ecoreInstances = new Hashtable<String,Resource>();
trace("Cleared test folder");
/* (2) Prepare write procedure files for all mapping sets involved */
failureMessage = "Failed to prepare write procedures: ";
prepareWriteProcedures();
trace("Prepared write procedures");
Runtime.getRuntime().gc();
/* (3) Do all translations, putting the results in the 'Tests' folder */
failureMessage = "Exception when doing translations: ";
doTranslations();
if (tracing) timer.report(); // first timings before making EMF instances
trace("Done all translations");
/* (4) Test that each translation result conforms to the structure recorded in
* its mapping set, or record discrepancies */
failureMessage = "Exception when testing result structures: ";
testResultStructures();
trace("Tested result structures");
/* (5) Make Ecore model instances from all translation sources and targets,
* putting them in the 'Tests' folder */
failureMessage = "Exception when creating Ecore model instances: ";
makeECoreModelInstances();
trace("Made Ecore model instances");
/* (6) For every translation, test that the result Ecore model instance
* is a subset of the source Ecore instance, or record discrepancies */
failureMessage = "Exception when comparing Ecore model instances: ";
compareSourceAndTargetEcoreInstances();
trace("Compared source and target instances");
/* (7) For every translation, check that the gaps in the result ECore instance
* (with respect to the source Ecore instance) are all expected because of gaps
* in the mappings (eg for a self-translation A=>A, there should be no gaps);
* or record discrepancies */
failureMessage = "Exception checking gaps in target instances: ";
checkGapsInTargetECoreInstances();
trace("Checked gaps in target instances");
/* (8) Summarise results for each tested translation in the Translation Summary view,
* and show all discrepancies in the test discrepancy view. Save these views in the Tests folder. */
timer.start(Timer.SHOW_VIEW);
showTranslationSummaryView();
timer.stop(Timer.SHOW_VIEW);
trace("Shown Translation Summary View");
timer.stop(Timer.TRANSLATION_TEST);
if (tracing) timer.report();
}
catch (Exception ex)
{
showMessage(failureMessage + ex.getMessage());
ex.printStackTrace();
showTranslationSummaryView();
return;
}
}
private void chooseSourcesForTest() throws MapperException
{
chosenSources = new Vector<DataSource>();
// to avoid choosing more than one data source for any mapping set
Hashtable<String,XOReader> mappingSetsByURI = new Hashtable<String,XOReader>();
for (Iterator<DataSource> it = dataSourceView.getActiveDataSources().iterator(); it.hasNext();)
{
DataSource ds = it.next();
// happens once per mapping set; choose the first active data source per mapping set
if (mappingSetsByURI.get(ds.mappingSetURIString()) == null)
{
mappingSetsByURI.put(ds.mappingSetURIString(), ds.getReader());
chosenSources.add(ds);
}
}
}
/* (1) Clear out the 'Tests' folder of the appropriate project (the project
* which contains the common class model of all the data sources) */
private void clearTestFolder() throws CoreException
{
theProject = chosenSources.get(0).getProject();
testFolder = theProject.getFolder("Tests");
if (!testFolder.exists())
{showMessage("There is no test folder in project '" + theProject.getName() + "'");return;}
// deletes have to be done in reverse order (length decreases)
int start = testFolder.members().length -1;
for (int i = start ; i > -1; i--)
{
IResource ir = testFolder.members()[i];
ir.delete(true, null);
}
}
/* (2)Validate all mapping sets involved, and prepare write procedure files for them */
private void prepareWriteProcedures() throws MapperException, CoreException
{
for (Iterator<DataSource> it = chosenSources.iterator(); it.hasNext();)
{
DataSource ds = it.next();
MappedStructure mainMappedStructure = ds.getReader().ms();
ds.getReader().giveTimer(timer, true);
timer.start(Timer.COMPILE);
// make and note a translation summary item for this source
TranslationSummaryItem tsItem = new TranslationSummaryItem(ds.getCode());
translationSummaryItems.put(ds.getCode(), tsItem);
// iterate over the main mapping set for the data source, and any others it imports
for (Enumeration<MappedStructure> en = mainMappedStructure.getAllImportedMappingSets().elements(); en.hasMoreElements();)
{
MappedStructure mappedStructure = en.nextElement();
// (possibly) validate the mapping set and record any diagnostics
if (validateMappingSet(mappedStructure))
{
trace("Validating " + mappedStructure.eResource().getURI().toString());
validateMappingSet(mappedStructure,tsItem, ds.getCode());
trace("Validated");
}
// make the procedures
trace("Making write procedure for " + mappedStructure.eResource().getURI().toString());
ProcedureCompiler procWriter = MakeTranslationActionDelegate.makeProcedureFile(mappedStructure);
noteCompileIssues(procWriter,tsItem,ds.getCode());
trace("Made or found write procedure for " + mappedStructure.eResource().getURI().toString());
}
timer.stop(Timer.COMPILE);
}
}
/**
* @param mappedStructure a mapping set
* @return true if it is to be validated, false if not.
* V3 mapping sets, which are auto-generated in specific named folders, are
* not to be validated, because:
* (a) being auto-generated, they have no faults; or if they do, the user can do nothing about them
* (b) There are lots of them, so Eclipse will run out of memory validating them
*/
private boolean validateMappingSet(MappedStructure mappedStructure)
{
boolean validate = validateMappingSets;
String location = mappedStructure.eResource().getURI().toString();
StringTokenizer st = new StringTokenizer(location,"/");
while (st.hasMoreTokens())
{
String folder = st.nextToken();
if (folder.equals("V3DataTypes")) validate = false;
if (folder.equals("V3RMIMs")) validate = false;
}
return validate;
}
/**
* validate a mapping set and note any issues for display in the translation issues view
* @param mappedStructure the mapping set
*/
private void validateMappingSet(MappedStructure mappedStructure, TranslationSummaryItem tsItem, String code)
{
String mappingSetName = FileUtil.getFileName(mappedStructure.eResource().getURI().toString());
// set up for validation
MapperValidator mv = new MapperValidator();
BasicDiagnostic allDiagnostics = new BasicDiagnostic();
Map<Object,Object> context = null;
// validate all nodes in the mapped structure tree
for (Iterator<EObject> it = mappedStructure.eAllContents(); it.hasNext();)
{
EObject node = it.next();
mv.publicValidate(node.eClass().getClassifierID(), node, allDiagnostics,context);
}
// key - issues description, to remove duplicates
Hashtable<String,ValidationIssue> issues = new Hashtable<String,ValidationIssue>();
// collect the validation issues arising, removing duplicates
int remove = "com.openMap1.mapper.impl.".length();
for (Iterator<Diagnostic> it = allDiagnostics.getChildren().iterator();it.hasNext();)
{
Diagnostic d = it.next();
String identifier = d.getData().get(0).getClass().getName().substring(remove);
ValidationIssue vi = new ValidationIssue(d.getCode(),identifier,d.getMessage(),mappingSetName);
vi.setCode(code);
ValidationIssue vprev = issues.get(vi.description());
if (vprev != null) {vprev.addOccurrence();}
else issues.put(vi.description(), vi);
}
// note the issues against the translation summary item
for (Enumeration<ValidationIssue> en = issues.elements();en.hasMoreElements();)
{
ValidationIssue vi = en.nextElement();
allMismatches.add(vi);
tsItem.addValidationIssue(vi);
}
}
private void noteCompileIssues(ProcedureCompiler procWriter, TranslationSummaryItem tsItem, String code)
{
for (Enumeration<String> en = procWriter.getCompilationIssues().keys();en.hasMoreElements();)
{
String pathString = en.nextElement();
List<CompilationIssue> vci = procWriter.getCompilationIssues().get(pathString);
for (Iterator<CompilationIssue> it = vci.iterator(); it.hasNext();)
{
CompilationIssue ci = it.next();
ci.setCode(code);
allMismatches.add(ci);
tsItem.addCompilationIssue(ci);
}
}
}
/* (3) Do all translations, putting the results in the 'Tests' folder */
private void doTranslations() throws MapperException, CoreException
{
/* (a) copy each source file into the test folder,
* with names like 'A.xml' using short codes for Data Sources.
* (the structure of each source file gets checked against its structure definition later)*/
for (Iterator<DataSource> it = chosenSources.iterator(); it.hasNext();)
{
DataSource ds = it.next();
/* get the extension (with no initial '*') for files before
* any 'in' wrapper transform. This will be '.xml' of '.txt' */
IFile newFile = testFolder.getFile(new Path(ds.getCode()+ ds.getExtension()));
newFile.create(ds.getInstanceFile().getContents(), true, null);
/* if any data source uses a wrapper transform, store the in-transformed version
* of the file in the tests folder (for user investigations; not used otherwise) */
if (ds.getReader().ms().hasWrapperClass())
{
Element inFileRoot = ds.getReader().ms().getXMLRoot(ds.getInstanceFile().getContents());
IFile newInFile = testFolder.getFile(new Path(ds.getCode()+ "_in.xml"));
EclipseFileUtil.writeOutputResource(inFileRoot.getOwnerDocument(), newInFile, true);
}
}
trace("Created source file copies for translation");
/* (b) make a list of all translation chains that will be done */
makeTranslationChainList();
trace("Made translation chain lists");
/* (c) do all the translations; for each one, make a translation
* summary item for the final result */
for (int t = 0; t < translationChains.size(); t++)
{
TranslationChain tc = translationChains.get(t);
tc.doTranslationChain();
trace("Done translation chain " + t);
}
}
private void makeTranslationChainList()
{
translationChains = new Vector<TranslationChain>();
// include all A=>B translations and self-translations A=>A
for (Iterator<DataSource> is = chosenSources.iterator(); is.hasNext();)
{
DataSource ds = is.next();
for (Iterator<DataSource> it = chosenSources.iterator(); it.hasNext();)
{
DataSource dt = it.next();
translationChains.add(new TranslationChain(ds,dt));
}
}
// include all A=>B=>A round trips
for (Iterator<DataSource> is = chosenSources.iterator(); is.hasNext();)
{
DataSource ds = is.next();
for (Iterator<DataSource> it = chosenSources.iterator(); it.hasNext();)
{
DataSource dt = it.next();
if (dt != ds)
{
TranslationChain tc = new TranslationChain(ds,dt);
tc.addLink(ds);
translationChains.add(tc);
}
}
}
// if there are 3 or more active sources, include a full round trip
if (chosenSources.size() > 2)
{
// make first link A=>B
TranslationChain tc =
new TranslationChain(chosenSources.get(0),chosenSources.get(1));
// add further links to C, D, etc.
for (int s = 2; s < chosenSources.size();s++)
tc.addLink(chosenSources.get(s));
// add final link back to A
tc.addLink(chosenSources.get(0));
translationChains.add(tc);
}
}
/* (4) Test that each translation source or result conforms to the structure recorded in
* its mapping set, or record the discrepancies */
private void testResultStructures() throws CoreException,MapperException
{
for (int m = 0; m < testFolder.members().length; m++)
{
timer.start(Timer.STRUCTURE_TEST);
IResource res = testFolder.members()[m];
if ((res instanceof IFile) &&
((res.getName().endsWith("xml"))|(res.getName().endsWith("txt"))))
{
IFile resultFile = (IFile)testFolder.members()[m];
String name = resultFile.getName();
// find the full code of the source or result, and get the Translation Summary Item
String fullResultCode = name.substring(0,name.length() - 4);
// do not look at results inside wrapper transformations - saved only for user inspection
if (!(fullResultCode.endsWith("_in")))
{
TranslationSummaryItem tsi = translationSummaryItems.get(fullResultCode);
if (tsi == null)
throw new MapperException("Cannot find translation summary item for code '" + fullResultCode + "'");
// find the structure code of the result structure; from a name 'ABC.xml', pick out the 'C'
String resultStructureCode = resultStructureCode(fullResultCode);
DataSource structureSource = getSourceByCode(resultStructureCode);
if (structureSource == null)
throw new MapperException("Cannot find data source for code '" + resultStructureCode + "'");
checkStructure(resultFile,structureSource,tsi);
}
}
timer.stop(Timer.STRUCTURE_TEST);
}
}
/** find the structure code of the result structure; from a name 'ABC', pick out the 'C' */
private String resultStructureCode(String fullResultCode)
{return fullResultCode.substring(fullResultCode.length()-1, fullResultCode.length());}
/** find the first source code of the result structure; from a name 'ABC', pick out the 'A' */
private String sourceCode(String fullResultCode)
{return fullResultCode.substring(0, 1);}
/**
* @param code a code 'A' ,'B' etc allocated to data sources in the data sources view
* @return the active data source with that code
*/
private DataSource getSourceByCode(String code)
{
DataSource ds = null;
for (int c = 0; c < chosenSources.size(); c++)
if (chosenSources.get(c).getCode().equals(code))
ds = chosenSources.get(c);
return ds;
}
/**
* check that the result of a translation (or an instance input to one)
* conforms to the structure definition in the mapped structure;
* record discrepancies where it does not - both in the global list
* of issues and in the Translation Summary Item for the translation.
*
* The check is made on two ways - once by XML schema validation (if there is a schema)
* and once against the Mapped Structure tree. The test against the mapped structure
* tree is the weaker of the two.
*
* @param resultFile the source or result file whose structure is tested
* @param structureSource the data source which defines the required structure
* @param tsi the translation summary item under which all issues are stored
*/
private void checkStructure(IFile resultFile,DataSource structureSource,TranslationSummaryItem tsi)
{
// Get the result file, if necessary doing an 'in' wrapper transformation
try{
MappedStructure mapStructure = structureSource.getReader().ms();
Element root = mapStructure.getXMLRoot(resultFile.getContents());
// do the schema validation (if there is a schema) and record the result
Vector<SchemaMismatch> schemaMismatches = mapStructure.schemaValidate(root);
// label the schema mismatches by the file, and save them
for (Iterator<SchemaMismatch> it = schemaMismatches.iterator();it.hasNext();)
{
SchemaMismatch sm = it.next();
sm.setFileName(resultFile.getName());
allMismatches.add(sm);
tsi.addSchemaMismatch(sm);
}
// do the (weaker) validation against the mapped structure, and record the result
Vector<StructureMismatch> structureMismatches = mapStructure.checkInstance(root);
// label the structure mismatches by the file, and save them
for (Iterator<StructureMismatch> it = structureMismatches.iterator();it.hasNext();)
{
StructureMismatch sm = it.next();
sm.setFileName(resultFile.getName());
allMismatches.add(sm);
tsi.addStructureMismatch(sm);
}
}
// catch an exception - assume it arises in applying the wrapper transform
catch (Exception ex)
{
StructureMismatch sm = new StructureMismatch("",StructureMismatch.STRUCTURE_IN_WRAPPER_TRANSFORM, ex.getMessage(),"");
sm.setFileName(resultFile.getName());
allMismatches.add(sm);
tsi.addStructureMismatch(sm);
}
}
/* (5) Make Ecore model instances from all translation sources and targets,
* putting them in the 'Tests' folder */
private void makeECoreModelInstances() throws MapperException, CoreException
{
ecoreInstances = new Hashtable<String,Resource>();
EMFInstanceFactory instanceFactory = new GenericEMFInstanceFactoryImpl();
instanceFactory.giveTimer(timer);
// Iterate over all '.xml' or '.txt' files (sources and results) in the tests folder
// record all the resources in the folder before you start adding to it
int len = testFolder.members().length;
IResource[] xmlResults = new IResource[len];
for (int m = 0; m < len; m++) xmlResults[m] = testFolder.members()[m];
// now start adding new files to the tests folder
for (int m = 0; m < len; m++)
{
IResource res = xmlResults[m];
String name = res.getName();
if ((res instanceof IFile) && ((name.endsWith("xml"))|(name.endsWith("txt"))))
{
IFile resultFile = (IFile)res;
String nameRoot = name.substring(0,name.length() - 4); // remove '.xml' or '.txt'
// do not look at results inside a wrapper in-transformation (made only for user inspection)
if (!(nameRoot.endsWith("_in")))
{
TranslationSummaryItem tsi = translationSummaryItems.get(nameRoot);
if (tsi == null)
throw new MapperException("Cannot find translation summary item for code '" + nameRoot + "'");
String resultCode = resultStructureCode(nameRoot);
DataSource structureSource = getSourceByCode(resultCode);
if (structureSource == null)
throw new MapperException("Cannot find data source for code '" + resultCode + "'");
XOReader reader = structureSource.getReader();
try{
// make up a URI for the ECore instance, in the tests folder
URI uri = URI.createURI("/" + theProject.getName() + "/Tests/" + nameRoot + ".model");
// point the data source reader at the correct instance (maybe after an 'in' wrapper transform)
Element root = reader.ms().getXMLRoot(resultFile.getContents());
reader.setRoot(root);
// make and save the Ecore model instance. 'true' means 'create a Container object'
trace("Making EMF Instance " + nameRoot);
timer.start(Timer.MAKE_EMF_INSTANCE);
Resource rs = instanceFactory.createModelInstanceInTranslationTest(reader, uri, true);
timer.stop(Timer.MAKE_EMF_INSTANCE);
// record any problems (eg exceptions thrown by property conversion code)
List<RunIssue> issues = instanceFactory.runIssues();
for (Iterator<RunIssue> it = issues.iterator();it.hasNext();)
{
RunIssue ri = it.next();
ri.setFileName(nameRoot);
tsi.addRunIssue(ri);
allMismatches.add(ri);
}
ecoreInstances.put(nameRoot, rs);
}
// catch an exception - assume it arises in applying the wrapper transform
catch (Exception ex)
{
StructureMismatch sm = new StructureMismatch("",StructureMismatch.STRUCTURE_IN_WRAPPER_TRANSFORM, ex.getMessage(),"");
sm.setFileName(resultFile.getName());
allMismatches.add(sm);
tsi.addStructureMismatch(sm);
}
}
}
}
}
/* (6) For every translation, test that the result Ecore model instance
* is a subset of the source Ecore instance, or record discrepancies */
private void compareSourceAndTargetEcoreInstances() throws CoreException,MapperException
{
for (Enumeration<String> en = ecoreInstances.keys();en.hasMoreElements();)
{
String name = en.nextElement();
if (name.length() > 1) // do only results, not sources
{
TranslationSummaryItem tsi = translationSummaryItems.get(name);
if (tsi == null)
throw new MapperException("Cannot find the translation summary item for code '" + name + "'");
// the source code is the first character of the result name
String sourceCode = name.substring(0,1);
Resource source = ecoreInstances.get(sourceCode);
Resource result = ecoreInstances.get(name);
trace("**Match " + sourceCode + " and " + name);
// compare each result with its source
timer.start(Timer.COMPARE_EMF_INSTANCE);
compareEcoreInstances(source, result, name, tsi);
timer.stop(Timer.COMPARE_EMF_INSTANCE);
}
}
}
private void compareEcoreInstances(Resource rSource, Resource rResult, String resultName,
TranslationSummaryItem tsi)
throws MapperException
{
if ((rSource != null) && (rResult != null))
{
EObject sourceRoot = rSource.getContents().get(0);
EObject resultRoot = rResult.getContents().get(0);
// ensure that arbitrarily assigned text keys will not fail to match
EcoreMatcher.equaliseTextKeys(sourceRoot);
EcoreMatcher.equaliseTextKeys(resultRoot);
String sourceCode = sourceCode(resultName);
String resultCode = resultStructureCode(resultName);
EcoreMatcher matcher = new EcoreMatcher(sourceRoot, resultRoot,
sourceCode,resultCode,
getSourceByCode(sourceCode).getReader(),
getSourceByCode(resultCode).getReader());
// find all semantic mismatches
Hashtable<String, SemanticMismatch> mismatches
= new Hashtable<String, SemanticMismatch>();
int sourceSize = matcher.treeMatch(sourceRoot, sourceRoot, mismatches); // expect no mismatches
// int resultSize = matcher.treeMatch(resultRoot, resultRoot, mismatches); // expect no mismatches; just for testing
int matches = matcher.treeMatch(sourceRoot, resultRoot, mismatches);
tsi.setSourceItemCount(sourceSize);
tsi.setResultItemCount(matches);
// System.out.println("Semantic scores: " + sourceSize + " " + resultSize + " " + matches);
// label the semantic mismatches by the file, and save them
for (Enumeration<SemanticMismatch> en = mismatches.elements();en.hasMoreElements();)
{
SemanticMismatch sm = en.nextElement();
sm.setFileName(resultName);
allMismatches.add(sm);
tsi.addSemanticMismatch(sm);
}
}
else throw new MapperException("Cannot find Ecore instance resources to check "
+ resultName);
}
/* (7) For every translation, check that the gaps in the result ECore instance
* (with respect to the source Ecore instance) are all expected because of gaps
* in the mappings (eg for a self-translation A=>A, there should be no gaps);
* or record discrepancies */
private void checkGapsInTargetECoreInstances(){}
/* (8) Summarise results for each tested translation in the Translation Summary view,
* and show all discrepancies in the test issue view. Save these views in the Tests folder. */
private void showTranslationSummaryView()
{
Vector<TranslationSummaryItem> tSumItems = new Vector<TranslationSummaryItem>();
for (Enumeration<TranslationSummaryItem> en = translationSummaryItems.elements();en.hasMoreElements();)
{tSumItems.add(en.nextElement());}
WorkBenchUtil.getTranslationSummaryView(true).showNewResult(tSumItems);
WorkBenchUtil.getTranslationIssueView(true).showNewResult(allMismatches);
WorkBenchUtil.page().activate(WorkBenchUtil.getTranslationSummaryView(true));
}
protected void showMessage(String title, String message) {
MessageDialog.openInformation(
shell,
title,
message);
}
/**
* default if you can't be bothered to make up a message title
* @param message
*/
protected void showMessage(String message)
{showMessage("Error",message);}
//----------------------------------------------------------------------------------------
// inner class for chains of Translations
//----------------------------------------------------------------------------------------
class TranslationChain
{
// Vector of data sources
Vector<DataSource> dataSourceChain = new Vector<DataSource>();
TranslationChain(DataSource start, DataSource target)
{
dataSourceChain.add(start);
dataSourceChain.add(target);
}
void addLink(DataSource newEnd)
{
dataSourceChain.add(newEnd);
}
/** appears not to be used */
Vector<String> resultsNeeded()
{
Vector<String> needed = new Vector<String>();
// first translation in the chain
String result = dataSourceChain.get(0).getCode() + dataSourceChain.get(1).getCode();
needed.add(result);
// subsequent translations, each using the result of the previous translation
for (int i = 2; i < dataSourceChain.size(); i++)
{
String link = dataSourceChain.get(i).getCode();
result = result + link;
needed.add(result);
}
return needed;
}
/**
* For a translation chain ABCD, do all the translations
* needed to make all intermediate results and the final result.
* first make AB from A,
* then make ABC from AB
* then make ABCD from ABC;
* but do not re-make any if they have been made already
*/
void doTranslationChain() throws MapperException, CoreException
{
DataSource previousSource = dataSourceChain.get(0);
String previousResultFileName = previousSource.getCode();
for (int t = 1; t < dataSourceChain.size();t++)
{
DataSource nextSource = dataSourceChain.get(t);
String nextResultFileName = previousResultFileName + nextSource.getCode();
IFile resultFile = testFolder.getFile(nextResultFileName + nextSource.getExtension());
// if this result file has not been made already, make it now
if (!resultFile.exists())
{
// make and record a Translation Summary Item
TranslationSummaryItem tsi = new TranslationSummaryItem(nextResultFileName);
translationSummaryItems.put(nextResultFileName, tsi);
// get the instance or result file which is the source for this translation
String sourceName = previousResultFileName + previousSource.getExtension();
IFile sourceFile = testFolder.getFile(sourceName);
doOneTranslation(previousSource,sourceFile,nextSource,resultFile,tsi);
} // end of if (!resultFile.exists())
//make it move on to the next translation;
previousResultFileName = nextResultFileName;
previousSource = nextSource;
}
}
}
/**
*
* @param previousSource the data source which defines the source language for this translation
* @param sourceFile the instance which is the source for this translation
* (may be the result of a previous translatioon to that language)
* @param nextSource the data source which defines the target language for this translation
* @param resultFile the IFile which the result is to be put into
* @param tsi the translation summary item to collect issues in this translation
*/
private void doOneTranslation(DataSource previousSource,IFile sourceFile,
DataSource nextSource, IFile resultFile,
TranslationSummaryItem tsi)
{
try
{
if (sourceFile.exists())
{
timer.start(Timer.TRANSLATE);
timer.start(Timer.MAKE_OBJECTGETTER);
// make the XML reader of the previous source point to the correct result file
Element sourceRoot = previousSource.getReader().ms().getXMLRoot(sourceFile.getContents());
XOReader reader = previousSource.getReader();
reader.setRoot(sourceRoot);
objectGetter oGet = null;
if (reader instanceof MDLXOReader)
oGet = new XMLObjectGetter((MDLXOReader)reader);
else if (reader instanceof objectGetter)
oGet = (objectGetter)reader;
else throw new MapperException("Cannot make objectGetter for source " + previousSource.mappingSetName());
timer.stop(Timer.MAKE_OBJECTGETTER);
// do the translation
trace("Starting translation from " + sourceFile.getName() + " to make " + resultFile.getName());
MappedStructure nextMapStructure = nextSource.getReader().ms();
timer.start(Timer.MAKE_TRANSLATOR);
XMLWriter translator = nextMapStructure.getXMLWriter(oGet, null, new SystemMessageChannel(), doRunTracing);
translator.giveTimer(timer, false);
timer.stop(Timer.MAKE_TRANSLATOR);
trace("Translator made for " + nextMapStructure.getMappingSetName());
Element resultRoot = translator.makeXMLDOM();
trace("Completed translation from " + sourceFile.getName() + " to make " + resultFile.getName());
if (resultRoot == null)
showMessage("Null root element in result of translation " + resultFile.getName());
else
{
trace("Result root before wrapper: " + XMLUtil.getLocalName(resultRoot));
// apply output wrapper transform if necessary; sometimes it depends on the input reader, to store XML fragments
Object output = nextMapStructure.makeOutputObject(resultRoot,reader);
// write the output file in the appropriate form (XML or text)
EclipseFileUtil.writeOutputObject(output,resultFile,nextMapStructure.getInstanceFileType());
/* if the result data source has a wrapper transform, store the result before
* the out-transform is done, for user examination (not used otherwise)*/
if (nextMapStructure.hasWrapperClass())
{
StringTokenizer st = new StringTokenizer(resultFile.getName(),".");
String resultNameRoot = st.nextToken();
IFile inResultFile = testFolder.getFile(new Path(resultNameRoot + "_in.xml"));
EclipseFileUtil.writeOutputResource(resultRoot.getOwnerDocument(), inResultFile, true);
}
}
Hashtable<String,Hashtable<String,RunIssue>> runIssues = translator.allRunIssues();
for (Enumeration<String> en = runIssues.keys();en.hasMoreElements();)
{
String pathString = en.nextElement();
Hashtable<String,RunIssue> issues = runIssues.get(pathString);
for (Enumeration<RunIssue> ep = issues.elements();ep.hasMoreElements();)
{
RunIssue ri = ep.nextElement();
ri.setFileName(tsi.resultCode());
allMismatches.add(ri);
tsi.addRunIssue(ri);
}
}
timer.stop(Timer.TRANSLATE);
}
else throw new MapperException("File " + sourceFile.getName() + " unexpectedly not found.");
}
/* any Exception when running the translation is recorded as a run issue,
* so it does not throw the whole translation test. */
catch (Exception ex)
{
ex.printStackTrace();
try{
Xpth dummy = new Xpth(new NamespaceSet(),"");
String message = ex.getMessage();
if (message == null) message = "Null pointer exception";
RunIssue ri = new RunIssue(RunIssue.RUN_FATAL_ERROR,"","",message,dummy,0);
tsi.addRunIssue(ri);
}
catch (XpthException ey) {}
timer.stop(Timer.TRANSLATE);
}
}
private void trace(String s)
{
if (tracing)
{
System.out.println(s);
//GenUtil.writeMemory();
}
}
}