blob: 0bacbf9d3fcd631ee58a6ec3e7d50641eb32815e [file] [log] [blame]
/*
* 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.refactoring.java.plugins;
import org.netbeans.modules.refactoring.java.api.ReplaceConstructorWithFactoryRefactoring;
import com.sun.source.tree.*;
import com.sun.source.util.TreePath;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import org.netbeans.api.java.source.*;
import org.netbeans.modules.refactoring.api.Problem;
import org.netbeans.modules.refactoring.java.spi.JavaRefactoringPlugin;
import static org.netbeans.modules.refactoring.java.plugins.Bundle.*;
import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
import org.netbeans.spi.java.hints.support.TransformationSupport;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
/**
*
* @author lahvac
*/
@NbBundle.Messages({"ERR_ReplaceWrongType=Cannot Replace Constructor with Factory of this object. A constructor has to be selected.",
"ERR_ReplaceWrongInnerType=Cannot Replace Constructor with Factory in non-static inner class."})
public class ReplaceConstructorWithFactoryPlugin extends JavaRefactoringPlugin {
private final ReplaceConstructorWithFactoryRefactoring refactoring;
private final AtomicBoolean cancel = new AtomicBoolean();
private final TreePathHandle treePathHandle;
public ReplaceConstructorWithFactoryPlugin(ReplaceConstructorWithFactoryRefactoring replaceConstructorRefactoring) {
this.refactoring = replaceConstructorRefactoring;
treePathHandle = refactoring.getRefactoringSource().lookup(TreePathHandle.class);
}
@Override
protected Problem preCheck(CompilationController javac) throws IOException {
javac.toPhase(JavaSource.Phase.RESOLVED);
Element constr = treePathHandle.resolveElement(javac);
if(constr.getKind() != ElementKind.CONSTRUCTOR) {
return new Problem(true, ERR_ReplaceWrongType());
}
Element enclosingElement = constr.getEnclosingElement();
if(!enclosingElement.getModifiers().contains(Modifier.STATIC) && enclosingElement.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
return new Problem(true, ERR_ReplaceWrongInnerType());
}
return null;
}
@Override
public Problem checkParameters() {
return null;
}
@Override
public Problem fastCheckParameters() {
String factoryName = refactoring.getFactoryName();
if (factoryName == null || factoryName.length() == 0) {
// FIXME: I18N
return new Problem(true, "No factory method name specified.");
}
if (!SourceVersion.isIdentifier(factoryName)) {
// FIXME: I18N
return new Problem(true, factoryName + " is not an identifier.");
}
return null;
}
public final Problem prepare(RefactoringElementsBag refactoringElements) {
cancel.set(false);
// TreePathHandle tph = refactoring.getRefactoringSource().lookup(TreePathHandle.class);
// FileObject file = tph.getFileObject();
// ClassPath source = ClassPath.getClassPath(file, ClassPath.SOURCE);
// FileObject sourceRoot = source.findOwnerRoot(file);
final TreePathHandle constr = treePathHandle;
final String[] ruleCode = new String[1];
final String[] toCode = new String[1];
try {
ModificationResult mod = JavaSource.forFileObject(constr.getFileObject()).runModificationTask(new Task<WorkingCopy>() {
@Override
public void run(WorkingCopy parameter) throws Exception {
parameter.toPhase(JavaSource.Phase.RESOLVED);
TreePath constrPath = constr.resolve(parameter);
if (constrPath == null || constrPath.getLeaf().getKind() != Tree.Kind.METHOD) {
return;
}
MethodTree constructor = (MethodTree) constrPath.getLeaf();
TypeElement parent = (TypeElement) parameter.getTrees().getElement(constrPath.getParentPath());
TreeMaker make = parameter.getTreeMaker();
StringBuilder parameters = new StringBuilder();
StringBuilder constraints = new StringBuilder();
StringBuilder realParameters = new StringBuilder();
int count = 1;
for (VariableTree vt : constructor.getParameters()) {
if (count > 1) {
parameters.append(", ");
constraints.append(" && ");
realParameters.append(", ");
}
realParameters.append(vt.getName());
parameters.append("$").append(count);
constraints.append("$").append(count).append(" instanceof ").append(parameter.getTrees().getTypeMirror(new TreePath(new TreePath(constrPath, vt), vt.getType())));
count++;
}
EnumSet<Modifier> factoryMods = EnumSet.of(Modifier.STATIC);
factoryMods.addAll(constructor.getModifiers().getFlags());
ClassTree parentTree = (ClassTree) constrPath.getParentPath().getLeaf();
List<? extends TypeParameterTree> typeParameters = parentTree.getTypeParameters();
List<ExpressionTree> arguments = new LinkedList<ExpressionTree>();
for (VariableTree vt : constructor.getParameters()) {
arguments.add(make.Identifier(vt.getName()));
}
List<ExpressionTree> typeArguments = new LinkedList<ExpressionTree>();
for (TypeParameterTree vt : typeParameters) {
typeArguments.add(make.Identifier(vt.getName()));
}
ExpressionTree ident = make.QualIdent(parent);
if(!typeArguments.isEmpty()) {
ident = (ExpressionTree) make.ParameterizedType(ident, typeArguments);
}
BlockTree body = make.Block(Collections.singletonList(make.Return(make.NewClass(null, Collections.EMPTY_LIST, ident, arguments, null))), false);
MethodTree factory = make.Method(make.Modifiers(factoryMods), refactoring.getFactoryName(), ident, typeParameters, constructor.getParameters(), Collections.<ExpressionTree>emptyList(), body, null);
parameter.rewrite(constrPath.getParentPath().getLeaf(), GeneratorUtilities.get(parameter).insertClassMember(parentTree, factory));
EnumSet<Modifier> constructorMods = EnumSet.of(Modifier.PRIVATE);
parameter.rewrite(constructor.getModifiers(), make.Modifiers(constructorMods));
StringBuilder rule = new StringBuilder();
boolean hasTypeParams = !parent.getTypeParameters().isEmpty();
rule.append("new ").append(parent.getQualifiedName()).append(hasTypeParams? "<$modifiers$>(" : "(").append(parameters).append(")");
if (constraints.length() > 0) {
rule.append(" :: ").append(constraints);
}
// rule.append(" => ").append(parent.getQualifiedName()).append(".").append(replaceConstructorRefactoring.getFactoryName()).append("(").append(parameters).append(");;");
ruleCode[0] = rule.toString();
toCode[0] = parent.getQualifiedName() + (hasTypeParams?".<$modifiers$>":".") + refactoring.getFactoryName() + "(" + parameters + ")";
toCode[0]+=";;";
}
});
if (ruleCode[0] == null) {
return new Problem(true, ERR_ReplaceWrongType());
}
List<ModificationResult> results = new ArrayList<ModificationResult>();
results.add(mod);
results.addAll(TransformationSupport.create(ruleCode[0] + " => " + toCode[0]).setCancel(cancel).processAllProjects());
ReplaceConstructorWithBuilderPlugin.createAndAddElements(refactoring, refactoringElements, results);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
return null/*XXX*/;
}
@Override
public void cancelRequest() {
cancel.set(true);
}
@Override
protected JavaSource getJavaSource(Phase p) {
ClasspathInfo cpInfo = getClasspathInfo(refactoring);
return JavaSource.create(cpInfo, treePathHandle.getFileObject());
}
}