blob: 3b396f371a741287f67ec8110d1c82cfd2cd9a2d [file] [log] [blame]
/*******************************************************************************
* 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();
}
}
}