KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > diff > PatchAction


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.diff;
21
22 import java.awt.Dialog JavaDoc;
23 import java.awt.event.ActionEvent JavaDoc;
24 import java.awt.event.ActionListener JavaDoc;
25 import java.io.*;
26 import java.util.ArrayList JavaDoc;
27 import java.util.HashMap JavaDoc;
28 import java.util.Map JavaDoc;
29 import java.util.StringTokenizer JavaDoc;
30 import java.util.List JavaDoc;
31 import javax.swing.JFileChooser JavaDoc;
32 import javax.swing.filechooser.FileFilter JavaDoc;
33 import org.openide.ErrorManager;
34 import org.openide.NotifyDescriptor;
35 import org.openide.filesystems.FileLock;
36 import org.openide.filesystems.FileObject;
37 import org.openide.filesystems.FileStateInvalidException;
38 import org.openide.filesystems.FileUtil;
39 import org.openide.nodes.Node;
40 import org.openide.util.HelpCtx;
41 import org.openide.util.NbBundle;
42 import org.openide.util.actions.NodeAction;
43 import org.openide.util.io.ReaderInputStream;
44 import org.netbeans.api.diff.Difference;
45 import org.netbeans.modules.diff.builtin.Patch;
46 import org.openide.DialogDescriptor;
47 import org.openide.DialogDisplayer;
48 import org.openide.util.RequestProcessor;
49
50 /**
51  * Patch Action. It asks for a patch file and applies it to the selected file.
52  *
53  * @author Martin Entlicher
54  */

