NetBeans Diff API

If you're creating some kind of text-based IDE or editor on the NetBeans Platform, integrating a Diff Viewer is possible via the NetBeans Diff API. It's quite new, since NetBeans IDE 6.7.

Here's a simple action that shows how the Diff API works (in its simplest scenario). For example, in real life, you wouldn't hardcode the files, but somehow identify them programmatically:

public final class DiffAction implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        StreamSource local = StreamSource.createSource("name1",
                "title1", "text/html", new File("/home/geertjan/file1.html"));
        StreamSource remote = StreamSource.createSource("name2",
                "title2", "text/html", new File("/home/geertjan/file2.html"));
        diff(local, remote);
    }

    public void diff(final StreamSource local, final StreamSource remote) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    DiffView view = Diff.getDefault().createDiff(local, remote);
                    TopComponent tc = new TopComponent();
                    tc.setDisplayName("Diff Viewer");
                    tc.setLayout(new BorderLayout());
                    tc.add(view.getComponent(), BorderLayout.CENTER);
                    tc.open();
                    tc.requestActive();
                } catch (IOException ex) {
                }
            }
        });
    }
    
}

The code above gives you a whole new window, exactly as shown below, with a visual diff of the two files you sent to the Diff API:

And this is how I register the above action in the layer. Take note especially of the line in bold, which is new in 6.8 and ensures that the action will be invoked asynchronously (and a spinning cursor automatically appears during processing):

<folder name="Actions">
    <folder name="Tools">
        <file name="org-demo-diffimpl-DiffAction.instance">
            <attr name="delegate" newvalue="org.demo.diffimpl.DiffAction"/>
            <attr name="displayName" bundlevalue="org.demo.diffimpl.Bundle#CTL_DiffAction"/>
            <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.alwaysEnabled"/>
            <attr name="noIconInMenu" boolvalue="false"/>
            <attr name="asynchronous" boolvalue="true"/>
        </file>
    </folder>
</folder>
<folder name="Menu">
    <folder name="Tools">
        <file name="org-demo-diffimpl-DiffAction.shadow">
            <attr name="originalFile" stringvalue="Actions/Tools/org-demo-diffimpl-DiffAction.instance"/>
            <attr name="position" intvalue="0"/>
        </file>
    </folder>
</folder>

Another good thing is that the sample code shown in the Use Cases section of the NetBeans Diff API javadoc is very clear and so you can begin using this component in your own applications right away.

Comments:

It sounds interesting.

Just look at :
(1)
my enhancement requests:
http://www.netbeans.org/issues/show_bug.cgi?id=155472
and
http://www.netbeans.org/issues/show_bug.cgi?id=159823
(2)
the idea behind is:
http://www.jroller.com/dmdevito/entry/the_netbeans_missing_distributions

Posted by Dominique De Vito on October 08, 2009 at 02:47 AM PDT #

It's a bit worrisome to see all those singletons floating around the netbeans api. Are there any plans to move towards something like osgi?

Posted by ocean on October 08, 2009 at 04:23 AM PDT #

@Dominique: Could you add a little more detail to your comment? How would moving to OSGi help and what singletons are you referring to? Don't get me wrong, I like OSGi as you might be able to see from my blog, but I simply didn't understand your comment :-).

Toni

Posted by Toni Epple on October 08, 2009 at 06:29 PM PDT #

Hi, I'm trying to use de Diff API as it says in the javadoc, the use case. But gives me two NullPointerExceptions in these classes:

org.netbeans.modules.diff.builtin.visualizer.editable.DecoratedEditorPane.paintComponent(DecoratedEditorPane.java:128)

org.netbeans.modules.diff.builtin.visualizer.editable.LineNumbersActionsBar.paintComponent(LineNumbersActionsBar.java:333)

It's something about the Editor settings being Null. I don't have it in an Action, only in a method inside the TopComponent, I don't know if there is a difference in that.

I will thank you any help you can give me

Posted by Walter on October 27, 2009 at 04:05 AM PDT #

Hello and thank you for a great blog. I have been learning a lot from you!

Now to my question... I am trying to implement a Diff viewer with merge capabilities just like when you diff files in the Netbeans IDE but after spending a whole day on it I am still getting nowhere. Could you maybe write a new post where you extend whats created in this post with merge functions? I can't find any information on the Internet about how to use the Diff API to create a Diff/Merge editor so hopefully many people could benefit from that! :)

Regards,
Henrik

Posted by Henrik on January 17, 2011 at 10:48 PM PST #

