Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Reorder for Java Editor Plug-in for Eclipse

DZone's Guide to

Reorder for Java Editor Plug-in for Eclipse

· Java Zone
Free Resource

What every Java engineer should know about microservices: Reactive Microservices Architecture.  Brought to you in partnership with Lightbend.

Reorder for Java Editor Plug-in for Eclipse

The asymmetrical way in which the , (comma) is used to separate the items in parameter and arguments lists always causes problem when one wants to reorder that list in Java editor. Is that why Java allows trailing commas in array initializer? ;) may be. The Reorder plug-in supports single click swapping of a parameter, argument or array initializer expressions with previous or next item in the list. Each item of the sequence can be a literal, an identifier or a more complex expression. The comma delimiter is correctly handled.

This plug-in adds two toolbar buttons to the Eclipse Java editor:

  • Swap backward
  • Swap forward
Reorder actions on the Java Editor toolbar

Reorder Actions

 Usage

 

With the caret at | in:

void process(int age, String |name, boolean member) {...}
clicking the Swap forward button yields:
void process(int age, boolean member, String |name) {...}
or clicking Swap backward button with the original source yields:
void process(String |name, int age, boolean member) {...}

Implementing actions

Extension Points

The following extension points declares the actions for swapping the items in the list. Note that these actions appear in the editor toolbar when the Java Editor is active.
   <extension
point="org.eclipse.ui.editorActions">
<editorContribution
id="Reorder.editorContribution"
targetID="org.eclipse.jdt.ui.CompilationUnitEditor">
<action
class="reorder.SwapForwardAction"
definitionId="Reorder.swapForwardCommand" <!-- commands -->
icon="icons/swapforward.png"
id="Reorder.swapForwardAction"
label="Swap Forward"
style="push"
toolbarPath="Normal/additions">
</action>
<action
class="reorder.SwapBackwardAction"
definitionId="Reorder.swapBackwardCommand" <!-- commands -->
 icon="icons/swapbackward.png"
id="Reorder.swapBackwardAction"
label="Swap Backward"
style="push"
toolbarPath="Normal/additions">
</action>
</editorContribution>
</extension>
The following extension point declares commands.

<extension
id="Reorder.swapCommands"
name="Swap Commands"
point="org.eclipse.ui.commands">
<command
categoryId="org.eclipse.jdt.ui.category.source"
id="Reorder.swapForwardCommand"
name="Swap Forward">
</command>
<command
categoryId="org.eclipse.jdt.ui.category.source"
id="Reorder.swapBackwardCommand"
name="Swap Backward">
</command>
</extension>

The following extension point declares key bindings for the commands.

<extension
point="org.eclipse.ui.bindings">
<!-- win32: M1=CTRL, M2=SHIFT, M3=ALT, M4=-
carbon: M1=COMMAND, M2=SHIFT, M3=ALT, M4=CTRL -->
<key
sequence="M1+," <!-- keybinding -->
contextId="org.eclipse.jdt.ui.javaEditorScope"
commandId="Reorder.swapBackwardCommand"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"/>
<key
sequence="M1+." <!-- keybinding -->
 contextId="org.eclipse.jdt.ui.javaEditorScope"
commandId="Reorder.swapForwardCommand"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"/>
</extension>

AbstractSwapAction

This is just an abstract base class which invokes the swap actions based on the bias returned by the sub classes.
package reorder;

import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ui.IEditorActionDelegate;
import org.eclipse.ui.IEditorPart;

import reorder.SwapOperations.Bias;

@SuppressWarnings("restriction")
public abstract class AbstractSwapAction implements IEditorActionDelegate {

protected JavaEditor targetEditor;

public void setActiveEditor(IAction action, IEditorPart targetEditor) {
this.targetEditor = null;
if (targetEditor instanceof JavaEditor) {
this.targetEditor = (JavaEditor) targetEditor;
}
}

public void run(IAction action) {
if (targetEditor != null) {
SwapOperations.swap(targetEditor, getBias());
}
}

// Sub classes specify the bias
protected abstract Bias getBias();

public void selectionChanged(IAction action, ISelection selection) {}
}

SwapBackwardAction

This action implements swapping with the preceding item.
package reorder;

import reorder.SwapOperations.Bias;

public class SwapBackwardAction extends AbstractSwapAction {

@Override
protected Bias getBias() {
// Swap with preceding item
return Bias.BACKWARD;
}

}

SwapForwardAction

This action implements swapping with the following item.
package reorder;

import reorder.SwapOperations.Bias;

public class SwapForwardAction extends AbstractSwapAction {

@Override
protected Bias getBias() {
// Swap with following item
 return Bias.FORWARD;
}

}

SwapOperations

This class implements the main logic of the Reorder plugin. Basically, the it uses the AST APIs to get parsed structure of the Java source code surrounding the caret in the Java editor.
  • It gets the ASTNode at caret position using NodeFinder API and starts traversing the parent ASTNodes until it find a Class Instance Creation, Method Invocation, Method Declaration or an Array initializer node.
  • Once found it gets the ASTNode's list of arguments, parameters or array initialization elements (as applicable) and stores the text of each node in a ordered list of items. While doing so, it also records the intervening white spaces as items.
  • It also records which item's extent surrounds the caret position. It records it as a current item.
  • Then, it swaps the current item with the following or preceding non-whitespace item in the list based on the action that was invoked - forward or backward swap.
  • Lastly it builds the string from the list of items and replaces the original text with the new string.
package reorder;

import java.util.LinkedList;
import java.util.List;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ArrayCreation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.internal.corext.dom.NodeFinder;
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.SharedASTProvider;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;

@SuppressWarnings("restriction")
public class SwapOperations {
enum Bias {
BACKWARD, FORWARD
};

@SuppressWarnings("unchecked")
static void swap(JavaEditor editor, Bias bias) {
// Editor not editable...bail out
if (!editor.isEditable()) {
Display.getCurrent().beep();
return;
}

// Get source viewer
ISourceViewer viewer = editor.getViewer();
if (viewer == null)
return;

// Get the caret position
Point selectedRange = viewer.getSelectedRange();
int caretAt = selectedRange.x;
int length = selectedRange.y;

// Get the Java element
ITypeRoot element = JavaUI.getEditorInputTypeRoot(editor
.getEditorInput());
if (element == null)
return;

// Get the compilation unit AST
CompilationUnit ast = SharedASTProvider.getAST(element,
SharedASTProvider.WAIT_YES, null);
if (ast == null)
return;

// Find the node at caret position
NodeFinder finder = new NodeFinder(caretAt, length);
ast.accept(finder);

ASTNode originalNode = finder.getCoveringNode();
ASTNode node;

List arguments = null;

// traverser the succesively outer node
node = originalNode;
while (node != null) {
node = node.getParent();

// Is it an interesting node? If so remember the interesting list of item
if (node instanceof ClassInstanceCreation) {
arguments = ((ClassInstanceCreation) node).arguments();
} else if (node instanceof MethodInvocation) {
arguments = ((MethodInvocation) node).arguments();
} else if (node instanceof MethodDeclaration) {
arguments = ((MethodDeclaration) node).parameters();
} else if (node instanceof ArrayCreation) {
ArrayCreation arrayCreation = (ArrayCreation) node;
ArrayInitializer initializer = arrayCreation.getInitializer();
if (initializer != null) {
arguments = initializer.expressions();
}
}

// Something to reorder
if (arguments != null && arguments.size() >= 2) {
final int firstStart = ((ASTNode) arguments.get(0))
.getStartPosition();
final int lastEnd = ((ASTNode) arguments.get(arguments.size() - 1))
.getStartPosition()
+ ((ASTNode) arguments.get(arguments.size() - 1))
.getLength();
// Is the caret in the range
if (firstStart <= caretAt && caretAt <= lastEnd) {
int caretOffset = -1;
List<String> tokens = new LinkedList<String>();
int currentTokenIndex = -1;
String currentToken = null;
int previousEnd = -1;

// Add items and intervening white spaces to a list
// Also remember the item that surrounds the caret
for (Object argument : arguments) {
ASTNode expression = (ASTNode) argument;
int startPosition = expression.getStartPosition();
int endPosition = startPosition + expression.getLength();
if (previousEnd != -1) {
try {
// capture separator and intervening whitespaces
tokens.add(viewer.getDocument().get(previousEnd, (startPosition-previousEnd)));
} catch (BadLocationException e) {
Activator.getDefault().getLog().log(new Status(IStatus.ERROR
,Activator.PLUGIN_ID
,"Could not get whitespace token."));
return;
}
}
// capture the token
final String token = argument.toString();
tokens.add(token);

if (startPosition <= caretAt && caretAt <= endPosition) {
// record the token that surrounds the caret and the
// caret offset from the start of that token
currentTokenIndex = tokens.size() - 1;
currentToken = token;
caretOffset = caretAt - (int) startPosition;
}
previousEnd = endPosition;
}

// Now do the reordering
if (tokens.size() > 0 && currentTokenIndex != -1) {
String current = tokens.get(currentTokenIndex);
switch (bias) {
case BACKWARD:
if (currentTokenIndex == 0) {
Display.getCurrent().beep();
return;
}
// Swap with previous non-whitespace token
String previous = tokens.get(currentTokenIndex - 2);
tokens.set(currentTokenIndex - 2, current);
tokens.set(currentTokenIndex, previous);
break;
case FORWARD:
if (currentTokenIndex == (tokens.size() - 1)) {
Display.getCurrent().beep();
return;
}
// Swap with following non-whitespace token
 String next = tokens.get(currentTokenIndex + 2);
tokens.set(currentTokenIndex + 2, current);
tokens.set(currentTokenIndex, next);
break;
}

// Build the insertion string
final StringBuilder stringBuilder = new StringBuilder();
int offset = 0;
final int[] moveCaretOffset = new int[1];
for (String token : tokens) {
final String text = token;
if (token == currentToken) {
moveCaretOffset[0] = offset + caretOffset;
}
offset += text.length();
stringBuilder.append(text);
}

// Now replace the old text with new text
try {
viewer.getDocument().replace(firstStart,
(lastEnd - firstStart), stringBuilder.toString());
} catch (BadLocationException e) {
Activator.getDefault().getLog().log(new Status(IStatus.ERROR
,Activator.PLUGIN_ID
,"Could not swap items."));
return;
}
viewer.setSelectedRange((int) firstStart
+ moveCaretOffset[0], 0);
}
}
break;
}
}
}
}

MANIFEST.MF

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Reorder Plug-in
Bundle-SymbolicName: Reorder;singleton:=true
Bundle-Version: 1.0.4
Bundle-Activator: reorder.Activator
Bundle-Vendor: Sandip V. Chitale
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
org.eclipse.jdt.core;bundle-version="3.4.0",
org.eclipse.jdt.ui;bundle-version="3.4.0",
org.eclipse.ui.editors;bundle-version="3.4.0",
org.eclipse.jface.text;bundle-version="3.4.0"
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Bundle-ActivationPolicy: lazy

Installation

The resources section has the link to the plugin jar which can be dropped into your Eclipse installation's plugins directory. Once installed you will be able to use the Swap forward and Swap backward actions when the JavaEditor is active.

Source Code

The source code is bundled in the plug-in's jar file. In fact you can import the source plug-in project into  PDE  and hack it further. The source code is also available here.

Conclusion

In this tutorial we built a simple yet useful Eclipse plug-in called - Reorder for Java Editor. It makes use of JDOM and AST APIs. It also demonstrates how to register actions for the Java Editor, how to declare commands and the associate them with actions and declare key bindings for the commands.

Java and all Java-based trademarks and logos are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States, other countries, or both.

Resources

Notes on the Eclipse Plug-in Architecture
PDE Does Plug-ins
Contributing Actions to the Eclipse Workbench
Download
Reorder for Java Editor Plug-in for Eclipse
Sources

Microservices for Java, explained. Revitalize your legacy systems (and your career) with Reactive Microservices Architecture, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}