| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.java.hints; |
| |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.IdentifierTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.ModifiersTree; |
| import com.sun.source.tree.NewClassTree; |
| import com.sun.source.tree.Scope; |
| import com.sun.source.tree.StatementTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.Tree.Kind; |
| import com.sun.source.tree.TypeParameterTree; |
| import com.sun.source.tree.VariableTree; |
| import com.sun.source.util.SourcePositions; |
| import com.sun.source.util.TreePath; |
| import com.sun.source.util.TreePathScanner; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.Modifier; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.ExecutableType; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.swing.JEditorPane; |
| import javax.swing.SwingUtilities; |
| import org.netbeans.api.java.source.ModificationResult; |
| import org.netbeans.api.java.source.Task; |
| import org.netbeans.api.java.source.CompilationInfo; |
| import org.netbeans.api.java.source.ElementUtilities.ElementAcceptor; |
| import org.netbeans.api.java.source.GeneratorUtilities; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.java.source.JavaSource.Phase; |
| import org.netbeans.api.java.source.TreeMaker; |
| import org.netbeans.api.java.source.TreePathHandle; |
| import org.netbeans.api.java.source.TreeUtilities; |
| import org.netbeans.api.java.source.WorkingCopy; |
| import org.netbeans.api.java.source.support.CaretAwareJavaSourceTaskFactory; |
| import org.netbeans.api.java.source.support.SelectionAwareJavaSourceTaskFactory; |
| import org.netbeans.modules.java.editor.rename.InstantRenamePerformer; |
| import org.netbeans.modules.java.hints.errors.Utilities; |
| import org.netbeans.modules.java.hints.spi.AbstractHint; |
| import org.netbeans.spi.editor.hints.ChangeInfo; |
| import org.netbeans.spi.editor.hints.ErrorDescription; |
| import org.netbeans.spi.editor.hints.ErrorDescriptionFactory; |
| import org.netbeans.spi.editor.hints.Fix; |
| import org.netbeans.spi.editor.hints.Severity; |
| import org.openide.cookies.EditorCookie; |
| import org.openide.filesystems.FileObject; |
| import org.openide.loaders.DataObject; |
| import org.openide.loaders.DataObjectNotFoundException; |
| import org.openide.util.Exceptions; |
| import org.openide.util.NbBundle; |
| import org.openide.util.Pair; |
| |
| /** |
| * |
| * @author Jan Lahoda |
| */ |
| public class ConvertAnonymousToInner extends AbstractHint { |
| |
| public ConvertAnonymousToInner() { |
| super(true, false, HintSeverity.CURRENT_LINE_WARNING); |
| } |
| |
| public Set<Kind> getTreeKinds() { |
| return EnumSet.of(Kind.NEW_CLASS); |
| } |
| |
| static Fix computeFix(CompilationInfo info, int selStart, int selEnd, boolean onlyHeader) { |
| TreePath tp = findNCT(info, info.getTreeUtilities().pathFor((selStart + selEnd + 1) / 2), selStart, selEnd, onlyHeader); |
| |
| if (tp == null) { |
| tp = findNCT(info, info.getTreeUtilities().pathFor((selStart + selEnd + 1) / 2 + 1), selStart, selEnd, onlyHeader); |
| } |
| |
| if (tp == null) { |
| return null; |
| } |
| |
| return new FixImpl(TreePathHandle.create(tp, info), info.getJavaSource(), info.getFileObject()); |
| } |
| |
| private static TreePath findNCT(CompilationInfo info, TreePath tp, int selStart, int selEnd, boolean onlyHeader) { |
| while (tp != null) { |
| if (tp.getLeaf().getKind() != Kind.NEW_CLASS) { |
| tp = tp.getParentPath(); |
| continue; |
| } |
| |
| NewClassTree nct = (NewClassTree) tp.getLeaf(); |
| |
| if (nct.getClassBody() == null) { |
| tp = tp.getParentPath(); |
| continue; |
| } |
| |
| if (selStart == selEnd) { |
| if (onlyHeader) { |
| long start = info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), nct.getClassBody()); |
| |
| if (selStart > start) { |
| return null; |
| } |
| } |
| |
| break; |
| } else { |
| long start = info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), nct); |
| long end = info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), nct); |
| |
| if (start == selStart && end == selEnd) { |
| break; |
| } |
| } |
| |
| tp = tp.getParentPath(); |
| } |
| |
| return tp; |
| } |
| |
| public List<ErrorDescription> run(CompilationInfo compilationInfo, TreePath treePath) { |
| int pos = CaretAwareJavaSourceTaskFactory.getLastPosition(compilationInfo.getFileObject()); |
| int[] selection = SelectionAwareJavaSourceTaskFactory.getLastSelection(compilationInfo.getFileObject()); |
| |
| if (selection == null) return null; |
| |
| Fix f = computeFix(compilationInfo, selection[0], selection[1], true); |
| |
| if (f == null) |
| return null; |
| |
| List<Fix> fixes = Collections.<Fix>singletonList(f); |
| String hintDescription = NbBundle.getMessage(ConvertAnonymousToInner.class, "HINT_ConvertAnonymousToInner"); |
| |
| return Collections.singletonList(ErrorDescriptionFactory.createErrorDescription(Severity.HINT, hintDescription, fixes, compilationInfo.getFileObject(), pos, pos)); |
| } |
| |
| public String getId() { |
| return ConvertAnonymousToInner.class.getName(); |
| } |
| |
| public String getDisplayName() { |
| return NbBundle.getMessage(ConvertAnonymousToInner.class, "DN_ConvertAnonymousToInner"); |
| } |
| |
| public String getDescription() { |
| return NbBundle.getMessage(ConvertAnonymousToInner.class, "DESC_ConvertAnonymousToInner"); |
| } |
| |
| private static class FixImpl implements Fix, Task<WorkingCopy> { |
| |
| private TreePathHandle tph; |
| private JavaSource js; |
| private FileObject file; |
| |
| public FixImpl(TreePathHandle tph, JavaSource js, FileObject file) { |
| this.tph = tph; |
| this.js = js; |
| this.file = file; |
| } |
| |
| public String getText() { |
| return NbBundle.getMessage(ConvertAnonymousToInner.class, "FIX_ConvertAnonymousToInner"); |
| } |
| |
| public ChangeInfo implement() throws IOException { |
| ModificationResult mr = js.runModificationTask(this); |
| |
| mr.commit(); |
| |
| final int[] newClassNameSpan = mr.getSpan(NEW_CLASS_TREE_TAG); |
| |
| if (newClassNameSpan != null) { |
| SwingUtilities.invokeLater(new Runnable() { |
| public void run() { |
| try { |
| EditorCookie cook = DataObject.find(file).getLookup().lookup(EditorCookie.class); |
| JEditorPane[] arr = cook.getOpenedPanes(); |
| if (arr == null) { |
| return; |
| } |
| arr[0].setCaretPosition((newClassNameSpan[0] + newClassNameSpan[1]) / 2); |
| InstantRenamePerformer.invokeInstantRename(arr[0]); |
| } catch (DataObjectNotFoundException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| }); |
| } |
| |
| return null; |
| } |
| |
| public void run(WorkingCopy parameter) throws Exception { |
| parameter.toPhase(Phase.RESOLVED); |
| TreePath tp = tph.resolve(parameter); |
| |
| convertAnonymousToInner(parameter, tp); |
| } |
| } |
| |
| private static final class DetectUsedVars extends TreePathScanner<Void, Set<VariableElement>> { |
| private CompilationInfo info; |
| private TreePath newClassToConvert; |
| |
| private DetectUsedVars(CompilationInfo info, TreePath newClassToConvert) { |
| this.info = info; |
| this.newClassToConvert = newClassToConvert; |
| } |
| |
| private static final Set<ElementKind> VARIABLES = EnumSet.of(ElementKind.EXCEPTION_PARAMETER, ElementKind.PARAMETER, ElementKind.LOCAL_VARIABLE); |
| |
| @Override |
| public Void visitIdentifier(IdentifierTree node, Set<VariableElement> p) { |
| Element el = info.getTrees().getElement(getCurrentPath()); |
| TreePath elPath = el != null ? info.getTrees().getPath(el) : null; |
| |
| if (el != null && elPath != null && VARIABLES.contains(el.getKind()) && !isParent(newClassToConvert, elPath)) { |
| p.add((VariableElement) el); |
| } |
| |
| return super.visitIdentifier(node, p); |
| } |
| |
| } |
| |
| private static final class DetectUseOfNonStaticMembers extends TreePathScanner<Boolean, Void> { |
| private CompilationInfo info; |
| private TreePath newClassToConvert; |
| |
| private DetectUseOfNonStaticMembers(CompilationInfo info, TreePath newClassToConvert) { |
| this.info = info; |
| this.newClassToConvert = newClassToConvert; |
| } |
| |
| @Override |
| public Boolean visitIdentifier(IdentifierTree node, Void p) { |
| Element el = info.getTrees().getElement(getCurrentPath()); |
| |
| if (el != null && (el.getKind().isField() || el.getKind() == ElementKind.METHOD) && !el.getModifiers().contains(Modifier.STATIC)) { |
| return true; |
| } |
| |
| return super.visitIdentifier(node, p); |
| } |
| |
| @Override |
| public Boolean reduce(Boolean r1, Boolean r2) { |
| return r1 == Boolean.TRUE || r2 == Boolean.TRUE; |
| } |
| |
| } |
| |
| private static boolean isParent(TreePath tp1, TreePath tp2) { |
| while (tp2 != null && tp1.getLeaf() != tp2.getLeaf()) { |
| tp2 = tp2.getParentPath(); |
| } |
| |
| if (tp2 == null) { |
| return false; |
| } |
| |
| return tp1.getLeaf() == tp2.getLeaf(); |
| } |
| |
| private static String generateName(CompilationInfo info, TreePath newClassToConvert, String prototype) { |
| Scope s = info.getTrees().getScope(newClassToConvert); |
| Integer extension = null; |
| |
| if (s == null) return prototype + "Impl"; //NOI18N |
| |
| while (true) { |
| String currentProposal = prototype + "Impl" + (extension == null ? "" : extension.toString()); //NOI18N |
| Scope currentScope = s; |
| boolean found = false; |
| |
| OUTER : while (currentScope != null) { |
| for (Element e : info.getElementUtilities().getLocalMembersAndVars(s, new ElementAcceptor() { |
| public boolean accept(Element e, TypeMirror type) { |
| return true; |
| } |
| })) { |
| if (e.getKind().isClass() || e.getKind().isInterface()) { |
| String sn = e.getSimpleName().toString(); |
| |
| if (currentProposal.equals(sn)) { |
| found = true; |
| break OUTER; |
| } |
| } |
| } |
| |
| currentScope = currentScope.getEnclosingScope(); |
| } |
| |
| if (!found) { |
| return currentProposal; |
| } |
| |
| extension = extension != null ? extension + 1 : 1; |
| } |
| } |
| |
| static void convertAnonymousToInner(WorkingCopy copy, TreePath newClassToConvert) { |
| TreeMaker make = copy.getTreeMaker(); |
| NewClassTree nct = (NewClassTree) newClassToConvert.getLeaf(); |
| |
| nct = GeneratorUtilities.get(copy).importComments(nct, newClassToConvert.getCompilationUnit()); |
| |
| Set<VariableElement> usedElementVariables = new LinkedHashSet<VariableElement>(); |
| |
| new DetectUsedVars(copy, newClassToConvert).scan(new TreePath(newClassToConvert, nct.getClassBody()), usedElementVariables); |
| boolean usesNonStaticMembers = new DetectUseOfNonStaticMembers(copy, newClassToConvert).scan(new TreePath(newClassToConvert, nct.getClassBody()), null) == Boolean.TRUE; |
| |
| TreePath tp = newClassToConvert; |
| TreePath parentPath = null; |
| while (tp != null && !TreeUtilities.CLASS_TREE_KINDS.contains(tp.getLeaf().getKind())) { |
| if (tp.getLeaf().getKind() == Tree.Kind.METHOD || tp.getLeaf().getKind() == Tree.Kind.BLOCK) { |
| parentPath = tp; |
| } |
| tp = tp.getParentPath(); |
| } |
| |
| ClassTree target = (ClassTree) tp.getLeaf(); |
| Element targetElement = copy.getTrees().getElement(tp); |
| |
| boolean isInAnonymousClass = false; |
| TreePath treePath = newClassToConvert.getParentPath(); |
| while (treePath != null) { |
| if (treePath.getLeaf().getKind() == Kind.NEW_CLASS) { |
| isInAnonymousClass = true; |
| break; |
| } |
| treePath = treePath.getParentPath(); |
| } |
| |
| TypeMirror superType = copy.getTrees().getTypeMirror(new TreePath(newClassToConvert, nct.getIdentifier())); |
| Element superTypeElement = copy.getTrees().getElement(new TreePath(newClassToConvert, nct.getIdentifier())); |
| |
| boolean isStaticContext = targetElement != null && superTypeElement != null; |
| Element currElement = superTypeElement; |
| if (isStaticContext) { |
| while (currElement != null && currElement.getEnclosingElement().getKind() != ElementKind.PACKAGE) { |
| if (!currElement.getModifiers().contains(Modifier.STATIC)) { |
| isStaticContext = false; |
| break; |
| } |
| |
| currElement = currElement.getEnclosingElement(); |
| } |
| } |
| |
| if (isStaticContext) { |
| while (targetElement != null && targetElement.getEnclosingElement().getKind() != ElementKind.PACKAGE) { |
| if (!targetElement.getModifiers().contains(Modifier.STATIC)) { |
| isStaticContext = false; |
| break; |
| } |
| targetElement = targetElement.getEnclosingElement(); |
| } |
| } |
| |
| Tree superTypeTree = make.Type(superType); |
| |
| Logger.getLogger(ConvertAnonymousToInner.class.getName()).log(Level.FINE, "usesNonStaticMembers = {0}", usesNonStaticMembers ); //NOI18N |
| |
| TreePath superConstructorCall = findSuperConstructorCall(copy, newClassToConvert); |
| |
| Element currentElement = copy.getTrees().getElement(newClassToConvert); |
| boolean errorConstructor = currentElement == null || currentElement.asType() == null || currentElement.asType().getKind() == TypeKind.ERROR; |
| boolean isEnclosedByStaticElem = false; |
| while (currentElement != null && currentElement.getEnclosingElement() != null) { |
| if (currentElement.getModifiers().contains(Modifier.STATIC)) { |
| isEnclosedByStaticElem = true; //enclosing method is static |
| break; |
| } |
| |
| currentElement = currentElement.getEnclosingElement(); |
| } |
| |
| Set<Modifier> modifset = null; |
| if (isInAnonymousClass) { |
| if ((isStaticContext && !usesNonStaticMembers) || isEnclosedByStaticElem) { |
| modifset = EnumSet.of(Modifier.STATIC); |
| } else { |
| modifset = EnumSet.noneOf(Modifier.class); |
| } |
| } else { |
| if ((isStaticContext && !usesNonStaticMembers) || isEnclosedByStaticElem) { |
| modifset = EnumSet.of(Modifier.PRIVATE, Modifier.STATIC); |
| } else { |
| modifset = EnumSet.of(Modifier.PRIVATE); |
| } |
| } |
| ModifiersTree classModifiers = make.Modifiers(modifset); |
| |
| List<Tree> members = new ArrayList<Tree>(); |
| List<VariableTree> constrArguments = new ArrayList<VariableTree>(); |
| List<StatementTree> constrBodyStatements = new ArrayList<StatementTree>(); |
| List<ExpressionTree> constrRealArguments = new ArrayList<ExpressionTree>(); |
| ModifiersTree emptyMods = make.Modifiers(EnumSet.noneOf(Modifier.class)); |
| List<ExpressionTree> nueSuperConstructorCallRealArguments = null; |
| |
| if (superConstructorCall != null && !errorConstructor) { |
| Element superConstructor = copy.getTrees().getElement(superConstructorCall); |
| |
| if (superConstructor != null && superConstructor.getKind() == ElementKind.CONSTRUCTOR) { |
| ExecutableElement ee = (ExecutableElement) superConstructor; |
| TypeMirror nctTypes = copy.getTrees().getTypeMirror(newClassToConvert); |
| |
| if (!Utilities.isValidType(nctTypes)) { |
| // issue #236082: try again, but strip the parent statement; must reattribute the part of the tree |
| TreePath skipPath = new TreePath(parentPath, newClassToConvert.getLeaf()); |
| copy.getTreeUtilities().attributeTree(newClassToConvert.getLeaf(), copy.getTrees().getScope(skipPath)); |
| nctTypes = copy.getTrees().getTypeMirror(skipPath); |
| } |
| |
| if (nctTypes.getKind() != TypeKind.DECLARED) { |
| StringBuilder debug = new StringBuilder(); |
| |
| debug.append(nctTypes.getKind()) |
| .append(":") |
| .append(nctTypes.toString()) |
| .append(":") |
| .append(newClassToConvert.getLeaf().toString()); |
| |
| SourcePositions sp = copy.getTrees().getSourcePositions(); |
| int s = (int) sp.getStartPosition(copy.getCompilationUnit(), newClassToConvert.getLeaf()); |
| int e = (int) sp.getEndPosition(copy.getCompilationUnit(), newClassToConvert.getLeaf()); |
| |
| if (e > s) { |
| debug.append(":"); |
| debug.append(copy.getText().substring(s, e)); |
| } |
| |
| assert false : debug.toString(); |
| } |
| |
| ExecutableType et = (ExecutableType) copy.getTypes().asMemberOf((DeclaredType) nctTypes, ee); |
| |
| if (!ee.getParameters().isEmpty()) { |
| nueSuperConstructorCallRealArguments = new LinkedList<ExpressionTree>(); |
| Iterator<? extends VariableElement> names = ee.getParameters().iterator(); |
| Iterator<? extends TypeMirror> types = et.getParameterTypes().iterator(); |
| |
| while (names.hasNext() && types.hasNext()) { |
| CharSequence name = names.next().getSimpleName(); |
| |
| constrArguments.add(make.Variable(emptyMods, name, make.Type(types.next()), null)); |
| nueSuperConstructorCallRealArguments.add(make.Identifier(name)); |
| } |
| } |
| } |
| } else if (errorConstructor) { |
| Pair<List<? extends TypeMirror>, List<String>> resolvedArguments = Utilities.resolveArguments(copy, newClassToConvert, nct.getArguments(), targetElement); |
| |
| if (resolvedArguments != null) { |
| nueSuperConstructorCallRealArguments = new LinkedList<ExpressionTree>(); |
| |
| Iterator<? extends TypeMirror> typeIt = resolvedArguments.first().iterator(); |
| Iterator<String> nameIt = resolvedArguments.second().iterator(); |
| |
| while (typeIt.hasNext() && nameIt.hasNext()) { |
| TypeMirror tm = typeIt.next(); |
| String argName = nameIt.next(); |
| |
| constrArguments.add(make.Variable(make.Modifiers(EnumSet.noneOf(Modifier.class)), argName, make.Type(tm), null)); |
| nueSuperConstructorCallRealArguments.add(make.Identifier(argName)); |
| } |
| } |
| } |
| |
| if (nueSuperConstructorCallRealArguments != null) { |
| constrBodyStatements.add(make.ExpressionStatement(make.MethodInvocation(Collections.<ExpressionTree>emptyList(), make.Identifier("super"), nueSuperConstructorCallRealArguments))); |
| } |
| |
| constrRealArguments.addAll(nct.getArguments()); |
| |
| ModifiersTree privateFinalMods = make.Modifiers(EnumSet.of(Modifier.PRIVATE, Modifier.FINAL)); |
| ModifiersTree emptyArgs = make.Modifiers(EnumSet.noneOf(Modifier.class)); |
| |
| for (VariableElement ve : usedElementVariables) { |
| members.add(make.Variable(privateFinalMods, ve.getSimpleName(), make.Type(ve.asType()), null)); |
| constrArguments.add(make.Variable(emptyArgs, ve.getSimpleName(), make.Type(ve.asType()), null)); |
| constrBodyStatements.add(make.ExpressionStatement(make.Assignment(make.MemberSelect(make.Identifier("this"), ve.getSimpleName()), make.Identifier(ve.getSimpleName())))); // NOI18N |
| constrRealArguments.add(make.Identifier(ve.getSimpleName())); |
| } |
| |
| List<Tree> oldMembers = new ArrayList<Tree>(nct.getClassBody().getMembers()); |
| |
| ModifiersTree constructorModifiers = make.Modifiers(EnumSet.of(Modifier.PUBLIC)); |
| |
| MethodTree constr = make.Method(constructorModifiers, "<init>", null, Collections.<TypeParameterTree>emptyList(), constrArguments, Collections.<ExpressionTree>emptyList(), make.Block(constrBodyStatements, false), null); // NOI18N |
| |
| members.add(constr); |
| members.addAll(oldMembers); |
| |
| String newClassName = generateName(copy, newClassToConvert, superTypeElement.getSimpleName().toString()); |
| |
| ClassTree clazz = make.Class(classModifiers, newClassName, Collections.<TypeParameterTree>emptyList(), superTypeElement.getKind().isClass() ? superTypeTree : null, superTypeElement.getKind().isClass() ? Collections.<Tree>emptyList() : Collections.<Tree>singletonList(superTypeTree), members); |
| |
| copy.rewrite(target, make.addClassMember(target, copy.getTreeMaker().asReplacementOf(clazz, nct))); |
| |
| IdentifierTree classNameTree = make.Identifier(newClassName); |
| NewClassTree nueNCT = make.NewClass(/*!!!*/null, Collections.<ExpressionTree>emptyList(), classNameTree, constrRealArguments, null); |
| |
| copy.rewrite(nct, nueNCT); |
| copy.tag(classNameTree, NEW_CLASS_TREE_TAG); |
| } |
| |
| public void cancel() { |
| } |
| |
| private static TreePath findSuperConstructorCall(final CompilationInfo info, TreePath nct) { |
| class FindSuperConstructorCall extends TreePathScanner<TreePath, Void> { |
| |
| private boolean stop; |
| |
| @Override |
| public TreePath scan(Tree tree, Void p) { |
| if (stop) return null; |
| return super.scan(tree, p); |
| } |
| |
| @Override |
| public TreePath visitMethodInvocation(MethodInvocationTree tree, Void v) { |
| if (false && info.getTreeUtilities().isSynthetic(getCurrentPath())) { |
| return null; |
| } |
| if (tree.getMethodSelect().getKind() == Kind.IDENTIFIER && "super".equals(((IdentifierTree) tree.getMethodSelect()).getName().toString())) { |
| stop = true; |
| return getCurrentPath(); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public TreePath reduce(TreePath first, TreePath second) { |
| if (first == null) { |
| return second; |
| } else { |
| return first; |
| } |
| } |
| |
| } |
| |
| return new FindSuperConstructorCall().scan(nct, null); |
| } |
| |
| private static final String NEW_CLASS_TREE_TAG = "new-class-tree-tag"; |
| } |