package com.openMap1.mapper.actions; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.StringTokenizer; import java.util.Vector; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.action.IAction; import org.eclipse.ui.IObjectActionDelegate; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.util.EclipseFileUtil; import com.openMap1.mapper.util.XMLUtil; import com.openMap1.mapper.views.WorkBenchUtil; /** * Action to make an 'Alternate EMF instance' - like a usual EMF instance, * except that the targets of non-containment references are represented by key * values, not by their position in the document. * @author robert * */ public class MakeAlternateEMFInstanceActionDelegate extends MapperActionDelegate implements IObjectActionDelegate{ public void run(IAction action) { tracing = false; trace("Making Alternate Ecore Instance "); IFile file = getSelectedFile(); if (file != null) try { // (1) Make an IFile for the result - same folder, add extension and '_alt' after file name, .xml extension IProject project = file.getProject(); IFolder folder = project.getFolder(file.getProjectRelativePath().removeLastSegments(1)); String fileName = file.getName(); String extension = file.getFileExtension(); String newFileName = fileName.substring(0,fileName.length() - extension.length()-1) + extension + "_alt.xml"; IFile newFile = folder.getFile(newFileName); if (newFile.exists()) { boolean confirm = WorkBenchUtil.askConfirm("File '" + newFileName + "' already exists", "Do you want to replace the existing file?"); if (!confirm) return; else newFile.delete(true, null); // not necessary? } // (2) read and process the instance Element rootElement = XMLUtil.getRootElement(file.getContents()); makeAlternateInstance(rootElement,newFile); } catch (MapperException ex) {showMessage(ex.getMessage());ex.printStackTrace();} catch (CoreException ex) {showMessage(ex.getMessage());ex.printStackTrace();} } private void makeAlternateInstance(Element rootElement,IFile newFile) throws MapperException { // find out what kind of URL fragment this instance uses for non-containment eReferences String startDefaultFragment = "//@"; boolean usesDefaultURLFragments = findAttributeValueStartingWith(rootElement,startDefaultFragment); String startOverrideFragment = "#//"; boolean usesOverrideURLFragments = findAttributeValueStartingWith(rootElement,startOverrideFragment); if (usesDefaultURLFragments && usesOverrideURLFragments) throw new MapperException("EMF Instance uses both default and override URL fragments; cannot convert"); // copy across the root element and its attributes Document doc = XMLUtil.makeOutDoc(); Element importedRoot = (Element)doc.importNode(rootElement, false); // false = shallow copy // initialise the path strings that will form the URL fragments String path = "/"; String start = startDefaultFragment; if (usesOverrideURLFragments) { start = startOverrideFragment; path = "#/"; } // deep copy of the documents, adding the attribute 'alt_id' with the URL fragment to each Element Hashtable<String,String> fragmentsFound = new Hashtable<String,String>(); copyWithIdAttribute(doc,rootElement,importedRoot,path,usesOverrideURLFragments,fragmentsFound,start); // warn the user of any URL fragments found as attribute values, but not matched noteProblems(fragmentsFound); // write out the alternate form EMF instance doc.appendChild(importedRoot); EclipseFileUtil.writeOutputResource(doc,newFile,true); // true = formatted } /** * @param el an element * @param start the string which might be the start of an attribute value * @return true if start is the start of any attribute value on the * element or the tree below it. */ private boolean findAttributeValueStartingWith(Element el, String start) { boolean found = false; for (int at = 0; at < el.getAttributes().getLength();at++) { Attr att = (Attr)el.getAttributes().item(at); String val = el.getAttribute(att.getName()); if (val.startsWith(start)) found = true; } for (Iterator<Element> ie = XMLUtil.childElements(el).iterator();ie.hasNext();) if (findAttributeValueStartingWith(ie.next(),start)) found = true; return found; } /** * * @param doc the Document for the alternate EMF form * @param el an element in the original persisted form * @param importedEl the corresponding element in the alternative persisted * @param path the path to be used in the map_id attributes of all elements * @param usesOverrideURLFragments; * @param fragments found a record of all URL fragments found and their status * @param start initial substring of any URL fragment * Copies the child elements and their attributes across, and adds the alt_id attribute; * and recursively copies all descendants across. */ private void copyWithIdAttribute(Document doc, Element el,Element importedEl,String path, boolean usesOverrideURLFragments, Hashtable<String,String> fragmentsFound,String start) throws MapperException { // note all the attributes of this element whose values are URL fragments addAttributeValuesFound(el,fragmentsFound,start); /* iterate over child elements; the child position, used in the URL fragments, must * be reset to zero whenever the element tag name changes. */ int childPosition = 0; String lastTagName = ""; for (Iterator<Element> it = XMLUtil.childElements(el).iterator();it.hasNext();) { Element child = it.next(); Element importedChild = (Element)doc.importNode(child, false); // reset the child position to zero if the tag name changes if (!child.getLocalName().equals(lastTagName)) childPosition = 0; lastTagName = child.getLocalName(); // extend the URL fragment appropriately String newPath = path + "/@" + child.getLocalName() + "." + childPosition; if (usesOverrideURLFragments) newPath = path + "/" + child.getAttribute("name"); childPosition++; // note the URL fragment of this child element, and set the attribute on the modified child addPathsFound(newPath,fragmentsFound); String idAttName = "alt_id"; importedChild.setAttribute(idAttName, newPath); // create modified versions of the descendants of the child node copyWithIdAttribute(doc,child,importedChild,newPath,usesOverrideURLFragments,fragmentsFound,start); // attach the modified child to its parent to go in the output document importedEl.appendChild(importedChild); } } /** * possible status values for each URL fragment */ private static String TRAIL_ONLY = "Trail only"; private static String ATTRIBUTE_ONLY = "Attribute only"; private static String TRAIL_AND_ATTRIBUTE = "Trail and attribute"; /** * * @param el * @param fragmentsFound * @param usesOverrideURLFragments * record that any attribute values matching the URL fragment pattern have been found * in the Element el */ private void addAttributeValuesFound(Element el, Hashtable<String,String>fragmentsFound,String start) { for (int at = 0; at < el.getAttributes().getLength();at++) { Attr att = (Attr)el.getAttributes().item(at); String val = el.getAttribute(att.getName()); if (val.startsWith(start)) { StringTokenizer st = new StringTokenizer(val," "); // treat each part of the value separated by ' ' as the value to match while (st.hasMoreTokens()) { String valPart = st.nextToken(); String previousStatus = fragmentsFound.get(valPart); String status = ATTRIBUTE_ONLY; if (previousStatus != null) { if (previousStatus.equals(TRAIL_ONLY))status = TRAIL_AND_ATTRIBUTE; if (previousStatus.equals(TRAIL_AND_ATTRIBUTE))status = TRAIL_AND_ATTRIBUTE; } fragmentsFound.put(valPart, status); } } } } /** * * @param newPath * @param fragmentsFound * record that a path has been found that will be used at the value of an 'alt_id' attribute, * to match a URL fragment */ private void addPathsFound(String path,Hashtable<String,String> fragmentsFound) throws MapperException { String previousStatus = fragmentsFound.get(path); String status = TRAIL_ONLY; if (previousStatus != null) { if (previousStatus.equals(ATTRIBUTE_ONLY)) status = TRAIL_AND_ATTRIBUTE; if (previousStatus.equals(TRAIL_AND_ATTRIBUTE)) status = TRAIL_AND_ATTRIBUTE; /* if previous status is TRAIL_ONLY or TRAIL_AND_ATTRIBUTE, DON'T throw an exception; * some trails of 'name' values do occur more than once */ } fragmentsFound.put(path, status); } /** * @param fragmentsFound all URL fragments found in the instance, with their status * If any fragments were found in an EReference attribute, but the node * corresponding to the fragment was not found, write a warning. */ private void noteProblems(Hashtable<String,String> fragmentsFound) { Vector<String> unmatchedFragments = new Vector<String>(); for (Enumeration<String> en = fragmentsFound.keys();en.hasMoreElements();) { String fragment = en.nextElement(); String status = fragmentsFound.get(fragment); if (status.equals(ATTRIBUTE_ONLY)) unmatchedFragments.add(fragment); } if (unmatchedFragments.size() > 0) showMessage("Warning: " + unmatchedFragments.size() + " EReference attributes were not matched, such as '" + unmatchedFragments.get(0) + "'"); } }