Did you look in the javadoc?

http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-diff/index.html?overview-summary.html

Posted by Geertjan Wielenga on January 17, 2011 at 10:50 PM PST #

Yes I have looked at the documentation. It mentions a class called MergeVisualizer that I have been trying to use but can't figure out how to.
The code I wrote: http://pastebin.com/3B0ucA3V

I have found three scenarios that all produce different result:

1) Using two files where a line have been removed from the latest file.
current.txt:
aaaaaaaa
bbbbbbbb
cccccccc

previous.txt:
aaaaaaaa
bbbbbbbb
cccccccc
dddddddd

This gives the output:
Number of diffs: 1
Diff 0
First test: dddddddd

Second text: null

And the exception:
java.lang.NullPointerException
at org.netbeans.modules.merge.builtin.visualizer.MergeControl.initialize(MergeControl.java:102)
at org.netbeans.modules.merge.builtin.visualizer.GraphicalMergeVisualizer.createView(GraphicalMergeVisualizer.java:134)
[catch] at DiffTest.MergeVisualizerAction$1.run(MergeVisualizerAction.java:44)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
at org.netbeans.core.TimableEventQueue.dispatchEvent(TimableEventQueue.java:137)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)

Looking at the source for MergeControl.java (http://www.java2s.com/Open-Source/Java-Document/IDE-Netbeans/diff/org/netbeans/modules/merge/builtin/visualizer/MergeControl.java.htm) it seem to check if the last difference end with a \\n. In this case the second text is null so a NullPointerException is received.

2) Using two files with only minor changes so that no null diffs are found
current.txt:
aaaaaaaa
bbbbbbbb
#cccccccc
dddddddd

previous.txt:
aaaaaaaa
bbbbbbbb
cccccccc
#dddddddd

This gives the output:
Number of diffs: 1
Diff 0
First test: cccccccc
#dddddddd

Second text: #cccccccc
dddddddd

And the exception:
java.lang.IndexOutOfBoundsException: Invalid line index=4 >= lineCount=4
at org.netbeans.editor.LineRootElement.getElement(LineRootElement.java:115)
at org.openide.text.NbDocument$DocumentRenderer.run(NbDocument.java:707)
at org.netbeans.editor.BaseDocument.render(BaseDocument.java:1409)
at org.openide.text.NbDocument$DocumentRenderer.renderToInt(NbDocument.java:682)
at org.openide.text.NbDocument.findLineOffset(NbDocument.java:187)
at org.netbeans.modules.merge.builtin.visualizer.MergePane.getRowStartFromLineOffset(MergePane.java:165)
at org.netbeans.modules.merge.builtin.visualizer.MergePane.addHighlight(MergePane.java:119)
at org.netbeans.modules.merge.builtin.visualizer.MergePanel.highlightRegion1(MergePanel.java:1478)
at org.netbeans.modules.merge.builtin.visualizer.MergeControl.setDiffHighlight(MergeControl.java:206)
at org.netbeans.modules.merge.builtin.visualizer.MergeControl.initialize(MergeControl.java:133)
at org.netbeans.modules.merge.builtin.visualizer.GraphicalMergeVisualizer.createView(GraphicalMergeVisualizer.java:134)
[catch] at DiffTest.MergeVisualizerAction$1.run(MergeVisualizerAction.java:44)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
at org.netbeans.core.TimableEventQueue.dispatchEvent(TimableEventQueue.java:137)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)

I do not have a clue about this exception.

3) Using two files with even less changes:
current.txt:
aaaaaaaa
bbbbbbbb
#cccccccc
dddddddd

previous.txt:
aaaaaaaa
bbbbbbbb
cccccccc
dddddddd

This gives the output:
Number of diffs: 1
Diff 0
First test: cccccccc

Second text: #cccccccc

It does not throw a exception and the TopComponent is opened but it is only a gray window. So the returned Component c does not seem to contain anything.
If I do a System.out.println(c.toString()) on it gives the following: c = org.netbeans.modules.merge.builtin.visualizer.MergeDialogComponent[Merge Conflicts Resolver,0,0,547x470,invalid,layout=java.awt.GridBagLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=256,maximumSize=,minimumSize=,preferredSize=]

But maybe I am using the class wrong, or should be using another class entirely?

Regards,
Henrik

Posted by Henrik on January 18, 2011 at 06:22 PM PST #

This is a terrific addition to the platform.

Thanks!!

Posted by Ranga on December 29, 2011 at 10:22 PM PST #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today