55 public class PatchAction extends NodeAction {
56     
57     /**
58      * For patch application use an encoding, that is able to convert all bytes
59      * to characters so that there is no loss in the file content.
60      */

61     private static final String JavaDoc PATCHING_IO_ENCODING = "ISO-8859-1"; // NOI18N
62
private static final String JavaDoc PREF_RECENT_PATCH_PATH = "patch.recentPatchDir";
63
64     /** Creates a new instance of PatchAction */
65     public PatchAction() {
66         putValue("noIconInMenu", Boolean.TRUE); // NOI18N
67
}
68
69     public String JavaDoc getName() {
70         return NbBundle.getMessage(PatchAction.class, "CTL_PatchActionName");
71     }
72
73     public boolean enable(Node[] nodes) {
74         if (nodes.length == 1) {
75             FileObject fo = DiffAction.getFileFromNode(nodes[0]);
76             if (fo != null) {
77                 try {
78                     // #63460
79
return fo.getURL().getProtocol().equals("file"); // NOI18N
80
} catch (FileStateInvalidException fsiex) {
81                     return false;
82                 }
83             }
84         }
85         return false;
86     }
87
88     /**
89      * @return false to run in AWT thread.
90      */

91     protected boolean asynchronous() {
92         return false;
93     }
94     
95     public void performAction(Node[] nodes) {
96         final FileObject fo = DiffAction.getFileFromNode(nodes[0]);
97         if (fo != null) {
98             final File patch = getPatchFor(fo);
99             if (patch == null) return ;
100             RequestProcessor.getDefault().post(new Runnable JavaDoc () {
101                 public void run() {
102                     Patch.FileDifferences[] fileDiffs;
103                     String JavaDoc patchContext = null;
104                     BufferedReader r = null;
105                     try {
106                         String JavaDoc encoding = PATCHING_IO_ENCODING;
107
108                         // Export diff patches are for sure in utf-8 encoding,
109
String JavaDoc MAGIC = "# This patch file was generated by NetBeans IDE"; // NOI18N
110
r = new BufferedReader(new FileReader(patch));
111                         String JavaDoc line = r.readLine();
112                         if (MAGIC.equals(line)) {
113                             encoding = "utf8"; // NOI18N
114
line = r.readLine();
115                             String JavaDoc MAGIC2 = "paths are relative to: "; // NOI18N
116
int idx = line.indexOf(MAGIC2);
117                             if (idx != -1) {
118                                 patchContext = line.substring(idx + MAGIC2.length());
119                             }
120                         }
121                         r.close();
122                         
123                         byte[] buffer = new byte[MAGIC.length()];
124                         InputStream in = new FileInputStream(patch);
125                         int read = in.read(buffer);
126                         in.close();
127                         if (read != -1 && MAGIC.equals(new String JavaDoc(buffer, "utf8"))) { // NOI18N
128
encoding = "utf8"; // NOI18N
129
}
130                         r = new BufferedReader(new InputStreamReader(new FileInputStream(patch), encoding));
131                         fileDiffs = Patch.parse(r);
132                     } catch (IOException ioex) {
133                         ErrorManager.getDefault().annotate(ioex,
134                             NbBundle.getMessage(PatchAction.class, "EXC_PatchParsingFailed", ioex.getLocalizedMessage()));
135                         ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ioex);
136                         ErrorManager.getDefault().notify(ErrorManager.USER, ioex);
137                         return ;
138                     } finally {
139                         try { r.close(); } catch (Exception JavaDoc e) { };
140                     }
141                     int numDiffs = 0;
142                     for (int i = 0; i < fileDiffs.length; i++) {
143                         numDiffs += fileDiffs[i].getDifferences().length;
144                     }
145                     if (numDiffs == 0) {
146                         DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(
147                             NbBundle.getMessage(PatchAction.class, "MSG_NoDifferences", patch.getName())));
148                         return ;
149                     }
150                     /*
151                     System.out.println("Have diffs = "+fileDiffs+", length = "+fileDiffs.length);
152                     for (int i = 0; i < fileDiffs.length; i++) {
153                         System.out.println(" Difference "+i+" : "+fileDiffs[i]);
154                         Difference[] diffs = fileDiffs[i].getDifferences();
155                         for (int j = 0; j < diffs.length; j++) {
156                             System.out.println(" Diff["+j+"] = "+diffs[j]);
157                             System.out.println("TEXT1 = \n"+diffs[j].getFirstText()+"TEXT2 = \n"+diffs[j].getSecondText()+"");
158                         }
159                     }
160                     */

161                     applyFileDiffs(fileDiffs, fo, patchContext);
162                 }
163             });
164         }
165     }
166
167     private File getPatchFor(FileObject fo) {
168         JFileChooser JavaDoc chooser = new JFileChooser JavaDoc();
169         String JavaDoc patchDirPath = DiffModuleConfig.getDefault().getPreferences().get(PREF_RECENT_PATCH_PATH, System.getProperty("user.home"));
170         File patchDir = new File(patchDirPath);
171         while (!patchDir.isDirectory()) {
172             patchDir = patchDir.getParentFile();
173             if (patchDir == null) {
174                 patchDir = new File(System.getProperty("user.home"));
175                 break;
176             }
177         }
178         FileUtil.preventFileChooserSymlinkTraversal(chooser, patchDir);
179         chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
180         String JavaDoc title = NbBundle.getMessage(PatchAction.class,
181             (fo.isData()) ? "TITLE_SelectPatchForFile"
182                           : "TITLE_SelectPatchForFolder", fo.getNameExt());
183         chooser.setDialogTitle(title);
184
185         // setup filters, default one filters patch files
186
FileFilter JavaDoc patchFilter = new javax.swing.filechooser.FileFilter JavaDoc() {
187             public boolean accept(File f) {
188                 return f.getName().endsWith("diff") || f.getName().endsWith("patch") || f.isDirectory(); // NOI18N
189
}
190             public String JavaDoc getDescription() {
191                 return NbBundle.getMessage(PatchAction.class, "CTL_PatchDialog_FileFilter");
192             }
193         };
194         chooser.addChoosableFileFilter(patchFilter);
195         chooser.setFileFilter(patchFilter);
196
197         chooser.setApproveButtonText(NbBundle.getMessage(PatchAction.class, "BTN_Patch"));
198         chooser.setApproveButtonMnemonic(NbBundle.getMessage(PatchAction.class, "BTN_Patch_mnc").charAt(0));
199         chooser.setApproveButtonToolTipText(NbBundle.getMessage(PatchAction.class, "BTN_Patch_tooltip"));
200         HelpCtx ctx = new HelpCtx(PatchAction.class.getName());
201         DialogDescriptor descriptor = new DialogDescriptor( chooser, title, true, new Object JavaDoc[0], null, 0, ctx, null );
202         final Dialog JavaDoc dialog = DialogDisplayer.getDefault().createDialog( descriptor );
203         dialog.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(PatchAction.class, "ACSD_PatchDialog"));
204
205         ChooserListener listener = new PatchAction.ChooserListener(dialog,chooser);
206     chooser.addActionListener(listener);
207         dialog.setVisible(true);
208
209         File selectedFile = listener.getFile();
210         if (selectedFile != null) {
211             DiffModuleConfig.getDefault().getPreferences().put(PREF_RECENT_PATCH_PATH, selectedFile.getParentFile().getAbsolutePath());
212         }
213         return selectedFile;
214     }
215
216     private void applyFileDiffs(Patch.FileDifferences[] fileDiffs, FileObject fo, String JavaDoc patchContext) {
217         ArrayList JavaDoc<String JavaDoc> notFoundFileNames = new ArrayList JavaDoc<String JavaDoc>();
218         ArrayList JavaDoc<FileObject> appliedFiles = new ArrayList JavaDoc<FileObject>();
219         Map JavaDoc<FileObject, FileObject> backups = new HashMap JavaDoc<FileObject, FileObject>();
220         boolean patchFailed = false;
221         for (int i = 0; i < fileDiffs.length; i++) {
222             //System.out.println("applyFileDiffs(): fileName = "+fileDiffs[i].getFileName());
223
FileObject targetFileObject;
224             if (fo.isData()) {
225                 targetFileObject = fo;
226             } else {
227                 String JavaDoc indexName = fileDiffs[i].getIndexName();
228                 if (indexName != null) {
229                     targetFileObject = fo.getFileObject(indexName);
230                 } else {
231                     targetFileObject = findChild(fo, fileDiffs[i].getFileName());
232                 }
233             }
234
235             if (targetFileObject == null) {
236                 Difference[] diffs = fileDiffs[i].getDifferences();
237                 String JavaDoc filePath = fileDiffs[i].getIndexName();
238                 if (diffs.length == 1 && diffs[0].getFirstStart() == 0 && filePath != null) {
239                     // create new targetFileObject
240
try {
241                         targetFileObject = FileUtil.createData(fo, filePath);
242                     } catch (IOException e) {
243                         ErrorManager err = ErrorManager.getDefault();
244                         err.annotate(e, "Patch can not create new file, skipping..."); // NOI18N
245
err.notify(ErrorManager.INFORMATIONAL, e);
246                     }
247                 }
248             }
249
250             if (targetFileObject == null) {
251                 String JavaDoc indexName = fileDiffs[i].getIndexName();
252                 if (indexName != null) {
253                     notFoundFileNames.add(FileUtil.getFileDisplayName(fo) + '/' + indexName);
254                 } else {
255                     notFoundFileNames.add("sourceHostPath:" + fileDiffs[i].getFileName());
256                 }
257             } else {
258                 FileObject backup = createFileBackup(targetFileObject);
259                 if (applyDiffsTo(fileDiffs[i].getDifferences(), targetFileObject)) {
260                     appliedFiles.add(targetFileObject);
261                     backups.put(targetFileObject, backup);
262                     targetFileObject.refresh(true);
263                 } else {
264                     patchFailed = true;
265                 }
266             }
267         }
268
269         if (notFoundFileNames.size() > 0) {
270             if (notFoundFileNames.size() == fileDiffs.length) {
271                 DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(
272                         patchContext == null ? NbBundle.getMessage(PatchAction.class, "MSG_WrongPatch") :
273                         NbBundle.getMessage(PatchAction.class, "MSG_WrongPatch_Hint", patchContext)));
274             } else {
275                 StringBuffer JavaDoc files = new StringBuffer JavaDoc();
276                 for (int i = 0; i < notFoundFileNames.size(); i++) {
277                     files.append(notFoundFileNames.get(i));
278                     if (i < notFoundFileNames.size() - 1) {
279                         files.append(", ");
280                     }
281                 }
282                 DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(
283                     NbBundle.getMessage(PatchAction.class, "MSG_NotFoundFiles", files)));
284             }
285         }
286         if (appliedFiles.size() > 0) {
287             String JavaDoc message = patchFailed ? NbBundle.getMessage(PatchAction.class, "MSG_PatchAppliedPartially") : NbBundle.getMessage(PatchAction.class, "MSG_PatchAppliedSuccessfully");
288             Object JavaDoc notifyResult = DialogDisplayer.getDefault().notify(
289                 new NotifyDescriptor.Confirmation(
290                     message,
291                     NotifyDescriptor.YES_NO_OPTION));
292             if (NotifyDescriptor.YES_OPTION.equals(notifyResult)) {
293                 showDiffs(appliedFiles, backups);
294             }
295             removeBackups(appliedFiles, backups);
296         }
297     }
298
299     /**
300      * Flaky heuristics to find remote path on local system.
301      */

