/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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.jetbrains.kotlin.idea.codeInsight.surroundWith;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiUtilCore;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.VariableDescriptor;
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils;
import org.jetbrains.kotlin.idea.core.ShortenReferences;
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.KotlinTypeKt;
import java.util.ArrayList;
import java.util.List;
public class MoveDeclarationsOutHelper {
public static PsiElement[] move(@NotNull PsiElement container, @NotNull PsiElement[] statements, boolean generateDefaultInitializers) {
if (statements.length == 0) {
return statements;
}
Project project = container.getProject();
List<PsiElement> resultStatements = new ArrayList<PsiElement>();
List<KtProperty> propertiesDeclarations = new ArrayList<KtProperty>();
// Dummy element to add new declarations at the beginning
KtPsiFactory psiFactory = KtPsiFactoryKt.KtPsiFactory(project);
PsiElement dummyFirstStatement = container.addBefore(psiFactory.createExpression("dummyStatement"), statements[0]);
try {
SearchScope scope = new LocalSearchScope(container);
int lastStatementOffset = statements[statements.length - 1].getTextRange().getEndOffset();
for (PsiElement statement : statements) {
if (needToDeclareOut(statement, lastStatementOffset, scope)) {
if (statement instanceof KtProperty && ((KtProperty) statement).getInitializer() != null) {
KtProperty property = (KtProperty) statement;
KtProperty declaration = createVariableDeclaration(property, generateDefaultInitializers);
declaration = (KtProperty) container.addBefore(declaration, dummyFirstStatement);
propertiesDeclarations.add(declaration);
container.addAfter(psiFactory.createNewLine(), declaration);
KtBinaryExpression assignment = createVariableAssignment(property);
resultStatements.add(property.replace(assignment));
}
else {
PsiElement newStatement = container.addBefore(statement, dummyFirstStatement);
container.addAfter(psiFactory.createNewLine(), newStatement);
container.deleteChildRange(statement, statement);
}
}
else {
resultStatements.add(statement);
}
}
}
finally {
dummyFirstStatement.delete();
}
ShortenReferences.DEFAULT.process(propertiesDeclarations);
return PsiUtilCore.toPsiElementArray(resultStatements);
}
@NotNull
private static KtBinaryExpression createVariableAssignment(@NotNull KtProperty property) {
String propertyName = property.getName();
assert propertyName != null : "Property should have a name " + property.getText();
KtBinaryExpression assignment = (KtBinaryExpression) KtPsiFactoryKt
.KtPsiFactory(property).createExpression(propertyName + " = x");
KtExpression right = assignment.getRight();
assert right != null : "Created binary expression should have a right part " + assignment.getText();
KtExpression initializer = property.getInitializer();
assert initializer != null : "Initializer should exist for property " + property.getText();
right.replace(initializer);
return assignment;
}
@NotNull
private static KtProperty createVariableDeclaration(@NotNull KtProperty property, boolean generateDefaultInitializers) {
KotlinType propertyType = getPropertyType(property);
String defaultInitializer = null;
if (generateDefaultInitializers && property.isVar()) {
defaultInitializer = CodeInsightUtils.defaultInitializer(propertyType);
}
return createProperty(property, propertyType, defaultInitializer);
}
@NotNull
private static KotlinType getPropertyType(@NotNull KtProperty property) {
BindingContext bindingContext = ResolutionUtils.analyze(property, BodyResolveMode.PARTIAL);
VariableDescriptor propertyDescriptor = bindingContext.get(BindingContext.VARIABLE, property);
assert propertyDescriptor != null : "Couldn't resolve property to property descriptor " + property.getText();
return propertyDescriptor.getType();
}
@NotNull
private static KtProperty createProperty(@NotNull KtProperty property, @NotNull KotlinType propertyType, @Nullable String initializer) {
KtTypeReference typeRef = property.getTypeReference();
String typeString = null;
if (typeRef != null) {
typeString = typeRef.getText();
}
else if (!KotlinTypeKt.isError(propertyType)) {
typeString = IdeDescriptorRenderers.SOURCE_CODE.renderType(propertyType);
}
return KtPsiFactoryKt.KtPsiFactory(property).createProperty(property.getName(), typeString, property.isVar(), initializer);
}
private static boolean needToDeclareOut(@NotNull PsiElement element, int lastStatementOffset, @NotNull SearchScope scope) {
if (element instanceof KtProperty ||
element instanceof KtClassOrObject ||
element instanceof KtFunction) {
PsiReference[] refs = ReferencesSearch.search(element, scope, false).toArray(PsiReference.EMPTY_ARRAY);
if (refs.length > 0) {
PsiReference lastRef = refs[refs.length - 1];
if (lastRef.getElement().getTextOffset() > lastStatementOffset) {
return true;
}
}
}
return false;
}
private MoveDeclarationsOutHelper() {
}
}