package app; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; /** * Main program to convert the old formats to new formats. */ public class Program { /* * In Rabbit 1.0, data about sessions are actually the sum of perspective * durations grouped by dates. In Rabbit 1.1, session tracking has became * independent, so it no longer relies on perspective data. Therefore data * about perspectives before 1.1 will be extract out to separate session * files (so that the user can still see them in Rabbit View), new data will * be tracked by a session tracker and stored in new files. * * For example, if we have a 1.0 data file "perspectiveEvents-2010-03.xml": * * <events> * <perspectiveEvents date="2010-03-01"> * <perspectiveEvent perspectiveId="org.eclipse.debug.ui.DebugPerspective" duration="1" /> * <perspectiveEvent perspectiveId="org.eclipse.pde.ui.PDEPerspective" duration="1" /> * </perspectiveEvents> * </events> * * The above data will be copied and modified to another file * "sessionEvents-2010-03.xml": * * <events> * <sessionEvents date="2010-03-01"> * <sessionEvents duration="2" /> * </sessionEvents> * </events> * */ /* * In Rabbit 1.0, file events are storing the IDs of the files, in 1.1 it will * be changed to store the paths of the files. This way renaming/moving files * within the workspace will no longer be monitored. The Rabbit 1.1 way thinks * renaming/moving files are parts of the history of the projects, so they * should be retained instead of mapping the old files to the new location * after renaming/moving. This also improves the performance of Rabbit as no * mapping needs to be held in memory. * * Before: * * <events> * <fileEvents date="2010-03-01"> * <fileEvent fileId="1298237445" duration="123" /> * </fileEvents> * </events> * * After: * * <events> * <fileEvents date="2010-03-01"> * <fileEvent filePath="/rabbit/plugin.xml" duration="123" /> * </fileEvents> * </events> * */ /** Date attribute */ private static final String ATTR_DATE = "date"; /** Duration attribute */ private static final String ATTR_DURATION = "duration"; /** File ID attribute */ private static final String ATTR_FILE_ID = "fileId"; /** File path attribute */ private static final String ATTR_FILE_PATH = "filePath"; /** Event list tag */ private static final String TAG_EVENT_LIST = "events"; /** File event tag */ private static final String TAG_FILE_EVENT = "fileEvent"; /** File event list tag */ private static final String TAG_FILE_EVENT_LIST = "fileEvents"; /** Perspective event tag */ private static final String TAG_PERSPECTIVE_EVENT = "perspectiveEvent"; /** Perspective event list tag */ private static final String TAG_PERSPECTIVE_EVENT_LIST = "perspectiveEvents"; /** Session event tag */ private static final String TAG_SESSION_EVENT = "sessionEvent"; /** Session event list tag */ private static final String TAG_SESSION_EVENT_LIST = "sessionEvents"; /** * An unmodifiable map of file IDs to file paths. */ private static Map<String, String> fileIdToFilePath; private static DocumentBuilder builder; private static Transformer transformer; static { try { builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); } catch (ParserConfigurationException e) { System.err.println(e.getMessage()); } try { transformer = TransformerFactory.newInstance().newTransformer(); } catch (TransformerConfigurationException e) { System.err.println(e.getMessage()); } catch (TransformerFactoryConfigurationError e) { System.err.println(e.getMessage()); } } public static void run() { String path = System.getProperty("user.home") + File.separator + "Rabbit"; File home = new File(path); File[] subdirs = home.listFiles(); if (subdirs == null) { return; } Map<String, String> fileIdToPath = new HashMap<String, String>(); Set<File> perspectiveEventFiles = new HashSet<File>(); Set<File> fileEventFiles = new HashSet<File>(); for (File dir : subdirs) { // Loads the files to be processed: for (File f : dir.listFiles()) { if (f.isDirectory()) { continue; } if (f.getName().startsWith("perspectiveEvents")) { perspectiveEventFiles.add(f); } else if (f.getName().startsWith("fileEvents")) { fileEventFiles.add(f); } } // Loads the resource file paths and IDs: File resourceFile = new File(dir.getAbsoluteFile() + File.separator + "ResourceDB" + File.separator + "Resources.xml"); if (resourceFile.exists()) { try { loadResourceMappings(resourceFile, fileIdToPath); } catch (SAXException e) { System.err.println(e.getMessage()); } catch (IOException e) { System.err.println(e.getMessage()); } } else { System.err.println("Not exist: " + resourceFile.getAbsolutePath()); } } fileIdToFilePath = Collections.unmodifiableMap(fileIdToPath); // Start converting: for (File file : perspectiveEventFiles) { try { handlePerspectiveEventFile(file); } catch (SAXException e) { System.err.println(e.getMessage()); } catch (IOException e) { System.err.println(e.getMessage()); } catch (TransformerException e) { System.err.println(e.getMessage()); } } for (File file : fileEventFiles) { try { handleFileEventFile(file, fileIdToFilePath); } catch (SAXException e) { System.err.println(e.getMessage()); } catch (IOException e) { System.err.println(e.getMessage()); } catch (TransformerException e) { System.err.println(e.getMessage()); } } System.out.println("Done."); } /** * Converts the file event data to the new format. * * @param file The file containing the XML data. * @param fileIdToPath The map containing the mapping of file IDs to file paths. * @throws NullPointerException If file is null. * @throws SAXException If any parse errors occur. * @throws IOException If any IO errors occur. * @throws TransformerException If an unrecoverable error occurs during the * course of the transformation while saving the data. */ static void handleFileEventFile(File file, Map<String, String> fileIdToPath) throws SAXException, IOException, TransformerException { Document document = builder.parse(file); Element root = (Element) document.getFirstChild(); NodeList eventLists = root.getElementsByTagName(TAG_FILE_EVENT_LIST); for (int i = 0; i < eventLists.getLength(); i++) { Element eventList = (Element) eventLists.item(i); NodeList oldEvents = eventList.getElementsByTagName(TAG_FILE_EVENT); for (int j = 0; j < oldEvents.getLength(); j++) { Element event = (Element) oldEvents.item(j); String fileId = event.getAttribute(ATTR_FILE_ID); if (fileId.equals("")) { continue; // No fileId, can't be processed, may be converted already? } String filePath = fileIdToPath.get(fileId); if (filePath != null) { event.setAttribute(ATTR_FILE_PATH, filePath); event.removeAttribute(ATTR_FILE_ID); } else { eventList.removeChild(event); } } } FileOutputStream out = new FileOutputStream(file); try { transformer.transform(new DOMSource(document), new StreamResult(out)); } finally { out.close(); } } /** * Converts the old perspective event data to separate session data. * Independent session tracking is introduced in Rabbit 1.1, will not rely on * perspective event data any more. * * @param file The file containing the XML data, which has the format * "perspectiveEvents-yyyy-MM.xml" as it name, where "yyyy" * represents the year, and "MM" represents the month. * @throws NullPointerException If file is null. * @throws SAXException If any parse errors occur. * @throws IOException If any IO errors occur. * @throws TransformerException If an unrecoverable error occurs during the * course of the transformation while saving the data. */ static void handlePerspectiveEventFile(File file) throws SAXException, IOException, TransformerException { Document oldDocument = builder.parse(file); Document newDocument = builder.newDocument(); Element newRoot = newDocument.createElement(TAG_EVENT_LIST); newDocument.appendChild(newRoot); Element oldRoot = (Element) oldDocument.getFirstChild(); NodeList oldEventLists = oldRoot.getElementsByTagName(TAG_PERSPECTIVE_EVENT_LIST); for (int i = 0; i < oldEventLists.getLength(); i++) { Element oldEventList = (Element) oldEventLists.item(i); Attr oldDate = oldEventList.getAttributeNode(ATTR_DATE); if (oldDate == null) { System.err.println("Attr == null"); continue; } Element newEventList = newDocument.createElement(TAG_SESSION_EVENT_LIST); Attr newDate = newDocument.createAttribute(oldDate.getName()); newDate.setValue(oldDate.getValue()); newEventList.setAttributeNode(newDate); newRoot.appendChild(newEventList); NodeList oldEvents = oldEventList.getElementsByTagName(TAG_PERSPECTIVE_EVENT); for (int j = 0; j < oldEvents.getLength(); j++) { Element oldEvent = (Element) oldEvents.item(j); Attr oldDuration = oldEvent.getAttributeNode(ATTR_DURATION); if (oldDuration == null) { System.err.println("Duration == null"); continue; } Element newEvent = newDocument.createElement(TAG_SESSION_EVENT); Attr newDuration = newDocument.createAttribute(oldDuration.getName()); newDuration.setValue(oldDuration.getValue()); newEvent.setAttributeNode(newDuration); newEventList.appendChild(newEvent); } } String fileName = file.getName(); fileName = "sessionEvents" + fileName.substring(fileName.indexOf('-')); file = new File(file.getParentFile().getAbsoluteFile() + File.separator + fileName); FileOutputStream out = new FileOutputStream(file); try { transformer.transform(new DOMSource(newDocument), new StreamResult(out)); } finally { out.close(); } } /** * Loads the resource mapping from the file into the map. * * @param file The file containing the mapping of file IDs and file paths. * @param idToPath The map that maps from file ID to file paths, results will * be put into this map. * @throws NullPointerException If file is null. * @throws SAXException If any parse errors occur. * @throws IOException If any IO errors occur. */ static void loadResourceMappings(File file, Map<String, String> idToPath) throws SAXException, IOException { Document document = builder.parse(file); Element root = (Element) document.getFirstChild(); NodeList resources = root.getElementsByTagName("resource"); for (int i = 0; i < resources.getLength(); i++) { Element resource = (Element) resources.item(i); NodeList resourceIds = resource.getElementsByTagName("resourceId"); for (int j = 0; j < resourceIds.getLength(); j++) { Element resourceId = (Element) resourceIds.item(j); String fileId = resourceId.getTextContent(); String filePath = resource.getAttribute("path"); idToPath.put(fileId, filePath); } } } }