001: /*
002: * Author: Chris Seguin
003: *
004: * This software has been developed under the copyleft
005: * rules of the GNU General Public License. Please
006: * consult the GNU General Public License for more
007: * details about use and distribution of this software.
008: */
009: package org.acm.seguin.refactor.method;
010:
011: import java.io.StringWriter;
012: import java.util.Iterator;
013: import java.util.LinkedList;
014: import net.sourceforge.jrefactory.ast.Node;
015: import net.sourceforge.jrefactory.ast.ASTBlockStatement;
016: import net.sourceforge.jrefactory.ast.ASTClassBody;
017: import net.sourceforge.jrefactory.ast.ASTClassBodyDeclaration;
018: import net.sourceforge.jrefactory.ast.ASTClassDeclaration;
019: import net.sourceforge.jrefactory.ast.ASTCompilationUnit;
020: import net.sourceforge.jrefactory.ast.ASTMethodDeclaration;
021: import net.sourceforge.jrefactory.ast.ASTReturnStatement;
022: import net.sourceforge.jrefactory.ast.ASTStatement;
023: import net.sourceforge.jrefactory.ast.ASTTypeDeclaration;
024: import net.sourceforge.jrefactory.ast.ASTUnmodifiedClassDeclaration;
025: import net.sourceforge.jrefactory.ast.SimpleNode;
026: import net.sourceforge.jrefactory.build.BuildExpression;
027: import net.sourceforge.jrefactory.factory.BufferParserFactory;
028: import net.sourceforge.jrefactory.query.Found;
029: import net.sourceforge.jrefactory.query.Search;
030: import org.acm.seguin.pretty.JavadocTags;
031: import net.sourceforge.jrefactory.ast.ModifierHolder;
032: import org.acm.seguin.awt.ExceptionPrinter;
033: import org.acm.seguin.pretty.PrettyPrintVisitor;
034: import org.acm.seguin.pretty.PrintData;
035: import org.acm.seguin.refactor.Refactoring;
036: import org.acm.seguin.refactor.RefactoringException;
037: import org.acm.seguin.summary.FileSummary;
038: import org.acm.seguin.summary.SummaryLoadVisitor;
039: import org.acm.seguin.summary.SummaryLoaderState;
040: import org.acm.seguin.summary.VariableSummary;
041: import net.sourceforge.jrefactory.parser.JavaParserTreeConstants;
042:
043: /**
044: * Refactoring class that extracts a portion of the method and creates a new
045: * method with what the user has selected.
046: *
047: *@author Chris Seguin
048: *@author <a href="JRefactory@ladyshot.demon.co.uk">Mike Atkinson</a>
049: *@version $Id: ExtractMethodRefactoring.java,v 1.7 2003/10/30 15:24:23 mikeatkinson Exp $
050: */
051: public class ExtractMethodRefactoring extends Refactoring {
052: private StringBuffer fullFile = null;
053: private String selection = null;
054: private String methodName = null;
055: private SimpleNode root;
056: private FileSummary mainFileSummary;
057: private FileSummary extractedMethodFileSummary;
058: private Node key;
059: private EMParameterFinder empf = null;
060: private StringBuffer signature;
061: /**
062: * Stores the return type for the extracted method
063: */
064: private Object returnType = null;
065: private int prot = PRIVATE;
066: private Object[] arguments = new Object[0];
067:
068: /**
069: * The extracted method should be private
070: */
071: public final static int PRIVATE = 0;
072: /**
073: * The extracted method should have package scope
074: */
075: public final static int PACKAGE = 1;
076: /**
077: * The extracted method should have protected scope
078: */
079: public final static int PROTECTED = 2;
080: /**
081: * The extracted method should have public scope
082: */
083: public final static int PUBLIC = 3;
084:
085: /**
086: * Constructor for the ExtractMethodRefactoring object
087: */
088: protected ExtractMethodRefactoring() {
089: super ();
090:
091: signature = new StringBuffer();
092: }
093:
094: /**
095: * Sets the FullFile attribute of the ExtractMethodRefactoring object
096: *
097: *@param value The new FullFile value
098: */
099: public void setFullFile(String value) {
100: fullFile = new StringBuffer(value);
101: }
102:
103: /**
104: * Sets the FullFile attribute of the ExtractMethodRefactoring object
105: *
106: *@param value The new FullFile value
107: */
108: public void setFullFile(StringBuffer value) {
109: fullFile = value;
110: }
111:
112: /**
113: * Sets the Selection attribute of the ExtractMethodRefactoring object
114: *
115: *@param value The new Selection value
116: */
117: public void setSelection(String value) throws RefactoringException {
118: if (value == null) {
119: throw new RefactoringException(
120: "Nothing has been selected, so nothing can be extracted");
121: }
122:
123: selection = value.trim();
124:
125: if (isStatement()) {
126: setReturnType(null);
127: } else {
128: setReturnType("boolean");
129: }
130: }
131:
132: /**
133: * Sets the MethodName attribute of the ExtractMethodRefactoring object
134: *
135: *@param value The new MethodName value
136: */
137: public void setMethodName(String value) {
138: methodName = value;
139: if ((methodName == null) || (methodName.length() == 0)) {
140: methodName = "extractedMethod";
141: }
142: }
143:
144: /**
145: * Sets the order of the parameters
146: *
147: *@param data The new ParameterOrder value
148: */
149: public void setParameterOrder(Object[] data) {
150: empf.setParameterOrder(data);
151: arguments = data;
152: }
153:
154: /**
155: * Sets the Protection attribute of the ExtractMethodRefactoring object
156: *
157: *@param value The new Protection value
158: */
159: public void setProtection(int value) {
160: prot = value;
161: }
162:
163: /**
164: * Sets the return type for the extracted method
165: *
166: *@param obj The new ReturnType value
167: */
168: public void setReturnType(Object obj) {
169: returnType = obj;
170: }
171:
172: /**
173: * Gets the Description attribute of the ExtractMethodRefactoring object
174: *
175: *@return The Description value
176: */
177: public String getDescription() {
178: return "Extract a method named " + methodName;
179: }
180:
181: /**
182: * Gets the FullFile attribute of the ExtractMethodRefactoring object
183: *
184: *@return The FullFile value
185: */
186: public String getFullFile() {
187: return fullFile.toString();
188: }
189:
190: /**
191: * Gets the Parameters attribute of the ExtractMethodRefactoring object
192: *
193: *@return The Parameters value
194: *@exception RefactoringException Description of Exception
195: */
196: public VariableSummary[] getParameters()
197: throws RefactoringException {
198: preconditions();
199: Search srch = new Search();
200: empf = prescan(srch);
201:
202: LinkedList list = empf.getList();
203: VariableSummary[] result = new VariableSummary[list.size()];
204: Iterator iter = list.iterator();
205: int count = 0;
206: while (iter.hasNext()) {
207: result[count] = (VariableSummary) iter.next();
208: count++;
209: }
210:
211: arguments = result;
212:
213: return result;
214: }
215:
216: /**
217: * Gets the possible return types
218: *
219: *@return The return types
220: *@exception RefactoringException problem in loading these
221: */
222: public Object[] getReturnTypes() throws RefactoringException {
223: if (empf == null) {
224: return null;
225: }
226: return empf.getReturnTypes();
227: }
228:
229: /**
230: * Gets the Statement attribute of the ExtractMethodRefactoring object
231: *
232: *@return The Statement value
233: */
234: public boolean isStatement() {
235: return (selection.indexOf(";") > 0)
236: || (selection.indexOf("}") > 0);
237: }
238:
239: /**
240: * Gets the Signature attribute of the ExtractMethodRefactoring object
241: *
242: *@return The Signature value
243: */
244: public String getSignature() {
245: signature.setLength(0);
246: signature.append(getProtection());
247: signature.append(" ");
248: signature.append(getReturnTypeString());
249: signature.append(" ");
250: signature.append(methodName);
251: signature.append("(");
252: for (int ndx = 0; ndx < arguments.length; ndx++) {
253: signature.append(((VariableSummary) arguments[ndx])
254: .getDeclaration());
255: if (ndx != arguments.length - 1) {
256: signature.append(", ");
257: }
258: }
259: signature.append(")");
260:
261: return signature.toString();
262: }
263:
264: /**
265: * Gets the return type for the extracted method
266: *
267: *@return The return type
268: */
269: public Object getReturnType() {
270: return returnType;
271: }
272:
273: /**
274: * Gets the ID attribute of the ExtractMethodRefactoring object
275: *
276: *@return The ID value
277: */
278: public int getID() {
279: return EXTRACT_METHOD;
280: }
281:
282: /**
283: * These items must be true before the refactoring will work
284: *
285: *@exception RefactoringException the problem that arose
286: */
287: protected void preconditions() throws RefactoringException {
288: if (fullFile == null) {
289: throw new RefactoringException("No file specified");
290: }
291:
292: if (selection == null) {
293: throw new RefactoringException("No selection specified");
294: }
295:
296: if (methodName == null) {
297: throw new RefactoringException("No method specified");
298: }
299:
300: root = getFileRoot();
301: if (root == null) {
302: throw new RefactoringException(
303: "Unable to parse the current file.\n"
304: + "Please make sure you can compile this file before\n"
305: + "trying to extract a method from it.");
306: }
307:
308: mainFileSummary = findVariablesUsed(root);
309:
310: SimpleNode newMethod = getMethodTree();
311: if (newMethod == null) {
312: throw new RefactoringException(
313: "Unable to parse the current selection.\n"
314: + "Please make sure you have highlighted the entire expression\n"
315: + "or set of statements.");
316: }
317: }
318:
319: /**
320: * Actually make the transformation
321: */
322: protected void transform() {
323: replaceAllInstances(root);
324: printFile(root);
325: }
326:
327: /**
328: * Gets the MethodTree attribute of the ExtractMethodRefactoring object
329: *
330: *@return The MethodTree value
331: */
332: private SimpleNode getMethodTree() {
333: String tempClass = "public class TempClass { " + makeMethod()
334: + "}";
335: BufferParserFactory bpf = new BufferParserFactory(tempClass);
336: SimpleNode root = bpf.getAbstractSyntaxTree(false,
337: ExceptionPrinter.getInstance());
338:
339: extractedMethodFileSummary = findVariablesUsed(root);
340:
341: ASTTypeDeclaration top = (ASTTypeDeclaration) root
342: .jjtGetFirstChild();
343: ASTClassDeclaration classDecl = (ASTClassDeclaration) top
344: .jjtGetFirstChild();
345: ASTUnmodifiedClassDeclaration unmodifiedClassDecl = (ASTUnmodifiedClassDeclaration) classDecl
346: .jjtGetFirstChild();
347: ASTClassBody classBody = (ASTClassBody) unmodifiedClassDecl
348: .jjtGetFirstChild();
349: ASTClassBodyDeclaration bodyDecl = (ASTClassBodyDeclaration) classBody
350: .jjtGetFirstChild();
351:
352: return (SimpleNode) bodyDecl.jjtGetFirstChild();
353: }
354:
355: /**
356: * Gets the FileRoot attribute of the ExtractMethodRefactoring object
357: *
358: *@return The FileRoot value
359: */
360: private SimpleNode getFileRoot() {
361: BufferParserFactory bpf = new BufferParserFactory(fullFile
362: .toString());
363: SimpleNode root = bpf.getAbstractSyntaxTree(true,
364: ExceptionPrinter.getInstance());
365: return root;
366: }
367:
368: /**
369: * Returns the protection
370: *
371: *@return The Protection value
372: */
373: private String getProtection() {
374: switch (prot) {
375: case PRIVATE:
376: return "private";
377: case PACKAGE:
378: return "";
379: case PROTECTED:
380: return "protected";
381: case PUBLIC:
382: return "public";
383: }
384: return "private";
385: }
386:
387: /**
388: * Returns the return type
389: *
390: *@return The ReturnType value
391: */
392: private String getReturnTypeString() {
393: if (returnType == null) {
394: return "void";
395: } else if (returnType instanceof String) {
396: return (String) returnType;
397: } else if (returnType instanceof VariableSummary) {
398: return ((VariableSummary) returnType).getTypeDecl()
399: .getName();
400: } else {
401: return returnType.toString();
402: }
403: }
404:
405: /**
406: * Replace all instances of code with a selected value
407: *
408: *@param root Description of Parameter
409: */
410: private void replaceAllInstances(SimpleNode root) {
411: EMBuilder builder = new EMBuilder();
412: builder.setMethodName(methodName);
413: builder.setStatement(isStatement());
414:
415: Search srch = new Search();
416: if (empf == null) {
417: empf = prescan(srch);
418: }
419: builder.setParameters(empf.getList());
420:
421: SimpleNode methodTree = addReturn(getMethodTree());
422:
423: Found result = srch.search(root, key);
424: updateModifiers((SimpleNode) result.getRoot(), methodTree);
425:
426: if (returnType instanceof VariableSummary) {
427: builder.setReturnSummary((VariableSummary) returnType);
428: FindLocalVariableDeclVisitor flvdv = new FindLocalVariableDeclVisitor();
429: methodTree.jjtAccept(flvdv, returnType);
430: builder.setLocalVariableNeeded(flvdv.isFound());
431: }
432:
433: SimpleNode firstResult = (SimpleNode) result.getRoot();
434: while (result != null) {
435: replaceExtractedMethod(result, builder);
436: result = srch.search(root, key);
437: }
438:
439: insertAtNextClass(firstResult, methodTree);
440: }
441:
442: /**
443: * Prints the file using the pretty printer option
444: *
445: *@param root Description of Parameter
446: */
447: private void printFile(SimpleNode root) {
448: StringWriter writer = new StringWriter();
449: JavadocTags.get().reload();
450: PrintData pd = new PrintData(writer);
451: PrettyPrintVisitor ppv = new PrettyPrintVisitor();
452: ppv.visit((ASTCompilationUnit) root, pd);
453: pd.close();
454:
455: String file = writer.toString();
456: if (file.length() > 0) {
457: fullFile = new StringBuffer(file);
458: }
459: }
460:
461: /**
462: * Creates a string with the new method in it
463: *
464: *@return the new method
465: */
466: private String makeMethod() {
467: if (isStatement()) {
468: return getSignature() + "{" + selection + "}";
469: } else {
470: return getSignature() + "{ return " + selection + "; }";
471: }
472: }
473:
474: /**
475: * Description of the Method
476: *
477: *@param node Description of Parameter
478: *@return Description of the Returned Value
479: */
480: private FileSummary findVariablesUsed(Node node) {
481: if (node == null) {
482: return null;
483: }
484: SummaryLoaderState state = new SummaryLoaderState();
485: node.jjtAccept(new SummaryLoadVisitor(), state);
486: return (FileSummary) state.getCurrentSummary();
487: }
488:
489: /**
490: * Finds the parameters
491: *
492: *@param result the location where the section was found
493: *@return Description of the Returned Value
494: */
495: private EMParameterFinder findParameters(Found result) {
496: EMParameterFinder empf = new EMParameterFinder();
497: empf.setMainFileSummary(mainFileSummary);
498: empf.setExtractFileSummary(extractedMethodFileSummary);
499: empf.setLocation(result.getRoot());
500: empf.run();
501: return empf;
502: }
503:
504: /**
505: * This allows us to scan for the parameters first
506: *
507: *@param srch Description of Parameter
508: *@return Description of the Returned Value
509: */
510: private EMParameterFinder prescan(Search srch) {
511: EMDigger digger = new EMDigger();
512: if (isStatement()) {
513: key = digger.last((ASTMethodDeclaration) getMethodTree());
514: } else {
515: key = digger.dig((ASTMethodDeclaration) getMethodTree());
516: }
517:
518: Found result = srch.search(root, key);
519:
520: EMParameterFinder parameterFinder = findParameters(result);
521:
522: LinkedList list = parameterFinder.getList();
523: arguments = new Object[list.size()];
524: Iterator iter = list.iterator();
525: int count = 0;
526: while (iter.hasNext()) {
527: arguments[count] = iter.next();
528: count++;
529: }
530:
531: return parameterFinder;
532: }
533:
534: /**
535: * Replaces the extracted method
536: *
537: *@param result where we found the portion to replace
538: *@param builder build a method invocation
539: */
540: private void replaceExtractedMethod(Found result, EMBuilder builder) {
541: int index = result.getIndex();
542: int length = key.jjtGetNumChildren();
543: Node location = result.getRoot();
544:
545: for (int ndx = 0; ndx < length; ndx++) {
546: location.jjtDeleteChild(index);
547: }
548: location.jjtInsertChild(builder.build(), index);
549: }
550:
551: /**
552: * Adds the return at the end of the method if one is necessary
553: *
554: *@param methodDecl The feature to be added to the Return attribute
555: *@return Description of the Returned Value
556: */
557: private SimpleNode addReturn(SimpleNode methodDecl) {
558: if (returnType instanceof VariableSummary) {
559: Node block = methodDecl.jjtGetChild(methodDecl
560: .jjtGetNumChildren() - 1);
561:
562: ASTBlockStatement blockStatement = new ASTBlockStatement(
563: JavaParserTreeConstants.JJTBLOCKSTATEMENT);
564:
565: ASTStatement statement = new ASTStatement(
566: JavaParserTreeConstants.JJTSTATEMENT);
567: blockStatement.jjtAddChild(statement, 0);
568:
569: ASTReturnStatement returnStatement = new ASTReturnStatement(
570: JavaParserTreeConstants.JJTRETURNSTATEMENT);
571: statement.jjtAddChild(returnStatement, 0);
572:
573: BuildExpression be = new BuildExpression();
574: String name = ((VariableSummary) returnType).getName();
575: returnStatement.jjtAddChild(be.buildName(name), 0);
576:
577: block
578: .jjtAddChild(blockStatement, block
579: .jjtGetNumChildren());
580: }
581:
582: return methodDecl;
583: }
584:
585: /**
586: * Adds the static and synchronized attributes to the extracted method
587: *
588: *@param currentNode where the body of the extracted method was found
589: *@param methodTree the method we are extracting
590: */
591: private void updateModifiers(SimpleNode currentNode,
592: SimpleNode methodTree) {
593: while (!(currentNode instanceof ASTMethodDeclaration)) {
594: currentNode = (SimpleNode) currentNode.jjtGetParent();
595: if (currentNode instanceof ASTClassBody) {
596: return;
597: }
598: }
599:
600: ASTMethodDeclaration extractedFrom = (ASTMethodDeclaration) currentNode;
601: ASTMethodDeclaration newMethod = (ASTMethodDeclaration) methodTree;
602:
603: //ModifierHolder efmh = extractedFrom.getModifiers();
604: //ModifierHolder nmmh = newMethod.getModifiers();
605: //nmmh.setStatic(efmh.isStatic());
606: //nmmh.setSynchronized(nmmh.isSynchronized());
607: newMethod.setStatic(extractedFrom.isStatic());
608: newMethod.setSynchronized(extractedFrom.isSynchronized());
609: }
610:
611: /**
612: * Inserts the method at the next class found when heading up from the first
613: * place where we replaced this value
614: *
615: *@param currentNode where this was found
616: *@param methodTree the method to be inserted
617: */
618: private void insertAtNextClass(SimpleNode currentNode,
619: SimpleNode methodTree) {
620: while (!(currentNode instanceof ASTClassBody)) {
621: currentNode = (SimpleNode) currentNode.jjtGetParent();
622: if (currentNode == null) {
623: return;
624: }
625: }
626:
627: currentNode.jjtInsertChild(methodTree, currentNode
628: .jjtGetNumChildren());
629: }
630: }
|