blob: 0d705dc8a47b175fb2033e742dfec8b70550bb3b [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.codeInspection.i18n;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.template.macro.MacroUtil;
import com.intellij.lang.properties.IProperty;
import com.intellij.lang.properties.PropertiesImplUtil;
import com.intellij.lang.properties.psi.PropertiesFile;
import com.intellij.lang.properties.psi.PropertyCreationHandler;
import com.intellij.lang.properties.references.I18nUtil;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.util.*;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.HashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.text.MessageFormat;
import java.util.*;
/**
* @author max
*/
public class JavaI18nUtil extends I18nUtil {
public static final PropertyCreationHandler DEFAULT_PROPERTY_CREATION_HANDLER = new PropertyCreationHandler() {
@Override
public void createProperty(final Project project, final Collection<PropertiesFile> propertiesFiles, final String key, final String value,
final PsiExpression[] parameters) throws IncorrectOperationException {
JavaI18nUtil.createProperty(project, propertiesFiles, key, value);
}
};
private JavaI18nUtil() {
}
@Nullable
public static TextRange getSelectedRange(Editor editor, final PsiFile psiFile) {
if (editor == null) return null;
String selectedText = editor.getSelectionModel().getSelectedText();
if (selectedText != null) {
return new TextRange(editor.getSelectionModel().getSelectionStart(), editor.getSelectionModel().getSelectionEnd());
}
PsiElement psiElement = psiFile.findElementAt(editor.getCaretModel().getOffset());
if (psiElement==null || psiElement instanceof PsiWhiteSpace) return null;
return psiElement.getTextRange();
}
public static boolean mustBePropertyKey(@NotNull Project project,
@NotNull PsiLiteralExpression expression,
@NotNull Map<String, Object> annotationAttributeValues) {
final PsiElement parent = expression.getParent();
if (parent instanceof PsiVariable) {
final PsiAnnotation annotation = AnnotationUtil.findAnnotation((PsiVariable)parent, AnnotationUtil.PROPERTY_KEY);
if (annotation != null) {
return processAnnotationAttributes(annotationAttributeValues, annotation);
}
}
return isPassedToAnnotatedParam(project, expression, AnnotationUtil.PROPERTY_KEY, annotationAttributeValues, null);
}
public static boolean isPassedToAnnotatedParam(@NotNull Project project,
@NotNull PsiExpression expression,
final String annFqn,
@Nullable Map<String, Object> annotationAttributeValues,
@Nullable final Set<PsiModifierListOwner> nonNlsTargets) {
expression = getToplevelExpression(project, expression);
final PsiElement parent = expression.getParent();
if (!(parent instanceof PsiExpressionList)) return false;
int idx = -1;
final PsiExpression[] args = ((PsiExpressionList)parent).getExpressions();
for (int i = 0; i < args.length; i++) {
PsiExpression arg = args[i];
if (PsiTreeUtil.isAncestor(arg, expression, false)) {
idx = i;
break;
}
}
if (idx == -1) return false;
PsiElement grParent = parent.getParent();
if (grParent instanceof PsiAnonymousClass) {
grParent = grParent.getParent();
}
if (grParent instanceof PsiCall) {
PsiMethod method = ((PsiCall)grParent).resolveMethod();
if (method != null && isMethodParameterAnnotatedWith(method, idx, null, annFqn, annotationAttributeValues, nonNlsTargets)) {
return true;
}
}
return false;
}
private static final Key<ParameterizedCachedValue<PsiExpression, Pair<Project, PsiExpression>>> TOP_LEVEL_EXPRESSION = Key.create("TOP_LEVEL_EXPRESSION");
private static final ParameterizedCachedValueProvider<PsiExpression, Pair<Project, PsiExpression>> TOP_LEVEL_PROVIDER =
new ParameterizedCachedValueProvider<PsiExpression, Pair<Project, PsiExpression>>() {
@Override
public CachedValueProvider.Result<PsiExpression> compute(Pair<Project, PsiExpression> pair) {
PsiExpression param = pair.second;
Project project = pair.first;
PsiExpression topLevel = getTopLevel(project, param);
ParameterizedCachedValue<PsiExpression, Pair<Project, PsiExpression>> cachedValue = param.getUserData(TOP_LEVEL_EXPRESSION);
assert cachedValue != null;
int i = 0;
for (PsiElement element = param; element != topLevel; element = element.getParent(), i++) {
if (i % 10 == 0) { // optimization: store up link to the top level expression in each 10nth element
element.putUserData(TOP_LEVEL_EXPRESSION, cachedValue);
}
}
return CachedValueProvider.Result.create(topLevel, PsiManager.getInstance(project).getModificationTracker());
}
};
@NotNull
public static PsiExpression getToplevelExpression(@NotNull final Project project, @NotNull final PsiExpression expression) {
if (expression instanceof PsiBinaryExpression || expression.getParent() instanceof PsiBinaryExpression) { //can be large, cache
return CachedValuesManager.getManager(project).getParameterizedCachedValue(expression, TOP_LEVEL_EXPRESSION, TOP_LEVEL_PROVIDER, true,
Pair.create(project, expression));
}
return getTopLevel(project, expression);
}
@NotNull
private static PsiExpression getTopLevel(Project project, @NotNull PsiExpression expression) {
int i = 0;
while (expression.getParent() instanceof PsiExpression) {
i++;
final PsiExpression parent = (PsiExpression)expression.getParent();
if (parent instanceof PsiConditionalExpression &&
((PsiConditionalExpression)parent).getCondition() == expression) break;
expression = parent;
if (expression instanceof PsiAssignmentExpression) break;
if (i > 10 && expression instanceof PsiBinaryExpression) {
ParameterizedCachedValue<PsiExpression, Pair<Project, PsiExpression>> value = expression.getUserData(TOP_LEVEL_EXPRESSION);
if (value != null && value.hasUpToDateValue()) {
return getToplevelExpression(project, expression); // optimization: use caching for big hierarchies
}
}
}
return expression;
}
public static boolean isMethodParameterAnnotatedWith(final PsiMethod method,
final int idx,
@Nullable Collection<PsiMethod> processed,
final String annFqn,
@Nullable Map<String, Object> annotationAttributeValues,
@Nullable final Set<PsiModifierListOwner> nonNlsTargets) {
if (processed != null) {
if (processed.contains(method)) return false;
}
else {
processed = new THashSet<PsiMethod>();
}
processed.add(method);
final PsiParameter[] params = method.getParameterList().getParameters();
PsiParameter param;
if (idx >= params.length) {
if (params.length == 0) {
return false;
}
PsiParameter lastParam = params [params.length-1];
if (lastParam.isVarArgs()) {
param = lastParam;
}
else {
return false;
}
}
else {
param = params[idx];
}
final PsiAnnotation annotation = AnnotationUtil.findAnnotation(param, annFqn);
if (annotation != null) {
return processAnnotationAttributes(annotationAttributeValues, annotation);
}
if (nonNlsTargets != null) {
nonNlsTargets.add(param);
}
final PsiMethod[] superMethods = method.findSuperMethods();
for (PsiMethod superMethod : superMethods) {
if (isMethodParameterAnnotatedWith(superMethod, idx, processed, annFqn, annotationAttributeValues, null)) return true;
}
return false;
}
private static boolean processAnnotationAttributes(@Nullable Map<String, Object> annotationAttributeValues, @NotNull PsiAnnotation annotation) {
if (annotationAttributeValues != null) {
final PsiAnnotationParameterList parameterList = annotation.getParameterList();
final PsiNameValuePair[] attributes = parameterList.getAttributes();
for (PsiNameValuePair attribute : attributes) {
final String name = attribute.getName();
if (annotationAttributeValues.containsKey(name)) {
annotationAttributeValues.put(name, attribute.getValue());
}
}
}
return true;
}
public static boolean isValidPropertyReference(@NotNull Project project,
@NotNull PsiLiteralExpression expression,
@NotNull String key,
@NotNull Ref<String> outResourceBundle) {
final HashMap<String, Object> annotationAttributeValues = new HashMap<String, Object>();
annotationAttributeValues.put(AnnotationUtil.PROPERTY_KEY_RESOURCE_BUNDLE_PARAMETER, null);
if (mustBePropertyKey(project, expression, annotationAttributeValues)) {
final Object resourceBundleName = annotationAttributeValues.get(AnnotationUtil.PROPERTY_KEY_RESOURCE_BUNDLE_PARAMETER);
if (!(resourceBundleName instanceof PsiExpression)) {
return false;
}
PsiExpression expr = (PsiExpression)resourceBundleName;
final Object value = JavaPsiFacade.getInstance(expr.getProject()).getConstantEvaluationHelper().computeConstantExpression(expr);
if (value == null) {
return false;
}
String bundleName = value.toString();
outResourceBundle.set(bundleName);
return isPropertyRef(expression, key, bundleName);
}
return true;
}
public static boolean isPropertyRef(final PsiLiteralExpression expression, final String key, final String resourceBundleName) {
if (resourceBundleName == null) {
return !PropertiesImplUtil.findPropertiesByKey(expression.getProject(), key).isEmpty();
}
else {
final List<PropertiesFile> propertiesFiles = propertiesFilesByBundleName(resourceBundleName, expression);
boolean containedInPropertiesFile = false;
for (PropertiesFile propertiesFile : propertiesFiles) {
containedInPropertiesFile |= propertiesFile.findPropertyByKey(key) != null;
}
return containedInPropertiesFile;
}
}
public static Set<String> suggestExpressionOfType(final PsiClassType type, final PsiLiteralExpression context) {
PsiVariable[] variables = MacroUtil.getVariablesVisibleAt(context, "");
Set<String> result = new LinkedHashSet<String>();
for (PsiVariable var : variables) {
PsiType varType = var.getType();
if (type == null || type.isAssignableFrom(varType)) {
result.add(var.getNameIdentifier().getText());
}
}
PsiExpression[] expressions = MacroUtil.getStandardExpressionsOfType(context, type);
for (PsiExpression expression : expressions) {
result.add(expression.getText());
}
if (type != null) {
addAvailableMethodsOfType(type, context, result);
}
return result;
}
private static void addAvailableMethodsOfType(final PsiClassType type, final PsiLiteralExpression context, final Collection<String> result) {
PsiScopesUtil.treeWalkUp(new PsiScopeProcessor() {
@Override
public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
if (element instanceof PsiMethod) {
PsiMethod method = (PsiMethod)element;
PsiType returnType = method.getReturnType();
if (returnType != null && TypeConversionUtil.isAssignable(type, returnType)
&& method.getParameterList().getParametersCount() == 0) {
result.add(method.getName() + "()");
}
}
return true;
}
@Override
public <T> T getHint(@NotNull Key<T> hintKey) {
return null;
}
@Override
public void handleEvent(@NotNull Event event, Object associated) {
}
}, context, null);
}
/**
* Returns number of different message format parameters in property value
*
* <i>Class {0} info: Class {0} extends class {1} and implements interface {2}</i>
* number of parameters is 3.
*
* @param propertyValue
* @return number of parameters from single property or 0 for wrong format
*/
public static int getPropertyValuePlaceholdersCount(final @NotNull String propertyValue) {
try {
return new MessageFormat(propertyValue).getFormatsByArgumentIndex().length;
} catch (final IllegalArgumentException e) {
return 0;
}
}
/**
* Returns number of different parameters in i18n message. For example, for string
*
* <i>Class {0} info: Class {0} extends class {1} and implements interface {2}</i> in one translation of property
* <i>Class {0} info: Class {0} extends class {1} </i> in other translation of property
*
* number of parameters is 3.
*
* @param expression i18n literal
* @return number of parameters
*/
public static int getPropertyValueParamsMaxCount(final @NotNull PsiLiteralExpression expression) {
int maxCount = -1;
for (PsiReference reference : expression.getReferences()) {
if (reference instanceof PsiPolyVariantReference) {
for (ResolveResult result : ((PsiPolyVariantReference)reference).multiResolve(false)) {
if (result.isValidResult() && result.getElement() instanceof IProperty) {
try {
final IProperty property = (IProperty)result.getElement();
final String propertyValue = property.getValue();
if (propertyValue == null) {
continue;
}
maxCount = Math.max(maxCount, getPropertyValuePlaceholdersCount(propertyValue));
}
catch (IllegalArgumentException ignored) {
}
}
}
}
}
return maxCount;
}
}