blob: 3d2edf2f298832b9be6b08f092cf84d1dd14420d [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.history.integration.revertion;
import com.intellij.history.LocalHistory;
import com.intellij.history.core.Content;
import com.intellij.history.core.Paths;
import com.intellij.history.core.changes.*;
import com.intellij.history.core.tree.Entry;
import com.intellij.history.integration.IdeaGateway;
import com.intellij.openapi.command.impl.DocumentUndoProvider;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.HashSet;
import com.intellij.util.io.ReadOnlyAttributeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class UndoChangeRevertingVisitor extends ChangeVisitor {
private final IdeaGateway myGateway;
private final Set<DelayedApply> myDelayedApplies = new HashSet<DelayedApply>();
private final long myFromChangeId;
private final long myToChangeId;
private boolean isReverting;
public UndoChangeRevertingVisitor(IdeaGateway gw, @NotNull Long fromChangeId, @Nullable Long toChangeId) {
myGateway = gw;
myFromChangeId = fromChangeId;
myToChangeId = toChangeId == null ? -1 : toChangeId;
}
protected boolean shouldRevert(Change c) {
if (c.getId() == myFromChangeId) {
isReverting = true;
}
return isReverting && !(c instanceof ContentChange);
}
protected void checkShouldStop(Change c) throws StopVisitingException {
if (c.getId() == myToChangeId) stop();
}
@Override
public void visit(CreateEntryChange c) throws StopVisitingException {
if (shouldRevert(c)) {
VirtualFile f = myGateway.findVirtualFile(c.getPath());
if (f != null) {
unregisterDelayedApplies(f);
try {
f.delete(LocalHistory.VFS_EVENT_REQUESTOR);
}
catch (IOException e) {
throw new RuntimeIOException(e);
}
}
}
checkShouldStop(c);
}
@Override
public void visit(ContentChange c) throws StopVisitingException {
if (shouldRevert(c)) {
try {
VirtualFile f = myGateway.findOrCreateFileSafely(c.getPath(), false);
registerDelayedContentApply(f, c.getOldContent(), c.getOldTimestamp());
}
catch (IOException e) {
throw new RuntimeIOException(e);
}
}
checkShouldStop(c);
}
@Override
public void visit(RenameChange c) throws StopVisitingException {
if (shouldRevert(c)) {
VirtualFile f = myGateway.findVirtualFile(c.getPath());
if (f != null) {
VirtualFile existing = f.getParent().findChild(c.getOldName());
try {
if (existing != null && !Comparing.equal(existing, f)) {
existing.delete(LocalHistory.VFS_EVENT_REQUESTOR);
}
f.rename(LocalHistory.VFS_EVENT_REQUESTOR, c.getOldName());
}
catch (IOException e) {
throw new RuntimeIOException(e);
}
}
}
checkShouldStop(c);
}
@Override
public void visit(ROStatusChange c) throws StopVisitingException {
if (shouldRevert(c)) {
VirtualFile f = myGateway.findVirtualFile(c.getPath());
if (f != null) {
registerDelayedROStatusApply(f, c.getOldStatus());
}
}
checkShouldStop(c);
}
@Override
public void visit(MoveChange c) throws StopVisitingException {
if (shouldRevert(c)) {
VirtualFile f = myGateway.findVirtualFile(c.getPath());
if (f != null) {
try {
VirtualFile parent = myGateway.findOrCreateFileSafely(c.getOldParent(), true);
VirtualFile existing = parent.findChild(f.getName());
if (existing != null) existing.delete(LocalHistory.VFS_EVENT_REQUESTOR);
f.move(LocalHistory.VFS_EVENT_REQUESTOR, parent);
}
catch (IOException e) {
throw new RuntimeIOException(e);
}
}
}
checkShouldStop(c);
}
@Override
public void visit(DeleteChange c) throws StopVisitingException {
if (shouldRevert(c)) {
try {
VirtualFile parent = myGateway.findOrCreateFileSafely(Paths.getParentOf(c.getPath()), true);
revertDeletion(parent, c.getDeletedEntry());
}
catch (IOException e) {
throw new RuntimeIOException(e);
}
}
checkShouldStop(c);
}
private void revertDeletion(VirtualFile parent, Entry e) throws IOException {
VirtualFile f = myGateway.findOrCreateFileSafely(parent, e.getName(), e.isDirectory());
if (e.isDirectory()) {
for (Entry child : e.getChildren()) revertDeletion(f, child);
}
else {
registerDelayedContentApply(f, e.getContent(), e.getTimestamp());
registerDelayedROStatusApply(f, e.isReadOnly());
}
}
private void registerDelayedContentApply(VirtualFile f, Content content, long timestamp) {
registerDelayedApply(new DelayedContentApply(f, content, timestamp));
}
private void registerDelayedROStatusApply(VirtualFile f, boolean isReadOnly) {
registerDelayedApply(new DelayedROStatusApply(f, isReadOnly));
}
private void registerDelayedApply(DelayedApply a) {
myDelayedApplies.remove(a);
myDelayedApplies.add(a);
}
private void unregisterDelayedApplies(VirtualFile fileOrDir) {
List<DelayedApply> toRemove = new ArrayList<DelayedApply>();
for (DelayedApply a : myDelayedApplies) {
if (VfsUtil.isAncestor(fileOrDir, a.getFile(), false)) {
toRemove.add(a);
}
}
for (DelayedApply a : toRemove) {
myDelayedApplies.remove(a);
}
}
@Override
public void finished() {
try {
for (DelayedApply a : myDelayedApplies) a.apply();
}
catch (IOException e) {
throw new RuntimeIOException(e);
}
}
private static abstract class DelayedApply {
protected VirtualFile myFile;
protected DelayedApply(VirtualFile f) {
myFile = f;
}
public VirtualFile getFile() {
return myFile;
}
public abstract void apply() throws IOException;
@Override
public boolean equals(Object o) {
if (!getClass().equals(o.getClass())) return false;
return myFile.equals(((DelayedApply)o).myFile);
}
@Override
public int hashCode() {
return getClass().hashCode() + 32 * myFile.hashCode();
}
}
private static class DelayedContentApply extends DelayedApply {
private final Content myContent;
private final long myTimestamp;
public DelayedContentApply(VirtualFile f, Content content, long timestamp) {
super(f);
myContent = content;
myTimestamp = timestamp;
}
@Override
public void apply() throws IOException {
if (!myContent.isAvailable()) return;
boolean isReadOnly = !myFile.isWritable();
ReadOnlyAttributeUtil.setReadOnlyAttribute(myFile, false);
Document doc = FileDocumentManager.getInstance().getCachedDocument(myFile);
DocumentUndoProvider.startDocumentUndo(doc);
try {
myFile.setBinaryContent(myContent.getBytes(), -1, myTimestamp);
}
finally {
DocumentUndoProvider.finishDocumentUndo(doc);
}
ReadOnlyAttributeUtil.setReadOnlyAttribute(myFile, isReadOnly);
}
}
private static class DelayedROStatusApply extends DelayedApply {
private final boolean isReadOnly;
private DelayedROStatusApply(VirtualFile f, boolean isReadOnly) {
super(f);
this.isReadOnly = isReadOnly;
}
public void apply() throws IOException {
ReadOnlyAttributeUtil.setReadOnlyAttribute(myFile, isReadOnly);
}
}
public static class RuntimeIOException extends RuntimeException {
public RuntimeIOException(Throwable cause) {
super(cause);
}
}
}