/*
* Copyright 2009-2016 the original author or authors.
*
* Licensed 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.codehaus.groovy.eclipse.refactoring.core.convert;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.eclipse.core.GroovyCore;
import org.codehaus.jdt.groovy.model.GroovyCompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
/**
* Common class to process the convert to method refactoring.
* Used by both the completion proposal and the refactor menu option.
*
* @author Geoff Denning
* @created Nov 15, 2011
*/
// FIXGWD: This class should be converted into a proper refactoring class which extends Refactoring.
public class ConvertToMethodRefactoring {
private final FieldNode targetField;
private IField field;
public ConvertToMethodRefactoring(GroovyCompilationUnit unit, int offset) {
this.targetField = getTargetField(unit, offset);
}
private FieldNode getTargetField(GroovyCompilationUnit unit, int offset) {
//if (unit.isOnBuildPath()) return null;
try {
FieldNode targetField = null;
IJavaElement maybeField = unit.getElementAt(offset);
boolean result = maybeField instanceof IField && ((IField) maybeField).getNameRange().getOffset() > 0;
if (result) {
field = (IField) maybeField;
// now check to see if the field is assigned to a closure
for (ClassNode clazz : unit.getModuleNode().getClasses()) {
if (clazz.getNameWithoutPackage().equals(maybeField.getParent().getElementName())) {
targetField = clazz.getDeclaredField(field.getElementName());
if (targetField != null && targetField.getInitialExpression() instanceof ClosureExpression) {
result = true;
break;
}
}
}
}
if (result) {
return targetField;
}
} catch (JavaModelException e) {
GroovyCore.logException("Oops", e);
}
return null;
}
public boolean isApplicable() {
return targetField != null && field != null && field.exists();
}
public void applyRefactoring(IDocument document) {
if (targetField != null) {
TextEdit thisEdit = findReplacement(document);
try {
if (thisEdit != null) {
thisEdit.apply(document);
}
} catch (Exception e) {
GroovyCore.logException("Oops.", e);
}
}
}
private TextEdit findReplacement(IDocument doc) {
try {
// find the opening parnn and the closing paren
int afterName = targetField.getNameEnd() + 1;
int openingBracket = findOpenBracket(doc, targetField, afterName);
int afterLastParam = findAfterLastParam(targetField); // -1 means that there are no params
int afterArrow = findAfterArrow(doc, afterLastParam); // -1 means that there are no params
return createEdit(doc, afterName, openingBracket, afterLastParam, afterArrow);
} catch (Exception e) {
GroovyCore.logException("Exception during convert to closure.", e);
return null;
}
}
private static int findOpenBracket(IDocument doc, FieldNode targetField, int afterName) throws BadLocationException {
int offset = afterName;
while (offset < doc.getLength() && doc.getChar(offset) != '{') {
offset++;
}
Parameter[] parameters = getClosureParameters(targetField);
if (parameters != null && parameters.length > 0) {
int firstParameterStart = parameters[0].getStart();
offset++;
// also eat up whitespace after the '{'
while (offset < firstParameterStart && Character.isWhitespace(doc.getChar(offset))) {
offset++;
}
offset--;
}
return offset;
}
/**
* @return the index after the last parameter, or -1 if no parameters
*/
private static int findAfterLastParam(FieldNode targetField) {
Parameter[] parameters = getClosureParameters(targetField);
if (parameters == null || parameters.length == 0) {
return -1;
} else {
Parameter lastParam = parameters[parameters.length - 1];
if (lastParam.getInitialExpression() != null) {
// hmmm...this is always null even when there are default values. Maybe doesn't work?
return lastParam.getInitialExpression().getEnd();
} else {
return lastParam.getNameEnd();
}
}
}
private static Parameter[] getClosureParameters(FieldNode targetField) {
ClosureExpression closure = (ClosureExpression) targetField.getInitialExpression();
Parameter[] parameters = closure.getParameters();
return parameters;
}
/**
* @param doc
* @param afterLastParam
* @return the value after the end of the arrow or -1 if no arguments
* @throws BadLocationException
*/
private static int findAfterArrow(IDocument doc, int afterLastParam) throws BadLocationException {
if (afterLastParam == -1) {
return -1;
}
int offset = afterLastParam;
while (offset < doc.getLength() - 1) {
if (doc.getChar(offset) == '-' && doc.getChar(offset+1) == '>') {
return ++offset;
}
offset++;
}
return -1;
}
private static TextEdit createEdit(IDocument doc, int afterName,
int openingBracket, int afterLastParam, int afterArrow) throws BadLocationException {
if (!(openingBracket < doc.getLength() && (doc.getChar(openingBracket) == '{') || Character.isWhitespace(doc.getChar(openingBracket)))) {
return null;
}
if (afterLastParam > -1 && afterArrow == -1) {
// couldn't find the arrow even though there were parameters, somnething
// strange has happened
return null;
}
TextEdit edit;
if (afterLastParam > -1) {
edit = new MultiTextEdit();
edit.addChild(new ReplaceEdit(afterName, openingBracket-afterName+1, "("));
edit.addChild(new ReplaceEdit(afterLastParam, afterArrow-afterLastParam+1, ") {"));
} else {
edit = new ReplaceEdit(afterName, openingBracket-afterName+1, "() {");
}
return edit;
}
}