package com.openMap1.mapper.actions;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;
import org.eclipse.jface.action.IAction;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.core.resources.IFile;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EPackage;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.util.EclipseFileUtil;
import com.openMap1.mapper.util.ModelUtil;
import com.openMap1.mapper.util.SystemMessageChannel;
import com.openMap1.mapper.util.FileUtil;
import com.openMap1.mapper.util.XMLUtil;
import com.openMap1.mapper.views.DebugInstanceView;
import com.openMap1.mapper.views.DebugView;
import com.openMap1.mapper.views.WorkBenchUtil;
import com.openMap1.mapper.mapping.DebugRow;
import com.openMap1.mapper.query.RDBReader;
import com.openMap1.mapper.reader.DebugPostBox;
import com.openMap1.mapper.reader.EMFInstanceFactoryImpl;
import com.openMap1.mapper.reader.EMFInstanceFactory;
import com.openMap1.mapper.reader.GenericEMFInstanceFactoryImpl;
import com.openMap1.mapper.reader.MDLXOReader;
import com.openMap1.mapper.reader.XOReader;
import com.openMap1.mapper.reader.objectToken;
import com.openMap1.mapper.structures.DBStructure;
import com.openMap1.mapper.AssocMapping;
import com.openMap1.mapper.AttributeDef;
import com.openMap1.mapper.ElementDef;
import com.openMap1.mapper.MappedStructure;
import com.openMap1.mapper.Mapping;
import com.openMap1.mapper.NodeDef;
import com.openMap1.mapper.NodeMappingSet;
import com.openMap1.mapper.ObjMapping;
import com.openMap1.mapper.PropMapping;
import com.openMap1.mapper.StructureType;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Makes an instance of the ecore model, using the selected mapping set
* to read an xml file provided by the user.
*
* This action may also be done in debugging mode, pausing on mappings
* which have a breakpoint.
*
* @author robert
*
*/
public class MakeModelInstanceActionDelegate extends MapperActionDelegate
implements IObjectActionDelegate,Runnable{
private MappedStructure mapRoot;
private EMFInstanceFactory mf;
private boolean debug;
// instance variables needed by the run() method to run debug in another thread
private Vector<objectToken> oReps;
private String instancePath;
private XOReader xor;
private DebugPostBox debugPostBox;
private String topClassName;
public void run(IAction action) {
try{
String path = "";
debug = (action.getId().equals("com.openMap1.mapper.Debug"));
if (debug) showDebugView();
// (1) find and open the mapping set
mapRoot = mappedStructure();
// (1b) possibly repair package names, if they have been changed in the class model
if (repairPackages())
{
boolean saveRepairs = WorkBenchUtil.askConfirm("Package Name Changes", "Do you want to save changes of package names in mappings?");
if (saveRepairs)
{
FileUtil.saveResource(mapRoot.eResource());
}
}
if (mapRoot.getStructureType() == StructureType.RDBMS)
{
// (2a) for RDBMS sources, get a location to make an XML Instance file, and make it
String[] modelExts= {};
path = FileUtil.getFilePathFromUser(targetPart,modelExts,"Select location to make XML Instance from database:",true);
if (path.equals("")) return;
makeXMLInstanceFromDatabase(mapRoot,path);
}
else
{
// (2b) show the dialog for the user to choose an input instance file
String[] exts = mapRoot.getExtensions();
path = FileUtil.getFilePathFromUser(targetPart,exts,"Select Instance",false);
if (path.equals("")) return;
}
// (3) Open the input file (possibly applying an input wrapper transformation)
Element XMLRoot = mapRoot.getXMLRoot(path);
if (XMLRoot == null) throw new MapperException("Could not open XML");
// (5) create the XML Reader
xor = mapRoot.getXOReader(XMLRoot, null, new SystemMessageChannel());
if (xor == null) throw new MapperException("Cannot create XML Reader");
/* (6) find which class is to be top of the Ecore tree (a) from an annotation
* on the class model, or (b) if there is no annotation, from the user */
topClassName = null;
EClass topClass = EMFInstanceFactoryImpl.getRecommendedTopClass(mapRoot.getClassModelRoot());
if (topClass != null) topClassName = topClass.getName();
if (topClassName == null) topClassName = askUserForTopClassName(xor,debug);
if (topClassName == null) return;
/* (7) if the XML represents no instances of the top class, fail with a message. */
oReps = xor.getAllLocalObjectTokens(topClassName);
if (oReps.size() == 0) throw new MapperException
("The XML instance represents no objects of the top class '" + topClassName + "'");
// (8) get a location for the output EMF instance (make it up if debugging, to save a dialogue)
// (9) create one Ecore model instance for each objectRep found for the class (if debugging, in anaother thread)
instancePath = "";
if (!debug)
{
String[] modelExts= {};
instancePath = FileUtil.getFilePathFromUser(targetPart,modelExts,"Select location for Model Instance",true);
if (instancePath.equals("")) return;
makeEcoreInstances();
}
else if ((debug) && (xor instanceof MDLXOReader))
{
// don't bother the user for a file name
instancePath = new StringTokenizer(path,".").nextToken() + ".debug";
// open the debug instance view and show the XML tree in it
DebugInstanceView debugInstanceView = WorkBenchUtil.getDebugInstanceView(true);
debugInstanceView.setXMLRoot(XMLRoot);
// set up the thread for debugging, the Debug view and the postbox
Thread readerThread = new Thread(this);
debugPostBox = new DebugPostBox(readerThread);
DebugView debugView = WorkBenchUtil.getDebugView(false);
debugView.setDebugPostbox(debugPostBox);
debugView.setDebugInstanceView(debugInstanceView);
// prepare the XOReader to run in debug mode
MDLXOReader reader = (MDLXOReader)xor;
reader.setDebugPostbox(debugPostBox);
// kick off creating the EMF instance, in the reader thread
debugPostBox.getReaderThread().start();
// wait for the reader thread to halt or complete. In either case, this action completes
debugView.awaitHaltOrResult();
// forget the top class name, to give the user a chance to set it next time
topClassName = null;
}
else throw new MapperException("XOReader does not use mappings in the mapping set, so cannot be debugged");
}
catch (Exception ex)
{
showMessage("Failed to write EMF instance: " + ex.getMessage());
System.out.println("Failed to write EMF instance: " + ex.getMessage());
for (int i = 0; i < ex.getStackTrace().length; i++) System.out.println(ex.getStackTrace()[i].toString());
}
}
/**
* for the Runnable interface, to make a new thread for debugging
*/
public void run()
{
try
{
// get the objects of the top class again, to debug object mappings
oReps = xor.getAllLocalObjectTokens(topClassName);
// make the EMF instance for each object retrieved
makeEcoreInstances();
}
catch (MapperException ex) {showMessage("Failed to write EMF instance in debug: " + ex.getMessage());}
debugPostBox.setCompleted(true);
}
/**
* Method to make Ecore instances, having defined the start objects (oReps)
* and the base file name for the results (instancePath).
* This method is called in the main thread (for normal making of an EMF instance)
* or ins a separate thread (for debugging)
* @throws MapperException
*/
private void makeEcoreInstances() throws MapperException
{
for (int index = 0; index < oReps.size(); index ++)
{
String fileName = instancePath;
// if there is more than one objectRep, distinguish the file names for each one
if (oReps.size() > 1) fileName = addIndexToFileName(instancePath, index + 1);
URI instanceURI = FileUtil.URIFromPath(fileName);
mf = new GenericEMFInstanceFactoryImpl();
mf.setNsUri(mapRoot.getClassModelRoot().getNsURI());
mf.setNsPrefix(mapRoot.getClassModelRoot().getNsPrefix());
mf.createModelInstance(xor,instanceURI,oReps.get(index));
}
}
/**
* give the user a list of all classes represented in the XML instance,
* (which have mappings in the top mapping set - not an imported one)
* for which there is one or more instance in the XML instance -
* so he can choose the top class for the ECore model instance.
* @param xor XOReader which uses the mappings to read an XML instance
* @return the name of the top class chosen by the user
* ; return null if he cancels or if there were none to choose from
*/
private String askUserForTopClassName(XOReader xor, boolean debug) throws MapperException
{
Vector<String> labels = new Vector<String>();
Vector<String> cNames = new Vector<String>();
Hashtable<String,String> classesSought = new Hashtable<String,String>();
for (Iterator<EClass> it = ModelUtil.getAllClasses(xor.classModel()).iterator();it.hasNext();)
{
EClass theClass = it.next();
String className = ModelUtil.getQualifiedClassName(theClass);
if ((classesSought.get(className) == null) && (xor.representsObject(className)))
{
classesSought.put(className,"1");
Vector<objectToken> oReps = xor.getAllLocalObjectTokens(className);
if (oReps.size() > 0)
{
String cName = className + "(" + oReps.size() + ")";
labels.add(cName);
cNames.add(className);
}
}
}
if (labels.size() == 0)
{
showMessage("Cannot make EMF Model Instance",
"Found no instances of any class represented in the XML");
return null; // no choices available
}
int chosen = WorkBenchUtil.chooseOneString("Choose top class for EMF Instance", targetPart, labels);
if (chosen == -1) return null; //user cancelled
return cNames.get(chosen);
}
/**
* @param fileName a file name entered by the user
* @param index an integer index
* @return the file name with '_' and the index appended to its root
*/
private String addIndexToFileName(String fileName, int index)
{
String newFileName = "";
StringTokenizer st = new StringTokenizer(fileName,".",true);
newFileName = newFileName + st.nextToken() + "_" + index;
while (st.hasMoreTokens()) newFileName = newFileName + st.nextToken();
return newFileName;
}
//--------------------------------------------------------------------------------------------------
// make and store an XML Instance from a mapped Relational Database
//--------------------------------------------------------------------------------------------------
/**
* Make an XML Instance from the database denoted in the mapped structure,
* and store it at the defined location
* FIXME this only works for small databases; need some dialogue and mechanism
* to make it selective for large databases
*/
private void makeXMLInstanceFromDatabase(MappedStructure mapRoot,String path)
throws MapperException
{
DBStructure database = (DBStructure)mapRoot.getStructureDefinition();
RDBReader rxq = new RDBReader(database,path);
Element rootNode = rxq.makeXMLDOM();
Document doc = rootNode.getOwnerDocument();
if (doc == null) System.out.println("Null document");
System.out.println("Root element name: " + XMLUtil.getLocalName(rootNode));
URI uri = FileUtil.URIFromPath(path);
IFile file = EclipseFileUtil.getFile(uri.toString());
System.out.println("File path: " + uri.toString());
if (file == null) System.out.println("null file");
EclipseFileUtil.writeOutputResource(doc, file, true);
}
//--------------------------------------------------------------------------------------------------------------
// Repair Package names in mappings in a Mapping Set, when package names have been changed in the mapped Ecore model
//--------------------------------------------------------------------------------------------------------------
/**
* find all mappings in the mapping set.
* For each one, find the class in the class model regardless of its package,
* and change the package name to be correct, if necessary
* @return true if any changes have been made
* @throws MapperException if any mapped class name is not fund in any package
*/
private boolean repairPackages() throws MapperException
{
EPackage classModel = mapRoot.getClassModelRoot();
ElementDef root = mapRoot.getRootElement();
if (root != null) return repairPackagesOnNode(classModel,root);
else return false;
}
/**
* recursive descent down a mapping set,
* finding all mappings and changing the package name if necessary
* @param classModel
* @param nDef
* @return
* @throws MapperException
*/
private boolean repairPackagesOnNode(EPackage classModel, NodeDef nDef) throws MapperException
{
boolean changesMade = false;
NodeMappingSet nms = nDef.getNodeMappingSet();
if (nms != null)
{
for (Iterator<ObjMapping> it = nms.getObjectMappings().iterator();it.hasNext();)
if (repairMapping(classModel,it.next())) changesMade = true;
for (Iterator<PropMapping> it = nms.getPropertyMappings().iterator();it.hasNext();)
if (repairMapping(classModel,it.next())) changesMade = true;
for (Iterator<AssocMapping> it = nms.getAssociationMappings().iterator();it.hasNext();)
{
AssocMapping am = it.next();
if (repairMapping(classModel,am.getMappedEnd1())) changesMade = true;
if (repairMapping(classModel,am.getMappedEnd2())) changesMade = true;
}
}
if (nDef instanceof ElementDef)
{
ElementDef elDef = (ElementDef)nDef;
for (Iterator<ElementDef> it = elDef.getChildElements().iterator();it.hasNext();)
if (repairPackagesOnNode(classModel, it.next())) changesMade = true;
for (Iterator<AttributeDef> it = elDef.getAttributeDefs().iterator();it.hasNext();)
if (repairPackagesOnNode(classModel, it.next())) changesMade = true;
}
return changesMade;
}
/**
* repair one mapping, changing the package name if necessary
* @param classModel
* @param m
* @return true if the package name has been changed
* @throws MapperException if the class name cannot be fond in any package
*/
private boolean repairMapping(EPackage classModel,Mapping m) throws MapperException
{
boolean changedPackage = false;
String className = m.getMappedClass();
String packageName = m.getMappedPackage();
// if the class can be found in the right package, do not look in other packages
EPackage ownPackage = ModelUtil.getNamedPackage(classModel, packageName);
if ((ownPackage != null) && (ModelUtil.getEClass(ownPackage, className) != null)) return false;
// otherwise, look for the class in all packages
boolean found = false;
for (Iterator<EPackage> it = classModel.getESubpackages().iterator();it.hasNext();)
{
EPackage pack = it.next();
EClass theClass = ModelUtil.getEClass(pack, className);
if (theClass != null)
{
found = true;
m.setMappedPackage(pack.getName());
changedPackage = true;
}
}
if (!found) throw new MapperException("Cannot find mapped class '" + className + "' in any package");
return changedPackage;
}
//--------------------------------------------------------------------------------------------------
// Debugging
//--------------------------------------------------------------------------------------------------
private void showDebugView()
{
WorkBenchUtil.getDebugView(true).showNewResult(new Vector<DebugRow>());
WorkBenchUtil.page().activate(WorkBenchUtil.getDebugView(true));
}
}