~ubuntu-branches/ubuntu/quantal/netbeans/quantal

« back to all changes in this revision

Viewing changes to subversion/main/src/org/netbeans/modules/subversion/ui/diff/ExportDiffAction.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
6
 * The contents of this file are subject to the terms of either the GNU
 
7
 * General Public License Version 2 only ("GPL") or the Common
 
8
 * Development and Distribution License("CDDL") (collectively, the
 
9
 * "License"). You may not use this file except in compliance with the
 
10
 * License. You can obtain a copy of the License at
 
11
 * http://www.netbeans.org/cddl-gplv2.html
 
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 
13
 * specific language governing permissions and limitations under the
 
14
 * License.  When distributing the software, include this License Header
 
15
 * Notice in each file and include the License file at
 
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 
17
 * particular file as subject to the "Classpath" exception as provided
 
18
 * by Sun in the GPL Version 2 section of the License file that
 
19
 * accompanied this code. If applicable, add the following below the
 
20
 * License Header, with the fields enclosed by brackets [] replaced by
 
21
 * your own identifying information:
 
22
 * "Portions Copyrighted [year] [name of copyright owner]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
26
 * The Original Software is NetBeans. The Initial Developer of the Original
 
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 
28
 * Microsystems, Inc. All Rights Reserved.
 
29
 *
 
30
 * If you wish your version of this file to be governed by only the CDDL
 
31
 * or only the GPL Version 2, indicate your decision by adding
 
32
 * "[Contributor] elects to include this software in this distribution
 
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 
34
 * single choice of license, a recipient has the option to distribute
 
35
 * your version of this file under either the CDDL, the GPL Version 2 or
 
36
 * to extend the choice of license to its licensees as provided above.
 
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 
38
 * Version 2 license, then the option applies only if the new code is
 
39
 * made subject to such option by the copyright holder.
 
40
 */
 
41
 
 
42
package org.netbeans.modules.subversion.ui.diff;
 
43
 
 
44
import org.netbeans.modules.diff.builtin.visualizer.TextDiffVisualizer;
 
45
import org.netbeans.modules.subversion.FileInformation;
 
46
import org.netbeans.modules.subversion.Subversion;
 
47
import org.netbeans.modules.subversion.SvnModuleConfig;
 
48
import org.netbeans.modules.subversion.client.SvnProgressSupport;
 
49
import org.netbeans.modules.subversion.util.Context;
 
50
import org.netbeans.modules.subversion.util.SvnUtils;
 
51
import org.netbeans.modules.subversion.ui.actions.ContextAction;
 
52
import org.netbeans.modules.versioning.util.AccessibleJFileChooser;
 
53
import org.netbeans.modules.versioning.util.Utils;
 
54
import org.netbeans.api.diff.Difference;
 
55
import org.netbeans.spi.diff.DiffProvider;
 
56
import org.openide.windows.TopComponent;
 
57
import org.openide.util.Lookup;
 
58
import org.openide.util.RequestProcessor;
 
59
import org.openide.util.NbBundle;
 
60
import org.openide.ErrorManager;
 
61
import org.openide.NotifyDescriptor;
 
62
import org.openide.DialogDisplayer;
 
63
import org.openide.DialogDescriptor;
 
64
import org.openide.nodes.Node;
 
65
import org.openide.awt.StatusDisplayer;
 
66
import javax.swing.*;
 
67
import java.io.*;
 
68
import java.util.*;
 
69
import java.util.List;
 
70
import java.awt.event.ActionListener;
 
71
import java.awt.event.ActionEvent;
 
72
import java.awt.*;
 
73
import org.netbeans.modules.subversion.FileStatusCache;
 
74
import org.netbeans.modules.subversion.client.SvnClientExceptionHandler;
 
75
import org.netbeans.modules.proxy.Base64Encoder;
 
76
import org.tigris.subversion.svnclientadapter.SVNClientException;
 
77
 
 
78
/**
 
79
 * Exports diff to file:
 
80
 *
 
81
 * <ul>
 
82
 * <li>for components that implements {@link DiffSetupSource} interface
 
83
 * exports actually displayed diff.
 
84
 *
 
85
 * <li>for DataNodes <b>local</b> differencies between the current
 
86
 * working copy and BASE repository version.
 
87
 * </ul>
 
88
 *  
 
89
 * @author Petr Kuzel
 
90
 */
 