302     private static FileObject findChild(FileObject folder, String JavaDoc child) {
303         child = child.replace(File.separatorChar, '/');
304         StringTokenizer JavaDoc tokenizer = new StringTokenizer JavaDoc(child, "/");
305         FileObject ch = null;
306         // FIXME it's for sure wrong, it can be confused by /path/DUPL/somethigs/DULP/some.file
307
while (tokenizer.hasMoreTokens()) {
308             String JavaDoc token = tokenizer.nextToken();
309             ch = folder.getFileObject(token);
310             if (ch != null && ch.isFolder()) {
311                 folder = ch;
312                 ch = null;
313             }
314         }
315         return ch;
316     }
317
318     private FileObject createFileBackup(FileObject fo) {
319         FileObject parent = fo.getParent();
320         FileLock lock = null;
321         InputStream in = null;
322         OutputStream out = null;
323         try {
324             FileObject orig = parent.getFileObject(fo.getNameExt(), "orig");
325             if (orig == null) {
326                 orig = parent.createData(fo.getNameExt(), "orig");
327             }
328             FileUtil.copy(in = fo.getInputStream(), out = orig.getOutputStream(lock = orig.lock()));
329             return orig;
330         } catch (IOException ioex) {
331             return null;
332         } finally {
333             try {
334                 if (in != null) in.close();
335                 if (out != null) out.close();
336             } catch (IOException ioex) {}
337             if (lock != null) lock.releaseLock();
338         }
339     }
340
341     private boolean applyDiffsTo(Difference[] diffs, FileObject fo) {
342 // System.err.println("applyDiffsTo("+fo.getPath() + " " + diffs.length);
343
// for (int i= 0; i<diffs.length; i++) {
344
// System.err.println("\t" + diffs[i]);
345
// }
346
File tmp;
347         try {
348             tmp = FileUtil.normalizeFile(File.createTempFile("patch", "tmp"));
349         } catch (IOException ioex) {
350             ErrorManager.getDefault().notify(ioex);
351             return false;
352         }
353         tmp.deleteOnExit();
354         InputStream in = null;
355         Reader r = null;
356         OutputStream out = null;
357         try {
358             r = new InputStreamReader(fo.getInputStream(), PATCHING_IO_ENCODING);
359             Reader patched = Patch.apply(diffs, r);
360             in = new ReaderInputStream(patched, PATCHING_IO_ENCODING);
361             out = new FileOutputStream(tmp);
362             FileUtil.copy(in, out);
363         } catch (IOException ioex) {
364 // ErrorManager.getDefault().notify(ErrorManager.getDefault().annotate(ioex,
365
// NbBundle.getMessage(PatchAction.class, "EXC_PatchApplicationFailed", ioex.getLocalizedMessage(), fo.getNameExt())));
366
String JavaDoc msg = NbBundle.getMessage(PatchAction.class, "EXC_PatchApplicationFailed", ioex.getLocalizedMessage(), fo.getNameExt());
367             NotifyDescriptor dd = new NotifyDescriptor.Message(msg);
368             DialogDisplayer.getDefault().notify (dd);
369             ErrorManager.getDefault().log (msg);
370             tmp.delete();
371             return false;
372         } finally {
373             try {
374                 if (r != null) r.close();
375             } catch (IOException ioex) {
376                 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ioex);
377             }
378             try {
379                 if (in != null) in.close();
380             } catch (IOException ioex) {
381                 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ioex);
382             }
383             try {
384                 if (out != null) out.close();
385             } catch (IOException ioex) {
386                 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ioex);
387             }
388
389         }
390         FileLock lock = null;
391         try {
392             FileUtil.copy(in = new FileInputStream(tmp), out = fo.getOutputStream(lock = fo.lock()));
393         } catch (IOException ioex) {
394             ErrorManager.getDefault().notify(ErrorManager.getDefault().annotate(ioex,
395                 NbBundle.getMessage(PatchAction.class, "EXC_CopyOfAppliedPatchFailed",
396                                     fo.getNameExt())));
397             return false;
398         } finally {
399             if (lock != null) {
400                 lock.releaseLock();
401             }
402             try {
403                 if (in != null) in.close();
404             } catch (IOException ioex) {
405                 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ioex);
406             }
407             try {
408                 if (out != null) out.close();
409             } catch (IOException ioex) {
410                 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ioex);
411             }
412
413         }
414         tmp.delete();
415         return true;
416         //TopManager.getDefault().notify(new NotifyDescriptor.Message(
417
// NbBundle.getMessage(PatchAction.class, "MSG_PatchAppliedSuccessfully")));
418
}
419
420     private void showDiffs(List JavaDoc<FileObject> files, Map JavaDoc<FileObject, FileObject> backups) {
421         for (int i = 0; i < files.size(); i++) {
422             FileObject file = files.get(i);
423             FileObject backup = backups.get(file);
424             DiffAction.performAction(backup, file, file);
425         }
426     }
427
428     /** Removes the backup copies of files upon the successful application
429      * of a patch (.orig files).
430      * @param files a list of files, to which the patch was successfully applied
431      * @param backups a map of a form original file -> backup file
432      */

