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.util.Iterator;
012: import net.sourceforge.jrefactory.ast.SimpleNode;
013: import org.acm.seguin.refactor.ComplexTransform;
014: import org.acm.seguin.refactor.Refactoring;
015: import org.acm.seguin.refactor.RefactoringException;
016: import org.acm.seguin.summary.FieldAccessSummary;
017: import org.acm.seguin.summary.FieldSummary;
018: import org.acm.seguin.summary.FileSummary;
019: import org.acm.seguin.summary.MessageSendSummary;
020: import org.acm.seguin.summary.MethodSummary;
021: import org.acm.seguin.summary.Summary;
022: import org.acm.seguin.summary.TypeDeclSummary;
023: import org.acm.seguin.summary.TypeSummary;
024: import org.acm.seguin.summary.VariableSummary;
025: import org.acm.seguin.summary.query.FieldQuery;
026: import org.acm.seguin.summary.query.GetTypeSummary;
027: import org.acm.seguin.summary.query.MethodQuery;
028: import org.acm.seguin.summary.query.SamePackage;
029:
030: /**
031: * Moves a method from one class to another. Generally used to move a method
032: * into a local variable or a parameter.
033: *
034: *@author Chris Seguin
035: */
036: public class MoveMethodRefactoring extends MethodRefactoring {
037: private MethodSummary methodSummary;
038: private TypeSummary typeSummary;
039: private Summary destination;
040:
041: /**
042: * Constructor for the MoveMethodRefactoring object
043: */
044: protected MoveMethodRefactoring() {
045: }
046:
047: /**
048: * Sets the Method attribute of the MoveMethodRefactoring object
049: *
050: *@param value The new Method value
051: */
052: public void setMethod(MethodSummary value) {
053: methodSummary = value;
054: Summary current = methodSummary;
055: while (!(current instanceof TypeSummary)) {
056: current = current.getParent();
057: }
058: typeSummary = (TypeSummary) current;
059: }
060:
061: /**
062: * Sets the Destination attribute of the MoveMethodRefactoring object
063: *
064: *@param value The new Destination value
065: */
066: public void setDestination(Summary value) {
067: destination = value;
068: }
069:
070: /**
071: * Gets the description of the refactoring
072: *
073: *@return the description
074: */
075: public String getDescription() {
076: return "Moving " + methodSummary.toString() + " from "
077: + typeSummary.toString() + " to "
078: + destination.toString();
079: }
080:
081: /**
082: * Gets the ID attribute of the MoveMethodRefactoring object
083: *
084: *@return The ID value
085: */
086: public int getID() {
087: return MOVE_METHOD;
088: }
089:
090: /**
091: * Describes the preconditions that must be true for this refactoring to be
092: * applied
093: *
094: *@exception RefactoringException thrown if one or more of the
095: * preconditions is not satisfied. The text of the exception provides a
096: * hint of what went wrong.
097: */
098: protected void preconditions() throws RefactoringException {
099: Iterator iter = methodSummary.getDependencies();
100: while ((iter != null) && (iter.hasNext())) {
101: Summary next = (Summary) iter.next();
102: // Check to see if we have any private fields without the appropriate getters/setters
103: if (next instanceof FieldAccessSummary) {
104: FieldAccessSummary fas = (FieldAccessSummary) next;
105: checkFieldAccess(fas);
106: } else if (next instanceof MessageSendSummary) {
107: MessageSendSummary mss = (MessageSendSummary) next;
108: checkMessageSend(mss);
109: }
110: }
111:
112: if (destination instanceof VariableSummary) {
113: VariableSummary varSummary = (VariableSummary) destination;
114: TypeDeclSummary typeDecl = varSummary.getTypeDecl();
115: TypeSummary destType = GetTypeSummary.query(typeDecl);
116:
117: if (destType == null) {
118: throw new RefactoringException("The parameter "
119: + varSummary.getName() + " is a primitive");
120: }
121:
122: FileSummary fileSummary = (FileSummary) destType
123: .getParent();
124: if (fileSummary.getFile() == null) {
125: throw new RefactoringException("The source code for "
126: + destType.getName() + " is not modifiable");
127: }
128: }
129: }
130:
131: /**
132: * Performs the transform on the rest of the classes
133: */
134: protected void transform() {
135: ComplexTransform transform = getComplexTransform();
136:
137: // Update the method declaration to have the proper permissions
138: SimpleNode methodDecl = removeMethod(typeSummary, transform);
139: if (methodDecl == null) {
140: return;
141: }
142:
143: update(methodDecl);
144:
145: TypeSummary destType;
146: if (destination instanceof VariableSummary) {
147: VariableSummary varSummary = (VariableSummary) destination;
148: TypeDeclSummary typeDecl = varSummary.getTypeDecl();
149: destType = GetTypeSummary.query(typeDecl);
150: } else if (destination instanceof TypeSummary) {
151: destType = (TypeSummary) destination;
152: } else {
153: return;
154: }
155:
156: addMethodToDest(transform, methodDecl, destType);
157: }
158:
159: /**
160: * Removes the method from the source
161: *
162: *@param source the source type
163: *@param transform the transform
164: *@return Description of the Returned Value
165: */
166: protected SimpleNode removeMethod(TypeSummary source,
167: ComplexTransform transform) {
168: RemoveMethodTransform rft = new RemoveMethodTransform(
169: methodSummary);
170: transform.add(rft);
171:
172: InvokeMovedMethodTransform immt = new InvokeMovedMethodTransform(
173: methodSummary, destination);
174: transform.add(immt);
175:
176: FileSummary fileSummary = (FileSummary) source.getParent();
177: transform.apply(fileSummary.getFile(), fileSummary.getFile());
178:
179: return rft.getMethodDeclaration();
180: }
181:
182: /**
183: * Adds the method to the destination class
184: *
185: *@param transform The feature to be added to the MethodToDest attribute
186: *@param methodDecl The feature to be added to the MethodToDest attribute
187: *@param dest The feature to be added to the MethodToDest attribute
188: */
189: protected void addMethodToDest(ComplexTransform transform,
190: SimpleNode methodDecl, TypeSummary dest) {
191: transform.clear();
192: AddMethodTransform aft = new AddMethodTransform(methodDecl);
193: transform.add(aft);
194:
195: AddMethodTypeVisitor visitor = new AddMethodTypeVisitor();
196: methodSummary.accept(visitor, transform);
197:
198: // Add appropriate import statements - to be determined later
199: FileSummary parentFileSummary = (FileSummary) dest.getParent();
200: transform.apply(parentFileSummary.getFile(), parentFileSummary
201: .getFile());
202: }
203:
204: /**
205: * Gets the name of the getter for the field
206: *
207: *@param summary the field summary
208: *@return the getter
209: */
210: private String getFieldGetter(FieldSummary summary) {
211: String typeName = summary.getType();
212: String prefix = "get";
213: if (typeName.equalsIgnoreCase("boolean")) {
214: prefix = "is";
215: }
216:
217: String name = summary.getName();
218:
219: return prefix + name.substring(0, 1).toUpperCase()
220: + name.substring(1);
221: }
222:
223: /**
224: * Gets the name of the setter for the field
225: *
226: *@param summary the field summary
227: *@return the setter
228: */
229: private String getFieldSetter(FieldSummary summary) {
230: String prefix = "set";
231: String name = summary.getName();
232: return prefix + name.substring(0, 1).toUpperCase()
233: + name.substring(1);
234: }
235:
236: /**
237: * Checks if we can properly transform the field access
238: *
239: *@param fas Description of Parameter
240: *@exception RefactoringException Description of Exception
241: */
242: private void checkFieldAccess(FieldAccessSummary fas)
243: throws RefactoringException {
244: if ((fas.getPackageName() == null)
245: && ((fas.getObjectName() == null) || fas
246: .getObjectName().equals("this"))) {
247: // Now we have to find the field
248: FieldSummary field = FieldQuery.find(typeSummary, fas
249: .getFieldName());
250: if (field != null) {
251: if (field.isPrivate()) {
252: checkForMethod(fas, field);
253: }
254: }
255: }
256: }
257:
258: /**
259: * For a private field, check that we have the correct setters or getters
260: * (as appropriate)
261: *
262: *@param fas Description of Parameter
263: *@param field Description of Parameter
264: *@exception RefactoringException Description of Exception
265: */
266: private void checkForMethod(FieldAccessSummary fas,
267: FieldSummary field) throws RefactoringException {
268: String methodName;
269: if (fas.isAssignment()) {
270: methodName = getFieldSetter(field);
271: } else {
272: methodName = getFieldGetter(field);
273: }
274: MethodSummary method = MethodQuery
275: .find(typeSummary, methodName);
276: if (method == null) {
277: throw new RefactoringException(
278: "Unable to find the appropriate method ("
279: + methodName
280: + ") for private field access in "
281: + typeSummary.getName());
282: }
283: }
284:
285: /**
286: * Updates the node fore move method
287: *
288: *@param node Description of Parameter
289: */
290: private void update(SimpleNode node) {
291: MoveMethodVisitor mmv = new MoveMethodVisitor(typeSummary,
292: methodSummary, destination);
293: node.jjtAccept(mmv, null);
294: }
295:
296: /**
297: * Description of the Method
298: *
299: *@param mss Description of Parameter
300: *@exception RefactoringException Description of Exception
301: */
302: private void checkMessageSend(MessageSendSummary mss)
303: throws RefactoringException {
304: if ((mss.getPackageName() == null)
305: && ((mss.getObjectName() == null) || mss
306: .getObjectName().equals("this"))) {
307: MethodSummary method = MethodQuery.find(typeSummary, mss
308: .getMessageName());
309: if (method == null) {
310: throw new RefactoringException(
311: "Unable to find the method ("
312: + mss.getMessageName() + ") in "
313: + typeSummary.getName());
314: }
315:
316: if (method.isPrivate()) {
317: throw new RefactoringException("Moving a method ("
318: + mss.getMessageName() + ") from "
319: + typeSummary.getName()
320: + " that requires private access is illegal");
321: }
322:
323: if (method.isPackage()) {
324: TypeSummary destType;
325: if (destination instanceof VariableSummary) {
326: VariableSummary varSummary = (VariableSummary) destination;
327: TypeDeclSummary typeDecl = varSummary.getTypeDecl();
328: destType = GetTypeSummary.query(typeDecl);
329: } else if (destination instanceof TypeSummary) {
330: destType = (TypeSummary) destination;
331: } else {
332: throw new RefactoringException(
333: "Cannot find the type associated with "
334: + destination.getName());
335: }
336:
337: if (!SamePackage.query(typeSummary, destType)) {
338: throw new RefactoringException(
339: "Moving a method ("
340: + mss.getMessageName()
341: + ") from "
342: + typeSummary.getName()
343: + " to a different package that requires package access is illegal.");
344: }
345: }
346: }
347: }
348: }
|