91
public class ExportDiffAction extends ContextAction {
 
92
    
 
93
    private static final int enabledForStatus =
 
94
            FileInformation.STATUS_VERSIONED_MERGE |
 
95
            FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY |
 
96
            FileInformation.STATUS_VERSIONED_DELETEDLOCALLY |
 
97
            FileInformation.STATUS_VERSIONED_REMOVEDLOCALLY |
 
98
            FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY |
 
99
            FileInformation.STATUS_VERSIONED_ADDEDLOCALLY;
 
100
 
 
101
    protected String getBaseName(Node [] activatedNodes) {
 
102
        return "CTL_MenuItem_ExportDiff";  // NOI18N
 
103
    }
 
104
 
 
105
    /**
 
106
     * First look for DiffSetupSource name then for super (context name).
 
107
     */
 
108
    public String getName() {
 
109
        TopComponent activated = TopComponent.getRegistry().getActivated();
 
110
        if (activated instanceof DiffSetupSource) {
 
111
            String setupName = ((DiffSetupSource)activated).getSetupDisplayName();
 
112
            if (setupName != null) {
 
113
                return NbBundle.getMessage(this.getClass(), getBaseName(getActivatedNodes()) + "_Context",  // NOI18N
 
114
                                            setupName);
 
115
            }
 
116
        }
 
117
        return super.getName();
 
118
    }
 
119
    
 
120
    public boolean enable(Node[] nodes) {
 
121
        Context ctx = SvnUtils.getCurrentContext(nodes);
 
122
        File[] files = getModifiedFiles(ctx, enabledForStatus);         
 
123
        if(files.length < 1) {
 
124
            return false;
 
125
        }  
 
126
        TopComponent activated = TopComponent.getRegistry().getActivated();
 
127
        if (activated instanceof DiffSetupSource) {
 
128
            return true;
 
129
        }
 
130
        return super.enable(nodes) && Lookup.getDefault().lookup(DiffProvider.class) != null;                
 
131
    }
 
132
 
 
133
    protected void performContextAction(final Node[] nodes) {
 
134
       
 
135
        // reevaluate fast enablement logic guess
 
136
 
 
137
        if(!Subversion.getInstance().checkClientAvailable()) {            
 
138
            return;
 
139
        }
 
140
        
 
141
        boolean noop;
 
142
        TopComponent activated = TopComponent.getRegistry().getActivated();
 
143
        if (activated instanceof DiffSetupSource) {
 
144
            noop = ((DiffSetupSource) activated).getSetups().isEmpty();
 
145
        } else {
 
146
            Context context = getContext(nodes);
 
147
            File [] files = SvnUtils.getModifiedFiles(context, FileInformation.STATUS_LOCAL_CHANGE);
 
148
            noop = files.length == 0;
 
149
        }
 
150
        if (noop) {
 
151
            NotifyDescriptor msg = new NotifyDescriptor.Message(NbBundle.getMessage(ExportDiffAction.class, "BK3001"), NotifyDescriptor.INFORMATION_MESSAGE);
 
152
            DialogDisplayer.getDefault().notify(msg);
 
153
            return;
 
154
        }
 
155
 
 
156
        final JFileChooser chooser = new AccessibleJFileChooser(NbBundle.getMessage(ExportDiffAction.class, "ACSD_Export"));
 
157
        chooser.setDialogTitle(NbBundle.getMessage(ExportDiffAction.class, "CTL_Export_Title"));
 
158
        chooser.setMultiSelectionEnabled(false);
 
159
        javax.swing.filechooser.FileFilter[] old = chooser.getChoosableFileFilters();
 
160
        for (int i = 0; i < old.length; i++) {
 
161
            javax.swing.filechooser.FileFilter fileFilter = old[i];
 
162
            chooser.removeChoosableFileFilter(fileFilter);
 
163
 
 
164
        }
 
165
        chooser.setCurrentDirectory(new File(SvnModuleConfig.getDefault().getPreferences().get("ExportDiff.saveFolder", System.getProperty("user.home")))); // NOI18N
 
166
        chooser.addChoosableFileFilter(new javax.swing.filechooser.FileFilter() {
 
167
            public boolean accept(File f) {
 
168
                return f.getName().endsWith("diff") || f.getName().endsWith("patch") || f.isDirectory();  // NOI18N
 
169
            }
 
170
            public String getDescription() {
 
171
                return NbBundle.getMessage(ExportDiffAction.class, "BK3002");
 
172
            }
 
173
        });
 
174
        
 
175
        chooser.setDialogType(JFileChooser.SAVE_DIALOG);        
 
176
        chooser.setApproveButtonMnemonic(NbBundle.getMessage(ExportDiffAction.class, "MNE_Export_ExportAction").charAt(0));
 
177
        chooser.setApproveButtonText(NbBundle.getMessage(ExportDiffAction.class, "CTL_Export_ExportAction"));
 
178
        DialogDescriptor dd = new DialogDescriptor(chooser, NbBundle.getMessage(ExportDiffAction.class, "CTL_Export_Title"));
 
179
        dd.setOptions(new Object[0]);
 
180
        final Dialog dialog = DialogDisplayer.getDefault().createDialog(dd);
 
181
 
 
182
        chooser.addActionListener(new ActionListener() {
 
183
            public void actionPerformed(ActionEvent e) {
 
184
                String state = e.getActionCommand();
 
185
                if (state.equals(JFileChooser.APPROVE_SELECTION)) {
 
186
                    File destination = chooser.getSelectedFile();
 
187
                    String name = destination.getName();
 
188
                    boolean requiredExt = false;
 
189
                    requiredExt |= name.endsWith(".diff");  // NOI18N
 
190
                    requiredExt |= name.endsWith(".dif");   // NOI18N
 
191
                    requiredExt |= name.endsWith(".patch"); // NOI18N
 
192
                    if (requiredExt == false) {
 
193
                        File parent = destination.getParentFile();
 
194
                        destination = new File(parent, name + ".patch"); // NOI18N
 
195
                    }
 
196
 
 
197
                    if (destination.exists()) {
 
198
                        NotifyDescriptor nd = new NotifyDescriptor.Confirmation(NbBundle.getMessage(ExportDiffAction.class, "BK3005", destination.getAbsolutePath()));
 
199
                        nd.setOptionType(NotifyDescriptor.YES_NO_OPTION);
 
200
                        DialogDisplayer.getDefault().notify(nd);
 
201
                        if (nd.getValue().equals(NotifyDescriptor.OK_OPTION) == false) {
 
202
                            return;
 
203
                        }
 
204
                    }
 
205
                    SvnModuleConfig.getDefault().getPreferences().put("ExportDiff.saveFolder", destination.getParent()); // NOI18N
 
206
                    final File out = destination;
 
207
                    RequestProcessor rp = Subversion.getInstance().getRequestProcessor();
 
208
                    SvnProgressSupport ps = new SvnProgressSupport() {
 
209
                        protected void perform() {
 
210
                            async(this, nodes, out);
 
211
                        }
 
212
                    };
 
213
                    ps.start(rp, null, getRunningName(nodes));
 
214
                }
 
215
                dialog.dispose();
 
216
            }
 
217
        });
 
218
        dialog.setVisible(true);
 
219
 
 
220
    }
 
221
 
 
222
    protected boolean asynchronous() {
 
223
        return false;
 
224
    }
 
225
    
 
226
    private void async(SvnProgressSupport progress, Node[] nodes, File destination) {
 
227
        boolean success = false;
 
228
        OutputStream out = null;
 
229
        int exportedFiles = 0;
 
230
        try {
 
231
 
 
232
            // prepare setups and common parent - root
 
233
 
 
234
            File root;
 
235
            List<Setup> setups;
 
236
 
 
237
            TopComponent activated = TopComponent.getRegistry().getActivated();
 
238
            if (activated instanceof DiffSetupSource) {
 
239
                setups = new ArrayList<Setup>(((DiffSetupSource) activated).getSetups());
 
240
                List<File> setupFiles = new ArrayList<File>(setups.size());
 
241
                for (Iterator i = setups.iterator(); i.hasNext();) {
 
242
                    Setup setup = (Setup) i.next();
 
243
                    setupFiles.add(setup.getBaseFile()); 
 
244
                }
 
245
                root = getCommonParent(setupFiles.toArray(new File[setupFiles.size()]));
 
246
            } else {
 
247
                Context context = getContext(nodes);
 
248
                File [] files = SvnUtils.getModifiedFiles(context, FileInformation.STATUS_LOCAL_CHANGE);
 
249
                root = getCommonParent(context.getRootFiles());
 
250
                setups = new ArrayList<Setup>(files.length);
 
251
                for (int i = 0; i < files.length; i++) {
 
252
                    File file = files[i];
 
253
                    Setup setup = new Setup(file, null, Setup.DIFFTYPE_LOCAL);
 
254
                    setups.add(setup);
 
255
                }
 
256
            }
 
257
            if (root == null) {
 
258
                NotifyDescriptor nd = new NotifyDescriptor(
 
259
                        NbBundle.getMessage(ExportDiffAction.class, "MSG_BadSelection_Prompt"), 
 
260
                        NbBundle.getMessage(ExportDiffAction.class, "MSG_BadSelection_Title"), 
 
261
                        NotifyDescriptor.DEFAULT_OPTION, NotifyDescriptor.ERROR_MESSAGE, null, null);
 
262
                DialogDisplayer.getDefault().notify(nd);
 
263
                return;
 
264
            }
 
265
 
 
266
            String sep = System.getProperty("line.separator"); // NOI18N
 
267
            out = new BufferedOutputStream(new FileOutputStream(destination));
 
268
            // Used by PatchAction as MAGIC to detect right encoding
 
269
            out.write(("# This patch file was generated by NetBeans IDE" + sep).getBytes("utf8"));  // NOI18N
 
270
            out.write(("# Following Index: paths are relative to: " + root.getAbsolutePath() + sep).getBytes("utf8"));  // NOI18N
 
271
            out.write(("# This patch can be applied using context Tools: Patch action on respective folder." + sep).getBytes("utf8"));  // NOI18N
 
272
            out.write(("# It uses platform neutral UTF-8 encoding and \\n newlines." + sep).getBytes("utf8"));  // NOI18N
 
273
            out.write(("# Above lines and this line are ignored by the patching process." + sep).getBytes("utf8"));  // NOI18N
 
274
 
 
275
 
 
276
            Collections.sort(setups, new Comparator<Setup>() {
 
277
                public int compare(Setup o1, Setup o2) {
 
278
                    return o1.getBaseFile().compareTo(o2.getBaseFile());
 
279
                }
 
280
            });
 
281
            Iterator<Setup> it = setups.iterator();
 
282
            int i = 0;
 
283
            while (it.hasNext()) {
 
284
                Setup setup = it.next();
 
285
                File file = setup.getBaseFile();                
 
286
                if (file.isDirectory()) continue;
 
287
                try {            
 
288
                    progress.setRepositoryRoot(SvnUtils.getRepositoryRootUrl(file));
 
289
                } catch (SVNClientException ex) {
 
290
                    SvnClientExceptionHandler.notifyException(ex, true, true);
 
291
                    return;
 
292
                }                           
 
293
                progress.setDisplayName(file.getName());
 
294
 
 
295
                String index = "Index: ";   // NOI18N
 
296
                String rootPath = root.getAbsolutePath();
 
297
                String filePath = file.getAbsolutePath();
 
298
                String relativePath = filePath;
 
299
                if (filePath.startsWith(rootPath)) {
 
300
                    relativePath = filePath.substring(rootPath.length() + 1).replace(File.separatorChar, '/');
 
301
                    index += relativePath + sep;
 
302
                    out.write(index.getBytes("utf8")); // NOI18N
 
303
                }
 
304
                exportDiff(setup, relativePath, out);
 
305
                i++;
 
306
            }
 
307
 
 
308
            exportedFiles = i;
 
309
            success = true;
 
310
        } catch (IOException ex) {
 
311
            ErrorManager.getDefault().annotate(ex, NbBundle.getMessage(ExportDiffAction.class, "BK3003"));
 
312
            ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);   // stack trace to log
 
313
            ErrorManager.getDefault().notify(ErrorManager.USER, ex);  // message to user
 
314
        } finally {
 
315
            if (out != null) {
 
316
                try {
 
317
                    out.flush();
 
318
                    out.close();
 
319
                } catch (IOException alreadyClsoed) {
 
320
                }
 
321
            }
 
322
            if (success) {
 
323
                StatusDisplayer.getDefault().setStatusText(NbBundle.getMessage(ExportDiffAction.class, "BK3004", new Integer(exportedFiles)));
 
324
                if (exportedFiles == 0) {
 
325
                    destination.delete();
 
326
                } else {
 
327
                    Utils.openFile(destination);
 
328
                }
 
329
            } else {
 
330
                destination.delete();
 
331
            }
 
332
 
 
333
        }
 
334
    }
 
