org.netbeans.modules.diff.PatchAction Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.modules.diff;
import java.awt.Dialog;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.nio.file.Files;
import java.util.*;
import java.text.DateFormat;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;
import org.netbeans.api.progress.ProgressHandle;
import org.openide.ErrorManager;
import org.openide.NotifyDescriptor;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileStateInvalidException;
import org.openide.filesystems.FileUtil;
import org.openide.nodes.Node;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.actions.NodeAction;
import org.netbeans.modules.diff.builtin.ContextualPatch;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.windows.IOProvider;
import org.openide.windows.InputOutput;
import org.openide.windows.OutputEvent;
import org.openide.windows.OutputListener;
import org.openide.windows.OutputWriter;
/**
* Patch Action. It asks for a patch file and applies it to the selected file.
*
* @author Martin Entlicher
* @author Maros Sandor
*/
public class PatchAction extends NodeAction {
private static final String PREF_RECENT_PATCH_PATH = "patch.recentPatchDir";
// for tests
private static boolean skipReport = false;
/** Creates a new instance of PatchAction */
public PatchAction() {
putValue("noIconInMenu", Boolean.TRUE); // NOI18N
}
@Override
public String getName() {
return NbBundle.getMessage(PatchAction.class, "CTL_PatchActionName");
}
@Override
public boolean enable(Node[] nodes) {
if (nodes.length == 1) {
FileObject fo = DiffAction.getFileFromNode(nodes[0]);
if (fo != null) {
// #63460
return fo.toURL().getProtocol().equals("file"); // NOI18N
}
}
return false;
}
/**
* @return false to run in AWT thread.
*/
@Override
protected boolean asynchronous() {
return false;
}
@Override
public void performAction(Node[] nodes) {
final FileObject fo = DiffAction.getFileFromNode(nodes[0]);
if (fo != null) {
final File patch = getPatchFor(fo);
if (patch == null) return ;
Utils.postParallel(new Runnable () {
@Override
public void run() {
performPatch(patch, FileUtil.toFile(fo));
}
});
}
}
public static boolean performPatch(File patch, File file) throws MissingResourceException {
List report = null;
try (ProgressHandle ph = ProgressHandle.createHandle(NbBundle.getMessage(PatchAction.class, "MSG_AplyingPatch", new Object[] {patch.getName()}))) {
ph.start();
ContextualPatch cp = ContextualPatch.create(patch, file);
try {
report = cp.patch(false, ph);
} catch (Exception ioex) {
ErrorManager.getDefault().annotate(ioex, NbBundle.getMessage(PatchAction.class, "EXC_PatchParsingFailed", ioex.getLocalizedMessage()));
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ioex);
ErrorManager.getDefault().notify(ErrorManager.USER, ioex);
return false;
}
}
return displayPatchReport(report, patch);
}
private static boolean displayPatchReport(List report, final File patchFile) {
List successful = new ArrayList();
List failed = new ArrayList();
for (ContextualPatch.PatchReport patchReport : report) {
switch (patchReport.getStatus()) {
case Patched:
successful.add(patchReport);
break;
case Failure:
case Missing:
failed.add(patchReport);
break;
}
}
InputOutput log = IOProvider.getDefault().getIO("Patch Report", false);
OutputWriter ow = log.getOut();
if (log.isClosed()) {
try {
ow.reset();
} catch (IOException ex) {
}
log.select();
}
try {
ow.print(DateFormat.getDateTimeInstance().format(new Date()));
ow.println(" ===========================================================================");
ow.print(NbBundle.getMessage(PatchAction.class, "MSG_PatchAction.output.patchFile")); //NOI18N
try {
ow.println(patchFile.getAbsolutePath(), new OutputListener() {
@Override
public void outputLineSelected (OutputEvent ev) {
}
@Override
public void outputLineAction (OutputEvent ev) {
Utils.openFile(patchFile);
}
@Override
public void outputLineCleared (OutputEvent ev) {
}
});
} catch (IOException ex) {
ow.println(patchFile.getAbsolutePath());
}
ow.println("--- Successfully Patched ---");
if (successful.size() > 0) {
for (ContextualPatch.PatchReport patchReport : successful) {
ow.println(patchReport.getFile().getAbsolutePath());
}
} else {
ow.println("");
}
ow.println("--- Failed ---");
if (failed.size() > 0) {
for (ContextualPatch.PatchReport patchReport : failed) {
ow.print(patchReport.getFile().getAbsolutePath());
ow.print(" (");
ow.print(patchReport.getFailure().getLocalizedMessage());
ow.println(" )");
}
} else {
ow.println("");
}
} finally {
ow.close();
}
if (successful.size() > 0) {
List binaries = new ArrayList();
List appliedFiles = new ArrayList();
Map backups = new HashMap();
for (ContextualPatch.PatchReport patchReport : successful) {
FileObject fo = FileUtil.toFileObject(patchReport.getFile());
FileObject backup = FileUtil.toFileObject(patchReport.getOriginalBackupFile());
if (patchReport.isBinary()) {
binaries.add(fo);
}
appliedFiles.add(fo);
backups.put(fo, backup);
}
if (skipReport) {
return failed.isEmpty();
}
String message = failed.size() > 0 ? NbBundle.getMessage(PatchAction.class, "MSG_PatchAppliedPartially") : NbBundle.getMessage(PatchAction.class, "MSG_PatchAppliedSuccessfully");
Object notifyResult = DialogDisplayer.getDefault().notify(
new NotifyDescriptor.Confirmation(
message,
NotifyDescriptor.YES_NO_OPTION));
if (NotifyDescriptor.YES_OPTION.equals(notifyResult)) {
showDiffs(appliedFiles, binaries, backups);
removeBackups(appliedFiles, backups, true);
} else {
removeBackups(appliedFiles, backups, false);
}
return failed.isEmpty();
} else {
DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(NbBundle.getMessage(PatchAction.class, "MSG_WrongPatch")));
return false;
}
}
private File getPatchFor(FileObject fo) {
JFileChooser chooser = new JFileChooser();
String patchDirPath = DiffModuleConfig.getDefault().getPreferences().get(PREF_RECENT_PATCH_PATH, System.getProperty("user.home"));
File patchDir = new File(patchDirPath);
while (!patchDir.isDirectory()) {
patchDir = patchDir.getParentFile();
if (patchDir == null) {
patchDir = new File(System.getProperty("user.home"));
break;
}
}
FileUtil.preventFileChooserSymlinkTraversal(chooser, patchDir);
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
String title = NbBundle.getMessage(PatchAction.class,
(fo.isData()) ? "TITLE_SelectPatchForFile"
: "TITLE_SelectPatchForFolder", fo.getNameExt());
chooser.setDialogTitle(title);
// setup filters, default one filters patch files
FileFilter patchFilter = new javax.swing.filechooser.FileFilter() {
@Override
public boolean accept(File f) {
return f.getName().endsWith("diff") || f.getName().endsWith("patch") || f.isDirectory(); // NOI18N
}
@Override
public String getDescription() {
return NbBundle.getMessage(PatchAction.class, "CTL_PatchDialog_FileFilter");
}
};
chooser.addChoosableFileFilter(patchFilter);
chooser.setFileFilter(patchFilter);
chooser.setApproveButtonText(NbBundle.getMessage(PatchAction.class, "BTN_Patch"));
chooser.setApproveButtonMnemonic(NbBundle.getMessage(PatchAction.class, "BTN_Patch_mnc").charAt(0));
chooser.setApproveButtonToolTipText(NbBundle.getMessage(PatchAction.class, "BTN_Patch_tooltip"));
HelpCtx ctx = new HelpCtx(PatchAction.class.getName());
DialogDescriptor descriptor = new DialogDescriptor( chooser, title, true, new Object[0], null, 0, ctx, null );
final Dialog dialog = DialogDisplayer.getDefault().createDialog( descriptor );
dialog.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(PatchAction.class, "ACSD_PatchDialog"));
ChooserListener listener = new PatchAction.ChooserListener(dialog,chooser);
chooser.addActionListener(listener);
dialog.setVisible(true);
File selectedFile = listener.getFile();
if (selectedFile != null) {
DiffModuleConfig.getDefault().getPreferences().put(PREF_RECENT_PATCH_PATH, selectedFile.getParentFile().getAbsolutePath());
}
return selectedFile;
}
private static void showDiffs(List files, List binaries, Map backups) {
for (int i = 0; i < files.size(); i++) {
FileObject file = files.get(i);
FileObject backup = backups.get(file);
if (binaries.contains(file)) continue;
if (backup == null) {
try {
backup = FileUtil.toFileObject(FileUtil.normalizeFile(Files.createTempFile("diff-empty-backup", "").toFile()));
} catch (IOException e) {
// ignore
}
}
DiffAction.performAction(backup, file, file);
}
}
/** Removes the backup copies of files upon the successful application
* of a patch (.orig files).
* @param files a list of files, to which the patch was successfully applied
* @param backups a map of a form original file -> backup file
*/
private static void removeBackups(List files, Map backups, boolean onExit) {
StringBuffer filenames=new StringBuffer(),
exceptions=new StringBuffer();
for (int i = 0; i < files.size(); i++) {
FileObject targetFileObject = files.get(i);
FileObject backup= backups.get(targetFileObject);
// delete files that become empty and they have a backup file
if (targetFileObject != null && targetFileObject.getSize() == 0) {
if (backup != null && backup.isValid() && backup.getSize() > 0) {
if (onExit) {
deleteOnExit(targetFileObject);
} else {
try {
targetFileObject.delete();
} catch (IOException e) {
ErrorManager err = ErrorManager.getDefault();
err.annotate(e, "Patch can not delete file, skipping...");
err.notify(ErrorManager.INFORMATIONAL, e);
}
}
}
}
if (backup != null && backup.isValid()) {
if (onExit) {
deleteOnExit(backup);
} else {
try {
backup.delete();
}
catch (IOException ex) {
filenames.append(FileUtil.getFileDisplayName(backup));
filenames.append('\n');
exceptions.append(ex.getLocalizedMessage());
exceptions.append('\n');
}
}
}
}
if (filenames.length()>0)
ErrorManager.getDefault().notify(
ErrorManager.getDefault().annotate(new IOException(),
NbBundle.getMessage(PatchAction.class,
"EXC_CannotRemoveBackup", filenames, exceptions)));
}
private static void deleteOnExit(FileObject fo) {
File file = FileUtil.toFile(fo);
if (file != null) {
file.deleteOnExit();
}
}
@Override
public HelpCtx getHelpCtx() {
return new HelpCtx(PatchAction.class);
}
class ChooserListener implements ActionListener{
private Dialog dialog;
private JFileChooser chooser;
private File file = null;
public ChooserListener(Dialog dialog,JFileChooser chooser){
super();
this.dialog = dialog;
this.chooser = chooser;
}
@Override
public void actionPerformed(ActionEvent e){
String command = e.getActionCommand();
if(command == JFileChooser.APPROVE_SELECTION){
if(dialog != null) {
file = chooser.getSelectedFile();
dialog.setVisible(false);
}
}else{
if(dialog != null){
file = null;
dialog.setVisible(false);
dialog.dispose();
}
}
}
public File getFile(){
return file;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy