blob: 17ce80babae32ddbb3e52f719cd9f3cab94a3de1 [file] [log] [blame]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>NetBeans Refactoring Module Tutorial - Part 1</title>
<link rel="stylesheet" type="text/css" href="https://netbeans.org/netbeans.css"/>
<meta name="AUDIENCE" content="NBUSER"/>
<meta name="TYPE" content="ARTICLE"/>
<meta name="EXPIRES" content="N"/>
<meta name="developer" content="ralph"/>
<meta name="indexed" content="y"/>
<meta name="description"
content="refactoring tutorial part 1"/>
<!-- Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. -->
<!-- Use is subject to license terms.-->
</head>
<body>
<h1>NetBeans Refactoring Module Tutorial - Part 1</h1>
<p>This tutorial demonstrates how to create a module suite that provides one or more Java Refactorings. The tutorial is split into three parts:
<ul class="toc">
<li>Part1: Initiate</li>
<li>Part2: Configurate and Execute</li>
<li>Part3: Testing and Distribution</li>
</ul>
At the end of this series we will have a fully tested implementation of the Inline Constant Refactoring.
</p>
<p><p><b>Contents</b></p>
<img src="https://platform.netbeans.org/images/articles/65/netbeans-stamp65.gif" class="stamp" width="114" height="114" alt="Content on this page applies to NetBeans IDE 6.5" title="Content on this page applies to NetBeans IDE 6.5"></p>
<ul class="toc">
<li><A HREF="#knowledge" CLASS="XRef">Prerequisite Knowledge</A>
<li><A HREF="#refactoring" CLASS="XRef">NetBeans Refactoring</A>
<li><A HREF="#setup" CLASS="XRef">Project Setup</A>
<li><A HREF="#initiate" CLASS="XRef">Initiate</A>
<li><A HREF="#implementation" CLASS="XRef">Implementation Factory</A>
</ul></p>
<p><b>To follow this tutorial, you need the software and resources listed in the following
table.</b></p>
<table>
<tbody>
<tr>
<th class="tblheader" scope="col">Software or Resource</th>
<th class="tblheader" scope="col">Version Required</th>
</tr>
<tr>
<td class="tbltd1"><a href="https://netbeans.org/downloads/index.html">NetBeans IDE</a></td>
<td class="tbltd1">version 6.7</td>
</tr>
<tr>
<td class="tbltd1"><a href="http://java.sun.com/javase/downloads/index.jsp">Java Developer Kit (JDK)</a></td>
<td class="tbltd1">Version 6 or<br/>version 5</td>
</tr>
</tbody>
</table>
<h2><a name="knowledge"></a>Prerequisite Knowledge</h2>
<p><ul><li>Using Refactorings in NetBeans</li><li>NetBeans top 10 API's</li><li>Java Infrastructure Tutorial</li></ul> </p>
<h2><a name="refactoring"></a>NetBeans Refactoring</h2>
<p>When you want to refactor in NetBeans, you will find yourself doing the following steps:
<ul><li>Select</li><li>Initiate</li><li>Configure</li><li>Preview</li><li>Execute</li><li>Undo</li></ul>
You begin by selecting the code you want to refactor. In NetBeans this can be done from the editor, the navigator or in your projects window. You initiate the refactoring by choosing it from the global menu, the contextual menu or by using a keyboard shortcut. If there are no problems found, you will be presented with the configuration dialog. After setting you desired settings, you can either choose for a preview from which you can execute the refactoring, or execute it immediately. Your code-base has been changed, but can still be undone by the Undo operation.</p>
<p>The steps Select, Preview and Undo are all automagically done by NetBeans. In this tutorial, Part 1, we will start by making the Initiation part. In Part 2 we will take care of the Configure and Execute steps and in Part 3 we will write tests and make it ready to distribute.</p>
<h2><a name="setup"></a>Project Setup</h2>
<p>For this tutorial you need to create a module suite, starting with just one module.<br>
SuiteName: InlineDemo<br>
ModuleName: InlineDemoAPI<br>
CodeNameBase: org.nb.inlinedemo<br>
XML Layer: org/nb/inlinedemo/layer.xml
</p>
<p>Our module will use different API's from the NetBeans Platform and IDE. Setup the dependencies as shown below:
<div><img src="../../images/tutorials/refactoring2/APIDependencies.png"></img></div></p>
<h2><a name="initiate"></a>Initiate</h2>
<p>A refactoring is initiated by choosing it from the global menu, the contextual menu or by using a keyboard shortcut. NetBeans will take care of the difficult parts, we will only have to create the Action and make an implementation factory.</p>
<h3>Action</h3>
<p><ol><li>Right-click the module project, choose New > Other and choose Action from the Module Development category.</li>
<li>Choose Conditionally Enabled, and keep the defaults. We will plugin our Action into the RefactoringActions, so we will change these settings later.</li>
<li><p>Choose Refactor in Category, only select the Editor Context Menu and choose text/x-java in Content Type. Position will be changed later.</p>
<p align="left"><img border="1" src="../../images/tutorials/refactoring2/RefactoringAction.png" alt="cookie action"></p></li>
<li><p>Type <tt>InlineAction</tt> in Class Name and <tt>Inline...</tt> in Display Name. Change the Package to <tt>org.nb.inlinedemo.ui</tt></p>
<p align="left"><img border="1" src="../../images/tutorials/refactoring2/RefactoringAction2.png" alt="cookie action"></p></li>
</ol></p>
<h3>RefactoringActions</h3>
<p>The NetBeans Refactoring API facilitates context sensitive actions. To use it, all we have to is register our action in the appropriate folder. Now we will also change how our action is created.
<ol><li>Open the Layer file and change it to the following:
<ol><li><pre class="examplecode">&lt;file name=&quot;org-nb-inlinedemo-ui-InlineAction.instance&quot;/&gt;</pre>
into
<pre class="examplecode">&lt;file name=&quot;org-nb-inlinedemo-ui-InlineAction.instance&quot;&gt;
&lt;attr name=&quot;instanceCreate&quot; methodvalue=&quot;org.nb.inlinedemo.api.ui.RefactoringActionsFactory.inlineAction&quot;/&gt;
&lt;/file&gt;</pre>
</li>
<li>
<pre class="examplecode">&lt;folder name=&quot;Popup&quot;&gt;</pre>
into
<pre class="examplecode">&lt;folder name=&quot;RefactoringActions&quot;&gt;</pre>
</li></ol></li>
<li>Your layer file should now look like this:
<pre class="examplecode">&lt;filesystem&gt;
&lt;folder name=&quot;Actions&quot;&gt;
&lt;folder name=&quot;Refactoring&quot;&gt;
&lt;file name=&quot;org-nb-inlinedemo-ui-InlineAction.instance&quot;&gt;
&lt;attr name=&quot;instanceCreate&quot; methodvalue=&quot;org.nb.inlinedemo.api.ui.RefactoringActionsFactory.inlineAction&quot;/&gt;
&lt;/file&gt;
&lt;/folder&gt;
&lt;/folder&gt;
&lt;folder name=&quot;Editors&quot;&gt;
&lt;folder name=&quot;text&quot;&gt;
&lt;folder name=&quot;x-java&quot;&gt;
&lt;folder name=&quot;RefactoringActions&quot;&gt;
&lt;file name=&quot;org-nb-inlinedemo-ui-InlineAction.shadow&quot;&gt;
&lt;attr name=&quot;originalFile&quot; stringvalue=&quot;Actions/Refactoring/org-nb-inlinedemo-ui-InlineAction.instance&quot;/&gt;
&lt;attr name=&quot;position&quot; intvalue=&quot;430&quot;/&gt;
&lt;/file&gt;
&lt;/folder&gt;
&lt;/folder&gt;
&lt;/folder&gt;
&lt;/folder&gt;
&lt;/filesystem&gt;</pre></li>
<li>Expand your layer file and expand <tt>&lt;this layer in context&gt;</tt>.</li><li>Goto Editors > text > x-java > RefactoringActions.</li>
<li>Here you can drag and drop the InlineAction to change the order. Put it right above introduce-variable.</li>
<li>Right-click the module project, choose New > Other and choose Java Class from the Java category.</li>
<li>Type <tt>RefactoringActionsFactory in Class Name and <tt>org.nb.inlinedemo.api.ui</tt> in Package.</tt></li>
<li>Add an empty private constructor and the following method to the class:
<pre class="examplecode">public static ContextAwareAction inlineAction() {
return InlineAction.findObject(InlineAction.class, true);
}</pre></li>
</ol></p>
<h3>RefactoringGlobalAction</h3>
<p>The next step for our Action is to let it subclass the RefactoringGlobalAction. There used to be an AbstractRefactoringAction, but this isn't used anymore. Because the RefactoringGlobalAction isn't in a public package, we will create a copy. The Java Refactoring module also created a copy and added some more functionality, so we will take that one.
<ol><li>Right-click the module project, choose New > Other and choose Java Class from the Java category.</li>
<li>Type <tt>RefactoringGlobalAction in Class Name and <tt>org.nb.inlinedemo.api.ui</tt> in Package.</tt></li>
<li>Change the contents of the file to the following:
<pre class="examplecode">
/**
* JavaRefactoringGlobalAction
* This class is copy of RefactoringGlobalAction, which is not in public packages
* @author Jan Becicka
*/
public abstract class JavaRefactoringGlobalAction extends NodeAction {
/** Creates a new JavaRefactoringGlobalActiongGlobalAction */
public JavaRefactoringGlobalAction(String name, Icon icon) {
setName(name);
setIcon(icon);
}
public final String getName() {
return (String) getValue(Action.NAME);
}
protected void setName(String name) {
putValue(Action.NAME, name);
}
protected void setMnemonic(char m) {
putValue(Action.MNEMONIC_KEY, new Integer(m));
}
private static String trim(String arg) {
arg = arg.replace("&", ""); // NOI18N
return arg.replace("...", ""); // NOI18N
}
public org.openide.util.HelpCtx getHelpCtx() {
return HelpCtx.DEFAULT_HELP;
}
protected Lookup getLookup(Node[] n) {
InstanceContent ic = new InstanceContent();
for (Node node:n)
ic.add(node);
if (n.length>0) {
EditorCookie tc = getTextComponent(n[0]);
if (tc != null) {
ic.add(tc);
}
}
ic.add(new Hashtable(0));
return new AbstractLookup(ic);
}
protected static EditorCookie getTextComponent(Node n) {
DataObject dobj = n.getCookie(DataObject.class);
if (dobj != null) {
EditorCookie ec = dobj.getCookie(EditorCookie.class);
if (ec != null) {
TopComponent activetc = TopComponent.getRegistry().getActivated();
if (activetc instanceof Pane) {
return ec;
}
}
}
return null;
}
public abstract void performAction(Lookup context);
protected abstract boolean enable(Lookup context);
public final void performAction(final Node[] activatedNodes) {
performAction(getLookup(activatedNodes));
}
protected boolean enable(Node[] activatedNodes) {
return enable(getLookup(activatedNodes));
}
@Override
public Action createContextAwareInstance(Lookup actionContext) {
return new ContextAction(actionContext);
}
public class ContextAction implements Action, Presenter.Menu, Presenter.Popup, Presenter.Toolbar {
Lookup context;
public ContextAction(Lookup context) {
this.context=context;
}
public Object getValue(String arg0) {
return JavaRefactoringGlobalAction.this.getValue(arg0);
}
public void putValue(String arg0, Object arg1) {
JavaRefactoringGlobalAction.this.putValue(arg0, arg1);
}
public void setEnabled(boolean arg0) {
JavaRefactoringGlobalAction.this.setEnabled(arg0);
}
public boolean isEnabled() {
return enable(context);
}
public void addPropertyChangeListener(PropertyChangeListener arg0) {
JavaRefactoringGlobalAction.this.addPropertyChangeListener(arg0);
}
public void removePropertyChangeListener(PropertyChangeListener arg0) {
JavaRefactoringGlobalAction.this.removePropertyChangeListener(arg0);
}
public void actionPerformed(ActionEvent arg0) {
JavaRefactoringGlobalAction.this.performAction(context);
}
public JMenuItem getMenuPresenter() {
if (isMethodOverridden(JavaRefactoringGlobalAction.this, "getMenuPresenter")) { // NOI18N
return JavaRefactoringGlobalAction.this.getMenuPresenter();
} else {
return new Actions.MenuItem(this, true);
}
}
public JMenuItem getPopupPresenter() {
if (isMethodOverridden(JavaRefactoringGlobalAction.this, "getPopupPresenter")) { // NOI18N
return JavaRefactoringGlobalAction.this.getPopupPresenter();
} else {
return new Actions.MenuItem(this, false);
}
}
public Component getToolbarPresenter() {
if (isMethodOverridden(JavaRefactoringGlobalAction.this, "getToolbarPresenter")) { // NOI18N
return JavaRefactoringGlobalAction.this.getToolbarPresenter();
} else {
final JButton button = new JButton();
Actions.connect(button, this);
return button;
}
}
private boolean isMethodOverridden(NodeAction d, String name) {
try {
Method m = d.getClass().getMethod(name, new Class[0]);
return m.getDeclaringClass() != CallableSystemAction.class;
} catch (java.lang.NoSuchMethodException ex) {
ex.printStackTrace();
throw new IllegalStateException("Error searching for method " + name + " in " + d); // NOI18N
}
}
}
}
</pre></li>
<li>Fix Imports and open InlineAction.</li>
<li>Instead of CookieAction, let InlineAction subclass our JavaRefactoringGlobalAction.</li>
<li>Remove the constructor and add the following:
<pre class="examplecode">
public InlineAction() {
super(NbBundle.getMessage(InlineAction.class, "CTL_InlineAction"), null);
putValue("noIconInMenu", Boolean.TRUE); // NOI18N
}
</pre></li>
<li>Change the Method <tt>performAction</tt> with this one:
<pre class="examplecode">
public void performAction(Lookup context) {
throw new UnsupportedOperationException("Not supported yet.");
}
</pre></li>
<li>Remove the methods: <tt>mode</tt>, <tt>getName</tt>, <tt>cookieClasses</tt> and <tt>initialize</tt></li>
<li>Add the enable method:
<pre class="examplecode">
protected boolean enable(Lookup context) {
return false;
}
</pre></li></ol>
You have now created a Refactoring Action which will be shown in the refactoring menu's.
</p>
<h2><a name="implementation"></a>Implementation Factory</h2>
<p>You can write the performAction and the enable method directly in your Action class, but we will create a Factory for that. Using a Factory here, will make it possible to write different implementations for the same Refactoring Action. The action can be used, for instanced for Java projects as well for Ruby projects.
<ol>
<li>Right-click the module project, choose New > Other and choose Java Class from the Java category.</li>
<li>Type <tt>ActionsImplementationProvider</tt> in Class Name and <tt>org.nb.inlinedemo.spi.ui</tt> in Package. This class can later be used by implementations of our refactoring, to register with the action.</li>
<li>Add the methods <tt>canInline</tt> and <tt>doInline</tt> using the following code:
<pre class="examplecode">
public boolean canInline(Lookup lookup) {
return false;
}
public void doInline(Lookup lookup) {
throw new UnsupportedOperationException("Not implemented"); // NOI18N
}
</pre></li>
<li>Right-click the module project, choose New > Other and choose Java Class from the Java category.</li>
<li>Type <tt>ActionsImplementationFactory</tt> in Class Name and <tt>org.nb.inlinedemo.ui</tt> in Package.</li>
<li>Add an empty private constructor.</li>
<li>The factory will try every implementation of our ActionsImplementationProvider to see if it can do the refactoring. Add the field implementations using the following code:
<pre class="examplecode">
private static final Lookup.Result&lt;ActionsImplementationProvider&gt; implementations =
Lookup.getDefault().lookup(new Lookup.Template&lt;ActionsImplementationProvider&gt;
(ActionsImplementationProvider.class));
</pre></li>
<li>Add the methods <tt>canInline</tt> and <tt>doInline</tt> using the following code:
<pre class="examplecode">
public static boolean canInline(Lookup lookup) {
for (ActionsImplementationProvider rafi : implementations.allInstances()) {
if (rafi.canInline(lookup)) {
return true;
}
}
return false;
}
public static void doInline(Lookup lookup) {
for (ActionsImplementationProvider rafi : implementations.allInstances()) {
if (rafi.canInline(lookup)) {
rafi.doInline(lookup);
return;
}
}
}
</pre></li>
<li>Open InlineAction and change the method body of performAction with:
<pre class="examplecode">ActionsImplementationFactory.doInline(context);</pre></li>
<li>Change the method body of enable with:
<pre class="examplecode">return ActionsImplementationFactory.canInline(context);</pre></li>
<li>The next step is to have your module export the <code>org.nb.inlinedemo.api.ui</code> and <code>org.nb.inlinedemo.spi.ui</code> package so other modules can see classes in it. Right click the module project and choose Properties.</li>
<li><p>In the API Versioning page in the Project Properties dialog box, check the checkbox for <code>org.nb.inlinedemo.api.ui</code> and <code>org.nb.inlinedemo.spi.ui</code> in the Public Packages list, shown below:</p>
<p><img border="1" src="../../images/tutorials/refactoring2/ExportPackage.png"/></p>
</ol></p>
<h2>Next Steps</h2>
You now have a working menu action, which only shows when a Java file is opened and is ready to be implemented. In the next tutorial we will write an implementation for this action.
</body>
</html>