335
 
 
336
    private static File getCommonParent(File [] files) {
 
337
        File root = files[0];
 
338
        if (root.isFile()) root = root.getParentFile();
 
339
        for (int i = 1; i < files.length; i++) {
 
340
            root = Utils.getCommonParent(root, files[i]);
 
341
            if (root == null) return null;
 
342
        }
 
343
        return root;
 
344
    }
 
345
 
 
346
    /** Writes contextual diff into given stream.*/
 
347
    private void exportDiff(Setup setup, String relativePath, OutputStream out) throws IOException {
 
348
        setup.initSources();
 
349
        DiffProvider diff = (DiffProvider) Lookup.getDefault().lookup(DiffProvider.class);
 
350
 
 
351
        Reader r1 = null;
 
352
        Reader r2 = null;
 
353
        Difference[] differences;
 
354
 
 
355
        try {
 
356
            r1 = setup.getFirstSource().createReader();
 
357
            if (r1 == null) r1 = new StringReader("");  // NOI18N
 
358
            r2 = setup.getSecondSource().createReader();
 
359
            if (r2 == null) r2 = new StringReader("");  // NOI18N
 
360
            differences = diff.computeDiff(r1, r2);
 
361
        } finally {
 
362
            if (r1 != null) try { r1.close(); } catch (Exception e) {}
 
363
            if (r2 != null) try { r2.close(); } catch (Exception e) {}
 
364
        }
 
365
 
 
366
        File file = setup.getBaseFile();
 
367
        try {
 
368
            InputStream is;
 
369
            if (!SvnUtils.getMimeType(file).startsWith("text/") && differences.length == 0) {
 
370
                // assume the file is binary 
 
371
                is = new ByteArrayInputStream(exportBinaryFile(file).getBytes("utf8"));  // NOI18N
 
372
            } else {
 
373
                r1 = setup.getFirstSource().createReader();
 
374
                if (r1 == null) r1 = new StringReader(""); // NOI18N
 
375
                r2 = setup.getSecondSource().createReader();
 
376
                if (r2 == null) r2 = new StringReader(""); // NOI18N
 
377
                TextDiffVisualizer.TextDiffInfo info = new TextDiffVisualizer.TextDiffInfo(
 
378
                    relativePath + " " + setup.getFirstSource().getTitle(), // NOI18N
 
379
                    relativePath + " " + setup.getSecondSource().getTitle(),  // NOI18N
 
380
                    null,
 
381
                    null,
 
382
                    r1,
 
383
                    r2,
 
384
                    differences
 
385
                );
 
386
                info.setContextMode(true, 3);
 
387
                String diffText = TextDiffVisualizer.differenceToUnifiedDiffText(info);
 
388
                is = new ByteArrayInputStream(diffText.getBytes("utf8"));  // NOI18N
 
389
            }
 
390
            while(true) {
 
391
                int i = is.read();
 
392
                if (i == -1) break;
 
393
                out.write(i);
 
394
            }
 
395
        } finally {
 
396
            if (r1 != null) try { r1.close(); } catch (Exception e) {}
 
397
            if (r2 != null) try { r2.close(); } catch (Exception e) {}
 
398
        }
 
399
    }
 
