001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.refactoring.java.plugins;
042:
043: import com.sun.source.tree.BlockTree;
044: import com.sun.source.tree.ClassTree;
045: import com.sun.source.tree.ExpressionTree;
046: import com.sun.source.tree.MethodTree;
047: import com.sun.source.tree.Tree;
048: import com.sun.source.tree.TypeParameterTree;
049: import com.sun.source.tree.VariableTree;
050: import com.sun.source.util.TreePath;
051: import java.io.IOException;
052: import java.net.URL;
053: import java.util.ArrayList;
054: import java.util.Collections;
055: import java.util.HashSet;
056: import java.util.Iterator;
057: import java.util.List;
058: import java.util.Set;
059: import javax.lang.model.element.Element;
060: import javax.lang.model.element.ElementKind;
061: import javax.lang.model.element.ExecutableElement;
062: import javax.lang.model.element.Modifier;
063: import javax.lang.model.element.PackageElement;
064: import javax.lang.model.element.TypeElement;
065: import javax.lang.model.element.TypeParameterElement;
066: import javax.lang.model.element.VariableElement;
067: import javax.lang.model.type.TypeMirror;
068: import javax.lang.model.util.Types;
069: import org.netbeans.api.java.source.CancellableTask;
070: import org.netbeans.api.java.source.CompilationController;
071: import org.netbeans.api.java.source.CompilationInfo;
072: import org.netbeans.api.java.source.ElementHandle;
073: import org.netbeans.api.java.source.GeneratorUtilities;
074: import org.netbeans.api.java.source.JavaSource;
075: import org.netbeans.api.java.source.ModificationResult;
076: import org.netbeans.api.java.source.TreeMaker;
077: import org.netbeans.api.java.source.TreePathHandle;
078: import org.netbeans.api.java.source.TypeMirrorHandle;
079: import org.netbeans.api.java.source.WorkingCopy;
080: import org.netbeans.modules.refactoring.api.AbstractRefactoring;
081: import org.netbeans.modules.refactoring.api.Problem;
082: import org.netbeans.modules.refactoring.java.spi.DiffElement;
083: import org.netbeans.modules.refactoring.java.RetoucheUtils;
084: import org.netbeans.modules.refactoring.java.api.ExtractInterfaceRefactoring;
085: import org.netbeans.modules.refactoring.java.spi.JavaRefactoringPlugin;
086: import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
087: import org.netbeans.modules.refactoring.spi.SimpleRefactoringElementImplementation;
088: import org.openide.filesystems.FileObject;
089: import org.openide.filesystems.Repository;
090: import org.openide.filesystems.URLMapper;
091: import org.openide.loaders.DataFolder;
092: import org.openide.loaders.DataObject;
093: import org.openide.loaders.DataObjectNotFoundException;
094: import org.openide.text.PositionBounds;
095: import org.openide.util.Exceptions;
096: import org.openide.util.Lookup;
097: import org.openide.util.NbBundle;
098: import org.openide.util.Utilities;
099: import org.openide.util.lookup.Lookups;
100:
101: /**
102: * Plugin that implements the core functionality of Extract Interface refactoring.
103: * <br>Extracts: <ul>
104: * <li>implements interfaces</li>
105: * <li>public nonstatic methods</li>
106: * <li>public static final fields</li>
107: * <li>XXX public static class/interface/enum/annotation type.<br><i>dangerous, it might contain
108: * elements that will be unaccessible from the new interface. Maybe reusing Move Class refactoring
109: * would be appropriate. Not implemented in 6.0 yet. Pre-6.0 implementation was not solved references at all.</i></li>
110: * </ul>
111: * XXX there should be option Copy/Move/AsIs javadoc.
112: *
113: * @author Martin Matula, Jan Pokorsky
114: */
115: public final class ExtractInterfaceRefactoringPlugin extends
116: JavaRefactoringPlugin {
117:
118: /** Reference to the parent refactoring instance */
119: private final ExtractInterfaceRefactoring refactoring;
120:
121: private String pkgName;
122:
123: /** class for extracting interface */
124: private ElementHandle<TypeElement> classHandle;
125:
126: /** Creates a new instance of ExtractInterfaceRefactoringPlugin
127: * @param refactoring Parent refactoring instance.
128: */
129: ExtractInterfaceRefactoringPlugin(
130: ExtractInterfaceRefactoring refactoring) {
131: this .refactoring = refactoring;
132: }
133:
134: @Override
135: public Problem fastCheckParameters() {
136: Problem result = null;
137:
138: String newName = refactoring.getInterfaceName();
139:
140: if (!Utilities.isJavaIdentifier(newName)) {
141: result = createProblem(result, true, NbBundle.getMessage(
142: ExtractInterfaceRefactoringPlugin.class,
143: "ERR_InvalidIdentifier", newName)); // NOI18N
144: return result;
145: }
146:
147: FileObject primFile = refactoring.getSourceType()
148: .getFileObject();
149: FileObject folder = primFile.getParent();
150: FileObject[] children = folder.getChildren();
151: for (FileObject child : children) {
152: if (!child.isVirtual() && child.getName().equals(newName)
153: && "java".equals(child.getExt())) { // NOI18N
154: result = createProblem(
155: result,
156: true,
157: NbBundle
158: .getMessage(
159: ExtractInterfaceRefactoringPlugin.class,
160: "ERR_ClassClash", newName,
161: pkgName)); // NOI18N
162: return result;
163: }
164: }
165:
166: return null;
167: }
168:
169: public Problem prepare(RefactoringElementsBag bag) {
170: FileObject primFile = refactoring.getSourceType()
171: .getFileObject();
172: try {
173: // create interface file
174: bag.add(refactoring, new CreateInterfaceElement(
175: refactoring, primFile.getParent(), classHandle));
176: UpdateClassTask.create(bag, primFile, refactoring,
177: classHandle);
178: } catch (IOException ex) {
179: throw (RuntimeException) new RuntimeException()
180: .initCause(ex);
181: }
182: return null;
183: }
184:
185: protected JavaSource getJavaSource(Phase p) {
186: return JavaSource.forFileObject(refactoring.getSourceType()
187: .getFileObject());
188: }
189:
190: @Override
191: protected Problem preCheck(CompilationController javac)
192: throws IOException {
193: // fire operation start on the registered progress listeners (1 step)
194: fireProgressListenerStart(AbstractRefactoring.PRE_CHECK, 1);
195: javac.toPhase(JavaSource.Phase.RESOLVED);
196: try {
197: TreePathHandle sourceType = refactoring.getSourceType();
198:
199: // check whether the element is valid
200: Problem result = isElementAvail(sourceType, javac);
201: if (result != null) {
202: // fatal error -> don't continue with further checks
203: return result;
204: }
205: if (!RetoucheUtils.isElementInOpenProject(sourceType
206: .getFileObject())) {
207: return new Problem(true, NbBundle.getMessage(
208: ExtractInterfaceRefactoringPlugin.class,
209: "ERR_ProjectNotOpened")); // NOI18N
210: }
211:
212: // check whether the element is an unresolved class
213: Element sourceElm = sourceType.resolveElement(javac);
214: if (sourceElm == null
215: || (sourceElm.getKind() != ElementKind.CLASS
216: && sourceElm.getKind() != ElementKind.INTERFACE && sourceElm
217: .getKind() != ElementKind.ENUM)) {
218: // fatal error -> return
219: return new Problem(true, NbBundle.getMessage(
220: ExtractInterfaceRefactoringPlugin.class,
221: "ERR_ElementNotAvailable")); // NOI18N
222: }
223:
224: classHandle = ElementHandle
225: .<TypeElement> create((TypeElement) sourceElm);
226:
227: PackageElement pkgElm = (PackageElement) javac
228: .getElementUtilities().outermostTypeElement(
229: sourceElm).getEnclosingElement();
230: pkgName = pkgElm.getQualifiedName().toString();
231:
232: // increase progress (step 1)
233: fireProgressListenerStep();
234:
235: // all checks passed -> return null
236: return null;
237: } finally {
238: // fire operation end on the registered progress listeners
239: fireProgressListenerStop();
240: }
241: }
242:
243: @Override
244: protected Problem checkParameters(CompilationController javac)
245: throws IOException {
246: if (refactoring.getMethods().isEmpty()
247: && refactoring.getFields().isEmpty()
248: && refactoring.getImplements().isEmpty()) {
249: return new Problem(true, NbBundle.getMessage(
250: ExtractInterfaceRefactoringPlugin.class,
251: "ERR_ExtractInterface_MembersNotAvailable")); // NOI18N);
252: }
253: // check whether the selected members are public and non-static in case of methods, static in other cases
254: // check whether all members belong to the source type
255: // XXX check if method params and return type will be accessible after extraction; likely not fatal
256: javac.toPhase(JavaSource.Phase.RESOLVED);
257:
258: TypeElement sourceType = (TypeElement) refactoring
259: .getSourceType().resolveElement(javac);
260: assert sourceType != null;
261:
262: Set<? extends Element> members = new HashSet<Element>(
263: sourceType.getEnclosedElements());
264:
265: for (ElementHandle<ExecutableElement> elementHandle : refactoring
266: .getMethods()) {
267: ExecutableElement elm = elementHandle.resolve(javac);
268: if (elm == null) {
269: return new Problem(true, NbBundle.getMessage(
270: ExtractInterfaceRefactoringPlugin.class,
271: "ERR_ElementNotAvailable")); // NOI18N
272: }
273: if (javac.getElementUtilities().isSynthetic(elm)
274: || elm.getKind() != ElementKind.METHOD) {
275: return new Problem(true, NbBundle.getMessage(
276: ExtractInterfaceRefactoringPlugin.class,
277: "ERR_ExtractInterface_UnknownMember", // NOI18N
278: elm.toString()));
279: }
280: if (!members.contains(elm)) {
281: return new Problem(true, NbBundle.getMessage(
282: ExtractInterfaceRefactoringPlugin.class,
283: "ERR_ExtractInterface_UnknownMember", // NOI18N
284: elm.toString()));
285: }
286: Set<Modifier> mods = elm.getModifiers();
287: if (!mods.contains(Modifier.PUBLIC)
288: || mods.contains(Modifier.STATIC)) {
289: return new Problem(true, NbBundle.getMessage(
290: ExtractInterfaceRefactoringPlugin.class,
291: "ERR_ExtractInterface_WrongModifiers", elm
292: .getSimpleName().toString())); // NOI18N
293: }
294: }
295:
296: for (ElementHandle<VariableElement> elementHandle : refactoring
297: .getFields()) {
298: VariableElement elm = elementHandle.resolve(javac);
299: if (elm == null) {
300: return new Problem(true, NbBundle.getMessage(
301: ExtractInterfaceRefactoringPlugin.class,
302: "ERR_ElementNotAvailable")); // NOI18N
303: }
304: if (javac.getElementUtilities().isSynthetic(elm)
305: || elm.getKind() != ElementKind.FIELD) {
306: return new Problem(true, NbBundle.getMessage(
307: ExtractInterfaceRefactoringPlugin.class,
308: "ERR_ExtractInterface_UnknownMember", // NOI18N
309: elm.toString()));
310: }
311: if (!members.contains(elm)) {
312: return new Problem(true, NbBundle.getMessage(
313: ExtractInterfaceRefactoringPlugin.class,
314: "ERR_ExtractInterface_UnknownMember", // NOI18N
315: elm.toString()));
316: }
317: Set<Modifier> mods = elm.getModifiers();
318: if (mods.contains(Modifier.PUBLIC)
319: && mods.contains(Modifier.STATIC)
320: && mods.contains(Modifier.FINAL)) {
321: VariableTree tree = (VariableTree) javac.getTrees()
322: .getTree(elm);
323: if (tree.getInitializer() != null) {
324: continue;
325: }
326: }
327: return new Problem(true, NbBundle.getMessage(
328: ExtractInterfaceRefactoringPlugin.class,
329: "ERR_ExtractInterface_WrongModifiers", elm
330: .getSimpleName().toString())); // NOI18N
331: }
332:
333: // XXX check refactoring.getImplements()
334:
335: return null;
336: }
337:
338: /**
339: * Finds all type parameters of <code>javaClass</code> that are referenced by
340: * any member that is going to be extract.
341: * @param refactoring the refactoring containing members to extract
342: * @param javac compilation info
343: * @param javaClass java class declaring parameters to find
344: * @return type parameters to extract
345: */
346: private static List<TypeMirror> findUsedGenericTypes(
347: ExtractInterfaceRefactoring refactoring,
348: CompilationInfo javac, TypeElement javaClass) {
349: List<TypeMirror> typeArgs = RetoucheUtils
350: .resolveTypeParamsAsTypes(javaClass.getTypeParameters());
351: if (typeArgs.isEmpty())
352: return typeArgs;
353:
354: Types typeUtils = javac.getTypes();
355: List<TypeMirror> result = new ArrayList<TypeMirror>(typeArgs
356: .size());
357:
358: // do not check fields since static fields cannot use type parameter of the enclosing class
359:
360: // check methods
361: for (Iterator<ElementHandle<ExecutableElement>> methodIter = refactoring
362: .getMethods().iterator(); methodIter.hasNext()
363: && !typeArgs.isEmpty();) {
364: ElementHandle<ExecutableElement> handle = methodIter.next();
365: ExecutableElement elm = handle.resolve(javac);
366:
367: RetoucheUtils.findUsedGenericTypes(typeUtils, typeArgs,
368: result, elm.getReturnType());
369:
370: for (Iterator<? extends VariableElement> paramIter = elm
371: .getParameters().iterator(); paramIter.hasNext()
372: && !typeArgs.isEmpty();) {
373: VariableElement param = paramIter.next();
374: RetoucheUtils.findUsedGenericTypes(typeUtils, typeArgs,
375: result, param.asType());
376: }
377: }
378:
379: // check implements
380: for (Iterator<TypeMirrorHandle<TypeMirror>> it = refactoring
381: .getImplements().iterator(); it.hasNext()
382: && !typeArgs.isEmpty();) {
383: TypeMirrorHandle<TypeMirror> handle = it.next();
384: TypeMirror implemetz = handle.resolve(javac);
385: RetoucheUtils.findUsedGenericTypes(typeUtils, typeArgs,
386: result, implemetz);
387: }
388:
389: return result;
390: }
391:
392: // --- REFACTORING ELEMENTS ------------------------------------------------
393:
394: /**
395: * creates new file with empty interface and adds type params if necessary
396: */
397: private static final class CreateInterfaceElement extends
398: SimpleRefactoringElementImplementation implements
399: CancellableTask<WorkingCopy> {
400: private final URL folderURL;
401: private URL ifcURL;
402: private final String ifcName;
403: private final ExtractInterfaceRefactoring refactoring;
404: private final ElementHandle<TypeElement> sourceType;
405:
406: private CreateInterfaceElement(
407: ExtractInterfaceRefactoring refactoring,
408: FileObject folder, ElementHandle<TypeElement> sourceType) {
409: this .refactoring = refactoring;
410: this .folderURL = URLMapper.findURL(folder,
411: URLMapper.INTERNAL);
412: this .ifcName = refactoring.getInterfaceName();
413: this .sourceType = sourceType;
414: }
415:
416: // --- SimpleRefactoringElementImpl methods ----------------------------------
417:
418: public void performChange() {
419: try {
420: FileObject folderFO = URLMapper
421: .findFileObject(folderURL);
422: if (folderFO == null)
423: return;
424:
425: // create new file
426:
427: // XXX not nice; user might modify the template to something entirely different from the interface
428: FileObject tempFO = Repository.getDefault()
429: .getDefaultFileSystem().findResource(
430: "Templates/Classes/Interface.java"); // NOI18N
431:
432: DataFolder folder = (DataFolder) DataObject
433: .find(folderFO);
434: DataObject template = DataObject.find(tempFO);
435: DataObject newIfcDO = template.createFromTemplate(
436: folder, ifcName);
437: this .ifcURL = URLMapper.findURL(newIfcDO
438: .getPrimaryFile(), URLMapper.INTERNAL);
439: refactoring.getContext().add(newIfcDO.getPrimaryFile());
440:
441: // add type params and members
442: JavaSource js = JavaSource.forFileObject(newIfcDO
443: .getPrimaryFile());
444: js.runModificationTask(this ).commit();
445: } catch (DataObjectNotFoundException ex) {
446: Exceptions.printStackTrace(ex);
447: } catch (IOException ex) {
448: Exceptions.printStackTrace(ex);
449: }
450: }
451:
452: @Override
453: public void undoChange() {
454: FileObject ifcFO = null;
455: if (ifcURL != null) {
456: ifcFO = URLMapper.findFileObject(ifcURL);
457: }
458: if (ifcFO != null) {
459: try {
460: ifcFO.delete();
461: } catch (IOException ex) {
462: Exceptions.printStackTrace(ex);
463: }
464: }
465: }
466:
467: public String getText() {
468: return NbBundle.getMessage(
469: ExtractInterfaceRefactoringPlugin.class,
470: "TXT_ExtractInterface_CreateIfc", ifcName); // NOI18N
471: }
472:
473: public String getDisplayText() {
474: return getText();
475: }
476:
477: public FileObject getParentFile() {
478: return URLMapper.findFileObject(folderURL);
479: }
480:
481: public PositionBounds getPosition() {
482: return null;
483: }
484:
485: public Lookup getLookup() {
486: FileObject fo = ifcURL == null ? null : URLMapper
487: .findFileObject(ifcURL);
488: return fo != null ? Lookups.singleton(fo) : Lookup.EMPTY;
489: }
490:
491: // --- CancellableTask methods ----------------------------------
492:
493: public void cancel() {
494:
495: }
496:
497: public void run(WorkingCopy wc) throws Exception {
498: wc.toPhase(JavaSource.Phase.RESOLVED);
499: ClassTree interfaceTree = findInterface(wc, ifcName);
500: TreeMaker make = wc.getTreeMaker();
501: GeneratorUtilities genUtils = GeneratorUtilities.get(wc);
502:
503: // add type parameters
504: List<TypeMirror> typeParams = findUsedGenericTypes(
505: refactoring, wc, sourceType.resolve(wc));
506: List<TypeParameterTree> newTypeParams = new ArrayList<TypeParameterTree>(
507: typeParams.size());
508: // lets retrieve param type trees from origin class since it is
509: // almost impossible to create them via TreeMaker
510: TypeElement sourceTypeElm = sourceType.resolve(wc);
511: for (TypeParameterElement typeParam : sourceTypeElm
512: .getTypeParameters()) {
513: TypeMirror origParam = typeParam.asType();
514: for (TypeMirror newParam : typeParams) {
515: if (wc.getTypes().isSameType(origParam, newParam)) {
516: Tree t = wc.getTrees().getTree(typeParam);
517: if (t.getKind() == Tree.Kind.TYPE_PARAMETER) {
518: newTypeParams.add((TypeParameterTree) t);
519: }
520: }
521: }
522:
523: }
524:
525: // add new fields
526: List<Tree> members = new ArrayList<Tree>();
527: for (ElementHandle<VariableElement> handle : refactoring
528: .getFields()) {
529: VariableElement memberElm = handle.resolve(wc);
530: VariableTree tree = (VariableTree) wc.getTrees()
531: .getTree(memberElm);
532: VariableTree newVarTree = make.Variable(make.Modifiers(
533: Collections.<Modifier> emptySet(), tree
534: .getModifiers().getAnnotations()), tree
535: .getName(), tree.getType(), tree
536: .getInitializer());
537: newVarTree = genUtils.importFQNs(newVarTree);
538: RetoucheUtils.copyJavadoc(memberElm, newVarTree, wc);
539: members.add(newVarTree);
540: }
541: // add newmethods
542: for (ElementHandle<ExecutableElement> handle : refactoring
543: .getMethods()) {
544: ExecutableElement memberElm = handle.resolve(wc);
545: MethodTree tree = wc.getTrees().getTree(memberElm);
546: MethodTree newMethodTree = make.Method(make.Modifiers(
547: Collections.<Modifier> emptySet(), tree
548: .getModifiers().getAnnotations()), tree
549: .getName(), tree.getReturnType(), tree
550: .getTypeParameters(), tree.getParameters(),
551: tree.getThrows(), (BlockTree) null, null);
552: newMethodTree = genUtils.importFQNs(newMethodTree);
553: RetoucheUtils.copyJavadoc(memberElm, newMethodTree, wc);
554: members.add(newMethodTree);
555: }
556: // add super interfaces
557: List<Tree> extendsList = new ArrayList<Tree>();
558: extendsList.addAll(interfaceTree.getImplementsClause());
559: for (TypeMirrorHandle<? extends TypeMirror> handle : refactoring
560: .getImplements()) {
561: // XXX check if interface is not aready there; the templates might be changed by user :-(
562: TypeMirror implMirror = handle.resolve(wc);
563: extendsList.add(make.Type(implMirror));
564: }
565: // create new interface
566: ClassTree newInterfaceTree = make.Interface(interfaceTree
567: .getModifiers(), interfaceTree.getSimpleName(),
568: newTypeParams, extendsList, Collections
569: .<Tree> emptyList());
570:
571: newInterfaceTree = genUtils.insertClassMembers(
572: newInterfaceTree, members);
573: wc.rewrite(interfaceTree, newInterfaceTree);
574: }
575:
576: // --- helper methods ----------------------------------
577:
578: private ClassTree findInterface(CompilationInfo javac,
579: String name) {
580: for (Tree tree : javac.getCompilationUnit().getTypeDecls()) {
581: if (Tree.Kind.CLASS == tree.getKind()
582: && javac.getTreeUtilities().isInterface(
583: (ClassTree) tree)
584: && name.contentEquals(((ClassTree) tree)
585: .getSimpleName())) {
586: return (ClassTree) tree;
587: }
588: }
589: throw new IllegalStateException(
590: "wrong template, cannot find the interface in "
591: + javac.getFileObject()); // NOI18N
592: }
593: }
594:
595: private final static class UpdateClassTask implements
596: CancellableTask<WorkingCopy> {
597: private final ExtractInterfaceRefactoring refactoring;
598: private final ElementHandle<TypeElement> sourceType;
599:
600: private UpdateClassTask(
601: ExtractInterfaceRefactoring refactoring,
602: ElementHandle<TypeElement> sourceType) {
603: this .sourceType = sourceType;
604: this .refactoring = refactoring;
605: }
606:
607: public static void create(RefactoringElementsBag bag,
608: FileObject fo, ExtractInterfaceRefactoring refactoring,
609: ElementHandle<TypeElement> sourceType)
610: throws IOException {
611: JavaSource js = JavaSource.forFileObject(fo);
612: ModificationResult modification = js
613: .runModificationTask(new UpdateClassTask(
614: refactoring, sourceType));
615: List<? extends ModificationResult.Difference> diffs = modification
616: .getDifferences(fo);
617: for (ModificationResult.Difference diff : diffs) {
618: bag.add(refactoring, DiffElement.create(diff, fo,
619: modification));
620: }
621: bag.registerTransaction(new RetoucheCommit(Collections
622: .singletonList(modification)));
623: }
624:
625: public void cancel() {
626: }
627:
628: public void run(WorkingCopy wc) throws Exception {
629: wc.toPhase(JavaSource.Phase.RESOLVED);
630: TypeElement clazz = this .sourceType.resolve(wc);
631: assert clazz != null;
632: ClassTree classTree = wc.getTrees().getTree(clazz);
633: TreeMaker maker = wc.getTreeMaker();
634: // fake interface since interface file does not exist yet
635: Tree interfaceTree;
636: List<TypeMirror> typeParams = findUsedGenericTypes(
637: refactoring, wc, clazz);
638: if (typeParams.isEmpty()) {
639: interfaceTree = maker.Identifier(refactoring
640: .getInterfaceName());
641: } else {
642: List<ExpressionTree> typeParamTrees = new ArrayList<ExpressionTree>(
643: typeParams.size());
644: for (TypeMirror typeParam : typeParams) {
645: Tree t = maker.Type(typeParam);
646: typeParamTrees.add((ExpressionTree) t);
647: }
648: interfaceTree = maker.ParameterizedType(maker
649: .Identifier(refactoring.getInterfaceName()),
650: typeParamTrees);
651: }
652:
653: Set<Tree> members2Remove = new HashSet<Tree>();
654: Set<Tree> interfaces2Remove = new HashSet<Tree>();
655:
656: members2Remove.addAll(getFields2Remove(wc, refactoring
657: .getFields()));
658: members2Remove.addAll(getMethods2Remove(wc, refactoring
659: .getMethods(), clazz));
660: interfaces2Remove.addAll(getImplements2Remove(wc,
661: refactoring.getImplements(), clazz));
662:
663: // filter out obsolete members
664: List<Tree> members2Add = new ArrayList<Tree>();
665: for (Tree tree : classTree.getMembers()) {
666: if (!members2Remove.contains(tree)) {
667: members2Add.add(tree);
668: }
669: }
670: // filter out obsolete implements trees
671: List<Tree> impls2Add = resolveImplements(classTree
672: .getImplementsClause(), interfaces2Remove,
673: interfaceTree);
674:
675: ClassTree nc;
676: if (clazz.getKind() == ElementKind.CLASS) {
677: nc = maker.Class(classTree.getModifiers(), classTree
678: .getSimpleName(),
679: classTree.getTypeParameters(), classTree
680: .getExtendsClause(), impls2Add,
681: members2Add);
682: } else if (clazz.getKind() == ElementKind.INTERFACE) {
683: nc = maker.Interface(classTree.getModifiers(),
684: classTree.getSimpleName(), classTree
685: .getTypeParameters(), impls2Add,
686: members2Add);
687: } else if (clazz.getKind() == ElementKind.ENUM) {
688: nc = maker.Enum(classTree.getModifiers(), classTree
689: .getSimpleName(), impls2Add, members2Add);
690: } else {
691: throw new IllegalStateException(classTree.toString());
692: }
693:
694: wc.rewrite(classTree, nc);
695: }
696:
697: private List<Tree> getFields2Remove(CompilationInfo javac,
698: List<ElementHandle<VariableElement>> members) {
699: if (members.isEmpty()) {
700: return Collections.<Tree> emptyList();
701: }
702: List<Tree> result = new ArrayList<Tree>(members.size());
703: for (ElementHandle<VariableElement> handle : members) {
704: VariableElement elm = handle.resolve(javac);
705: assert elm != null;
706: Tree t = javac.getTrees().getTree(elm);
707: assert t != null;
708: result.add(t);
709: }
710:
711: return result;
712: }
713:
714: private List<Tree> getMethods2Remove(CompilationInfo javac,
715: List<ElementHandle<ExecutableElement>> members,
716: TypeElement clazz) {
717: if (members.isEmpty()) {
718: return Collections.<Tree> emptyList();
719: }
720: boolean isInterface = clazz.getKind() == ElementKind.INTERFACE;
721: List<Tree> result = new ArrayList<Tree>(members.size());
722: for (ElementHandle<ExecutableElement> handle : members) {
723: ExecutableElement elm = handle.resolve(javac);
724: assert elm != null;
725:
726: if (isInterface
727: || elm.getModifiers().contains(
728: Modifier.ABSTRACT)) {
729: // it is interface method nor abstract method
730: Tree t = javac.getTrees().getTree(elm);
731: assert t != null;
732: result.add(t);
733: }
734: }
735:
736: return result;
737: }
738:
739: private List<Tree> getImplements2Remove(CompilationInfo javac,
740: List<TypeMirrorHandle<TypeMirror>> members,
741: TypeElement clazz) {
742: if (members.isEmpty()) {
743: return Collections.<Tree> emptyList();
744: }
745:
746: // resolve members to remove
747: List<TypeMirror> memberTypes = new ArrayList<TypeMirror>(
748: members.size());
749: for (TypeMirrorHandle<TypeMirror> handle : members) {
750: TypeMirror tm = handle.resolve(javac);
751: memberTypes.add(tm);
752: }
753:
754: ClassTree classTree = javac.getTrees().getTree(clazz);
755: List<Tree> result = new ArrayList<Tree>();
756: Types types = javac.getTypes();
757:
758: // map TypeMirror to Tree
759: for (Tree tree : classTree.getImplementsClause()) {
760: TreePath path = javac.getTrees().getPath(
761: javac.getCompilationUnit(), tree);
762: TypeMirror existingTM = javac.getTrees().getTypeMirror(
763: path);
764:
765: for (TypeMirror tm : memberTypes) {
766: if (types.isSameType(tm, existingTM)) {
767: result.add(tree);
768: break;
769: }
770: }
771: }
772:
773: return result;
774: }
775:
776: private static List<Tree> resolveImplements(
777: List<? extends Tree> allImpls, Set<Tree> impls2Remove,
778: Tree impl2Add) {
779: List<Tree> ret;
780: if (allImpls == null) {
781: ret = new ArrayList<Tree>(1);
782: } else {
783: ret = new ArrayList<Tree>(allImpls.size() + 1);
784: ret.addAll(allImpls);
785: }
786:
787: if (impls2Remove != null && !impls2Remove.isEmpty()) {
788: ret.removeAll(impls2Remove);
789: }
790: ret.add(impl2Add);
791: return ret;
792: }
793: }
794:
795: }
|