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.type;
010:
011: import java.io.*;
012: import java.util.*;
013: import net.sourceforge.jrefactory.ast.*;
014: import net.sourceforge.jrefactory.ast.ModifierHolder;
015: import org.acm.seguin.refactor.AddImportTransform;
016: import org.acm.seguin.refactor.ComplexTransform;
017: import org.acm.seguin.refactor.Refactoring;
018: import org.acm.seguin.refactor.RefactoringException;
019: import org.acm.seguin.refactor.method.AddConcreteMethod;
020: import org.acm.seguin.summary.*;
021: import org.acm.seguin.summary.query.*;
022:
023: /**
024: * Refactoring that extracts the interface from the dialog
025: *
026: *@author Grant Watson
027: *@created November 27, 2000
028: */
029: public class ExtractInterfaceRefactoring extends Refactoring {
030: private String m_interfaceName;
031: private String m_packageName;
032: private Vector m_summaryList = new Vector();
033: private ComplexTransform m_complexTransform;
034:
035: /**
036: * Constructor for the ExtractInterfaceRefactoring object
037: */
038: protected ExtractInterfaceRefactoring() {
039: m_complexTransform = getComplexTransform();
040: }
041:
042: /**
043: * Sets the interface name for the new interface. If the name contains a
044: * package name, then the package name is also set.
045: *
046: *@param interfaceName The new InterfaceName value
047: */
048: public void setInterfaceName(String interfaceName) {
049: if (interfaceName.indexOf('.') != -1) {
050: m_packageName = interfaceName.substring(0, interfaceName
051: .lastIndexOf('.'));
052: m_interfaceName = interfaceName.substring(interfaceName
053: .lastIndexOf('.') + 1);
054: } else {
055: m_interfaceName = interfaceName;
056: }
057: }
058:
059: /**
060: * Sets the PackageName attribute of the ExtractInterfaceRefactoring object
061: *
062: *@param packageName The new PackageName value
063: */
064: public void setPackageName(String packageName) {
065: m_packageName = packageName;
066: }
067:
068: /**
069: * Gets the Description attribute of the ExtractInterfaceRefactoring object
070: *
071: *@return The Description value
072: */
073: public String getDescription() {
074: return "Extract Interface.";
075: }
076:
077: /**
078: * Gets the ID attribute of the ExtractInterfaceRefactoring object
079: *
080: *@return The ID value
081: */
082: public int getID() {
083: return EXTRACT_INTERFACE;
084: }
085:
086: /**
087: * Adds a class that will implement the new interface
088: *
089: *@param packageName The feature to be added to the ImplementingClass
090: * attribute
091: *@param className The feature to be added to the ImplementingClass
092: * attribute
093: */
094: public void addImplementingClass(String packageName,
095: String className) {
096: TypeSummary summary = GetTypeSummary.query(PackageSummary
097: .getPackageSummary(packageName), className);
098: addImplementingClass(summary);
099: }
100:
101: /**
102: * Adds a feature to the ImplementingClass attribute of the
103: * ExtractInterfaceRefactoring object
104: *
105: *@param summary The feature to be added to the ImplementingClass attribute
106: */
107: public void addImplementingClass(TypeSummary summary) {
108: if (summary != null) {
109: m_summaryList.addElement(summary);
110: }
111: }
112:
113: /**
114: * Description of the Method
115: *
116: *@exception RefactoringException Description of Exception
117: */
118: protected void preconditions() throws RefactoringException {
119: if (m_interfaceName == null) {
120: throw new RefactoringException(
121: "Interface name is not specified");
122: }
123: if (m_summaryList.size() == 0) {
124: throw new RefactoringException(
125: "Unable to find type to extract interface from");
126: }
127: }
128:
129: /**
130: * this performs the refactoring
131: */
132: protected void transform() {
133: File newFile = createInterfaceFile();
134: // Add declarations of the common methods to the interface
135: Vector methodSummaries = getMethodSummaries();
136: for (int i = 0; i < methodSummaries.size(); i++) {
137: MethodSummary ms = (MethodSummary) methodSummaries
138: .elementAt(i);
139: m_complexTransform.add(new AddConcreteMethod(ms));
140: }
141: // Add necessary import statements to support parameter and return types
142: Iterator importTypes = getImportTypes(methodSummaries);
143: while ((importTypes != null) && (importTypes.hasNext())) {
144: TypeDeclSummary decl = (TypeDeclSummary) importTypes.next();
145: TypeSummary type = GetTypeSummary.query(decl);
146: // If the type is not found, don't attempt to add an import statement
147: if (type != null) {
148: m_complexTransform.add(new AddImportTransform(type));
149: }
150: }
151: m_complexTransform.apply(newFile, newFile);
152: /*
153: * Delete the backup file for the intermediate new interface file to
154: * ensure that an 'undo' does not recover it.
155: */
156: newFile = new File(newFile.getAbsolutePath() + ".0");
157: newFile.delete();
158: addInterfaceToClasses();
159: }
160:
161: /**
162: * Gets a list of public method summaries that are common to all classes for
163: * which an interface is being extracted.
164: *
165: *@return The MethodSummaries value
166: */
167: private Vector getMethodSummaries() {
168: Vector firstClassMethods = new Vector();
169: // Add all relevant methods from the first class.
170: TypeSummary ts = (TypeSummary) m_summaryList.elementAt(0);
171: Iterator methods = ts.getMethods();
172: while (methods.hasNext()) {
173: MethodSummary ms = (MethodSummary) methods.next();
174: //ModifierHolder mh = ms.getModifiers();
175: /*
176: * Include only public, non-static, non-constructor methods.
177: * Private and protected methods are not allowed in interfaces and
178: * methods that are package-protected in an interface need to be
179: * implemented by public methods in implementing classes (I think).
180: */
181: if (ms.isPublic() && (!ms.isConstructor())
182: && (!ms.isStatic())) {
183: // synchronized modifier is not allowed for interfaces.
184: ms.setSynchronized(false);
185: firstClassMethods.addElement(ms);
186: }
187: }
188: return commonMethods(firstClassMethods);
189: }
190:
191: /**
192: * Gets a list of the TypeDeclSummaries for the return types and parameters
193: * in the list of MethodSummaries supplied.
194: *
195: *@param methodSummaries Description of Parameter
196: *@return The ImportTypes value
197: */
198: private Iterator getImportTypes(Vector methodSummaries) {
199: HashMap importTypes = new HashMap();
200: for (int i = 0; i < methodSummaries.size(); i++) {
201: MethodSummary ms = (MethodSummary) methodSummaries
202: .elementAt(i);
203: // Add return type to list
204: TypeDeclSummary retType = ms.getReturnType();
205: String typeName = retType.getName();
206: if ((!(typeName.equals("void")))
207: && (importTypes.get(typeName) == null)) {
208: importTypes.put(typeName, retType);
209: }
210: Iterator params = ms.getParameters();
211: // Add parameter types to list
212: while ((params != null) && (params.hasNext())) {
213: VariableSummary vs = (VariableSummary) params.next();
214: TypeDeclSummary param = vs.getTypeDecl();
215: typeName = param.getName();
216: if (importTypes.get(typeName) == null) {
217: importTypes.put(typeName, param);
218: }
219: }
220: // Add exception types to list
221: Iterator exceptions = ms.getExceptions();
222: while ((exceptions != null) && (exceptions.hasNext())) {
223: TypeDeclSummary exception = (TypeDeclSummary) exceptions
224: .next();
225: typeName = exception.getName();
226: if (importTypes.get(typeName) == null) {
227: importTypes.put(typeName, exception);
228: }
229: }
230: }
231: return importTypes.values().iterator();
232: }
233:
234: /**
235: * Adds the name of the newly created interface to the implements clause of
236: * each class selected for the refactoring.
237: */
238: private void addInterfaceToClasses() {
239: for (int i = 0; i < m_summaryList.size(); i++) {
240: TypeSummary ts = (TypeSummary) m_summaryList.elementAt(i);
241: FileSummary fileSummary = (FileSummary) ts.getParent();
242: File file = fileSummary.getFile();
243: ASTName interfaceName = new ASTName();
244:
245: String currentPackageName = ts.getPackageSummary()
246: .getName();
247: /*
248: * If the interface package differs from the class package, then
249: * specify the interface package name
250: */
251: if ((m_packageName.length() > 0)
252: && !(currentPackageName.equals(m_packageName))) {
253: interfaceName.fromString(m_packageName + "."
254: + m_interfaceName);
255: } else {
256: interfaceName.fromString(m_interfaceName);
257: }
258: m_complexTransform.clear();
259: // Very Important so we don't re-apply the interface transforms
260: m_complexTransform
261: .add(new AddImplementedInterfaceTransform(
262: interfaceName));
263:
264: if (!m_packageName.equals(currentPackageName)) {
265: m_complexTransform.add(new AddImportTransform(
266: interfaceName));
267: }
268: m_complexTransform.apply(file, new File(file
269: .getAbsolutePath()));
270: }
271: }
272:
273: /**
274: * Eliminates methods that don't occurr in all classes and returns the
275: * resulting Vector of common methods.
276: *
277: *@param initialMethods Description of Parameter
278: *@return Description of the Returned Value
279: */
280: private Vector commonMethods(Vector initialMethods) {
281: Vector result = new Vector();
282: for (int i = 0; i < initialMethods.size(); i++) {
283: boolean keep = true;
284: outerloop: for (int j = 1; j < m_summaryList.size(); j++) {
285: TypeSummary ts = (TypeSummary) m_summaryList
286: .elementAt(j);
287: Iterator methods = ts.getMethods();
288: while (methods.hasNext()) {
289: MethodSummary ms = (MethodSummary) methods.next();
290: if (ms.equals((MethodSummary) initialMethods
291: .elementAt(i))) {
292: continue outerloop;
293: }
294: }
295: keep = false;
296: }
297: if (keep) {
298: MethodSummary ms = (MethodSummary) initialMethods
299: .elementAt(i);
300: result.addElement(initialMethods.elementAt(i));
301: }
302: }
303: return result;
304: }
305:
306: /**
307: * Creates a new interface file.
308: *
309: *@return Description of the Returned Value
310: */
311: private File createInterfaceFile() {
312: File newFile = null;
313: TypeSummary ts = (TypeSummary) m_summaryList.elementAt(0);
314: PackageSummary ps = ts.getPackageSummary();
315:
316: if (m_packageName == null) {
317: m_packageName = ps.getName();
318: }
319: CreateNewInterface cni = new CreateNewInterface(ts,
320: m_packageName, m_interfaceName);
321: try {
322: newFile = cni.run();
323: } catch (RefactoringException re) {
324: re.printStackTrace();
325: return null;
326: }
327: m_complexTransform.createFile(newFile);
328: return newFile;
329: }
330: }
|