| /******************************************************************************* |
| * Copyright (c) 2005, 2015 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ltk.internal.core.refactoring.history; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.io.Writer; |
| import java.nio.charset.Charset; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.Collection; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.TimeZone; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.w3c.dom.Text; |
| |
| import org.eclipse.core.filesystem.EFS; |
| import org.eclipse.core.filesystem.IFileInfo; |
| import org.eclipse.core.filesystem.IFileStore; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.MultiStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| |
| import org.eclipse.ltk.core.refactoring.IRefactoringCoreStatusCodes; |
| import org.eclipse.ltk.core.refactoring.RefactoringContribution; |
| import org.eclipse.ltk.core.refactoring.RefactoringCore; |
| import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; |
| import org.eclipse.ltk.core.refactoring.RefactoringDescriptorProxy; |
| import org.eclipse.ltk.core.refactoring.RefactoringSessionDescriptor; |
| import org.eclipse.ltk.core.refactoring.history.RefactoringHistory; |
| import org.eclipse.ltk.internal.core.refactoring.BasicElementLabels; |
| import org.eclipse.ltk.internal.core.refactoring.IRefactoringSerializationConstants; |
| import org.eclipse.ltk.internal.core.refactoring.Messages; |
| import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages; |
| import org.eclipse.ltk.internal.core.refactoring.RefactoringCorePlugin; |
| import org.eclipse.ltk.internal.core.refactoring.RefactoringSessionReader; |
| import org.eclipse.ltk.internal.core.refactoring.RefactoringSessionTransformer; |
| |
| /** |
| * Manager for persistable refactoring histories. |
| * |
| * @since 3.2 |
| */ |
| public final class RefactoringHistoryManager { |
| |
| /** The index component delimiter */ |
| public static final char DELIMITER_COMPONENT= '\t'; |
| |
| /** The index entry delimiter */ |
| public static final char DELIMITER_ENTRY= '\n'; |
| |
| /** The calendar instance */ |
| private static final Calendar fgCalendar= Calendar.getInstance(TimeZone.getTimeZone("GMT+00:00")); //$NON-NLS-1$ |
| |
| /** |
| * Checks whether the argument map is well-formed. |
| * <p> |
| * All arguments contained in the map are checked according to the rules of |
| * {@link RefactoringDescriptor}. |
| * </p> |
| * |
| * @param arguments |
| * the argument map |
| * @throws CoreException |
| * if the argument violates any of the constraints |
| */ |
| public static void checkArgumentMap(final Map<String, String> arguments) throws CoreException { |
| Assert.isNotNull(arguments); |
| for (final Iterator<? extends Entry<?, ?>> iterator= arguments.entrySet().iterator(); iterator.hasNext();) { |
| final Entry<?, ?> entry= iterator.next(); |
| if (entry.getKey() instanceof String) { |
| final String string= (String) entry.getKey(); |
| final char[] characters= string.toCharArray(); |
| if (characters.length == 0) { |
| throw new CoreException(new Status(IStatus.ERROR, RefactoringCore.ID_PLUGIN, IRefactoringCoreStatusCodes.REFACTORING_HISTORY_FORMAT_ERROR, RefactoringCoreMessages.RefactoringHistoryManager_empty_argument, null)); |
| } |
| for (int index= 0; index < characters.length; index++) { |
| if (Character.isWhitespace(characters[index])) |
| throw new CoreException(new Status(IStatus.ERROR, RefactoringCore.ID_PLUGIN, IRefactoringCoreStatusCodes.REFACTORING_HISTORY_FORMAT_ERROR, RefactoringCoreMessages.RefactoringHistoryManager_whitespace_argument_key, null)); |
| } |
| } else { |
| throw new CoreException(new Status(IStatus.ERROR, RefactoringCore.ID_PLUGIN, IRefactoringCoreStatusCodes.REFACTORING_HISTORY_FORMAT_ERROR, Messages.format(RefactoringCoreMessages.RefactoringHistoryManager_non_string_argument, entry.getKey()), null)); |
| } |
| if (! (entry.getValue() instanceof String)) { |
| throw new CoreException(new Status(IStatus.ERROR, RefactoringCore.ID_PLUGIN, IRefactoringCoreStatusCodes.REFACTORING_HISTORY_FORMAT_ERROR, Messages.format(RefactoringCoreMessages.RefactoringHistoryManager_non_string_value, entry.getKey()), null)); |
| } |
| } |
| } |
| |
| /** |
| * Creates a new core exception representing an I/O error. |
| * |
| * @param exception |
| * the throwable to wrap |
| * @return the core exception |
| */ |
| private static CoreException createCoreException(final Throwable exception) { |
| return new CoreException(new Status(IStatus.ERROR, RefactoringCore.ID_PLUGIN, IRefactoringCoreStatusCodes.REFACTORING_HISTORY_IO_ERROR, exception.getLocalizedMessage(), exception)); |
| } |
| |
| /** |
| * Escapes the specified string for the history index. |
| * |
| * @param string |
| * the string for the history index |
| * @return the escaped string |
| */ |
| public static String escapeString(final String string) { |
| if (string.indexOf(DELIMITER_COMPONENT) < 0) { |
| final int length= string.length(); |
| final StringBuilder buffer= new StringBuilder(length + 16); |
| for (int index= 0; index < length; index++) { |
| final char character= string.charAt(index); |
| if (DELIMITER_COMPONENT == character) |
| buffer.append(DELIMITER_COMPONENT); |
| buffer.append(character); |
| } |
| return buffer.toString(); |
| } |
| return string; |
| } |
| |
| /** |
| * Returns the argument map of the specified descriptor. |
| * |
| * @param descriptor |
| * the refactoring descriptor |
| * @return the argument map, or <code>null</code> |
| */ |
| public static Map<String, String> getArgumentMap(final RefactoringDescriptor descriptor) { |
| Map<String, String> arguments= null; |
| final RefactoringContribution contribution= RefactoringContributionManager.getInstance().getRefactoringContribution(descriptor.getID()); |
| if (contribution != null) |
| arguments= contribution.retrieveArgumentMap(descriptor); |
| else if (descriptor instanceof DefaultRefactoringDescriptor) |
| arguments= ((DefaultRefactoringDescriptor) descriptor).getArguments(); |
| return arguments; |
| } |
| |
| /** |
| * Reads refactoring descriptor proxies. |
| * |
| * @param store |
| * the file store to read |
| * @param project |
| * the name of the project, or <code>null</code> for the |
| * workspace |
| * @param collection |
| * the collection of proxies to fill in |
| * @param start |
| * the start time stamp, inclusive |
| * @param end |
| * the end time stamp, inclusive |
| * @param monitor |
| * the progress monitor to use |
| * @param task |
| * the task label to use |
| * @throws CoreException |
| * if an error occurs |
| */ |
| private static void readRefactoringDescriptorProxies(final IFileStore store, final String project, final Collection<RefactoringDescriptorProxy> collection, final long start, final long end, final IProgressMonitor monitor, final String task) throws CoreException { |
| try { |
| monitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_retrieving_history, 22); |
| final IFileInfo info= store.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 2, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)); |
| if (!info.isDirectory() && info.exists() && store.getName().equalsIgnoreCase(RefactoringHistoryService.NAME_INDEX_FILE)) { |
| try (InputStream stream= store.openInputStream(EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL))) { |
| final RefactoringDescriptorProxy[] proxies= readRefactoringDescriptorProxies(stream, project, start, end); |
| for (RefactoringDescriptorProxy proxy : proxies) { |
| collection.add(proxy); |
| } |
| monitor.worked(1); |
| } catch (IOException exception) { |
| throw createCoreException(exception); |
| } finally { |
| monitor.worked(1); |
| } |
| } else |
| monitor.worked(4); |
| if (monitor.isCanceled()) |
| throw new OperationCanceledException(); |
| final IFileStore[] stores= store.childStores(EFS.NONE, new SubProgressMonitor(monitor, 2, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)); |
| final IProgressMonitor subMonitor= new SubProgressMonitor(monitor, 12); |
| try { |
| subMonitor.beginTask(task, stores.length); |
| for (IFileStore s : stores) { |
| readRefactoringDescriptorProxies(s, project, collection, start, end, new SubProgressMonitor(subMonitor, 1), task); |
| } |
| } finally { |
| subMonitor.done(); |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * Reads refactoring descriptor proxies from the specified input stream. |
| * <p> |
| * The refactoring descriptor proxies are returned in no particular order. |
| * </p> |
| * |
| * @param stream |
| * the input stream where to read from |
| * @param project |
| * the name of the project, or <code>null</code> for the |
| * workspace |
| * @param start |
| * the start time stamp, inclusive |
| * @param end |
| * the end time stamp, inclusive |
| * @return An array of refactoring descriptor proxies |
| * @throws IOException |
| * if an input/output error occurs |
| */ |
| public static RefactoringDescriptorProxy[] readRefactoringDescriptorProxies(final InputStream stream, final String project, final long start, final long end) throws IOException { |
| final List<DefaultRefactoringDescriptorProxy> list= new ArrayList<>(); |
| final BufferedReader reader= new BufferedReader(new InputStreamReader(stream, IRefactoringSerializationConstants.OUTPUT_ENCODING)); |
| while (reader.ready()) { |
| final String line= reader.readLine(); |
| if (line != null) { |
| final int index= line.indexOf(DELIMITER_COMPONENT); |
| if (index > 0) { |
| try { |
| final long stamp= Long.valueOf(line.substring(0, index)).longValue(); |
| if (stamp >= start && stamp <= end) |
| list.add(new DefaultRefactoringDescriptorProxy(unescapeString(line.substring(index + 1)), project, stamp)); |
| } catch (NumberFormatException exception) { |
| // Just skip |
| } |
| } |
| } |
| } |
| return list.toArray(new RefactoringDescriptorProxy[list.size()]); |
| } |
| |
| /** |
| * Reads default refactoring descriptors from the specified input stream. |
| * <p> |
| * The refactoring descriptors are returned in no particular order. |
| * </p> |
| * |
| * @param stream |
| * the input stream where to read from |
| * @return An array of refactoring descriptor proxies |
| * @throws CoreException |
| * if an error occurs while reading the descriptors |
| */ |
| public static DefaultRefactoringDescriptor[] readRefactoringDescriptors(final InputStream stream) throws CoreException { |
| final List<RefactoringDescriptor> list= new ArrayList<>(64); |
| readRefactoringDescriptors(stream, list, new NullProgressMonitor()); |
| return list.toArray(new DefaultRefactoringDescriptor[list.size()]); |
| } |
| |
| /** |
| * Reads default refactoring descriptors from the specified input stream. |
| * |
| * @param stream |
| * the input stream where to read from |
| * @param collection |
| * the list of descriptors read from the history |
| * @param monitor |
| * the progress monitor to use |
| * @throws CoreException |
| * if an error occurs while reading the descriptors |
| */ |
| private static void readRefactoringDescriptors(final InputStream stream, final Collection<RefactoringDescriptor> collection, final IProgressMonitor monitor) throws CoreException { |
| try { |
| monitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_retrieving_history, 1); |
| final RefactoringDescriptor[] results= new RefactoringSessionReader(true, null).readSession(new InputSource(new BufferedInputStream(stream))).getRefactorings(); |
| for (RefactoringDescriptor result : results) { |
| collection.add(result); |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * Removes the refactoring history index tree spanned by the specified file |
| * store. |
| * |
| * @param store |
| * the file store spanning the history index tree |
| * @param monitor |
| * the progress monitor to use |
| * @param task |
| * the task label to use |
| * @throws CoreException |
| * if an error occurs while removing the index tree |
| */ |
| private static void removeIndexTree(final IFileStore store, final IProgressMonitor monitor, final String task) throws CoreException { |
| try { |
| monitor.beginTask(task, 16); |
| final IFileInfo info= store.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)); |
| if (info.isDirectory()) { |
| if (info.getName().equalsIgnoreCase(RefactoringHistoryService.NAME_HISTORY_FOLDER)) |
| return; |
| final IFileStore[] stores= store.childStores(EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)); |
| final IProgressMonitor subMonitor= new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL); |
| try { |
| subMonitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_updating_history, stores.length); |
| for (IFileStore s : stores) { |
| final IFileInfo current= s.fetchInfo(EFS.NONE, new SubProgressMonitor(subMonitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)); |
| if (current.isDirectory()) { |
| final char[] characters= s.getName().toCharArray(); |
| for (int offset= 0; offset < characters.length; offset++) { |
| if (Character.isDigit(characters[offset])) |
| return; |
| else |
| continue; |
| } |
| } |
| } |
| } finally { |
| subMonitor.done(); |
| } |
| } |
| final IFileStore parent= store.getParent(); |
| store.delete(0, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)); |
| removeIndexTree(parent, new SubProgressMonitor(monitor, 12, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL), task); |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * Sorts the refactoring descriptor proxies in ascending order of their time |
| * stamps. |
| * |
| * @param descriptors |
| * the refactoring descriptors |
| */ |
| public static void sortRefactoringDescriptorsAscending(final RefactoringDescriptor[] descriptors) { |
| Arrays.sort(descriptors, new Comparator<RefactoringDescriptor>() { |
| |
| @Override |
| public int compare(RefactoringDescriptor first, RefactoringDescriptor second) { |
| long delta= first.getTimeStamp() - second.getTimeStamp(); |
| if (delta > 0) |
| return 1; |
| else if (delta < 0) |
| return -1; |
| return 0; |
| } |
| }); |
| } |
| |
| /** |
| * Sorts the refactoring descriptor proxies in ascending order of their time |
| * stamps. |
| * |
| * @param proxies |
| * the refactoring descriptor proxies |
| */ |
| public static void sortRefactoringDescriptorsAscending(final RefactoringDescriptorProxy[] proxies) { |
| Arrays.sort(proxies, new Comparator<RefactoringDescriptorProxy>() { |
| |
| @Override |
| public int compare(RefactoringDescriptorProxy first, RefactoringDescriptorProxy second) { |
| long delta= first.getTimeStamp() - second.getTimeStamp(); |
| if (delta > 0) |
| return 1; |
| else if (delta < 0) |
| return -1; |
| return 0; |
| } |
| }); |
| } |
| |
| /** |
| * Sorts the refactoring descriptor proxies in descending order of their |
| * time stamps. |
| * |
| * @param proxies |
| * the refactoring descriptor proxies |
| */ |
| public static void sortRefactoringDescriptorsDescending(final RefactoringDescriptorProxy[] proxies) { |
| Arrays.sort(proxies, new Comparator<RefactoringDescriptorProxy>() { |
| |
| @Override |
| public int compare(RefactoringDescriptorProxy first, RefactoringDescriptorProxy second) { |
| long delta= second.getTimeStamp() - first.getTimeStamp(); |
| if (delta > 0) |
| return 1; |
| else if (delta < 0) |
| return -1; |
| return 0; |
| } |
| }); |
| } |
| |
| /** |
| * Returns a path representing the history part for the specified time |
| * stamp. |
| * |
| * @param stamp |
| * the time stamp |
| * @return A path representing the folder of the history part |
| */ |
| public static IPath stampToPath(final long stamp) { |
| fgCalendar.setTimeInMillis(stamp); |
| final StringBuilder buffer= new StringBuilder(256); |
| buffer.append(fgCalendar.get(Calendar.YEAR)); |
| buffer.append(IPath.SEPARATOR); |
| buffer.append(fgCalendar.get(Calendar.MONTH) + 1); |
| buffer.append(IPath.SEPARATOR); |
| buffer.append(fgCalendar.get(Calendar.WEEK_OF_YEAR)); |
| return new Path(buffer.toString()); |
| } |
| |
| /** |
| * Transforms the specified refactoring descriptor into a DOM node. |
| * |
| * @param descriptor |
| * the descriptor to transform |
| * @param projects |
| * <code>true</code> to include project information, |
| * <code>false</code> otherwise |
| * @return the DOM node representing the refactoring descriptor |
| * @throws CoreException |
| * if an error occurs while transforming the descriptor |
| */ |
| private static Document transformDescriptor(final RefactoringDescriptor descriptor, final boolean projects) throws CoreException { |
| final RefactoringSessionTransformer transformer= new RefactoringSessionTransformer(projects); |
| try { |
| transformer.beginSession(null, IRefactoringSerializationConstants.CURRENT_VERSION); |
| try { |
| final String id= descriptor.getID(); |
| transformer.beginRefactoring(id, descriptor.getTimeStamp(), descriptor.getProject(), descriptor.getDescription(), descriptor.getComment(), descriptor.getFlags()); |
| final Map<String, String> arguments= getArgumentMap(descriptor); |
| if (arguments != null) { |
| checkArgumentMap(arguments); |
| for (final Iterator<Entry<String, String>> iterator= arguments.entrySet().iterator(); iterator.hasNext();) { |
| final Entry<String, String> entry= iterator.next(); |
| transformer.createArgument(entry.getKey(), entry.getValue()); |
| } |
| } |
| } finally { |
| transformer.endRefactoring(); |
| } |
| } finally { |
| transformer.endSession(); |
| } |
| return transformer.getResult(); |
| } |
| |
| /** |
| * Un-escapes the specified string from the history index. |
| * |
| * @param string |
| * the string from the history index |
| * @return the un-escaped string |
| */ |
| public static String unescapeString(final String string) { |
| if (string.indexOf(DELIMITER_COMPONENT) < 0) { |
| final int length= string.length(); |
| final StringBuilder buffer= new StringBuilder(length); |
| for (int index= 0; index < length; index++) { |
| final char character= string.charAt(index); |
| if (DELIMITER_COMPONENT == character) { |
| if (index < length - 1) { |
| final char escape= string.charAt(index + 1); |
| if (DELIMITER_COMPONENT == escape) |
| continue; |
| } |
| } |
| buffer.append(character); |
| } |
| return buffer.toString(); |
| } |
| return string; |
| } |
| |
| /** |
| * Writes the specified index entry to the refactoring history. |
| * |
| * @param file |
| * the history index file |
| * @param proxies |
| * the refactoring descriptors |
| * @param flags |
| * the flags to use (either {@link EFS#NONE} or |
| * {@link EFS#APPEND}) |
| * @param monitor |
| * the progress monitor to use |
| * @param task |
| * the task label |
| * @throws CoreException |
| * if an error occurs while writing the index entry |
| * @throws IOException |
| * if an input/output error occurs |
| */ |
| private static void writeIndexEntry(final IFileStore file, final RefactoringDescriptorProxy[] proxies, final int flags, final IProgressMonitor monitor, final String task) throws CoreException, IOException { |
| OutputStream output= null; |
| try { |
| monitor.beginTask(task, 2); |
| file.getParent().mkdir(EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)); |
| output= new BufferedOutputStream(file.openOutputStream(flags, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL))); |
| writeRefactoringDescriptorProxies(output, proxies); |
| } finally { |
| monitor.done(); |
| if (output != null) { |
| try { |
| output.close(); |
| } catch (IOException exception) { |
| // Do nothing |
| } |
| } |
| } |
| } |
| |
| /** |
| * Writes refactoring descriptor proxies to the specified output stream. |
| * |
| * @param stream |
| * the output stream where to write to |
| * @param proxies |
| * the refactoring descriptor proxies to write |
| * @throws IOException |
| * if an input/output error occurs |
| */ |
| public static void writeRefactoringDescriptorProxies(final OutputStream stream, final RefactoringDescriptorProxy[] proxies) throws IOException { |
| final StringBuilder buffer= new StringBuilder(proxies.length * 64); |
| sortRefactoringDescriptorsAscending(proxies); |
| for (RefactoringDescriptorProxy proxy : proxies) { |
| buffer.append(proxy.getTimeStamp()); |
| buffer.append(DELIMITER_COMPONENT); |
| buffer.append(escapeString(proxy.getDescription())); |
| buffer.append(DELIMITER_ENTRY); |
| } |
| stream.write(buffer.toString().getBytes(IRefactoringSerializationConstants.OUTPUT_ENCODING)); |
| } |
| |
| /** |
| * A simple XML writer. Using this instead of the javax.xml.transform classes allows |
| * compilation against JCL Foundation (bug 80053). |
| */ |
| private static final class DOMWriter extends PrintWriter { |
| |
| /* constants */ |
| private static final String XML_VERSION= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; //$NON-NLS-1$ |
| |
| /** |
| * Creates a new DOM writer on the given output writer. |
| * |
| * @param output the output writer |
| */ |
| public DOMWriter(Writer output) { |
| super(output); |
| } |
| |
| public void printDocument(Document doc) { |
| println(XML_VERSION); |
| printElement(doc.getDocumentElement()); |
| } |
| |
| /** |
| * Prints the given element. |
| * |
| * @param element the element to print |
| */ |
| public void printElement(Element element) { |
| // Ensure extra whitespace is not emitted next to a Text node, |
| // as that will result in a situation where the restored text data is not the |
| // same as the saved text data. |
| boolean hasChildren= element.hasChildNodes(); |
| startTag(element, hasChildren); |
| if (hasChildren) { |
| boolean prevWasText= false; |
| NodeList children= element.getChildNodes(); |
| for (int i= 0; i < children.getLength(); i++) { |
| Node node= children.item(i); |
| if (node instanceof Element) { |
| if (!prevWasText) { |
| println(); |
| } |
| printElement((Element) children.item(i)); |
| prevWasText= false; |
| } else if (node instanceof Text) { |
| print(getEscaped(node.getNodeValue())); |
| prevWasText= true; |
| } |
| } |
| if (!prevWasText) { |
| println(); |
| } |
| endTag(element); |
| } |
| } |
| |
| private void startTag(Element element, boolean hasChildren) { |
| StringBuilder sb= new StringBuilder(); |
| sb.append("<"); //$NON-NLS-1$ |
| sb.append(element.getTagName()); |
| NamedNodeMap attributes= element.getAttributes(); |
| for (int i= 0; i < attributes.getLength(); i++) { |
| Attr attribute= (Attr) attributes.item(i); |
| sb.append(" "); //$NON-NLS-1$ |
| sb.append(attribute.getName()); |
| sb.append("=\""); //$NON-NLS-1$ |
| sb.append(getEscaped(String.valueOf(attribute.getValue()))); |
| sb.append("\""); //$NON-NLS-1$ |
| } |
| sb.append(hasChildren ? ">" : "/>"); //$NON-NLS-1$ //$NON-NLS-2$ |
| print(sb.toString()); |
| } |
| |
| private void endTag(Element element) { |
| StringBuilder sb= new StringBuilder(); |
| sb.append("</"); //$NON-NLS-1$ |
| sb.append(element.getNodeName()); |
| sb.append(">"); //$NON-NLS-1$ |
| print(sb.toString()); |
| } |
| |
| private static void appendEscapedChar(StringBuilder buffer, char c) { |
| String replacement= getReplacement(c); |
| if (replacement != null) { |
| buffer.append('&'); |
| buffer.append(replacement); |
| buffer.append(';'); |
| } else { |
| buffer.append(c); |
| } |
| } |
| |
| private static String getEscaped(String s) { |
| StringBuilder result= new StringBuilder(s.length() + 10); |
| for (int i= 0; i < s.length(); ++i) { |
| appendEscapedChar(result, s.charAt(i)); |
| } |
| return result.toString(); |
| } |
| |
| private static String getReplacement(char c) { |
| // Encode special XML characters into the equivalent character references. |
| // The first five are defined by default for all XML documents. |
| // The next three (#xD, #xA, #x9) are encoded to avoid them |
| // being converted to spaces on deserialization |
| // (fixes bug 93720) |
| switch (c) { |
| case '<': |
| return "lt"; //$NON-NLS-1$ |
| case '>': |
| return "gt"; //$NON-NLS-1$ |
| case '"': |
| return "quot"; //$NON-NLS-1$ |
| case '\'': |
| return "apos"; //$NON-NLS-1$ |
| case '&': |
| return "amp"; //$NON-NLS-1$ |
| case '\r': |
| return "#x0D"; //$NON-NLS-1$ |
| case '\n': |
| return "#x0A"; //$NON-NLS-1$ |
| case '\u0009': |
| return "#x09"; //$NON-NLS-1$ |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * Writes refactoring session descriptor to the specified output stream. |
| * |
| * @param stream |
| * the output stream where to write to |
| * @param descriptor |
| * the refactoring session descriptors to write |
| * @param stamps |
| * <code>true</code> to write time stamps as well, |
| * <code>false</code> otherwise |
| * @throws CoreException |
| * if an error occurs while writing the refactoring session |
| * descriptor |
| */ |
| public static void writeRefactoringSession(final OutputStream stream, final RefactoringSessionDescriptor descriptor, final boolean stamps) throws CoreException { |
| final RefactoringSessionTransformer transformer= new RefactoringSessionTransformer(true); |
| final RefactoringDescriptor[] descriptors= descriptor.getRefactorings(); |
| try { |
| transformer.beginSession(descriptor.getComment(), descriptor.getVersion()); |
| for (RefactoringDescriptor current : descriptors) { |
| if (current != null) { |
| try { |
| long stamp= stamps ? current.getTimeStamp() : -1; |
| transformer.beginRefactoring(current.getID(), stamp, current.getProject(), current.getDescription(), current.getComment(), current.getFlags()); |
| final Map<String, String> arguments= getArgumentMap(current); |
| if (arguments != null) { |
| checkArgumentMap(arguments); |
| for (final Iterator<Entry<String, String>> iterator= arguments.entrySet().iterator(); iterator.hasNext();) { |
| final Entry<String, String> entry= iterator.next(); |
| transformer.createArgument(entry.getKey(), entry.getValue()); |
| } |
| } |
| } finally { |
| transformer.endRefactoring(); |
| } |
| } |
| } |
| } finally { |
| transformer.endSession(); |
| } |
| final Document result= transformer.getResult(); |
| writeNode(stream, result); |
| } |
| |
| private static void writeNode(final OutputStream stream, Document document) { |
| OutputStreamWriter outputStreamWriter= new OutputStreamWriter(stream, Charset.forName("UTF-8")); //$NON-NLS-1$ |
| @SuppressWarnings("resource") |
| DOMWriter writer= new DOMWriter(outputStreamWriter); |
| writer.printDocument(document); |
| writer.flush(); |
| } |
| |
| /** The cached session descriptor, or <code>null</code> */ |
| private RefactoringSessionDescriptor fCachedDescriptor= null; |
| |
| /** The cached document, or <code>null</code> */ |
| private Document fCachedDocument= null; |
| |
| /** The cached path, or <code>null</code> */ |
| private IPath fCachedPath= null; |
| |
| /** The cached file store, or <code>null</code> */ |
| private IFileStore fCachedStore= null; |
| |
| /** The history file store */ |
| private final IFileStore fHistoryStore; |
| |
| /** |
| * The non-empty name of the managed project, or <code>null</code> for the |
| * workspace |
| */ |
| private final String fProjectName; |
| |
| /** |
| * Creates a new refactoring history manager. |
| * |
| * @param store |
| * the history file store |
| * @param name |
| * the non-empty name of the managed project, or |
| * <code>null</code> for the workspace |
| */ |
| RefactoringHistoryManager(final IFileStore store, final String name) { |
| Assert.isNotNull(store); |
| Assert.isTrue(name == null || !"".equals(name)); //$NON-NLS-1$ |
| fHistoryStore= store; |
| fProjectName= name; |
| } |
| |
| /** |
| * Adds the specified refactoring descriptor to the refactoring history. |
| * |
| * @param descriptor |
| * the refactoring descriptor to add |
| * @param sort |
| * <code>true</code> if the refactoring descriptor should be |
| * inserted into the history according to its time stamp, |
| * <code>false</code> if the descriptor is assumed to be the |
| * most recent one, and its simply appended |
| * @param monitor |
| * the progress monitor to use |
| * @throws CoreException |
| * if an error occurs while adding the descriptor to the history |
| */ |
| void addRefactoringDescriptor(final RefactoringDescriptor descriptor, final boolean sort, final IProgressMonitor monitor) throws CoreException { |
| try { |
| monitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_updating_history, 18); |
| final long stamp= descriptor.getTimeStamp(); |
| if (stamp >= 0) { |
| final IPath path= stampToPath(stamp); |
| final IFileStore folder= fHistoryStore.getFileStore(path); |
| final IFileStore history= folder.getChild(RefactoringHistoryService.NAME_HISTORY_FILE); |
| final IFileStore index= folder.getChild(RefactoringHistoryService.NAME_INDEX_FILE); |
| final RefactoringDescriptorProxy[] proxies= new RefactoringDescriptorProxy[] { new DefaultRefactoringDescriptorProxy(descriptor.getDescription(), descriptor.getProject(), descriptor.getTimeStamp())}; |
| if (history.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)).exists()) { |
| InputStream input= null; |
| try { |
| input= new BufferedInputStream(history.openInputStream(EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL))); |
| final Document document= getCachedDocument(path, input); |
| try { |
| input.close(); |
| input= null; |
| } catch (IOException exception) { |
| // Do nothing |
| } |
| monitor.worked(1); |
| final Document result= transformDescriptor(descriptor, false); |
| if (result != null) { |
| boolean found= false; |
| final NodeList list= result.getElementsByTagName(IRefactoringSerializationConstants.ELEMENT_REFACTORING); |
| final Element root= document.getDocumentElement(); |
| if (sort) { |
| final String string= Long.toString(stamp); |
| for (int offset= 0; offset < list.getLength(); offset++) { |
| final Element element= (Element) list.item(offset); |
| final String attribute= element.getAttribute(IRefactoringSerializationConstants.ATTRIBUTE_STAMP); |
| if (attribute != null) { |
| if (string.compareTo(attribute) > 0) { |
| root.insertBefore(document.importNode(element, true), element); |
| found= true; |
| break; |
| } |
| } |
| } |
| } |
| if (!found) |
| root.appendChild(document.importNode(list.item(0), true)); |
| writeHistoryEntry(history, document, new SubProgressMonitor(monitor, 10, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL), RefactoringCoreMessages.RefactoringHistoryService_updating_history); |
| if (sort) { |
| final Set<RefactoringDescriptorProxy> set= new HashSet<>(64); |
| readRefactoringDescriptorProxies(index, null, set, 0, Long.MAX_VALUE, new SubProgressMonitor(monitor, 2), RefactoringCoreMessages.RefactoringHistoryService_updating_history); |
| writeIndexEntry(index, set.toArray(new RefactoringDescriptorProxy[set.size()]), EFS.NONE, new SubProgressMonitor(monitor, 3, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL), RefactoringCoreMessages.RefactoringHistoryService_updating_history); |
| } else |
| writeIndexEntry(index, proxies, EFS.APPEND, new SubProgressMonitor(monitor, 5, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL), RefactoringCoreMessages.RefactoringHistoryService_updating_history); |
| } |
| } catch (ParserConfigurationException exception) { |
| throw createCoreException(exception); |
| } catch (IOException exception) { |
| throw createCoreException(exception); |
| } catch (SAXException exception) { |
| throw createCoreException(exception); |
| } finally { |
| if (input != null) { |
| try { |
| input.close(); |
| } catch (IOException exception) { |
| // Do nothing |
| } |
| } |
| } |
| } else { |
| try { |
| final Document result= transformDescriptor(descriptor, false); |
| writeHistoryEntry(history, result, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL), RefactoringCoreMessages.RefactoringHistoryService_updating_history); |
| writeIndexEntry(index, proxies, EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL), RefactoringCoreMessages.RefactoringHistoryService_updating_history); |
| } catch (IOException exception) { |
| throw createCoreException(exception); |
| } |
| } |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * Returns the cached refactoring history document. |
| * |
| * @param path |
| * the path of the document |
| * @param input |
| * the input stream where to read the document |
| * @return the cached refactoring history document |
| * @throws SAXException |
| * if an error occurs while parsing the history entry |
| * @throws IOException |
| * if an input/output error occurs |
| * @throws ParserConfigurationException |
| * if an error occurs in the parser configuration |
| */ |
| private Document getCachedDocument(final IPath path, final InputStream input) throws SAXException, IOException, ParserConfigurationException { |
| if (path.equals(fCachedPath) && fCachedDocument != null) |
| return fCachedDocument; |
| DocumentBuilder parser= DocumentBuilderFactory.newInstance().newDocumentBuilder(); |
| parser.setErrorHandler(new DefaultHandler()); |
| final Document document= parser.parse(new InputSource(input)); |
| fCachedDocument= document; |
| fCachedPath= path; |
| return document; |
| } |
| |
| /** |
| * Returns the cached refactoring session descriptor. |
| * |
| * @param store |
| * the file store of the descriptor |
| * @param projectName |
| * project name, or <code>null</code> for the workspace |
| * @param input |
| * the input stream where to read the descriptor |
| * @return the cached refactoring session descriptor |
| * @throws CoreException |
| * if an error occurs while reading the session |
| */ |
| private RefactoringSessionDescriptor getCachedSession(final IFileStore store, String projectName, final InputStream input) throws CoreException { |
| if (store.equals(fCachedStore) && fCachedDescriptor != null) |
| return fCachedDescriptor; |
| final RefactoringSessionDescriptor descriptor; |
| try { |
| descriptor= new RefactoringSessionReader(false, projectName).readSession(new InputSource(input)); |
| fCachedDescriptor= descriptor; |
| fCachedStore= store; |
| return descriptor; |
| } catch (CoreException e) { |
| throw new CoreException(new MultiStatus( |
| RefactoringCorePlugin.getPluginId(), |
| IRefactoringCoreStatusCodes.REFACTORING_HISTORY_IO_ERROR, |
| new IStatus[] { e.getStatus() }, |
| Messages.format(RefactoringCoreMessages.RefactoringHistoryManager_error_reading_file, BasicElementLabels.getURLPart(store.toURI().toString())), |
| null)); |
| } |
| } |
| |
| /** |
| * Reads the refactoring history from disk. |
| * |
| * @param start |
| * the start time stamp, inclusive |
| * @param end |
| * the end time stamp, inclusive |
| * @param monitor |
| * the progress monitor to use |
| * @return the refactoring history |
| */ |
| RefactoringHistory readRefactoringHistory(final long start, final long end, final IProgressMonitor monitor) { |
| try { |
| monitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_retrieving_history, 200); |
| final Set<RefactoringDescriptorProxy> set= new HashSet<>(); |
| try { |
| if (fHistoryStore.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 20, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)).exists()) |
| readRefactoringDescriptorProxies(fHistoryStore, fProjectName, set, start, end, new SubProgressMonitor(monitor, 80), RefactoringCoreMessages.RefactoringHistoryService_retrieving_history); |
| final IFileStore store= EFS.getLocalFileSystem().getStore(RefactoringCorePlugin.getDefault().getStateLocation()).getChild(RefactoringHistoryService.NAME_HISTORY_FOLDER).getChild(RefactoringHistoryService.NAME_WORKSPACE_PROJECT); |
| if (store.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 20, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)).exists()) |
| readRefactoringDescriptorProxies(store, null, set, start, end, new SubProgressMonitor(monitor, 80), RefactoringCoreMessages.RefactoringHistoryService_retrieving_history); |
| } catch (CoreException exception) { |
| RefactoringCorePlugin.log(exception); |
| } |
| final RefactoringDescriptorProxy[] proxies= new RefactoringDescriptorProxy[set.size()]; |
| set.toArray(proxies); |
| return new RefactoringHistoryImplementation(proxies); |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * Removes refactoring descriptors from the managed history. |
| * <p> |
| * All refactoring descriptors must be from the history entry denoted by the |
| * specified path. |
| * </p> |
| * |
| * @param proxies |
| * the refactoring descriptors |
| * @param path |
| * the path of the history entry |
| * @param monitor |
| * the progress monitor to use |
| * @param task |
| * the task label to use |
| * @throws CoreException |
| * if an error occurs |
| */ |
| private void removeRefactoringDescriptors(final RefactoringDescriptorProxy[] proxies, final IPath path, final IProgressMonitor monitor, final String task) throws CoreException { |
| try { |
| monitor.beginTask(task, 5); |
| final IFileStore folder= fHistoryStore.getFileStore(path); |
| final IFileStore index= folder.getChild(RefactoringHistoryService.NAME_INDEX_FILE); |
| if (index.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)).exists()) { |
| final Set<RefactoringDescriptorProxy> resultingProxies= new HashSet<>(64); |
| readRefactoringDescriptorProxies(index, null, resultingProxies, 0, Long.MAX_VALUE, new SubProgressMonitor(monitor, 1), task); |
| if (resultingProxies.size() == proxies.length) |
| removeIndexTree(folder, new SubProgressMonitor(monitor, 1), task); |
| else { |
| final IFileStore history= folder.getChild(RefactoringHistoryService.NAME_HISTORY_FILE); |
| if (history.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)).exists()) { |
| InputStream input= null; |
| Document document= null; |
| try { |
| input= new BufferedInputStream(history.openInputStream(EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL))); |
| document= getCachedDocument(path, input); |
| } catch (ParserConfigurationException exception) { |
| throw createCoreException(exception); |
| } catch (IOException exception) { |
| throw createCoreException(exception); |
| } catch (SAXException exception) { |
| throw createCoreException(exception); |
| } finally { |
| if (input != null) { |
| try { |
| input.close(); |
| } catch (IOException exception) { |
| // Do nothing |
| } |
| } |
| } |
| final Set<Node> removedNodes= new HashSet<>(proxies.length); |
| final NodeList list= document.getElementsByTagName(IRefactoringSerializationConstants.ELEMENT_REFACTORING); |
| final int length= list.getLength(); |
| for (int offset= 0; offset < length; offset++) { |
| final Node node= list.item(offset); |
| final NamedNodeMap attributes= node.getAttributes(); |
| if (attributes != null) { |
| final Node item= attributes.getNamedItem(IRefactoringSerializationConstants.ATTRIBUTE_STAMP); |
| if (item != null) { |
| final String value= item.getNodeValue(); |
| if (value != null) { |
| for (RefactoringDescriptorProxy proxy : proxies) { |
| final long stamp= proxy.getTimeStamp(); |
| if (value.equals(String.valueOf(stamp))) { |
| resultingProxies.remove(new DefaultRefactoringDescriptorProxy(proxy.getDescription(), proxy.getProject(), stamp)); |
| removedNodes.add(node); |
| } |
| } |
| } |
| } |
| } |
| } |
| for (final Iterator<Node> iterator= removedNodes.iterator(); iterator.hasNext();) { |
| final Node node= iterator.next(); |
| node.getParentNode().removeChild(node); |
| } |
| try { |
| writeIndexEntry(index, resultingProxies.toArray(new RefactoringDescriptorProxy[resultingProxies.size()]), EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL), task); |
| writeHistoryEntry(history, document, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL), task); |
| } catch (IOException exception) { |
| throw createCoreException(exception); |
| } |
| } |
| } |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * Removes refactoring descriptors from the managed history. |
| * |
| * @param proxies |
| * the refactoring descriptors |
| * @param monitor |
| * the progress monitor to use |
| * @param task |
| * the task label to use |
| * @throws CoreException |
| * if an error occurs |
| */ |
| void removeRefactoringDescriptors(final RefactoringDescriptorProxy[] proxies, final IProgressMonitor monitor, final String task) throws CoreException { |
| try { |
| final Map<IPath, Collection<RefactoringDescriptorProxy>> paths= new HashMap<>(); |
| monitor.beginTask(task, proxies.length + 300); |
| for (RefactoringDescriptorProxy proxy : proxies) { |
| final IPath path= stampToPath(proxy.getTimeStamp()); |
| Collection<RefactoringDescriptorProxy> collection= paths.get(path); |
| if (collection == null) { |
| collection= new ArrayList<>(64); |
| paths.put(path, collection); |
| } |
| collection.add(proxy); |
| } |
| final IProgressMonitor subMonitor= new SubProgressMonitor(monitor, 300); |
| try { |
| final Set<Entry<IPath, Collection<RefactoringDescriptorProxy>>> entries= paths.entrySet(); |
| subMonitor.beginTask(task, entries.size()); |
| for (final Iterator<Entry<IPath, Collection<RefactoringDescriptorProxy>>> iterator= entries.iterator(); iterator.hasNext();) { |
| final Entry<IPath, Collection<RefactoringDescriptorProxy>> entry= iterator.next(); |
| final Collection<RefactoringDescriptorProxy> collection= entry.getValue(); |
| removeRefactoringDescriptors(collection.toArray(new RefactoringDescriptorProxy[collection.size()]), entry.getKey(), new SubProgressMonitor(subMonitor, 1), task); |
| } |
| } finally { |
| subMonitor.done(); |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * Requests the resolved refactoring descriptor associated with the given |
| * proxy. |
| * |
| * @param proxy |
| * the refactoring descriptor proxy |
| * @param monitor |
| * the progress monitor to use |
| * @return the associated refactoring descriptor, or <code>null</code> |
| */ |
| RefactoringDescriptor requestDescriptor(final RefactoringDescriptorProxy proxy, final IProgressMonitor monitor) { |
| try { |
| monitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_resolving_information, 2); |
| final long stamp= proxy.getTimeStamp(); |
| if (stamp >= 0) { |
| InputStream input= null; |
| try { |
| final IFileStore folder= fHistoryStore.getFileStore(stampToPath(stamp)); |
| final IFileStore file= folder.getChild(RefactoringHistoryService.NAME_HISTORY_FILE); |
| if (file.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)).exists()) { |
| input= new BufferedInputStream(file.openInputStream(EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL))); |
| final RefactoringSessionDescriptor descriptor= getCachedSession(file, fProjectName, input); |
| if (descriptor != null) { |
| final RefactoringDescriptor[] descriptors= descriptor.getRefactorings(); |
| for (RefactoringDescriptor refactoringDescriptor : descriptors) { |
| if (refactoringDescriptor.getTimeStamp() == stamp) { |
| return refactoringDescriptor; |
| } |
| } |
| } |
| } |
| } catch (CoreException exception) { |
| RefactoringCorePlugin.log(exception); |
| } finally { |
| try { |
| if (input != null) |
| input.close(); |
| } catch (IOException exception) { |
| RefactoringCorePlugin.log(exception); |
| } |
| } |
| } |
| } finally { |
| monitor.done(); |
| } |
| return null; |
| } |
| |
| /** |
| * Sets the comment of the specified refactoring. |
| * |
| * @param proxy |
| * the refactoring descriptor proxy |
| * @param comment |
| * the comment |
| * @param monitor |
| * the progress monitor to use |
| * @throws CoreException |
| * if an error occurs while setting the comment |
| */ |
| void setComment(final RefactoringDescriptorProxy proxy, final String comment, final IProgressMonitor monitor) throws CoreException { |
| try { |
| monitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_updating_history, 100); |
| final long stamp= proxy.getTimeStamp(); |
| if (stamp >= 0) { |
| final IPath path= stampToPath(stamp); |
| final IFileStore folder= fHistoryStore.getFileStore(path); |
| final IFileStore history= folder.getChild(RefactoringHistoryService.NAME_HISTORY_FILE); |
| if (history.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 20, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)).exists()) { |
| InputStream input= null; |
| try { |
| input= new BufferedInputStream(history.openInputStream(EFS.NONE, new SubProgressMonitor(monitor, 40, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL))); |
| final Document document= getCachedDocument(path, input); |
| try { |
| input.close(); |
| input= null; |
| } catch (IOException exception) { |
| // Do nothing |
| } |
| final String time= String.valueOf(stamp); |
| final NodeList list= document.getElementsByTagName(IRefactoringSerializationConstants.ELEMENT_REFACTORING); |
| for (int index= 0; index < list.getLength(); index++) { |
| final Element element= (Element) list.item(index); |
| if (time.equals(element.getAttribute(IRefactoringSerializationConstants.ATTRIBUTE_STAMP))) { |
| element.setAttribute(IRefactoringSerializationConstants.ATTRIBUTE_COMMENT, comment); |
| break; |
| } |
| } |
| writeHistoryEntry(history, document, new SubProgressMonitor(monitor, 40, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL), RefactoringCoreMessages.RefactoringHistoryService_updating_history); |
| } catch (ParserConfigurationException exception) { |
| throw createCoreException(exception); |
| } catch (IOException exception) { |
| throw createCoreException(exception); |
| } catch (SAXException exception) { |
| throw createCoreException(exception); |
| } finally { |
| if (input != null) { |
| try { |
| input.close(); |
| } catch (IOException exception) { |
| // Do nothing |
| } |
| } |
| } |
| } |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * Writes the specified document node into the refactoring history. |
| * |
| * @param file |
| * the refactoring history file |
| * @param document |
| * the document representing the history entry |
| * @param monitor |
| * the progress monitor to use |
| * @param task |
| * the task label |
| * @throws CoreException |
| * if an error occurs while adding the history entry |
| */ |
| private void writeHistoryEntry(final IFileStore file, final Document document, final IProgressMonitor monitor, final String task) throws CoreException { |
| OutputStream output= null; |
| try { |
| monitor.beginTask(task, 2); |
| file.getParent().mkdir(EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)); |
| output= new BufferedOutputStream(file.openOutputStream(EFS.NONE, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL))); |
| writeNode(output, document); |
| } finally { |
| fCachedDocument= null; |
| fCachedPath= null; |
| fCachedDescriptor= null; |
| fCachedStore= null; |
| if (output != null) { |
| try { |
| output.close(); |
| } catch (IOException exception) { |
| // Do nothing |
| } |
| } |
| monitor.done(); |
| } |
| } |
| } |