/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.refactor; import com.intellij.codeInsight.ChangeContextUtil; import com.intellij.lang.ASTNode; import com.intellij.openapi.util.io.FileUtil; import com.intellij.psi.JavaTokenType; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiIdentifier; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiReference; import com.intellij.psi.impl.PsiImplUtil; import com.intellij.psi.impl.source.tree.JavaElementType; import com.intellij.psi.impl.source.tree.LeafElement; import com.intellij.refactoring.listeners.RefactoringElementListener; import com.intellij.refactoring.rename.ClassHidesImportedClassUsageInfo; import com.intellij.refactoring.rename.CollidingClassImportUsageInfo; import com.intellij.refactoring.rename.RenameJavaClassProcessor; import com.intellij.refactoring.rename.ResolvableCollisionUsageInfo; import com.intellij.usageView.UsageInfo; import com.intellij.util.IncorrectOperationException; import gw.config.CommonServices; import gw.internal.gosu.parser.java.LeafASTNode; import gw.lang.reflect.TypeSystem; import gw.plugin.ij.lang.GosuElementType; import gw.plugin.ij.lang.parser.GosuElementTypes; import gw.plugin.ij.lang.psi.api.expressions.IGosuIdentifier; import gw.plugin.ij.lang.psi.util.GosuPsiParseUtil; import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; import java.util.ArrayList; public class GosuRenameClassProcessor extends RenameJavaClassProcessor { @Override public void renameElement(PsiElement element, String newName, UsageInfo[] usages, RefactoringElementListener listener) throws IncorrectOperationException { PsiClass aClass = (PsiClass) element; if( aClass.getName().equals( newName ) ) { // Same name, nothing to do return; } ArrayList<UsageInfo> postponedCollisions = new ArrayList<>(); // rename all references for (final UsageInfo usage : usages) { if (usage instanceof ResolvableCollisionUsageInfo) { if (usage instanceof CollidingClassImportUsageInfo) { ((CollidingClassImportUsageInfo)usage).getImportStatement().delete(); } else { postponedCollisions.add(usage); } } } String path = aClass.getContainingFile().getViewProvider().getVirtualFile().getPath(); path = path.replace("/" + aClass.getName() + ".", "/" + newName + "."); File ioFIle = new File(path); boolean bCreatedNewFile = false; try { // Note this won't create a new file on Windows if the name is different only by case. // In this situation we don't need to create another file since internally we are able // to resolve a type name based on a file case-insensitively (on Windows). bCreatedNewFile = ioFIle.createNewFile(); if( bCreatedNewFile ) { TypeSystem.created(CommonServices.getFileSystem().getIFile(ioFIle)); StringBuilder source = generateSource(aClass, newName); FileUtil.writeToFile(ioFIle, source.toString()); } } catch (IOException e) { e.printStackTrace(); } // do actual rename ChangeContextUtil.encodeContextInfo(aClass, true, false); boolean isRenameFile = isRenameFileOnRenaming(aClass); setName(aClass, newName); for (UsageInfo usage : usages) { if (!(usage instanceof ResolvableCollisionUsageInfo)) { final PsiReference ref = usage.getReference(); if (ref == null) continue; try { ref.bindToElement(aClass); } catch (IncorrectOperationException e) {//fall back to old scheme ref.handleElementRename(newName); } } } if( bCreatedNewFile ) { ioFIle.delete(); } if (isRenameFile) { PsiFile file = (PsiFile)aClass.getParent(); String fileName = file.getName(); int dotIndex = fileName.lastIndexOf('.'); file.setName(dotIndex >= 0 ? newName + "." + fileName.substring(dotIndex + 1) : newName); } ChangeContextUtil.decodeContextInfo(aClass, null, null); //to make refs to other classes from this one resolve to their old referent // resolve collisions for (UsageInfo postponedCollision : postponedCollisions) { ClassHidesImportedClassUsageInfo collision = (ClassHidesImportedClassUsageInfo) postponedCollision; collision.resolveCollision(); } listener.elementRenamed(aClass); } private StringBuilder generateSource(PsiClass aClass, String newName) { StringBuilder source = new StringBuilder(); generateSource(aClass.getContainingFile().getNode(), aClass.getName(), newName, source); return source; } private void generateSource(ASTNode node, String oldName, String newName, StringBuilder source) { if (node instanceof LeafElement) { String text = node.getText(); if (node.getElementType() == JavaTokenType.IDENTIFIER && text.equals(oldName)) { PsiElement psi = node.getPsi().getParent(); if (psi instanceof PsiClass) { text = newName; } else if (psi instanceof PsiMethod && ((PsiMethod) psi).getName().equals(oldName)) { text = newName; } else if (psi instanceof PsiReference) { PsiElement resolve = ((PsiReference) psi).resolve(); if (resolve instanceof PsiClass && ((PsiClass) resolve).getName().equals(oldName)) { text = newName; } } } source.append(text); } else { for (ASTNode child : node.getChildren(null)) { generateSource(child, oldName, newName, source); } } } public PsiElement setName(PsiClass aClass, @NotNull String newName) throws IncorrectOperationException{ String oldName = aClass.getName(); PsiIdentifier nameIdentifier = aClass.getNameIdentifier(); if (nameIdentifier instanceof IGosuIdentifier) { GosuPsiParseUtil.setName(nameIdentifier, newName); } else { PsiImplUtil.setName(nameIdentifier, newName); } // This is the Java implementation //PsiImplUtil.setName(aClass.getNameIdentifier(), newName); // rename constructors for (PsiMethod method : aClass.getConstructors()) { if (method.getName().equals(oldName)) { method.setName(newName); } } return aClass; } private boolean isRenameFileOnRenaming(PsiClass aClass) { final PsiElement parent = aClass.getParent(); if (parent instanceof PsiFile) { PsiFile file = (PsiFile)parent; String fileName = file.getName(); int dotIndex = fileName.lastIndexOf('.'); String name = dotIndex >= 0 ? fileName.substring(0, dotIndex) : fileName; String oldName = aClass.getName(); return name.equals(oldName); } else { return false; } } }