/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nUtil.java

https://bitbucket.org/nbargnesi/idea · Java · 344 lines · 289 code · 29 blank · 26 comment · 82 complexity · c9b8034133d0ba9a3f49463713d1f1be MD5 · raw file

  1. /*
  2. * Copyright 2000-2009 JetBrains s.r.o.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.intellij.codeInspection.i18n;
  17. import com.intellij.codeInsight.AnnotationUtil;
  18. import com.intellij.codeInsight.template.macro.MacroUtil;
  19. import com.intellij.lang.properties.IProperty;
  20. import com.intellij.lang.properties.PropertiesUtil;
  21. import com.intellij.lang.properties.psi.PropertiesFile;
  22. import com.intellij.lang.properties.psi.PropertyCreationHandler;
  23. import com.intellij.lang.properties.references.I18nUtil;
  24. import com.intellij.openapi.editor.Editor;
  25. import com.intellij.openapi.project.Project;
  26. import com.intellij.openapi.util.Key;
  27. import com.intellij.openapi.util.Pair;
  28. import com.intellij.openapi.util.Ref;
  29. import com.intellij.openapi.util.TextRange;
  30. import com.intellij.psi.*;
  31. import com.intellij.psi.scope.PsiScopeProcessor;
  32. import com.intellij.psi.scope.util.PsiScopesUtil;
  33. import com.intellij.psi.util.*;
  34. import com.intellij.util.IncorrectOperationException;
  35. import com.intellij.util.containers.HashMap;
  36. import gnu.trove.THashSet;
  37. import org.jetbrains.annotations.NotNull;
  38. import org.jetbrains.annotations.Nullable;
  39. import java.text.MessageFormat;
  40. import java.util.*;
  41. /**
  42. * @author max
  43. */
  44. public class JavaI18nUtil extends I18nUtil {
  45. public static final PropertyCreationHandler DEFAULT_PROPERTY_CREATION_HANDLER = new PropertyCreationHandler() {
  46. public void createProperty(final Project project, final Collection<PropertiesFile> propertiesFiles, final String key, final String value,
  47. final PsiExpression[] parameters) throws IncorrectOperationException {
  48. JavaI18nUtil.createProperty(project, propertiesFiles, key, value);
  49. }
  50. };
  51. private JavaI18nUtil() {
  52. }
  53. @Nullable
  54. public static TextRange getSelectedRange(Editor editor, final PsiFile psiFile) {
  55. if (editor == null) return null;
  56. String selectedText = editor.getSelectionModel().getSelectedText();
  57. if (selectedText != null) {
  58. return new TextRange(editor.getSelectionModel().getSelectionStart(), editor.getSelectionModel().getSelectionEnd());
  59. }
  60. PsiElement psiElement = psiFile.findElementAt(editor.getCaretModel().getOffset());
  61. if (psiElement==null || psiElement instanceof PsiWhiteSpace) return null;
  62. return psiElement.getTextRange();
  63. }
  64. public static boolean mustBePropertyKey(@NotNull Project project,
  65. @NotNull PsiLiteralExpression expression,
  66. @NotNull Map<String, Object> annotationAttributeValues) {
  67. final PsiElement parent = expression.getParent();
  68. if (parent instanceof PsiVariable) {
  69. final PsiAnnotation annotation = AnnotationUtil.findAnnotation((PsiVariable)parent, AnnotationUtil.PROPERTY_KEY);
  70. if (annotation != null) {
  71. return processAnnotationAttributes(annotationAttributeValues, annotation);
  72. }
  73. }
  74. return isPassedToAnnotatedParam(project, expression, AnnotationUtil.PROPERTY_KEY, annotationAttributeValues, null);
  75. }
  76. public static boolean isPassedToAnnotatedParam(@NotNull Project project,
  77. @NotNull PsiExpression expression,
  78. final String annFqn,
  79. @Nullable Map<String, Object> annotationAttributeValues,
  80. @Nullable final Set<PsiModifierListOwner> nonNlsTargets) {
  81. expression = getToplevelExpression(project, expression);
  82. final PsiElement parent = expression.getParent();
  83. if (!(parent instanceof PsiExpressionList)) return false;
  84. int idx = -1;
  85. final PsiExpression[] args = ((PsiExpressionList)parent).getExpressions();
  86. for (int i = 0; i < args.length; i++) {
  87. PsiExpression arg = args[i];
  88. if (PsiTreeUtil.isAncestor(arg, expression, false)) {
  89. idx = i;
  90. break;
  91. }
  92. }
  93. if (idx == -1) return false;
  94. PsiElement grParent = parent.getParent();
  95. if (grParent instanceof PsiAnonymousClass) {
  96. grParent = grParent.getParent();
  97. }
  98. if (grParent instanceof PsiCall) {
  99. PsiMethod method = ((PsiCall)grParent).resolveMethod();
  100. if (method != null && isMethodParameterAnnotatedWith(method, idx, null, annFqn, annotationAttributeValues, nonNlsTargets)) {
  101. return true;
  102. }
  103. }
  104. return false;
  105. }
  106. private static final Key<ParameterizedCachedValue<PsiExpression, Pair<Project, PsiExpression>>> TOP_LEVEL_EXPRESSION = Key.create("TOP_LEVEL_EXPRESSION");
  107. private static final ParameterizedCachedValueProvider<PsiExpression, Pair<Project, PsiExpression>> TOP_LEVEL_PROVIDER =
  108. new ParameterizedCachedValueProvider<PsiExpression, Pair<Project, PsiExpression>>() {
  109. @Override
  110. public CachedValueProvider.Result<PsiExpression> compute(Pair<Project, PsiExpression> pair) {
  111. PsiExpression param = pair.second;
  112. Project project = pair.first;
  113. PsiExpression topLevel = getTopLevel(project, param);
  114. ParameterizedCachedValue<PsiExpression, Pair<Project, PsiExpression>> cachedValue = param.getUserData(TOP_LEVEL_EXPRESSION);
  115. assert cachedValue != null;
  116. int i = 0;
  117. for (PsiElement element = param; element != topLevel; element = element.getParent(), i++) {
  118. if (i % 10 == 0) { // optimization: store up link to the top level expression in each 10nth element
  119. element.putUserData(TOP_LEVEL_EXPRESSION, cachedValue);
  120. }
  121. }
  122. return CachedValueProvider.Result.create(topLevel, PsiManager.getInstance(project).getModificationTracker());
  123. }
  124. };
  125. @NotNull
  126. public static PsiExpression getToplevelExpression(@NotNull final Project project, @NotNull final PsiExpression expression) {
  127. if (expression instanceof PsiBinaryExpression || expression.getParent() instanceof PsiBinaryExpression) { //can be large, cache
  128. return CachedValuesManager.getManager(project).getParameterizedCachedValue(expression, TOP_LEVEL_EXPRESSION, TOP_LEVEL_PROVIDER, true,
  129. Pair.create(project, expression));
  130. }
  131. return getTopLevel(project, expression);
  132. }
  133. @NotNull
  134. private static PsiExpression getTopLevel(Project project, @NotNull PsiExpression expression) {
  135. int i = 0;
  136. while (expression.getParent() instanceof PsiExpression) {
  137. i++;
  138. final PsiExpression parent = (PsiExpression)expression.getParent();
  139. if (parent instanceof PsiConditionalExpression &&
  140. ((PsiConditionalExpression)parent).getCondition() == expression) break;
  141. expression = parent;
  142. if (expression instanceof PsiAssignmentExpression) break;
  143. if (i > 10 && expression instanceof PsiBinaryExpression) {
  144. ParameterizedCachedValue<PsiExpression, Pair<Project, PsiExpression>> value = expression.getUserData(TOP_LEVEL_EXPRESSION);
  145. if (value != null && value.hasUpToDateValue()) {
  146. return getToplevelExpression(project, expression); // optimization: use caching for big hierarchies
  147. }
  148. }
  149. }
  150. return expression;
  151. }
  152. public static boolean isMethodParameterAnnotatedWith(final PsiMethod method,
  153. final int idx,
  154. @Nullable Collection<PsiMethod> processed,
  155. final String annFqn,
  156. @Nullable Map<String, Object> annotationAttributeValues,
  157. @Nullable final Set<PsiModifierListOwner> nonNlsTargets) {
  158. if (processed != null) {
  159. if (processed.contains(method)) return false;
  160. }
  161. else {
  162. processed = new THashSet<PsiMethod>();
  163. }
  164. processed.add(method);
  165. final PsiParameter[] params = method.getParameterList().getParameters();
  166. PsiParameter param;
  167. if (idx >= params.length) {
  168. if (params.length == 0) {
  169. return false;
  170. }
  171. PsiParameter lastParam = params [params.length-1];
  172. if (lastParam.isVarArgs()) {
  173. param = lastParam;
  174. }
  175. else {
  176. return false;
  177. }
  178. }
  179. else {
  180. param = params[idx];
  181. }
  182. final PsiAnnotation annotation = AnnotationUtil.findAnnotation(param, annFqn);
  183. if (annotation != null) {
  184. return processAnnotationAttributes(annotationAttributeValues, annotation);
  185. }
  186. if (nonNlsTargets != null) {
  187. nonNlsTargets.add(param);
  188. }
  189. final PsiMethod[] superMethods = method.findSuperMethods();
  190. for (PsiMethod superMethod : superMethods) {
  191. if (isMethodParameterAnnotatedWith(superMethod, idx, processed, annFqn, annotationAttributeValues, null)) return true;
  192. }
  193. return false;
  194. }
  195. private static boolean processAnnotationAttributes(@Nullable Map<String, Object> annotationAttributeValues, @NotNull PsiAnnotation annotation) {
  196. if (annotationAttributeValues != null) {
  197. final PsiAnnotationParameterList parameterList = annotation.getParameterList();
  198. final PsiNameValuePair[] attributes = parameterList.getAttributes();
  199. for (PsiNameValuePair attribute : attributes) {
  200. final String name = attribute.getName();
  201. if (annotationAttributeValues.containsKey(name)) {
  202. annotationAttributeValues.put(name, attribute.getValue());
  203. }
  204. }
  205. }
  206. return true;
  207. }
  208. public static boolean isValidPropertyReference(@NotNull Project project,
  209. @NotNull PsiLiteralExpression expression,
  210. @NotNull String key,
  211. @NotNull Ref<String> outResourceBundle) {
  212. final HashMap<String, Object> annotationAttributeValues = new HashMap<String, Object>();
  213. annotationAttributeValues.put(AnnotationUtil.PROPERTY_KEY_RESOURCE_BUNDLE_PARAMETER, null);
  214. if (mustBePropertyKey(project, expression, annotationAttributeValues)) {
  215. final Object resourceBundleName = annotationAttributeValues.get(AnnotationUtil.PROPERTY_KEY_RESOURCE_BUNDLE_PARAMETER);
  216. if (!(resourceBundleName instanceof PsiExpression)) {
  217. return false;
  218. }
  219. PsiExpression expr = (PsiExpression)resourceBundleName;
  220. final Object value = JavaPsiFacade.getInstance(expr.getProject()).getConstantEvaluationHelper().computeConstantExpression(expr);
  221. if (value == null) {
  222. return false;
  223. }
  224. String bundleName = value.toString();
  225. outResourceBundle.set(bundleName);
  226. return isPropertyRef(expression, key, bundleName);
  227. }
  228. return true;
  229. }
  230. public static boolean isPropertyRef(final PsiLiteralExpression expression, final String key, final String resourceBundleName) {
  231. if (resourceBundleName == null) {
  232. return !PropertiesUtil.findPropertiesByKey(expression.getProject(), key).isEmpty();
  233. }
  234. else {
  235. final List<PropertiesFile> propertiesFiles = propertiesFilesByBundleName(resourceBundleName, expression);
  236. boolean containedInPropertiesFile = false;
  237. for (PropertiesFile propertiesFile : propertiesFiles) {
  238. containedInPropertiesFile |= propertiesFile.findPropertyByKey(key) != null;
  239. }
  240. return containedInPropertiesFile;
  241. }
  242. }
  243. public static Set<String> suggestExpressionOfType(final PsiClassType type, final PsiLiteralExpression context) {
  244. PsiVariable[] variables = MacroUtil.getVariablesVisibleAt(context, "");
  245. Set<String> result = new LinkedHashSet<String>();
  246. for (PsiVariable var : variables) {
  247. PsiType varType = var.getType();
  248. if (type == null || type.isAssignableFrom(varType)) {
  249. result.add(var.getNameIdentifier().getText());
  250. }
  251. }
  252. PsiExpression[] expressions = MacroUtil.getStandardExpressionsOfType(context, type);
  253. for (PsiExpression expression : expressions) {
  254. result.add(expression.getText());
  255. }
  256. if (type != null) {
  257. addAvailableMethodsOfType(type, context, result);
  258. }
  259. return result;
  260. }
  261. private static void addAvailableMethodsOfType(final PsiClassType type, final PsiLiteralExpression context, final Collection<String> result) {
  262. PsiScopesUtil.treeWalkUp(new PsiScopeProcessor() {
  263. public boolean execute(@NotNull PsiElement element, ResolveState state) {
  264. if (element instanceof PsiMethod) {
  265. PsiMethod method = (PsiMethod)element;
  266. PsiType returnType = method.getReturnType();
  267. if (returnType != null && TypeConversionUtil.isAssignable(type, returnType)
  268. && method.getParameterList().getParametersCount() == 0) {
  269. result.add(method.getName() + "()");
  270. }
  271. }
  272. return true;
  273. }
  274. public <T> T getHint(@NotNull Key<T> hintKey) {
  275. return null;
  276. }
  277. public void handleEvent(Event event, Object associated) {
  278. }
  279. }, context, null);
  280. }
  281. /**
  282. * Returns number of different parameters in i18n message. For example, for string
  283. * <i>Class {0} info: Class {0} extends class {1} and implements interface {2}</i>
  284. * number of parameters is 3.
  285. *
  286. * @param expression i18n literal
  287. * @return number of parameters
  288. */
  289. public static int getPropertyValueParamsMaxCount(final PsiLiteralExpression expression) {
  290. int maxCount = -1;
  291. for (PsiReference reference : expression.getReferences()) {
  292. if (reference instanceof PsiPolyVariantReference) {
  293. for (ResolveResult result : ((PsiPolyVariantReference)reference).multiResolve(false)) {
  294. if (result.isValidResult() && result.getElement() instanceof IProperty) {
  295. String value = ((IProperty)result.getElement()).getValue();
  296. MessageFormat format;
  297. try {
  298. format = new MessageFormat(value);
  299. }
  300. catch (Exception e) {
  301. continue; // ignore syntax error
  302. }
  303. try {
  304. int count = format.getFormatsByArgumentIndex().length;
  305. maxCount = Math.max(maxCount, count);
  306. }
  307. catch (IllegalArgumentException ignored) {
  308. }
  309. }
  310. }
  311. }
  312. }
  313. return maxCount;
  314. }
  315. }