400
 
 
401
    /**
 
402
     * Utility method that returns all non-excluded modified files that are
 
403
     * under given roots (folders) and have one of specified statuses.
 
404
     *
 
405
     * @param context context to search
 
406
     * @param includeStatus bit mask of file statuses to include in result
 
407
     * @return File [] array of Files having specified status
 
408
     */
 
409
    public static File [] getModifiedFiles(Context context, int includeStatus) {
 
410
        File[] all = Subversion.getInstance().getStatusCache().listFiles(context, includeStatus);
 
411
        List<File> files = new ArrayList<File>();
 
412
        for (int i = 0; i < all.length; i++) {
 
413
            File file = all[i];            
 
414
            files.add(file);            
 
415
        }
 
416
        
 
417
        // ensure that command roots (files that were explicitly selected by user) are included in Diff
 
418
        FileStatusCache cache = Subversion.getInstance().getStatusCache();
 
419
        File [] rootFiles = context.getRootFiles();
 
420
        for (int i = 0; i < rootFiles.length; i++) {
 
421
            File file = rootFiles[i];
 
422
            if (file.isFile() && (cache.getStatus(file).getStatus() & includeStatus) != 0 && !files.contains(file)) {
 
423
                files.add(file);
 
424
            }
 
425
        }
 
426
        return files.toArray(new File[files.size()]);
 
427
    }
 
428
        
 
429
    private String exportBinaryFile(File file) throws IOException {
 
430
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
431
        StringBuilder sb = new StringBuilder((int) file.length());
 
432
        if (file.canRead()) {
 
433
            Utils.copyStreamsCloseAll(baos, new FileInputStream(file));
 
434
        }
 
435
        sb.append("MIME: application/octet-stream; encoding: Base64; length: " + (file.canRead() ? file.length() : -1)); // NOI18N
 
436
        sb.append(System.getProperty("line.separator")); // NOI18N
 
437
        sb.append(Base64Encoder.encode(baos.toByteArray(), true));
 
438
        sb.append(System.getProperty("line.separator")); // NOI18N
 
439
        return sb.toString();
 
440
    }
 
441
}