/* * Copyright 2003-2016 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 jetbrains.mps.idea.java.usages; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; import com.intellij.util.IncorrectOperationException; import jetbrains.mps.ide.project.ProjectHelper; import jetbrains.mps.idea.core.psi.impl.MPSPsiNode; import jetbrains.mps.idea.core.psi.impl.MPSPsiProvider; import jetbrains.mps.idea.core.refactoring.NodePtr; import jetbrains.mps.idea.java.psiStubs.JavaForeignIdBuilder; import jetbrains.mps.idea.java.refactoring.MoveRenameBatch; import jetbrains.mps.smodel.ModelAccessHelper; import jetbrains.mps.smodel.ModelImports; import jetbrains.mps.smodel.SNodeId.Foreign; import jetbrains.mps.smodel.SNodePointer; import jetbrains.mps.smodel.StaticReference; import jetbrains.mps.util.Computable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeId; import org.jetbrains.mps.openapi.model.SNodeReference; /** * danilla 3/24/13 */ public class IdPrefixReference implements PsiReference { private SNodeReference myTarget; private String myRole; private PsiElement myParent; public IdPrefixReference(SNodeReference target, String role, PsiElement fosterFather) { myTarget = target; myRole = role; myParent = fosterFather; } @Override public PsiElement getElement() { return myParent; } @Override public TextRange getRangeInElement() { return TextRange.EMPTY_RANGE; } @Nullable @Override public PsiElement resolve() { ApplicationManager.getApplication().assertReadAccessAllowed(); return new ModelAccessHelper(ProjectHelper.getModelAccess(myParent.getProject())).runReadAction(new Computable<PsiElement>() { @Override public PsiElement compute() { return MPSPsiProvider.getInstance(myParent.getProject()).getPsi(myTarget); } }); } @NotNull @Override public String getCanonicalText() { return "MPS id prefix reference"; } @Override public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException { SNodeId targetNodeId = ((SNodePointer) myTarget).getNodeId(); String str = targetNodeId.toString(); String newStr; int lastDot = str.lastIndexOf("."); if (lastDot < 0) { newStr = Foreign.ID_PREFIX + newElementName; } else { newStr = str.substring(0, lastDot + 1) + newElementName; } final NodePtr newTarget = new NodePtr(((SNodePointer) myTarget).getModelReference(), new Foreign(newStr)); SNodeReference source = ((MPSPsiNode) myParent).getSNodeReference(); myParent.getProject().getComponent(MoveRenameBatch.class).scheduleIdPrefixRefUpdate(source, myRole, new Runnable() { @Override public void run() { handleRename(newTarget); } }); return myParent; } @Override public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException { SNodeReference source = ((MPSPsiNode) myParent).getSNodeReference(); final NodePtr newTargetPtr = JavaForeignIdBuilder.computeNodePtr(element); myParent.getProject().getComponent(MoveRenameBatch.class).scheduleIdPrefixRefUpdate(source, myRole, new Runnable() { @Override public void run() { handleRename(newTargetPtr); } }); return myParent; } private void handleRename(NodePtr newNode) { SNodePointer oldNode = (SNodePointer) myTarget; SNode source = ((MPSPsiNode) myParent).getSNodeReference().resolve(ProjectHelper.getProjectRepository(myParent.getProject())); String oldId = source.getReference(myRole).getTargetNodeId().toString(); // replacing all proper occurences String what = oldNode.getNodeId().toString(); what = what.startsWith("~") ? what.substring(1) : what; String replacement = newNode.getNodeId().toString(); replacement = replacement.startsWith("~") ? replacement.substring(1) : replacement; String newId = carefullyReplace(oldId, what, replacement); source.setReference(myRole, StaticReference.create(myRole, source, newNode.getSModelReference(), new Foreign(newId))); // add model import if needed if (!oldNode.getModelReference().equals(newNode.getSModelReference())) { SModel model = ((MPSPsiNode) myParent).getSNodeReference().resolve(ProjectHelper.getProjectRepository(myParent.getProject())).getModel(); SModelReference newTargetModel = newNode.getSModelReference(); new ModelImports(model).addModelImport(newTargetModel); } } private String carefullyReplace(String input, String what, String replacement) { String result = input; int len = what.length(); int idx = input.indexOf(what); while (idx >= 0) { if (idx != 0) { char c = result.charAt(idx - 1); if (c == '.' || Character.isLetterOrDigit(c)) { // java foreign id specific knowledge idx = result.indexOf(what, idx + 1); continue; } } result = result.substring(0, idx) + replacement + result.substring(idx + len); idx = result.indexOf(what, idx + len); } return result; } @Override public boolean isReferenceTo(PsiElement element) { if (!(element instanceof MPSPsiNode)) return false; return myTarget.equals(((MPSPsiNode) element).getSNodeReference()); } @NotNull @Override public Object[] getVariants() { return new Object[0]; //To change body of implemented methods use File | Settings | File Templates. } // Q: should it be true? @Override public boolean isSoft() { return false; } }