blob: 63671a25a6f9e148317a842445c9e6d733d90c66 [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 2"/>
<!-- 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 2</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>This tutorial is a follow up on NetBeans Refactoring Module Tutorial for NetBeans Platform 6.7 Part 1. You will need the knowledge and modules created by that tutorial as a starting point for this one.</p>
<h2><a name="refactoring"></a>NetBeans Refactoring</h2>
<p>In Part 1 of this tutorial we talked about the following steps you do, when you refactor in NetBeans.
<ul><li>Select</li><li>Initiate</li><li>Configure</li><li>Preview</li><li>Execute</li><li>Undo</li></ul></p>
<p>The steps Select, Preview and Undo are all automagically done by NetBeans. In this tutorial, Part 2, we will take care of the Configure and Execute steps. Part 3 will look at writing tests and make it ready to distribute.</p>
<h2><a name="setup"></a>Project Setup</h2>
<p>For this tutorial you need the module suite created in Part 1.
<ol><li>Open the module suite InlineDemo.</li>
<li>Add a new module to the suite with the name <tt>InlineDemoJava</tt> and the CodeNameBase <tt>org.nb.inlinedemo.java</tt>. We won't need an XML layer in this module, as we are going to use the Action from our API.</li>
</ol></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/APIDependencies2.png"></img></div></p>
<h2><a name="refactoring"></a>The Refactoring</h2>
<p>We start by writing the Refactoring. The refactoring class serves as an API for invoking the refactoring. Also it is used by various refactoring plugins to determine refactoring parameters. This class itself should do almost no work - all the work is done by the plugins. The refactoring class usually contains just getters and setters for the refactoring parameters.<ol>
<li>Right-click the InlineDemoJava project, choose New > Other and choose Java Class from the Java category.</li>
<li>Type <tt>InlineRefactoring</tt> in Class Name and <tt>org.nb.inlinedemo.java.api</tt> in Package.</li>
<li>Extend the class AbstractRefactoring from the package <tt>org.netbeans.modules.refactoring.api</tt>.</li>
<li>Add a constructor using the following code:
<pre class="examplecode">
public InlineRefactoring(TreePathHandle field) {
super(Lookups.fixed(field));
}
</pre></li>
<li>For the Inline Constant refactoring, we need to know the name and the initialization string of the constant. Add the fields to the class
<pre class="examplecode">
private Tree initializerTree;
private String fieldName;
</pre></li>
<li>Use the Encapsulate Field refactoring to create a Getter and Setter for the fields.</li>
<li>The next step is to have your module export the <code>org.nb.inlinedemo.java.api</code> package so other modules can use it.</li>
<li><p>In the API Versioning page in the Project Properties dialog box, check the checkbox for <code>org.nb.inlinedemo.java.api</code> in the Public Packages list, shown below:</p>
<p><img border="1" src="../../images/tutorials/refactoring2/ExportPackage2.png"/></p>
</ol></p>
<h2>Refactoring Plugin</h2>
<p>Every refactoring has plugins - it should have at least one plugin - these actually do all the work. The refactoring module itself should provide the basic plugin that does the J2SE refactoring. Other modules can add other plugins - this makes them able to participate in the refactoring. For example, when using inline on a constant, a J2EE plugin needs to take care of replacing all the occurrences inside JSP documents.<ol>
<li>Right-click the InlineDemoJava project, choose New > Other and choose Java Class from the Java category.</li>
<li>Type <tt>InlineRefactoringPlugin</tt> in Class Name and <tt>org.nb.inlinedemo.java.plugins</tt> in Package.</li>
<li>Extend the class JavaRefactoringPlugin from the package <tt>org.netbeans.modules.refactoring.java.spi</tt>.</li>
<li>Click the lightbulb and implement all abstract methods.</li>
<li>Create a constructor and two fields using the following code:
<pre class="examplecode">
private final InlineRefactoring refactoring;
private final TreePathHandle treePathHandle;
public InlineRefactoringPlugin(InlineRefactoring inlineRefactoring) {
refactoring = inlineRefactoring;
treePathHandle = refactoring.getRefactoringSource().lookup(TreePathHandle.class);
}
</pre></li>
<li>Fill the method <tt>getJavaSource</tt> with this:<pre class="examplecode">
protected JavaSource getJavaSource(Phase p) {
if (treePathHandle == null) {
return null;
}
switch (p) {
case PRECHECK:
case FASTCHECKPARAMETERS:
return JavaSource.forFileObject(treePathHandle.getFileObject());
case CHECKPARAMETERS:
ClasspathInfo cpInfo = getClasspathInfo(refactoring);
JavaSource source = JavaSource.create(cpInfo, treePathHandle.getFileObject());
return source;
}
throw new IllegalStateException();
}
</pre></li>
<li>Only two methods need to be implemented; <tt>precheck</tt> and <tt>prepare</tt>. Prepare is called when the refactoring is executed and therefore, you have to implement it. Precheck is called before the configuration dialog is displayed. Add precheck using the following code:
<pre class="examplecode">
protected Problem preCheck(CompilationController info) throws IOException {
Problem preCheckProblem = null;
fireProgressListenerStart(InlineRefactoring.PRE_CHECK, 2);
info.toPhase(JavaSource.Phase.RESOLVED);
Element el = treePathHandle.resolveElement(info);
preCheckProblem = isElementAvail(treePathHandle, info);
if (preCheckProblem != null) {
return preCheckProblem;
}
preCheckProblem = isSourceElement(el, info);
if (preCheckProblem != null) {
return preCheckProblem;
}
switch (el.getKind()) {
case FIELD:
fireProgressListenerStep();
Set&lt;Modifier&gt; modifiers = el.getModifiers();
ArrayList&lt;Modifier&gt; needed = new ArrayList&lt;Modifier&gt;(2);
needed.add(Modifier.FINAL);
needed.add(Modifier.STATIC);
if (!modifiers.containsAll(needed)) {
preCheckProblem = createProblem(preCheckProblem, false, NbBundle.getMessage(InlineRefactoringPlugin.class, &quot;ERR_InlineNonConstant&quot;, el));
}
fireProgressListenerStep();
VariableTree tree = (VariableTree) info.getTrees().getTree(el);
refactoring.setFieldName(tree.getName().toString());
refactoring.setInitializerTree(tree.getInitializer());
if(refactoring.getInitializerTree() == null) {
preCheckProblem = createProblem(preCheckProblem, true, NbBundle.getMessage(InlineRefactoringPlugin.class, &quot;ERR_InlineNoInitializer&quot;));
return preCheckProblem;
}
break;
default:
preCheckProblem = createProblem(preCheckProblem, true, NbBundle.getMessage(InlineRefactoringPlugin.class, &quot;ERR_InlineWrongType&quot;));
}
fireProgressListenerStop();
return preCheckProblem;
}
</pre>This code checks three things<ol><li>Is the selected element a Field?</li><li>Is the selected Field static and final?</li><li>Is the field initialized during declaration?</li></ol> If one of these tests fail, the problem is presented to the user.</li>
<li>Create a Bundle.properties file in <tt>org.nb.inlinedemo.java.plugins</tt> and add the following string:
<pre class="examplecode">
ERR_InlineNonConstant=Cannot inline a field which is not a constant field.
ERR_InlineWrongType=Inline Refactoring can only inline constant fields.
ERR_InlineNoInitializer=Cannot find the field initializer.
ERR_ProjectNotOpened=Cannot refactor {0} that is defined outside of an open project.
ERR_CannnotRefactorLibrary=Cannot change parameters of "{0}" which overrides method from library class.
</pre></li>
<li>Precheck uses the method isSourceElement to check if the selected element is in an opened project and is not part of a library. Add it using the following code:
<pre class="examplecode">
public static final Problem isSourceElement(Element el, CompilationInfo info) {
Problem preCheckProblem = null;
if (isFromLibrary(el, info.getClasspathInfo())) { //NOI18N
preCheckProblem = new Problem(true, NbBundle.getMessage(
InlineRefactoringPlugin.class, "ERR_CannotRefactorLibraryClass",
el.getEnclosingElement()));
return preCheckProblem;
}
FileObject file = SourceUtils.getFile(el, info.getClasspathInfo());
// isFromLibrary already checked file for null
if (!isElementInOpenProject(file)) {
preCheckProblem = new Problem(true, NbBundle.getMessage(
InlineRefactoringPlugin.class,
"ERR_ProjectNotOpened",
FileUtil.getFileDisplayName(file)));
return preCheckProblem;
}
return null;
}
public static boolean isElementInOpenProject(FileObject f) {
if (f == null) {
return false;
}
Project p = FileOwnerQuery.getOwner(f);
return isOpenProject(p);
}
public static boolean isFromLibrary(Element element, ClasspathInfo info) {
FileObject file = SourceUtils.getFile(element, info);
if (file == null) {
//no source for given element. Element is from library
return true;
}
return FileUtil.getArchiveFile(file) != null;
}
private static boolean isOpenProject(Project p) {
return OpenProjects.getDefault().isProjectOpen(p);
}
</pre></li>
<li>The prepare method itself is quite small. It only queries for the files relevant to this refactoring and delegates the hard work to a TransformTask. Fill the prepare method using the following code:
<pre class="examplecode">
public Problem prepare(RefactoringElementsBag elements) {
if (treePathHandle == null) {
return null;
}
Set&lt;FileObject&gt; a = getRelevantFiles();
fireProgressListenerStart(ProgressEvent.START, a.size());
TransformTask transform = new TransformTask(new InlineTransformer(refactoring.getInitializerTree()), treePathHandle);
Problem problem = createAndAddElements(a, transform, elements, refactoring);
fireProgressListenerStop();
return problem;
}
</pre></li>
<li>The getRelevantFiles method will need to look like this:
<pre class="examplecode">
private Set&lt;FileObject&gt; getRelevantFiles() {
ClasspathInfo cpInfo = getClasspathInfo(refactoring);
final Set&lt;FileObject&gt; set = new HashSet&lt;FileObject&gt;();
JavaSource source = JavaSource.create(cpInfo, treePathHandle.getFileObject());
try {
source.runUserActionTask(new CancellableTask&lt;CompilationController&gt;() {
public void cancel() {
throw new UnsupportedOperationException(&quot;Not supported yet.&quot;); // NOI18N
}
public void run(CompilationController info) throws Exception {
final ClassIndex idx = info.getClasspathInfo().getClassIndex();
info.toPhase(JavaSource.Phase.RESOLVED);
Element el = treePathHandle.resolveElement(info);
ElementHandle&lt;TypeElement&gt; enclosingType;
if (el instanceof TypeElement) {
enclosingType = ElementHandle.create((TypeElement) el);
} else {
enclosingType = ElementHandle.create(info.getElementUtilities().enclosingTypeElement(el));
}
set.add(SourceUtils.getFile(el, info.getClasspathInfo()));
if (!el.getModifiers().contains(Modifier.PRIVATE)) {
set.addAll(idx.getResources(enclosingType, EnumSet.of(ClassIndex.SearchKind.FIELD_REFERENCES), EnumSet.of(ClassIndex.SearchScope.SOURCE)));
}
}
}, true);
} catch (IOException ioe) {
throw (RuntimeException) new RuntimeException().initCause(ioe);
}
return set;
}
</pre></li>
</ol></p>
<h2>Refactoring Transformer</h2>
<p>Although the name may let you think otherwise, the Transformer will not change your source files. Instead it will create RefactoringElements - each of the elements represent a single change that the refactoring should do. So, in case of our Inline Constant Refactoring, the field declaration and every usage of the field being inlined would have a corresponding RefactoringElement. These elements will then be used by the refactoring preview, to execute the refactoring and by the undo refactoring. To create the transformer, we will make use of the RefactoringVisitor from the package <tt>org.netbeans.modules.refactoring.java.spi</tt>.
<ol><li>First, create a new class <tt>InlineTransformer</tt> in the package <tt>org.nb.inlinedemo.plugins</tt></li>
<li>Extend the class RefactoringVisitor and use the following code:
<pre class="examplecode">
private final Tree initializerTree;
public InlineTransformer(Tree initializerTree) {
this.initializerTree = initializerTree;
}
</pre></li>
<li>First, we will remove the declaration of the field. Use the following code:
<pre class="examplecode">
@Override
public Tree visitClass(ClassTree node, Element p) {
Tree tree = workingCopy.getTrees().getTree(p);
if (!node.getMembers().contains(tree)) {
return super.visitClass(node, p);
}
ClassTree nNode = make.removeClassMember(node, tree);
rewrite(node, nNode);
return super.visitClass(node, p);
}
</pre></li>
<li>Second, we need to replace every usage of the field with the string from the initializer. For this, we use the following three methods:
<pre class="examplecode">
@Override
public Tree visitIdentifier(IdentifierTree node, Element p) {
renameUsageIfMatch(getCurrentPath(), node, p);
return super.visitIdentifier(node, p);
}
@Override
public Tree visitMemberSelect(MemberSelectTree node, Element p) {
renameUsageIfMatch(getCurrentPath(), node, p);
return super.visitMemberSelect(node, p);
}
private void renameUsageIfMatch(TreePath path, Tree tree, Element elementToFind) {
if (workingCopy.getTreeUtilities().isSynthetic(path)) {
return;
}
Trees trees = workingCopy.getTrees();
Element el = workingCopy.getTrees().getElement(path);
if (el == null) {
path = path.getParentPath();
if (path != null &amp;& path.getLeaf().getKind() == Tree.Kind.IMPORT) {
ImportTree impTree = (ImportTree) path.getLeaf();
if (!impTree.isStatic()) {
return;
}
Tree idTree = impTree.getQualifiedIdentifier();
if (idTree.getKind() != Tree.Kind.MEMBER_SELECT) {
return;
}
final Name id = ((MemberSelectTree) idTree).getIdentifier();
if (id == null || id.contentEquals(&quot;*&quot;)) { // NOI18N
// skip import static java.lang.Math.*
return;
}
Tree classTree = ((MemberSelectTree) idTree).getExpression();
path = trees.getPath(workingCopy.getCompilationUnit(), classTree);
el = trees.getElement(path);
if (el == null) {
return;
}
Iterator iter = workingCopy.getElementUtilities().getMembers(el.asType(), new ElementUtilities.ElementAcceptor() {
public boolean accept(Element e, TypeMirror type) {
return id.equals(e.getSimpleName());
}
}).iterator();
if (iter.hasNext()) {
el = (Element) iter.next();
}
if (iter.hasNext()) {
return;
}
} else {
return;
}
}
if (el.equals(elementToFind)) {
rewrite(tree, initializerTree);
}
}
</pre></li></ol>
</p>
<h2>JavaRefactoringsPluginFactory</h2>
<p>Plugins are instantiated by the refactoring class automatically when some code creates an instance of a refactoring. The instantiation of the plugins is done by plugin factories that get called by the refactoring.<ol>
<li>Create the class <tt>JavaRefactoringsPluginFactory</tt> in the package <tt>org.nb.inlinedemo.java.plugins</tt></li>
<li>Implement the interface <tt>RefactoringPluginFactory</tt> and register it using the following annotation:
<pre class="examplecode">
@org.openide.util.lookup.ServiceProvider(service=org.netbeans.modules.refactoring.spi.RefactoringPluginFactory.class, position=100)
</pre></li>
<li>Add the method createInstance:
<pre class="exampleCode">
public RefactoringPlugin createInstance(AbstractRefactoring refactoring) {
if (refactoring instanceof InlineRefactoring) {
return new InlineRefactoringPlugin((InlineRefactoring) refactoring);
}
return null;
}
</pre></li></ol>
Our refactoring itself is now finished, it does the needed checks and transforms the java code. But, before it can be connected to our action, we first have to make the InlineUI.
</p>
<h2>InlineUI</h2>
<p>InlineUI will be an implementation of RefactoringUI interface. It plugs into the refactoring framework to which it provides a refactoring parameters panel, display name of the refactoring, reference to the Refactoring Class, etc.<ol><li>Create the class <tt>InlineUI</tt> in the package <tt>org.nb.inlinedemo.java.ui</tt> using the following code:
<pre class="examplecode">
public class InlineUI implements RefactoringUI {
private CustomRefactoringPanel panel;
private InlineRefactoring refactoring;
InlineUI(TreePathHandle selectedElement, CompilationInfo info) {
refactoring = new InlineRefactoring(selectedElement);
}
public String getName() {
return NbBundle.getMessage(InlineUI.class, &quot;LBL_Inline&quot;);
}
public String getDescription() {
String name = refactoring.getFieldName();
return new MessageFormat(NbBundle.getMessage(InlineUI.class, &quot;DSC_Inline&quot;)).format(
new Object[]{name});
}
public boolean isQuery() {
return false;
}
public CustomRefactoringPanel getPanel(ChangeListener parent) {
if (panel == null) {
panel = new InlinePanel(refactoring);
}
return panel;
}
public Problem setParameters() {
return setParameters(false);
}
public Problem checkParameters() {
return setParameters(true);
}
public boolean hasParameters() {
return true;
}
public AbstractRefactoring getRefactoring() {
return refactoring;
}
public HelpCtx getHelpCtx() {
return new HelpCtx(InlineUI.class);
}
private Problem setParameters(boolean checkOnly) {
if (checkOnly) {
return refactoring.fastCheckParameters();
} else {
return refactoring.checkParameters();
}
}
}
</pre></li>
<li>Create a <tt>Bundle.properties</tt> in <tt>org.nb.inlinedemo.java.ui</tt> and add the following strings:
<pre class="examplecode">
LBL_Inline=Inline
DSC_Inline=Inline instances of {0}?;
</pre></li>
</ol></p>
<h2>InlinePanel</h2>
<p>Now we will create the configuration panel. Because there are no parameters to be set for this refactoring, the user will be presented with a simple message.<ol>
<li>Add a new JPanel to <tt>org.nb.inlinedemo.java.ui</tt> with the name <tt>InlinePanel</tt>.</li>
<li>Add a JLabel to the center of the panel with the text: <tt>Inline instances of {0}?</tt></li>
<li>Using the editor let the panel implement <tt>CustomRefactoringPanel</tt> from the package <tt>org.netbeans.modules.refactoring.spi.ui</tt></li>
<li>Change the body of <tt>getComponent</tt> to:
<pre class="examplecode">
return this;
</pre></li>
<li>Change the body of <tt>initialize</tt> to:
<pre class="examplecode">
jLabel1.setText(new MessageFormat(NbBundle.getMessage(InlineUI.class, &quot;DSC_Inline&quot;)).format(
new Object[]{refactoring.getFieldName()}));
</pre></li>
<li>Add the parameter <tt>InlineRefactoring refactoring</tt> to the constructor. Select the parameter, click the lightbulb and choose Create Field.</li>
</ol></p>
<h2>ActionsProvider</h2>
<p>Now we can connect the refactoring to our Action. We will need do create the methods canInline and doInline. The first method (canInline) determines when the action should be enabled based on the currently active (selected) nodes in the IDE. By convention the implementation of this method should not do anything expensive - preferably it should not tough the Java metadata and decide purely on whether there are JavaDataObjects behind the selected nodes and how many nodes are selected (some actions may be applicable to several nodes at once as in case of Pull Up refactoring, where you can select several members to be pulled up, some actions may be able operate on a single node only). For performance reasons the coInline method does not get information about the position of the caret in the editor - that's why the checks in this method should be weak. Most of the other checks are be done in refactoring preCheck() method (we talked about this method earlier), which can provide user with a descriptive message for why the refactoring cannot be performed on a selected object and how user can fix it.<ol>
<li>Create the class <tt>JavaRefactoringActionsProvider</tt> in the package <tt>org.nb.inlinedemo.java.ui</tt>.</li>
<li>Extend the class <tt>ActionsImplementationProvider</tt> and register it using the annotation:
<pre class="examplecode">
@org.openide.util.lookup.ServiceProvider(service = org.nb.inlinedemo.spi.ui.ActionsImplementationProvider.class, position = 100)
</pre></li>
<li>Add the following code to the class:
<pre class="examplecode">
@Override
public boolean canInline(Lookup lookup) {
Collection&lt;? extends Node&gt; nodes = new HashSet&lt;Node&gt;(lookup.lookupAll(Node.class));
if (nodes.size() != 1) {
return false;
}
Node n = nodes.iterator().next();
TreePathHandle tph = n.getLookup().lookup(TreePathHandle.class);
if (tph != null) {
return RetoucheUtils.isRefactorable(tph.getFileObject());
}
DataObject dob = n.getCookie(DataObject.class);
if (dob == null) {
return false;
}
FileObject fo = dob.getPrimaryFile();
if (RetoucheUtils.isRefactorable(fo)) { //NOI18N
return true;
}
return false;
}
@Override
public void doInline(Lookup lookup) {
Runnable task;
EditorCookie ec = lookup.lookup(EditorCookie.class);
if (isFromEditor(ec)) {
task = new TextComponentTask(ec) {
@Override
protected RefactoringUI createRefactoringUI(TreePathHandle selectedElement, int startOffset, int endOffset, CompilationInfo info) {
return new InlineUI(selectedElement, info);
}
};
} else if (nodeHandle(lookup)) {
task = new TreePathHandleTask(new HashSet&lt;Node&gt;(lookup.lookupAll(Node.class)), true) {
RefactoringUI ui;
@Override
protected void treePathHandleResolved(TreePathHandle handle, CompilationInfo javac) {
ui = new InlineUI(handle, javac);
}
@Override
protected RefactoringUI createRefactoringUI(Collection&lt;TreePathHandle&gt; handles) {
return ui;
}
};
} else {
task = new NodeToFileObjectTask(Collections.singleton(lookup.lookup(Node.class))) {
RefactoringUI ui;
@Override
protected void nodeTranslated(Node node, Collection&lt;TreePathHandle&gt; handles, CompilationInfo javac) {
TreePathHandle tph = handles.iterator().next();
ui = new InlineUI(tph, javac);
}
@Override
protected RefactoringUI createRefactoringUI(FileObject[] selectedElements, Collection&lt;TreePathHandle&gt; handles) {
return ui;
}
};
}
RetoucheUtils.invokeAfterScanFinished(task, getActionName(RefactoringActionsFactory.inlineAction()));
}
static boolean isFromEditor(EditorCookie ec) {
if (ec != null &amp;& ec.getOpenedPanes() != null) {
TopComponent activetc = TopComponent.getRegistry().getActivated();
if (activetc instanceof CloneableEditorSupport.Pane) {
return true;
}
}
return false;
}
static boolean nodeHandle(Lookup lookup) {
Node n = lookup.lookup(Node.class);
if (n!=null) {
if (n.getLookup().lookup(TreePathHandle.class)!=null)
return true;
}
return false;
}
static String getActionName(Action action) {
String arg = (String) action.getValue(Action.NAME);
arg = arg.replace(&quot;&&quot;, &quot;&quot;); // NOI18N
return arg.replace(&quot;...&quot;, &quot;&quot;); // NOI18N
}
public static abstract class TextComponentTask implements Runnable, CancellableTask&lt;CompilationController&gt; {
private JTextComponent textC;
private int caret;
private int start;
private int end;
private RefactoringUI ui;
public TextComponentTask(EditorCookie ec) {
this.textC = ec.getOpenedPanes()[0];
this.caret = textC.getCaretPosition();
this.start = textC.getSelectionStart();
this.end = textC.getSelectionEnd();
assert caret != -1;
assert start != -1;
assert end != -1;
}
public void cancel() {
}
public void run(CompilationController cc) throws Exception {
TreePath selectedElement = null;
cc.toPhase(Phase.RESOLVED);
selectedElement = cc.getTreeUtilities().pathFor(caret);
//workaround for issue 89064
if (selectedElement.getLeaf().getKind() == Tree.Kind.COMPILATION_UNIT) {
List&lt;? extends Tree&gt; decls = cc.getCompilationUnit().getTypeDecls();
if (!decls.isEmpty()) {
selectedElement = TreePath.getPath(cc.getCompilationUnit(), decls.get(0));
}
}
ui = createRefactoringUI(TreePathHandle.create(selectedElement, cc), start, end, cc);
}
public final void run() {
try {
JavaSource source = JavaSource.forDocument(textC.getDocument());
source.runUserActionTask(this, true);
} catch (IOException ioe) {
ErrorManager.getDefault().notify(ioe);
return ;
}
TopComponent activetc = TopComponent.getRegistry().getActivated();
if (ui!=null) {
UI.openRefactoringUI(ui, activetc);
} else {
JOptionPane.showMessageDialog(null,NbBundle.getMessage(JavaRefactoringActionsProvider.class, &quot;ERR_CannotRenameKeyword&quot;));
}
}
protected abstract RefactoringUI createRefactoringUI(TreePathHandle selectedElement,int startOffset,int endOffset, CompilationInfo info);
}
public static abstract class TreePathHandleTask implements Runnable, CancellableTask&lt;CompilationController&gt; {
private Collection&lt;TreePathHandle&gt; handles = new ArrayList&lt;TreePathHandle&gt;();
private TreePathHandle current;
boolean renameFile;
public TreePathHandleTask(Collection&lt;? extends Node&gt; nodes) {
this(nodes, false);
}
public TreePathHandleTask(Collection&lt;? extends Node&gt; nodes, boolean useFirstHandle) {
for (Node n:nodes) {
TreePathHandle temp = n.getLookup().lookup(TreePathHandle.class);
if (temp!=null) {
handles.add(temp);
if (useFirstHandle) {
break;
}
}
}
}
public void cancel() {
}
public void run(CompilationController info) throws Exception {
info.toPhase(Phase.ELEMENTS_RESOLVED);
Element el = current.resolveElement(info);
if (el!=null &amp;&amp; el instanceof TypeElement &amp;& !((TypeElement)el).getNestingKind().isNested()) {
if (info.getFileObject().getName().equals(el.getSimpleName().toString())) {
renameFile = true;
}
}
treePathHandleResolved(current, info);
}
public void run() {
for (TreePathHandle handle:handles) {
FileObject f = handle.getFileObject();
current = handle;
JavaSource source = JavaSource.forFileObject(f);
assert source != null;
try {
source.runUserActionTask(this, true);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
TopComponent activetc = TopComponent.getRegistry().getActivated();
RefactoringUI ui = createRefactoringUI(handles);
if (ui!=null) {
UI.openRefactoringUI(ui, activetc);
} else {
JOptionPane.showMessageDialog(null,NbBundle.getMessage(JavaRefactoringActionsProvider.class, &quot;ERR_CannotRenameKeyword&quot;));
}
}
/**
* This is the place where subclasses may collect info about handles.
* @param handle handle
* @param javac context of running transaction
*/
protected void treePathHandleResolved(TreePathHandle handle, CompilationInfo javac) {
}
protected abstract RefactoringUI createRefactoringUI(Collection&lt;TreePathHandle&gt; handles);
}
public static abstract class NodeToFileObjectTask implements Runnable, CancellableTask&lt;CompilationController&gt; {
private Collection&lt;? extends Node&gt; nodes;
public NonRecursiveFolder pkg[];
Collection&lt;TreePathHandle&gt; handles = new ArrayList&lt;TreePathHandle&gt;();
private Node currentNode;
public NodeToFileObjectTask(Collection&lt;? extends Node&gt; nodes) {
this.nodes = nodes;
}
public void cancel() {
}
public void run(CompilationController info) throws Exception {
info.toPhase(Phase.ELEMENTS_RESOLVED);
Collection&lt;TreePathHandle&gt; handlesPerNode = new ArrayList&lt;TreePathHandle&gt;();
CompilationUnitTree unit = info.getCompilationUnit();
Collection&lt;TreePathHandle&gt; publicHandles = new ArrayList&lt;TreePathHandle&gt;();
Collection&lt;TreePathHandle&gt; sameNameHandles = new ArrayList&lt;TreePathHandle&gt;();
for (Tree t: unit.getTypeDecls()) {
Element e = info.getTrees().getElement(TreePath.getPath(unit, t));
if (e == null || !(e.getKind().isClass() || e.getKind().isInterface())) {
// syntax errors #111195
continue;
}
if (e.getSimpleName().toString().equals(info.getFileObject().getName())) {
TreePathHandle representedObject = TreePathHandle.create(TreePath.getPath(unit,t),info);
sameNameHandles.add(representedObject);
}
if (e.getModifiers().contains(Modifier.PUBLIC)) {
TreePathHandle representedObject = TreePathHandle.create(TreePath.getPath(unit,t),info);
publicHandles.add(representedObject);
}
}
if (!publicHandles.isEmpty()) {
handlesPerNode.addAll(publicHandles);
} else {
handlesPerNode.addAll(sameNameHandles);
}
if (!handlesPerNode.isEmpty()) {
handles.addAll(handlesPerNode);
nodeTranslated(currentNode, handlesPerNode, info);
}
}
public void run() {
FileObject[] fobs = new FileObject[nodes.size()];
pkg = new NonRecursiveFolder[fobs.length];
int i = 0;
for (Node node:nodes) {
DataObject dob = node.getCookie(DataObject.class);
if (dob!=null) {
fobs[i] = dob.getPrimaryFile();
if (RetoucheUtils.isJavaFile(fobs[i])) {
JavaSource source = JavaSource.forFileObject(fobs[i]);
assert source != null;
try {
currentNode = node;
// XXX this could be optimize by ClasspasthInfo in case of more than one file
source.runUserActionTask(this, true);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
} finally {
currentNode = null;
}
}
pkg[i++] = node.getLookup().lookup(NonRecursiveFolder.class);
}
}
RefactoringUI ui = createRefactoringUI(fobs, handles);
if (ui!=null) {
UI.openRefactoringUI(ui);
} else {
JOptionPane.showMessageDialog(null,NbBundle.getMessage(JavaRefactoringActionsProvider.class, &quot;ERR_NoTypeDecls&quot;));
}
}
/**
* Notifies subclasses about the translation.
* This is the place where subclasses may collect info about handles.
* @param node node that is translated
* @param handles handles translated from the node
* @param javac context of running translation
*/
protected void nodeTranslated(Node node, Collection&lt;TreePathHandle&gt; handles, CompilationInfo javac) {
}
protected abstract RefactoringUI createRefactoringUI(FileObject[] selectedElement, Collection&lt;TreePathHandle&gt; handles);
}
</pre></li>
<li>The file RetoucheUtils is in the current version not in a public package. We will need to change the dependency on Java Refactoring to its implementation version. Expand the libraries node and select Java Refactoring.</li>
<li>Right-click and choose Edit...</li>
<li><p>Check Implementation Version as shown below.</p><p><img border="1" src="../../images/tutorials/refactoring2/ImplementationVersion.png"/></p></li></ol></p>
<h2>Next Steps</h2>
You now have a working Inline Constant Refactoring. In the next tutorial we will test the refactoring and look at distribution.
</body>
</html>