blob: d8dba5019c9bd20e9f5764821c065986172cbf9e [file] [log] [blame]
/*
* Copyright (c) 2010-2021 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
*/
package org.eclipse.scout.sdk.s2i.nls
import com.intellij.codeInsight.AnnotationUtil
import com.intellij.codeInsight.AnnotationUtil.isAnnotated
import com.intellij.patterns.*
import com.intellij.patterns.PsiJavaPatterns.psiElement
import com.intellij.patterns.PsiJavaPatterns.psiExpression
import com.intellij.patterns.StandardPatterns.or
import com.intellij.patterns.XmlPatterns.xmlAttribute
import com.intellij.patterns.XmlPatterns.xmlTag
import com.intellij.psi.*
import com.intellij.psi.impl.source.tree.ElementType
import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl
import com.intellij.psi.xml.XmlAttributeValue
import com.intellij.util.ArrayUtil
import com.intellij.util.ProcessingContext
import org.eclipse.scout.sdk.core.s.apidef.IScoutVariousApi
import org.eclipse.scout.sdk.core.s.apidef.ScoutApi
import org.eclipse.scout.sdk.core.s.nls.TranslationStores
import org.eclipse.scout.sdk.core.s.nls.query.TranslationPatterns
import org.eclipse.scout.sdk.core.util.Strings
import java.util.*
/**
* The JS patterns are in [org.eclipse.scout.sdk.s2i.nls.completion.NlsCompletionContributorForJs] because the dependency to the JS module is optional!
*/
object PsiTranslationPatterns {
/**
* A pattern selecting <scout:message> tags and the containing keys attributes. This pattern is valid for .xml files (which includes .html files).
*/
val HTML_KEY_PATTERN = htmlKeyPattern()
/**
* A pattern selecting all arguments passed to Java methods having the @NlsKey annotation. This pattern is valid for .java files.
*/
val JAVA_NLS_KEY_PATTERN = or(javaNlsKeyArgumentPattern(), javaTextsGetPattern() /* legacy case */)
private fun getTranslationKeyFromJava(element: PsiElement?): String? {
val key = resolveExpressionToString(element) {
JAVA_NLS_KEY_PATTERN.accepts(it)
}
if (key != null) {
return key
}
// a method call directly returning the nls key
return (element as? PsiCall)
?.takeIf { JAVA_NLS_KEY_PATTERN.accepts(it) }
?.resolveMethod()
?.body
?.children
?.mapNotNull { it as? PsiReturnStatement }
?.map { it.returnValue }
?.mapNotNull { resolveExpressionToString(it) }
?.firstOrNull()
}
private fun resolveExpressionToString(element: PsiElement?, locationFilter: ((Any?) -> Boolean)? = null): String? {
if (element is PsiLiteralExpressionImpl) {
return asStringLiteral(element)
.takeIf { locationFilter == null || locationFilter(it) }
?.value as? String
}
// reference to variable or constant
val variable = (element as? PsiReference)
?.takeIf { locationFilter == null || locationFilter(it) }
?.resolve() as? PsiVariable ?: return null
val constant = variable.computeConstantValue() as? String
if (constant != null) {
return constant
}
return asStringLiteral(variable.initializer)?.value as? String
}
private fun asStringLiteral(element: PsiElement?) = (element as? PsiLiteralExpressionImpl)
?.takeIf { ElementType.STRING_LITERALS.contains(it.literalElementType) }
private fun getTranslationKeyFromHtml(element: PsiElement?): String? {
val takeIf = (element as? XmlAttributeValue)
?.takeIf { HTML_KEY_PATTERN.accepts(it) }
return takeIf
?.value
}
private fun htmlKeyPattern(): PsiElementPattern.Capture<PsiElement> = PlatformPatterns.psiElement()
.withParent(xmlAttribute(TranslationPatterns.HtmlScoutMessagePattern.ATTRIBUTE_NAME)
.withParent(xmlTag().withName(TranslationPatterns.HtmlScoutMessagePattern.SCOUT_MESSAGE_TAG_NAME)))
private fun javaNlsKeyArgumentPattern(): PsiExpressionPattern.Capture<PsiExpression> {
val patternsForAllScoutVersions = ScoutApi.allKnown()
.map { it.NlsKey().fqn() }
.distinct()
.map { psiExpression().with(ArgumentToMethodParameterHavingAnnotation(it)) }
.toArray<ElementPattern<PsiExpression>> { len -> arrayOfNulls(len) }
if (patternsForAllScoutVersions.size == 1) {
return psiExpression().and(patternsForAllScoutVersions[0])
}
return psiExpression().andOr(*patternsForAllScoutVersions)
}
/**
* To support Scout version < 10.0.40 where the @NlsKey annotation did not exist yet.
* Can be removed if only Scout versions >= 10.0.40 are supported.
*/
private fun javaTextsGetPattern(): PsiJavaElementPattern.Capture<PsiElement> {
val patternsForAllScoutVersions = ScoutApi.allKnown()
.map { it.TEXTS() }
.distinct()
.map { javaTextsGetPattern(it) }
.toArray<ElementPattern<PsiLiteralExpression>> { len -> arrayOfNulls(len) }
if (patternsForAllScoutVersions.size == 1) {
return psiElement().and(patternsForAllScoutVersions[0])
}
return psiElement().andOr(*patternsForAllScoutVersions)
}
private fun javaTextsGetPattern(texts: IScoutVariousApi.TEXTS): ElementPattern<PsiLiteralExpression> {
val stringFqn = String::class.java.name
val localeFqn = Locale::class.java.name
val wildcardArgument = ".."
val getWithoutLocale = PsiJavaPatterns.psiMethod()
.withName(texts.methodName)
.definedInClass(texts.fqn())
.withParameters(stringFqn, wildcardArgument)
val getWithLocale = PsiJavaPatterns.psiMethod()
.withName(texts.methodName)
.definedInClass(texts.fqn())
.withParameters(localeFqn, stringFqn, wildcardArgument)
val getWithFallbackWithoutLocale = PsiJavaPatterns.psiMethod()
.withName(texts.withFallbackMethodName)
.definedInClass(texts.fqn())
.withParameters(stringFqn, stringFqn, wildcardArgument)
val getWithFallbackWithLocale = PsiJavaPatterns.psiMethod()
.withName(texts.withFallbackMethodName)
.definedInClass(texts.fqn())
.withParameters(localeFqn, stringFqn, stringFqn, wildcardArgument)
return or(
PsiJavaPatterns.literalExpression().methodCallParameter(0, getWithoutLocale),
PsiJavaPatterns.literalExpression().methodCallParameter(1, getWithLocale),
PsiJavaPatterns.literalExpression().methodCallParameter(0, getWithFallbackWithoutLocale),
PsiJavaPatterns.literalExpression().methodCallParameter(1, getWithFallbackWithoutLocale),
PsiJavaPatterns.literalExpression().methodCallParameter(1, getWithFallbackWithLocale),
PsiJavaPatterns.literalExpression().methodCallParameter(2, getWithFallbackWithLocale)
)
}
private class ArgumentToMethodParameterHavingAnnotation(val annotationFqn: String) : PatternCondition<PsiExpression>("argumentToMethodParameterHavingAnnotation=$annotationFqn") {
override fun accepts(expression: PsiExpression, context: ProcessingContext?): Boolean {
val parent = expression.parent as? PsiExpressionList ?: return false
val grandParent = parent.parent as? PsiCall ?: return false
val parameterIndex = ArrayUtil.indexOf(parent.expressions, expression)
if (parameterIndex < 0) return false
val calledMethod = grandParent.resolveMethod() ?: return false
return isMethodParameterAnnotated(calledMethod, parameterIndex)
}
private fun isMethodParameterAnnotated(method: PsiMethod, paramIndex: Int): Boolean {
val params = method.parameterList.parameters
if (paramIndex >= params.size) return false
val param = params[paramIndex]
return isAnnotated(param, annotationFqn, AnnotationUtil.CHECK_TYPE or AnnotationUtil.CHECK_HIERARCHY)
}
}
class JavaTranslationSpec(element: PsiElement) : TranslationLanguageSpec(element, TranslationStores.DependencyScope.JAVA, "\"", PsiTranslationPatterns::getTranslationKeyFromJava) {
override fun createNewLiteral(text: String): PsiElement {
val literalValue = Strings.toStringLiteral(decorateTranslationKey(text), stringDelimiter, true).toString()
return JavaPsiFacade.getElementFactory(element.project).createExpressionFromText(literalValue, element)
}
}
class HtmlTranslationSpec(element: PsiElement) : TranslationLanguageSpec(element, TranslationStores.DependencyScope.JAVA, "\"", PsiTranslationPatterns::getTranslationKeyFromHtml) {
override fun createNewLiteral(text: String): PsiElement = XmlElementFactory.getInstance(element.project)
.createAttribute(TranslationPatterns.HtmlScoutMessagePattern.ATTRIBUTE_NAME, decorateTranslationKey(text), element)
.valueElement!!
}
}