433     private static void removeBackups(List JavaDoc<FileObject> files, Map JavaDoc<FileObject, FileObject> backups) {
434         StringBuffer JavaDoc filenames=new StringBuffer JavaDoc(),
435                      exceptions=new StringBuffer JavaDoc();
436         for (int i = 0; i < files.size(); i++) {
437             FileObject targetFileObject = files.get(i);
438             FileObject backup= backups.get(targetFileObject);
439
440             // delete files that become empry
441
if (targetFileObject.getSize() == 0) {
442                 try {
443                     targetFileObject.delete();
444                 } catch (IOException e) {
445                     ErrorManager err = ErrorManager.getDefault();
446                     err.annotate(e, "Patch can not delete file, skipping...");
447                     err.notify(ErrorManager.INFORMATIONAL, e);
448                 }
449             }
450
451             try {
452                 backup.delete();
453             }
454             catch (IOException ex) {
455                 filenames.append(FileUtil.getFileDisplayName(backup));
456                 filenames.append('\n');
457                 exceptions.append(ex.getLocalizedMessage());
458                 exceptions.append('\n');
459             }
460         }
461         if (filenames.length()>0)
462             ErrorManager.getDefault().notify(
463                 ErrorManager.getDefault().annotate(new IOException(),
464                     NbBundle.getMessage(PatchAction.class,
465                         "EXC_CannotRemoveBackup", filenames, exceptions)));
466     }
467    
468     public HelpCtx getHelpCtx() {
469         return new HelpCtx(PatchAction.class);
470     }
471
472     class ChooserListener implements ActionListener JavaDoc{
473         private Dialog JavaDoc dialog;
474         private JFileChooser JavaDoc chooser;
475         private File file = null;
476
477         public ChooserListener(Dialog JavaDoc dialog,JFileChooser JavaDoc chooser){
478             super();
479             this.dialog = dialog;
480             this.chooser = chooser;
481         }
482
483         public void actionPerformed(ActionEvent JavaDoc e){
484             String JavaDoc command = e.getActionCommand();
485             if(command == JFileChooser.APPROVE_SELECTION){
486                 if(dialog != null) {
487                     file = chooser.getSelectedFile();
488                     dialog.setVisible(false);
489
490                 }
491             }else{
492                 if(dialog != null){
493                     file = null;
494                     dialog.setVisible(false);
495                 }
496             }
497         }
498         public File getFile(){
499             return file;
500         }
501     }
502
503 }
504
Popular Tags