blob: fe9940dc1eb9366582fbc22df170ca8432877b9b [file] [log] [blame]
/*
* Copyright 2000-2012 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.codeInspection.i18n.folding;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.folding.JavaCodeFoldingSettings;
import com.intellij.codeInspection.i18n.JavaI18nUtil;
import com.intellij.lang.ASTNode;
import com.intellij.lang.StdLanguages;
import com.intellij.lang.folding.FoldingBuilderEx;
import com.intellij.lang.folding.FoldingDescriptor;
import com.intellij.lang.properties.IProperty;
import com.intellij.lang.properties.parsing.PropertiesElementTypes;
import com.intellij.lang.properties.psi.Property;
import com.intellij.lang.properties.psi.impl.PropertyImpl;
import com.intellij.lang.properties.psi.impl.PropertyStubImpl;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.JavaConstantExpressionEvaluator;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author Konstantin Bulenkov
*/
public class PropertyFoldingBuilder extends FoldingBuilderEx {
private static final int FOLD_MAX_LENGTH = 50;
private static final Key<IProperty> CACHE = Key.create("i18n.property.cache");
public static final IProperty NULL = new PropertyImpl(new PropertyStubImpl(null, null), PropertiesElementTypes.PROPERTY);
@Override
@NotNull
public FoldingDescriptor[] buildFoldRegions(@NotNull PsiElement element, @NotNull Document document, boolean quick) {
if (!(element instanceof PsiJavaFile) || quick || !isFoldingsOn()) {
return FoldingDescriptor.EMPTY;
}
final PsiJavaFile file = (PsiJavaFile) element;
final Project project = file.getProject();
final List<FoldingDescriptor> result = new ArrayList<FoldingDescriptor>();
boolean hasJsp = ContainerUtil.intersects(Arrays.asList(StdLanguages.JSP, StdLanguages.JSPX), file.getViewProvider().getLanguages());
//hack here because JspFile PSI elements are not threaded correctly via nextSibling/prevSibling
file.accept(hasJsp ? new JavaRecursiveElementVisitor() {
@Override
public void visitLiteralExpression(PsiLiteralExpression expression) {
checkLiteral(project, expression, result);
}
} : new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitLiteralExpression(PsiLiteralExpression expression) {
checkLiteral(project, expression, result);
}
});
return result.toArray(new FoldingDescriptor[result.size()]);
}
private static boolean isFoldingsOn() {
return JavaCodeFoldingSettings.getInstance().isCollapseI18nMessages();
}
private static void checkLiteral(Project project, PsiLiteralExpression expression, List<FoldingDescriptor> result) {
if (isI18nProperty(project, expression)) {
final IProperty property = getI18nProperty(project, expression);
final HashSet<Object> set = new HashSet<Object>();
set.add(property != null ? property : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
final String msg = formatI18nProperty(expression, property);
final PsiElement parent = expression.getParent();
if (!msg.equals(expression.getText()) &&
parent instanceof PsiExpressionList &&
((PsiExpressionList)parent).getExpressions()[0] == expression) {
final PsiExpressionList expressions = (PsiExpressionList)parent;
final int count = JavaI18nUtil.getPropertyValueParamsMaxCount(expression);
final PsiExpression[] args = expressions.getExpressions();
if (args.length == 1 + count && parent.getParent() instanceof PsiMethodCallExpression) {
boolean ok = true;
for (int i = 1; i < count + 1; i++) {
Object value = JavaConstantExpressionEvaluator.computeConstantExpression(args[i], false);
if (value == null) {
if (!(args[i] instanceof PsiReferenceExpression)) {
ok = false;
break;
}
}
}
if (ok) {
result.add(new FoldingDescriptor(ObjectUtils.assertNotNull(parent.getParent().getNode()), parent.getParent().getTextRange(), null, set));
return;
}
}
}
result.add(new FoldingDescriptor(ObjectUtils.assertNotNull(expression.getNode()), expression.getTextRange(), null, set));
}
}
@Override
public String getPlaceholderText(@NotNull ASTNode node) {
final PsiElement element = SourceTreeToPsiMap.treeElementToPsi(node);
if (element instanceof PsiLiteralExpression) {
return getI18nMessage(element.getProject(), (PsiLiteralExpression)element);
} else if (element instanceof PsiMethodCallExpression) {
return formatMethodCallExpression(element.getProject(), (PsiMethodCallExpression)element);
}
return element.getText();
}
private static String formatMethodCallExpression(Project project, PsiMethodCallExpression methodCallExpression) {
final PsiExpression[] args = methodCallExpression.getArgumentList().getExpressions();
if (args.length > 0
&& args[0] instanceof PsiLiteralExpression
&& args[0].isValid()
&& isI18nProperty(project, (PsiLiteralExpression)args[0])) {
final int count = JavaI18nUtil.getPropertyValueParamsMaxCount((PsiLiteralExpression)args[0]);
if (args.length == 1 + count) {
String text = getI18nMessage(project, (PsiLiteralExpression)args[0]);
for (int i = 1; i < count + 1; i++) {
Object value = JavaConstantExpressionEvaluator.computeConstantExpression(args[i], false);
if (value == null) {
if (args[i] instanceof PsiReferenceExpression) {
value = "{" + args[i].getText() + "}";
}
else {
text = null;
break;
}
}
text = text.replace("{" + (i - 1) + "}", value.toString());
}
if (text != null) {
if (!text.equals(methodCallExpression.getText())) {
text = text.replace("''", "'");
}
return text.length() > FOLD_MAX_LENGTH ? text.substring(0, FOLD_MAX_LENGTH - 3) + "...\"" : text;
}
}
}
return methodCallExpression.getText();
}
private static String getI18nMessage(@NotNull Project project, PsiLiteralExpression literal) {
final IProperty property = getI18nProperty(project, literal);
return property == null ? literal.getText() : formatI18nProperty(literal, property);
}
@Nullable
private static IProperty getI18nProperty(Project project, PsiLiteralExpression literal) {
final Property property = (Property)literal.getUserData(CACHE);
if (property == NULL) return null;
if (property != null && isValid(property, literal)) return property;
if (isI18nProperty(project, literal)) {
final PsiReference[] references = literal.getReferences();
for (PsiReference reference : references) {
if (reference instanceof PsiPolyVariantReference) {
final ResolveResult[] results = ((PsiPolyVariantReference)reference).multiResolve(false);
for (ResolveResult result : results) {
final PsiElement element = result.getElement();
if (element instanceof IProperty) {
IProperty p = (IProperty)element;
literal.putUserData(CACHE, p);
return p;
}
}
} else {
final PsiElement element = reference.resolve();
if (element instanceof IProperty) {
IProperty p = (IProperty)element;
literal.putUserData(CACHE, p);
return p;
}
}
}
}
return null;
}
private static boolean isValid(Property property, PsiLiteralExpression literal) {
if (literal == null || property == null || !property.isValid()) return false;
return StringUtil.unquoteString(literal.getText()).equals(property.getKey());
}
private static String formatI18nProperty(PsiLiteralExpression literal, IProperty property) {
return property == null ?
literal.getText() : "\"" + property.getValue() + "\"";
}
@Override
public boolean isCollapsedByDefault(@NotNull ASTNode node) {
return isFoldingsOn();
}
public static boolean isI18nProperty(@NotNull Project project, @NotNull PsiLiteralExpression expr) {
if (! isStringLiteral(expr)) return false;
final IProperty property = expr.getUserData(CACHE);
if (property == NULL) return false;
if (property != null) return true;
final Map<String, Object> annotationParams = new HashMap<String, Object>();
annotationParams.put(AnnotationUtil.PROPERTY_KEY_RESOURCE_BUNDLE_PARAMETER, null);
final boolean isI18n = JavaI18nUtil.mustBePropertyKey(project, expr, annotationParams);
if (!isI18n) {
expr.putUserData(CACHE, NULL);
}
return isI18n;
}
private static boolean isStringLiteral(PsiLiteralExpression expr) {
final String text;
if (expr == null || (text = expr.getText()) == null) return false;
return text.startsWith("\"") && text.endsWith("\"") && text.length() > 2;
}
}