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.File;
012: import java.io.IOException;
013: import java.util.Iterator;
014: import java.util.LinkedList;
015: import java.util.StringTokenizer;
016: import net.sourceforge.jrefactory.ast.*;
017: import org.acm.seguin.pretty.PrettyPrintFile;
018: import org.acm.seguin.refactor.RefactoringException;
019: import org.acm.seguin.refactor.method.AddConcreteMethod;
020: import org.acm.seguin.refactor.method.AddConstructor;
021: import org.acm.seguin.refactor.method.AddMethodTypeVisitor;
022: import org.acm.seguin.summary.FileSummary;
023: import org.acm.seguin.summary.MethodSummary;
024: import org.acm.seguin.summary.PackageSummary;
025: import org.acm.seguin.summary.Summary;
026: import org.acm.seguin.summary.TypeDeclSummary;
027: import org.acm.seguin.summary.TypeSummary;
028: import org.acm.seguin.summary.query.GetPackageSummary;
029: import org.acm.seguin.summary.query.GetTypeSummary;
030: import org.acm.seguin.summary.query.SamePackage;
031: import org.acm.seguin.summary.query.TopLevelDirectory;
032: import net.sourceforge.jrefactory.parser.JavaParserTreeConstants;
033:
034: /**
035: * This object creates a class from nothing. It is responsible for building
036: * up the parse tree from scratch to create a new class.
037: *
038: *@author Chris Seguin
039: */
040: public class CreateClass {
041: private TypeSummary typeSummary;
042: private String newClassName;
043: private boolean isParent;
044: private boolean isAbstract;
045: private boolean isFinal;
046: private String packageNameString;
047: private String scope;
048:
049: /**
050: * Constructor for the CreateClass object
051: *
052: *@param existing The existing class we are building upon
053: *@param className The name of the new class
054: *@param parent Are we building a parent or child from the existing
055: * type
056: *@param packageName the name of the package that the class is in
057: */
058: public CreateClass(TypeSummary existing, String className,
059: boolean parent, String packageName) {
060: typeSummary = existing;
061: newClassName = className;
062: isParent = parent;
063: isAbstract = true;
064: isFinal = false;
065: packageNameString = packageName;
066: scope = "";
067: }
068:
069: /**
070: * Constructor for the CreateClass object
071: *
072: *@param existing The existing class we are building upon
073: *@param className The name of the new class
074: *@param parent Are we building a parent or child from the existing type
075: */
076: public CreateClass(TypeSummary existing, String className,
077: boolean parent) {
078: typeSummary = existing;
079: newClassName = className;
080: isParent = parent;
081: isAbstract = true;
082: isFinal = false;
083: packageNameString = null;
084: scope = "";
085: }
086:
087: /**
088: * Constructor for the CreateClass object
089: */
090: CreateClass() {
091: typeSummary = null;
092: newClassName = null;
093: }
094:
095: /**
096: * Sets the PackageName attribute of the CreateClass object
097: *
098: *@param value The new PackageName value
099: */
100: public void setPackageName(String value) {
101: packageNameString = value;
102: }
103:
104: /**
105: * Sets the Scope attribute of the CreateClass object
106: *
107: *@param value The new Scope value
108: */
109: public void setScope(String value) {
110: scope = value;
111: }
112:
113: /**
114: * Sets the Abstract attribute of the CreateClass object
115: *
116: *@param way The new Abstract value
117: */
118: public void setAbstract(boolean way) {
119: isAbstract = way;
120: }
121:
122: /**
123: * Sets the Final attribute of the CreateClass object
124: *
125: *@param way The new Final value
126: */
127: public void setFinal(boolean way) {
128: isFinal = way;
129: }
130:
131: /**
132: * Creates the the designated class
133: *
134: *@return Description of the Returned Value
135: *@exception RefactoringException Description of Exception
136: */
137: public File run() throws RefactoringException {
138: if (newClassName == null) {
139: throw new RefactoringException("No class name specified");
140: }
141:
142: if (typeSummary == null) {
143: throw new RefactoringException("No type to build upon");
144: }
145:
146: if (packageNameString == null) {
147: packageNameString = GetPackageSummary.query(typeSummary)
148: .getName();
149: }
150:
151: // Create the AST
152: ASTCompilationUnit root = new ASTCompilationUnit(0);
153:
154: // Create the package statement
155: int nextIndex = 0;
156:
157: if ((packageNameString != null)
158: && (packageNameString.length() > 0)) {
159: ASTPackageDeclaration packDecl = createPackageDeclaration();
160: root.jjtAddChild(packDecl, 0);
161: nextIndex++;
162: }
163:
164: TypeSummary parentSummary = null;
165: ASTName parentName;
166: if (isParent) {
167: TypeDeclSummary parentDecl = typeSummary.getParentClass();
168: parentSummary = GetTypeSummary.query(parentDecl);
169: if (parentSummary == null) {
170: parentSummary = GetTypeSummary.query(PackageSummary
171: .getPackageSummary("java.lang"), "Object");
172: }
173: } else {
174: parentSummary = typeSummary;
175: }
176: parentName = getNameFromSummary(parentSummary);
177:
178: // If necessary, create the import statement
179: int typeIndex = nextIndex;
180: boolean added = addImportStatement(parentSummary, parentName,
181: root, nextIndex);
182: if (added) {
183: typeIndex++;
184:
185: parentName = new ASTName();
186: parentName.addNamePart(parentSummary.getName());
187: }
188:
189: // Create the class
190: ASTTypeDeclaration td = createTypeDeclaration(parentName);
191: root.jjtAddChild(td, typeIndex);
192:
193: addConstructors(parentSummary, root);
194:
195: if (!isAbstract) {
196: addMethods(typeSummary, root);
197: }
198:
199: // Print this new one
200: File dest = print(newClassName, root);
201: return dest;
202: }
203:
204: /**
205: * Converts the type summary into a name
206: *
207: *@param summary the summary
208: *@return the name
209: */
210: ASTName getNameFromSummary(TypeSummary summary) {
211: ASTName name = new ASTName();
212: if ((summary == null) || summary.getName().equals("Object")) {
213: name.fromString("Object");
214: } else {
215: PackageSummary packageSummary = getPackageSummary(summary);
216: if (packageSummary.isTopLevel()) {
217: name.fromString(summary.getName());
218: } else if (!isSamePackage(packageNameString, summary)) {
219: name.fromString(packageSummary.getName() + "."
220: + summary.getName());
221: } else {
222: name.fromString(summary.getName());
223: }
224: }
225:
226: return name;
227: }
228:
229: /**
230: * Gets the SamePackage attribute of the AddAbstractParent object
231: *
232: *@param parentSummary Description of Parameter
233: *@param packageName Description of Parameter
234: *@return The SamePackage value
235: */
236: boolean isSamePackage(String packageName, TypeSummary parentSummary) {
237: return (parentSummary != null)
238: && SamePackage.query(packageName, parentSummary);
239: }
240:
241: /**
242: * Creates the package declaration
243: *
244: *@return the package declaration
245: */
246: ASTPackageDeclaration createPackageDeclaration() {
247: ASTPackageDeclaration packDecl = new ASTPackageDeclaration(
248: JavaParserTreeConstants.JJTPACKAGEDECLARATION);
249: ASTName packName = new ASTName();
250: packName.fromString(packageNameString);
251: packDecl.jjtAddChild(packName, 0);
252:
253: return packDecl;
254: }
255:
256: /**
257: * Adds the import statement and returns true if the import statement was
258: * necessary
259: *
260: *@param parentSummary the parent summary
261: *@param parentName the parent name
262: *@param root the tree being built
263: *@return true if the import statement was added
264: */
265: boolean addImportStatement(TypeSummary parentSummary,
266: ASTName parentName, ASTCompilationUnit root, int index) {
267: if (!isImportRequired(parentSummary)) {
268: return false;
269: }
270:
271: // Create the import statement
272: ASTImportDeclaration importDecl = new ASTImportDeclaration(
273: JavaParserTreeConstants.JJTIMPORTDECLARATION);
274: importDecl.jjtAddChild(parentName, 0);
275: root.jjtAddChild(importDecl, index);
276: return true;
277: }
278:
279: /**
280: * Creates the type declaration
281: *
282: *@param grandparentName Description of Parameter
283: *@return the modified class
284: */
285: ASTTypeDeclaration createTypeDeclaration(ASTName grandparentName) {
286: ASTTypeDeclaration td = new ASTTypeDeclaration();
287:
288: ASTClassDeclaration cd = createModifiedClass(grandparentName);
289: td.jjtAddChild(cd, 0);
290:
291: return td;
292: }
293:
294: /**
295: * Creates the modified class
296: *
297: *@param grandparentName The name of the parent class
298: *@return the modified class
299: */
300: ASTClassDeclaration createModifiedClass(ASTName grandparentName) {
301: ASTClassDeclaration cd = new ASTClassDeclaration(
302: JavaParserTreeConstants.JJTCLASSDECLARATION);
303: if (isAbstract) {
304: cd.addModifier("abstract");
305: }
306: if (isFinal) {
307: cd.addModifier("final");
308: }
309: if (scope.length() > 0) {
310: cd.addModifier(scope);
311: }
312:
313: ASTUnmodifiedClassDeclaration ucd = createClassBody(
314: newClassName, grandparentName);
315: cd.jjtAddChild(ucd, 0);
316:
317: return cd;
318: }
319:
320: /**
321: * Creates the body. The protection level is package so it can be easily
322: * tested.
323: *
324: *@param parentName Description of Parameter
325: *@param grandparentName Description of Parameter
326: *@return the class
327: */
328: ASTUnmodifiedClassDeclaration createClassBody(String parentName,
329: ASTName grandparentName) {
330: ASTUnmodifiedClassDeclaration ucd = new ASTUnmodifiedClassDeclaration(
331: JavaParserTreeConstants.JJTUNMODIFIEDCLASSDECLARATION);
332: ucd.setName(parentName);
333: ucd.jjtAddChild(grandparentName, 0);
334: ucd.jjtAddChild(new ASTClassBody(
335: JavaParserTreeConstants.JJTCLASSBODY), 1);
336: return ucd;
337: }
338:
339: /**
340: * Prints the file
341: *
342: *@param name The name of the object
343: *@param root The root of the tree
344: *@return The file that the parse tree was written to
345: */
346: File print(String name, SimpleNode root) {
347: File parent = getDirectory();
348: File destFile = new File(parent, name + ".java");
349:
350: try {
351: (new PrettyPrintFile()).apply(destFile, root);
352: } catch (Throwable thrown) {
353: thrown.printStackTrace(System.out);
354: }
355:
356: return destFile;
357: }
358:
359: /**
360: * Determines if we need to add an import
361: *
362: *@param parentSummary the parent summary
363: *@return true if the import is necessary
364: */
365: private boolean isImportRequired(TypeSummary parentSummary) {
366: return !isSamePackage(packageNameString, parentSummary)
367: && !isSamePackage("java.lang", parentSummary);
368: }
369:
370: /**
371: * Gets the package summary
372: *
373: *@param base The type whose package was are concerned about
374: *@return the package summary
375: */
376: private PackageSummary getPackageSummary(TypeSummary base) {
377: return GetPackageSummary.query(base);
378: }
379:
380: /**
381: * Gets the SameParent attribute of the AddAbstractParent object
382: *
383: *@param one Description of Parameter
384: *@param two Description of Parameter
385: *@return The SameParent value
386: */
387: private boolean isSameParent(TypeSummary one, TypeSummary two) {
388: if (isObject(one)) {
389: return isObject(two);
390: }
391:
392: if (isObject(two)) {
393: return false;
394: }
395:
396: return one.equals(two);
397: }
398:
399: /**
400: * Gets the Object attribute of the AddAbstractParent object
401: *
402: *@param item Description of Parameter
403: *@return The Object value
404: */
405: private boolean isObject(TypeSummary item) {
406: if (item == null) {
407: return true;
408: }
409:
410: if (item.getName().equals("Object")) {
411: return true;
412: }
413:
414: return false;
415: }
416:
417: /**
418: * Creates a file object pointing to the directory that this class should be
419: * created into
420: *
421: *@return the directory
422: */
423: private File getDirectory() {
424: return TopLevelDirectory.getPackageDirectory(typeSummary,
425: packageNameString);
426: }
427:
428: /**
429: * Adds the constructors
430: *
431: *@param parentType The feature to be added to the Constructors attribute
432: *@param root The feature to be added to the Constructors attribute
433: */
434: private void addConstructors(TypeSummary parentType, SimpleNode root) {
435: Iterator iter = parentType.getMethods();
436: if (iter != null) {
437: while (iter.hasNext()) {
438: MethodSummary next = (MethodSummary) iter.next();
439: if (next.isConstructor()) {
440: AddConstructor ac = new AddConstructor(next,
441: newClassName);
442: ac.update(root);
443: AddMethodTypeVisitor amtv = new AddMethodTypeVisitor(
444: false);
445: amtv.visit(next, root);
446: }
447: }
448: }
449: }
450:
451: /**
452: * Adds the methods
453: *
454: *@param type The feature to be added to the Methods attribute
455: *@param root The feature to be added to the Methods attribute
456: */
457: private void addMethods(TypeSummary type, SimpleNode root) {
458: AbstractMethodFinder finder = new AbstractMethodFinder(type);
459: LinkedList list = finder.getList();
460: Iterator iter = list.iterator();
461: while (iter.hasNext()) {
462: MethodSummary next = (MethodSummary) iter.next();
463: AddConcreteMethod ac = new AddConcreteMethod(next);
464: ac.update(root);
465: AddMethodTypeVisitor amtv = new AddMethodTypeVisitor(false);
466: amtv.visit(next, root);
467: }
468: }
469: }
|