1 19 20 package org.netbeans.modules.diff; 21 22 import java.awt.Dialog ; 23 import java.awt.event.ActionEvent ; 24 import java.awt.event.ActionListener ; 25 import java.io.*; 26 import java.util.ArrayList ; 27 import java.util.HashMap ; 28 import java.util.Map ; 29 import java.util.StringTokenizer ; 30 import java.util.List ; 31 import javax.swing.JFileChooser ; 32 import javax.swing.filechooser.FileFilter ; 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 55 public class PatchAction extends NodeAction { 56 57 61 private static final String PATCHING_IO_ENCODING = "ISO-8859-1"; private static final String PREF_RECENT_PATCH_PATH = "patch.recentPatchDir"; 63 64 65 public PatchAction() { 66 putValue("noIconInMenu", Boolean.TRUE); } 68 69 public String 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 return fo.getURL().getProtocol().equals("file"); } catch (FileStateInvalidException fsiex) { 81 return false; 82 } 83 } 84 } 85 return false; 86 } 87 88 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 () { 101 public void run() { 102 Patch.FileDifferences[] fileDiffs; 103 String patchContext = null; 104 BufferedReader r = null; 105 try { 106 String encoding = PATCHING_IO_ENCODING; 107 108 String MAGIC = "# This patch file was generated by NetBeans IDE"; r = new BufferedReader(new FileReader(patch)); 111 String line = r.readLine(); 112 if (MAGIC.equals(line)) { 113 encoding = "utf8"; line = r.readLine(); 115 String MAGIC2 = "paths are relative to: "; 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 (buffer, "utf8"))) { encoding = "utf8"; } 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 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 161 applyFileDiffs(fileDiffs, fo, patchContext); 162 } 163 }); 164 } 165 } 166 167 private File getPatchFor(FileObject fo) { 168 JFileChooser chooser = new JFileChooser (); 169 String 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 title = NbBundle.getMessage(PatchAction.class, 181 (fo.isData()) ? "TITLE_SelectPatchForFile" 182 : "TITLE_SelectPatchForFolder", fo.getNameExt()); 183 chooser.setDialogTitle(title); 184 185 FileFilter patchFilter = new javax.swing.filechooser.FileFilter () { 187 public boolean accept(File f) { 188 return f.getName().endsWith("diff") || f.getName().endsWith("patch") || f.isDirectory(); } 190 public String 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 [0], null, 0, ctx, null ); 202 final Dialog 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 patchContext) { 217 ArrayList <String > notFoundFileNames = new ArrayList <String >(); 218 ArrayList <FileObject> appliedFiles = new ArrayList <FileObject>(); 219 Map <FileObject, FileObject> backups = new HashMap <FileObject, FileObject>(); 220 boolean patchFailed = false; 221 for (int i = 0; i < fileDiffs.length; i++) { 222 FileObject targetFileObject; 224 if (fo.isData()) { 225 targetFileObject = fo; 226 } else { 227 String 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 filePath = fileDiffs[i].getIndexName(); 238 if (diffs.length == 1 && diffs[0].getFirstStart() == 0 && filePath != null) { 239 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..."); err.notify(ErrorManager.INFORMATIONAL, e); 246 } 247 } 248 } 249 250 if (targetFileObject == null) { 251 String 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 files = new StringBuffer (); 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 message = patchFailed ? NbBundle.getMessage(PatchAction.class, "MSG_PatchAppliedPartially") : NbBundle.getMessage(PatchAction.class, "MSG_PatchAppliedSuccessfully"); 288 Object 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 302 private static FileObject findChild(FileObject folder, String child) { 303 child = child.replace(File.separatorChar, '/'); 304 StringTokenizer tokenizer = new StringTokenizer (child, "/"); 305 FileObject ch = null; 306 while (tokenizer.hasMoreTokens()) { 308 String 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 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 String 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 } 419 420 private void showDiffs(List <FileObject> files, Map <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 433 private static void removeBackups(List <FileObject> files, Map <FileObject, FileObject> backups) { 434 StringBuffer filenames=new StringBuffer (), 435 exceptions=new StringBuffer (); 436 for (int i = 0; i < files.size(); i++) { 437 FileObject targetFileObject = files.get(i); 438 FileObject backup= backups.get(targetFileObject); 439 440 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 { 473 private Dialog dialog; 474 private JFileChooser chooser; 475 private File file = null; 476 477 public ChooserListener(Dialog dialog,JFileChooser chooser){ 478 super(); 479 this.dialog = dialog; 480 this.chooser = chooser; 481 } 482 483 public void actionPerformed(ActionEvent e){ 484 String 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 |