blob: a0234777aa866be8c9fb084bac8f28c9589e7b6e [file] [log] [blame]
/*
* Copyright 2000-2014 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 com.intellij.psi.impl.source.tree;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.lang.StdLanguages;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.templateLanguages.OuterLanguageElement;
import com.intellij.util.IncorrectOperationException;
import java.util.Map;
public class JavaTreeCopyHandler implements TreeCopyHandler {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.JavaTreeCopyHandler");
@Override
public TreeElement decodeInformation(TreeElement element, final Map<Object, Object> decodingState) {
boolean shallDecodeEscapedTexts = shallEncodeEscapedTexts(element, decodingState);
if (element instanceof CompositeElement) {
if (element.getElementType() == JavaElementType.JAVA_CODE_REFERENCE ||
element.getElementType() == JavaElementType.REFERENCE_EXPRESSION) {
PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)SourceTreeToPsiMap.treeElementToPsi(element);
final PsiClass refClass = element.getCopyableUserData(JavaTreeGenerator.REFERENCED_CLASS_KEY);
if (refClass != null) {
element.putCopyableUserData(JavaTreeGenerator.REFERENCED_CLASS_KEY, null);
PsiManager manager = refClass.getManager();
JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(refClass.getProject());
PsiElement refElement1 = ref.resolve();
try {
if (refClass != refElement1 && !manager.areElementsEquivalent(refClass, refElement1)) {
if (((CompositeElement)element).findChildByRole(ChildRole.QUALIFIER) == null) {
// can restore only if short (otherwise qualifier should be already restored)
ref = (PsiJavaCodeReferenceElement)ref.bindToElement(refClass);
}
}
else {
// shorten references to the same package and to inner classes that can be accessed by short name
ref = (PsiJavaCodeReferenceElement)codeStyleManager.shortenClassReferences(ref, JavaCodeStyleManager.DO_NOT_ADD_IMPORTS);
}
return (TreeElement)SourceTreeToPsiMap.psiElementToTree(ref);
}
catch (IncorrectOperationException e) {
((PsiImportHolder) ref.getContainingFile()).importClass(refClass);
}
}
else {
final PsiMember refMember = element.getCopyableUserData(JavaTreeGenerator.REFERENCED_MEMBER_KEY);
if (refMember != null) {
LOG.assertTrue(ref instanceof PsiReferenceExpression);
element.putCopyableUserData(JavaTreeGenerator.REFERENCED_MEMBER_KEY, null);
PsiElement refElement1 = ref.resolve();
if (refMember != refElement1 && !refMember.getManager().areElementsEquivalent(refMember, refElement1)) {
try {
ref = (PsiJavaCodeReferenceElement) ((PsiReferenceExpression)ref).bindToElementViaStaticImport(refMember.getContainingClass());
}
catch (IncorrectOperationException e) {
// TODO[yole] ignore?
}
return (TreeElement)SourceTreeToPsiMap.psiElementToTree(ref);
}
}
}
}
else if (element.getElementType() == JavaElementType.MODIFIER_LIST) {
if (element.getUserData(INTERFACE_MODIFIERS_FLAG_KEY) != null) {
element.putUserData(INTERFACE_MODIFIERS_FLAG_KEY, null);
try {
PsiModifierList modifierList = (PsiModifierList)SourceTreeToPsiMap.treeElementToPsi(element);
if (element.getTreeParent().getElementType() == JavaElementType.FIELD) {
modifierList.setModifierProperty(PsiModifier.PUBLIC, true);
modifierList.setModifierProperty(PsiModifier.STATIC, true);
modifierList.setModifierProperty(PsiModifier.FINAL, true);
}
else if (element.getTreeParent().getElementType() == JavaElementType.METHOD ||
element.getTreeParent().getElementType() == JavaElementType.ANNOTATION_METHOD) {
modifierList.setModifierProperty(PsiModifier.PUBLIC, true);
modifierList.setModifierProperty(PsiModifier.ABSTRACT, true);
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
}
else if (shallDecodeEscapedTexts && element instanceof LeafElement && !(element instanceof OuterLanguageElement)) {
if (!isInCData(element)) {
final String original = element.getText();
final String escaped = StringUtil.escapeXml(original);
if (!Comparing.equal(original, escaped) && element.getCopyableUserData(ALREADY_ESCAPED) == null) {
LeafElement copy = ((LeafElement)element).replaceWithText(escaped);
copy.putCopyableUserData(ALREADY_ESCAPED, Boolean.TRUE);
return copy;
}
}
}
return null;
}
private static final Key<Boolean> ALREADY_ESCAPED = new Key<Boolean>("ALREADY_ESCAPED");
private static final Key<Boolean> ESCAPEMENT_ENGAGED = new Key<Boolean>("ESCAPEMENT_ENGAGED");
private static boolean conversionMayApply(ASTNode element) {
PsiElement psi = element.getPsi();
if (psi == null || !psi.isValid()) return false;
final PsiFile file = psi.getContainingFile();
final Language baseLanguage = file.getViewProvider().getBaseLanguage();
return baseLanguage == StdLanguages.JSPX && file.getLanguage() != baseLanguage;
}
@Override
public void encodeInformation(final TreeElement element, final ASTNode original, final Map<Object, Object> encodingState) {
boolean shallEncodeEscapedTexts = shallEncodeEscapedTexts(original, encodingState);
if (original instanceof CompositeElement) {
if (original.getElementType() == JavaElementType.JAVA_CODE_REFERENCE || original.getElementType() == JavaElementType.REFERENCE_EXPRESSION) {
encodeInformationInRef(element, original);
}
else if (original.getElementType() == JavaElementType.MODIFIER_LIST
&& (original.getTreeParent().getElementType() == JavaElementType.FIELD || original.getTreeParent().getElementType() == JavaElementType.METHOD || original.getTreeParent().getElementType() == JavaElementType.ANNOTATION_METHOD)
&& original.getTreeParent().getTreeParent().getElementType() == JavaElementType.CLASS
&& (((PsiClass)SourceTreeToPsiMap.treeElementToPsi(original.getTreeParent().getTreeParent())).isInterface()
|| ((PsiClass)SourceTreeToPsiMap.treeElementToPsi(original.getTreeParent().getTreeParent())).isAnnotationType())) {
element.putUserData(INTERFACE_MODIFIERS_FLAG_KEY, Boolean.TRUE);
}
}
else if (shallEncodeEscapedTexts && original instanceof LeafElement && !(original instanceof OuterLanguageElement)) {
if (!isInCData(original)) {
final String originalText = element.getText();
final String unescapedText = StringUtil.unescapeXml(originalText);
if (!Comparing.equal(originalText, unescapedText)) {
LeafElement replaced = ((LeafElement)element).rawReplaceWithText(unescapedText);
element.putCopyableUserData(ALREADY_ESCAPED, null);
replaced.putCopyableUserData(ALREADY_ESCAPED, null);
}
}
}
}
private static Boolean shallEncodeEscapedTexts(final ASTNode original, final Map<Object, Object> encodingState) {
Boolean shallEncodeEscapedTexts = (Boolean)encodingState.get(ESCAPEMENT_ENGAGED);
if (shallEncodeEscapedTexts == null) {
shallEncodeEscapedTexts = conversionMayApply(original);
encodingState.put(ESCAPEMENT_ENGAGED, shallEncodeEscapedTexts);
}
return shallEncodeEscapedTexts;
}
private static boolean isInCData(ASTNode element) {
ASTNode leaf = element;
while (leaf != null) {
if (leaf instanceof OuterLanguageElement) {
return leaf.getText().indexOf("<![CDATA[") >= 0;
}
leaf = TreeUtil.prevLeaf(leaf);
}
return false;
}
private static void encodeInformationInRef(TreeElement ref, ASTNode original) {
if (original.getElementType() == JavaElementType.REFERENCE_EXPRESSION) {
final PsiJavaCodeReferenceElement javaRefElement = (PsiJavaCodeReferenceElement)SourceTreeToPsiMap.treeElementToPsi(original);
assert javaRefElement != null;
final JavaResolveResult resolveResult = javaRefElement.advancedResolve(false);
final PsiElement target = resolveResult.getElement();
if (target instanceof PsiClass &&
original.getTreeParent().getElementType() == JavaElementType.REFERENCE_EXPRESSION) {
ref.putCopyableUserData(JavaTreeGenerator.REFERENCED_CLASS_KEY, (PsiClass)target);
}
else if ((target instanceof PsiMethod || target instanceof PsiField) &&
((PsiMember) target).hasModifierProperty(PsiModifier.STATIC) &&
resolveResult.getCurrentFileResolveScope() instanceof PsiImportStaticStatement) {
ref.putCopyableUserData(JavaTreeGenerator.REFERENCED_MEMBER_KEY, (PsiMember) target);
}
}
else if (original.getElementType() == JavaElementType.JAVA_CODE_REFERENCE) {
switch (((PsiJavaCodeReferenceElementImpl)original).getKind(((PsiJavaCodeReferenceElementImpl)original).getContainingFile())) {
case PsiJavaCodeReferenceElementImpl.CLASS_NAME_KIND:
case PsiJavaCodeReferenceElementImpl.CLASS_OR_PACKAGE_NAME_KIND:
case PsiJavaCodeReferenceElementImpl.CLASS_IN_QUALIFIED_NEW_KIND:
final PsiElement target = SourceTreeToPsiMap.<PsiJavaCodeReferenceElement>treeToPsiNotNull(original).resolve();
if (target instanceof PsiClass) {
ref.putCopyableUserData(JavaTreeGenerator.REFERENCED_CLASS_KEY, (PsiClass)target);
}
break;
case PsiJavaCodeReferenceElementImpl.PACKAGE_NAME_KIND:
case PsiJavaCodeReferenceElementImpl.CLASS_FQ_NAME_KIND:
case PsiJavaCodeReferenceElementImpl.CLASS_FQ_OR_PACKAGE_NAME_KIND:
break;
default:
LOG.assertTrue(false);
}
}
else {
LOG.error("Wrong element type: " + original.getElementType());
}
}
private static final Key<Boolean> INTERFACE_MODIFIERS_FLAG_KEY = Key.create("INTERFACE_MODIFIERS_FLAG_KEY");
}