Refactoring: rename kotlin-android-plugin to idea-android

This commit is contained in:
Natalia Ukhorskaya
2016-03-14 11:49:20 +03:00
parent d2de74226e
commit 334c6ba71a
20 changed files with 5 additions and 5 deletions
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="android-plugin" level="project" />
<orderEntry type="library" name="gradle-and-groovy-plugin" level="project" />
<orderEntry type="library" name="idea-full" level="project" />
<orderEntry type="module" module-name="idea" />
<orderEntry type="module" module-name="ide-common" />
<orderEntry type="module" module-name="idea-analysis" />
<orderEntry type="module" module-name="util" />
<orderEntry type="module" module-name="frontend" />
<orderEntry type="module" module-name="frontend.java" />
<orderEntry type="module" module-name="compiler-tests" scope="TEST" />
</component>
</module>
@@ -0,0 +1 @@
org.jetbrains.kotlin.android.KotlinOutputParser
@@ -0,0 +1,5 @@
<html>
<body>
This inspection reports identifiers in Android projects which are not accepted by the Android runtime (for example, method names containing spaces).
</body>
</html>
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2015 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 org.jetbrains.kotlin.android;
import com.android.ide.common.blame.parser.PatternAwareOutputParser;
import com.android.ide.common.blame.parser.util.OutputLineReader;
import com.android.utils.ILogger;
import java.util.List;
public class KotlinOutputParser implements PatternAwareOutputParser {
@Override
public boolean parse(String s, OutputLineReader reader, List list, ILogger logger) {
return KotlinOutputParserHelperKt.parse(s, reader, list, logger);
}
}
@@ -0,0 +1,307 @@
/*
* Copyright 2010-2015 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 org.jetbrains.kotlin.android
import com.android.ide.common.blame.parser.util.OutputLineReader
import com.android.utils.ILogger
import com.google.common.collect.ImmutableList
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.text.StringUtil
import java.io.File
import java.lang.reflect.Constructor
import java.util.*
import java.util.regex.Pattern
import java.lang.reflect.Array as RArray
fun parse(line: String, reader: OutputLineReader, messages: MutableList<Any>, logger: ILogger): Boolean {
val colonIndex1 = line.colon()
val severity = if (colonIndex1 >= 0) line.substringBeforeAndTrim(colonIndex1) else return false
if (!severity.startsWithSeverityPrefix()) return false
val lineWoSeverity = line.substringAfterAndTrim(colonIndex1)
val colonIndex2 = lineWoSeverity.colon().skipDriveOnWin(lineWoSeverity)
if (colonIndex2 >= 0) {
val path = lineWoSeverity.substringBeforeAndTrim(colonIndex2)
val file = File(path)
if (!file.isFile && FileUtilRt.getExtension(file.name) != "kt") {
return addMessage(KotlinOutputParserHelper.createMessage(logger, severity, lineWoSeverity.amendNextLinesIfNeeded(reader)), messages)
}
val lineWoPath = lineWoSeverity.substringAfterAndTrim(colonIndex2)
val colonIndex3 = lineWoPath.colon()
if (colonIndex3 >= 0) {
val position = lineWoPath.substringBeforeAndTrim(colonIndex3)
val matcher = POSITION_PATTERN.matcher(position)
val message = lineWoPath.substringAfterAndTrim(colonIndex3).amendNextLinesIfNeeded(reader)
if (matcher.matches()) {
val lineNumber = matcher.group(1)
val symbolNumber = matcher.group(2)
if (lineNumber != null && symbolNumber != null) {
try {
return addMessage(KotlinOutputParserHelper.createMessage(logger, severity, message, path, lineNumber.toInt(), symbolNumber.toInt(), symbolNumber.toInt()), messages)
}
catch (e: NumberFormatException) {
// ignore
}
}
}
return addMessage(KotlinOutputParserHelper.createMessage(logger, severity, message), messages)
}
else {
return addMessage(KotlinOutputParserHelper.createMessage(logger, severity, lineWoSeverity.amendNextLinesIfNeeded(reader)), messages)
}
}
return false
}
private val COLON = ":"
private val POSITION_PATTERN = Pattern.compile("\\(([0-9]*), ([0-9]*)\\)")
private fun String.amendNextLinesIfNeeded(reader: OutputLineReader): String {
var nextLine = reader.readLine()
val builder = StringBuilder(this)
while (nextLine != null && nextLine.isNextMessage().not()) {
builder.append("\n").append(nextLine)
if (!reader.hasNextLine()) break
nextLine = reader.readLine()
}
if (nextLine != null) {
// This code is needed for compatibility with AS 2.0 and IDEA 15.0, because of difference in android plugins
val positionField = try {
reader.javaClass.getDeclaredField("myPosition")
}
catch(e: Throwable) {
null
}
if (positionField != null) {
positionField.isAccessible = true
positionField.setInt(reader, positionField.getInt(reader) - 1)
}
}
return builder.toString()
}
private fun String.isNextMessage(): Boolean {
val colonIndex1 = indexOf(COLON)
return colonIndex1 == 0
|| (colonIndex1 >= 0 && substring(0, colonIndex1).startsWithSeverityPrefix()) // Next Kotlin message
|| StringUtil.containsIgnoreCase(this, "FAILURE")
|| StringUtil.containsIgnoreCase(this, "FAILED")
}
private fun String.startsWithSeverityPrefix(): Boolean {
return when (this.trim()) {
"e", "w", "i", "v" -> true
else -> false
}
}
private fun String.substringAfterAndTrim(index: Int) = substring(index + 1).trim()
private fun String.substringBeforeAndTrim(index: Int) = substring(0, index).trim()
private fun String.colon() = indexOf(COLON)
private fun Int.skipDriveOnWin(line: String): Int {
return if (this == 1) line.indexOf(COLON, this + 1) else this
}
private fun addMessage(message: Any?, messages: MutableList<Any>): Boolean {
if (message == null) return false
var duplicatesPrevious = false
val messageCount = messages.size
if (messageCount > 0) {
val lastMessage = messages.get(messageCount - 1)
duplicatesPrevious = lastMessage == message
}
if (!duplicatesPrevious) {
messages.add(message)
}
return true
}
object KotlinOutputParserHelper {
private val isNewAndroidPlugin: Boolean
private val packagePrefix: String
private val severityObjectMap: HashMap<String, Any> = HashMap()
init {
isNewAndroidPlugin = try {
Class.forName("com.android.ide.common.blame.Message")
true
}
catch (e: ClassNotFoundException) {
false
}
packagePrefix = if (isNewAndroidPlugin) "com.android.ide.common.blame" else "com.android.ide.common.blame.output"
loadSeverityEnums()
}
private val simpleMessageConstructor: Constructor<*> by lazy {
if (!isNewAndroidPlugin) {
val messageClass = Class.forName("$packagePrefix.GradleMessage")
val messageKindClass = Class.forName("$packagePrefix.GradleMessage\$Kind")
messageClass.getConstructor(messageKindClass, String::class.java)
}
else {
val messageClass = Class.forName("$packagePrefix.Message")
val messageKindClass = Class.forName("$packagePrefix.Message\$Kind")
messageClass.getConstructor(
messageKindClass,
String::class.java,
String::class.java,
ImmutableList::class.java)
}
}
private val complexMessageConstructor: Constructor<*> by lazy {
if (!isNewAndroidPlugin) {
val messageClass = Class.forName("$packagePrefix.GradleMessage")
val messageKindClass = Class.forName("$packagePrefix.GradleMessage\$Kind")
messageClass.getConstructor(
messageKindClass,
String::class.java, String::class.java,
Int::class.java, Int::class.java)
}
else {
val messageClass = Class.forName("$packagePrefix.Message")
val messageKindClass = Class.forName("$packagePrefix.Message\$Kind")
val sourceFilePositionClass = Class.forName("$packagePrefix.SourceFilePosition")
val sourceFilePositionArrayClass = Class.forName("[L$packagePrefix.SourceFilePosition;")
messageClass.getConstructor(
messageKindClass,
String::class.java,
sourceFilePositionClass,
sourceFilePositionArrayClass)
}
}
private val sourceFilePositionConstructor: Constructor<*> by lazy {
assert(isNewAndroidPlugin) { "This property should be used only for New Android Plugin" }
val sourcePositionClass = Class.forName("$packagePrefix.SourcePosition")
val sourceFilePositionClass = Class.forName("$packagePrefix.SourceFilePosition")
sourceFilePositionClass.getConstructor(File::class.java, sourcePositionClass)
}
private val sourcePositionConstructor: Constructor<*> by lazy {
assert(isNewAndroidPlugin) { "This property should be used only for New Android Plugin" }
val sourcePositionClass = Class.forName("$packagePrefix.SourcePosition")
sourcePositionClass.getConstructor(Int::class.java, Int::class.java, Int::class.java)
}
private val sourcePositionVarargArg: Any by lazy {
assert(isNewAndroidPlugin) { "This property should be used only for New Android Plugin" }
val sourceFilePositionClass = Class.forName("$packagePrefix.SourceFilePosition")
RArray.newInstance(sourceFilePositionClass, 0)
}
private fun loadSeverityEnums() {
val messageKindClass = if (isNewAndroidPlugin)
Class.forName("$packagePrefix.Message\$Kind")
else
Class.forName("$packagePrefix.GradleMessage\$Kind")
val messageKindConstants = messageKindClass.enumConstants as Array<Any>
for (kind in messageKindConstants) {
when(kind.toString()) {
"ERROR" -> severityObjectMap.put("e", kind)
"WARNING" -> severityObjectMap.put("w", kind)
"INFO" -> severityObjectMap.put("i", kind)
"SIMPLE" -> severityObjectMap.put("v", kind)
}
}
}
fun createMessage(
logger: ILogger,
severity: String,
text: String,
file: String? = null,
lineNumber: Int? = null,
columnIndex: Int? = null,
offset: Int? = null
): Any? {
try {
val severityConst: Any = severityObjectMap[severity] ?: return null
if (isNewAndroidPlugin) {
return createNewMessage(severityConst, text.trim(), file, lineNumber, columnIndex, offset)
}
else {
return createOldMessage(severityConst, text.trim(), file, lineNumber, columnIndex)
}
}
catch(e: Throwable) {
logger.error(e, "Exception from KotlinOutputParser")
}
return null
}
private fun createNewMessage(
severityConst: Any,
text: String,
file: String?,
lineNumber: Int?,
columnIndex: Int?,
offset: Int?
): Any? {
if (file == null || lineNumber == null || columnIndex == null || offset == null) {
return simpleMessageConstructor.newInstance(
severityConst,
text,
text,
ImmutableList.of<Any>())
}
else {
val sourcePositionObj = sourcePositionConstructor.newInstance(lineNumber - 1, columnIndex - 1, offset)
val sourceFilePositionObj = sourceFilePositionConstructor.newInstance(File(file), sourcePositionObj)
return complexMessageConstructor.newInstance(
severityConst,
text,
sourceFilePositionObj,
sourcePositionVarargArg)
}
}
private fun createOldMessage(
severityConst: Any,
text: String,
file: String?,
lineNumber: Int?,
columnIndex: Int?
): Any? {
if (file == null || lineNumber == null || columnIndex == null) {
return simpleMessageConstructor.newInstance(severityConst, text)
}
else {
return complexMessageConstructor.newInstance(
severityConst,
text, file,
lineNumber, columnIndex)
}
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2016 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 org.jetbrains.kotlin.android.actions
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.EditorNotificationPanel
import com.intellij.ui.EditorNotifications
class KotlinNewActivityNotification : EditorNotifications.Provider<EditorNotificationPanel>() {
override fun getKey() = KEY
override fun createNotificationPanel(file: VirtualFile, editor: FileEditor): EditorNotificationPanel? {
if (NewKotlinActivityAction.willBeConvertedToKotlin(file)) {
return EditorNotificationPanel().apply {
setText("This file will be converted to Kotlin after Gradle project sync")
}
}
return null
}
companion object {
private val KEY = Key.create<EditorNotificationPanel>("Kotlin.new.activity.notification")
}
}
@@ -0,0 +1,222 @@
/*
* Copyright 2010-2016 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 org.jetbrains.kotlin.android.actions
import com.android.tools.idea.gradle.GradleSyncState
import com.android.tools.idea.gradle.project.GradleSyncListener
import com.intellij.history.core.RevisionsCollector
import com.intellij.history.core.revisions.Revision
import com.intellij.history.integration.LocalHistoryImpl
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiJavaFile
import org.jetbrains.android.facet.AndroidFacet
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.actions.JavaToKotlinAction
import org.jetbrains.kotlin.idea.refactoring.toPsiFile
import java.io.File
import kotlin.reflect.functions
import kotlin.reflect.memberFunctions
private val NEW_KOTLIN_ACTIVITY_START_LABEL = "Start New Kotlin Activity Action"
private val NEW_KOTLIN_ACTIVITY_END_LABEL = "Finish New Kotlin Activity Action"
class NewKotlinActivityAction: AnAction(KotlinIcons.FILE) {
companion object {
internal fun attachGradleSyncListener(project: Project) {
subscribe(project, gradleSyncListener)
}
internal fun willBeConvertedToKotlin(file: VirtualFile): Boolean {
return javaFilesToKotlin?.any {
it.virtualFile == file
} ?: false
}
private val LOG = Logger.getInstance(NewKotlinActivityAction::class.java)
private var javaFilesToKotlin: List<PsiJavaFile>? = null
private fun convertFilesAfterProjectSync(files: List<PsiJavaFile>) {
javaFilesToKotlin = files
}
private val gradleSyncListener = object: GradleSyncListener.Adapter() {
override fun syncSucceeded(project: Project) {
convertFiles(project)
}
override fun syncFailed(project: Project, errorMessage: String) {
convertFiles(project)
}
private fun convertFiles(project: Project) {
if (javaFilesToKotlin != null) {
DumbService.getInstance(project).smartInvokeLater {
val filesToConvert = javaFilesToKotlin!!.filter { it.isValid }
if (filesToConvert.isNotEmpty()) {
JavaToKotlinAction.convertFiles(filesToConvert, project, false)
}
javaFilesToKotlin = null
}
}
}
}
private fun subscribe(project: Project, listener: GradleSyncListener) {
try {
val subscribeFun = GradleSyncState::class.functions.find { it.name == "subscribe" && it.parameters.count() == 2 }
if (subscribeFun != null) {
// AS 2.0
subscribeFun.call(project, listener)
}
else {
// AS 1.5
val connection = project.messageBus.connect(project)
connection.subscribe(GradleSyncState.GRADLE_SYNC_TOPIC, gradleSyncListener)
}
}
catch(e: Throwable) {
LOG.error(e)
}
}
}
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
LocalHistoryImpl.getInstanceImpl().putSystemLabel(project, NEW_KOTLIN_ACTIVITY_START_LABEL)
val isSuccess = showWizard(e.dataContext)
LocalHistoryImpl.getInstanceImpl().putSystemLabel(project, NEW_KOTLIN_ACTIVITY_END_LABEL)
if (!isSuccess) return
val localHistory = LocalHistoryImpl.getInstanceImpl()
val gateway = localHistory.gateway!!
val localHistoryFacade = localHistory.facade
val revisionsCollector = RevisionsCollector(
localHistoryFacade, gateway.createTransientRootEntry(),
project.baseDir.path, project.locationHash, null)
val revisions = revisionsCollector.result
var endRevision: Revision? = null
for (rev in revisions) {
val label = rev.label ?: continue
if (label == NEW_KOTLIN_ACTIVITY_END_LABEL) {
endRevision = rev
}
if (label == NEW_KOTLIN_ACTIVITY_START_LABEL && endRevision != null) {
val javaFiles = arrayListOf<PsiJavaFile>()
val differences = endRevision.getDifferencesWith(rev)
for (difference in differences) {
if (difference.right == null && difference.isFile) {
val file = File(difference.left.path)
if (file.extension == "java") {
val psiFile = file.toPsiFile(project) as? PsiJavaFile
if (psiFile != null) {
javaFiles.add(psiFile)
}
}
}
}
if (javaFiles.isNotEmpty()) {
val syncState = GradleSyncState.getInstance(project)
if (syncState.isSyncInProgress) {
convertFilesAfterProjectSync(javaFiles)
}
else {
JavaToKotlinAction.convertFiles(javaFiles, project, false)
}
}
break
}
}
}
private fun showWizard(dataContext: DataContext): Boolean {
try {
val wizardClass = try {
// AS 1.5
Class.forName("com.android.tools.idea.wizard.NewAndroidActivityWizard")
}
catch(e: ClassNotFoundException) {
// AS 2.0
Class.forName("com.android.tools.idea.npw.NewAndroidActivityWizard")
}
val constructor = wizardClass.getConstructor(Module::class.java, VirtualFile::class.java, File::class.java)
val wizard = constructor.newInstance(
LangDataKeys.MODULE.getData(dataContext),
CommonDataKeys.VIRTUAL_FILE.getData(dataContext),
null)
wizardClass.kotlin.functions.firstOrNull { it.name == "init" }?.call(wizard)
return (wizardClass.kotlin.functions.firstOrNull { it.name == "showAndGet" }?.call(wizard) as? Boolean) ?: false
}
catch(e: Throwable) {
LOG.error(e)
return false
}
}
override fun update(e: AnActionEvent) {
val view = LangDataKeys.IDE_VIEW.getData(e.dataContext)
val module = LangDataKeys.MODULE.getData(e.dataContext)
val facet = if (module != null) AndroidFacet.getInstance(module) else null
val presentation = e.presentation
val isProjectReady = facet != null && isProjectReady(facet)
presentation.text = "Kotlin Activity" + if (isProjectReady) "" else " (Project not ready)"
presentation.isVisible = view != null && facet != null && isVisible(facet)
}
private fun isVisible(facet: AndroidFacet): Boolean {
try {
val shouldSetVisible = AndroidFacet::class.memberFunctions.singleOrNull {
it.name == "isGradleProject" || it.name == "requiresAndroidModel"
}
return shouldSetVisible?.call(facet) != null
}
catch(e: Throwable) {
LOG.error(e)
return false
}
}
private fun isProjectReady(facet: AndroidFacet): Boolean {
try {
val getAndroidProjectInfoFun = AndroidFacet::class.memberFunctions.singleOrNull {
it.name == "getIdeaAndroidProject" || it.name == "getAndroidModel"
}
return getAndroidProjectInfoFun?.call(facet) != null
}
catch(e: Throwable) {
LOG.error(e)
return false
}
}
override fun hashCode() = 0
override fun equals(other: Any?) = other is NewKotlinActivityAction
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2016 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 org.jetbrains.kotlin.android
import com.intellij.psi.PsiElement
import org.jetbrains.android.facet.AndroidFacet
fun PsiElement.getAndroidFacetForFile(): AndroidFacet? {
val file = containingFile ?: return null
return AndroidFacet.getInstance(file)
}
@@ -0,0 +1,84 @@
/*
* Copyright 2010-2016 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 org.jetbrains.kotlin.android.configure;
import com.intellij.openapi.module.Module;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.KotlinPluginUtil;
import org.jetbrains.kotlin.idea.configuration.KotlinWithGradleConfigurator;
import org.jetbrains.kotlin.resolve.TargetPlatform;
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
public class KotlinAndroidGradleModuleConfigurator extends KotlinWithGradleConfigurator {
public static final String NAME = "android-gradle";
private static final String APPLY_KOTLIN_ANDROID = "apply plugin: 'kotlin-android'";
@NotNull
@Override
public String getName() {
return NAME;
}
@NotNull
@Override
public TargetPlatform getTargetPlatform() {
return JvmPlatform.INSTANCE;
}
@NotNull
@Override
public String getPresentableText() {
return "Android with Gradle";
}
@Override
public boolean isApplicable(@NotNull Module module) {
return KotlinPluginUtil.isAndroidGradleModule(module);
}
@Override
protected String getApplyPluginDirective() {
return APPLY_KOTLIN_ANDROID;
}
@Override
protected boolean addSourceSetsBlock(@NotNull GroovyFile file) {
GrClosableBlock androidBlock = getAndroidBlock(file);
return addLastExpressionInBlockIfNeeded(SOURCE_SET, getSourceSetsBlock(androidBlock));
}
@Override
protected boolean addElementsToFile(@NotNull GroovyFile groovyFile, boolean isTopLevelProjectFile, @NotNull String version) {
if (isTopLevelProjectFile) {
return addElementsToProjectFile(groovyFile, version);
}
else {
return addElementsToModuleFile(groovyFile, version);
}
}
@NotNull
private static GrClosableBlock getAndroidBlock(@NotNull GroovyFile file) {
return getBlockOrCreate(file, "android");
}
KotlinAndroidGradleModuleConfigurator() {
}
}
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2016 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 org.jetbrains.kotlin.android.facet
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.android.actions.NewKotlinActivityAction
class KotlinAndroidStartupManager(private val project: Project) {
init {
NewKotlinActivityAction.attachGradleSyncListener(project)
}
}
@@ -0,0 +1,89 @@
/*
* Copyright 2010-2016 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 org.jetbrains.kotlin.android.inspection
import com.intellij.codeInsight.FileModificationService
import com.intellij.codeInspection.*
import com.intellij.ide.DataManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.refactoring.rename.RenameHandlerRegistry
import org.jetbrains.kotlin.android.getAndroidFacetForFile
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
class IllegalIdentifierInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
return object : KtVisitorVoid() {
override fun visitElement(element: PsiElement) {
if (element.node.elementType != KtTokens.IDENTIFIER) return
val text = element.text
// '`' can't be escaped now
if (!text.startsWith('`') || !text.endsWith('`')) return
val unquotedName = KtPsiUtil.unquoteIdentifier(text)
// This is already an error
if (unquotedName.isEmpty()) return
if (!unquotedName.all { isValidDalvikCharacter(it) } && checkAndroidFacet(element)) {
holder.registerProblem(element,
"Identifier not allowed in Android projects",
ProblemHighlightType.GENERIC_ERROR,
RenameIdentifierFix())
}
}
fun checkAndroidFacet(element: PsiElement): Boolean {
return element.getAndroidFacetForFile() != null || ApplicationManager.getApplication().isUnitTestMode
}
// https://source.android.com/devices/tech/dalvik/dex-format.html#string-syntax
fun isValidDalvikCharacter(c: Char) = when (c) {
in 'A'..'Z' -> true
in 'a'..'z' -> true
in '0'..'9' -> true
'$', '-', '_' -> true
in '\u00a1' .. '\u1fff' -> true
in '\u2010' .. '\u2027' -> true
in '\u2030' .. '\ud7ff' -> true
in '\ue000' .. '\uffef' -> true
else -> false
}
}
}
class RenameIdentifierFix : LocalQuickFix {
override fun getName() = "Rename"
override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.psiElement ?: return
val file = element.containingFile ?: return
if (!FileModificationService.getInstance().prepareFileForWrite(file)) return
val editorManager = FileEditorManager.getInstance(project)
val editor = editorManager.getSelectedEditor(file.virtualFile) ?: return
val dataContext = DataManager.getInstance().getDataContext(editor.component)
val renameHandler = RenameHandlerRegistry.getInstance().getRenameHandler(dataContext)
renameHandler?.invoke(project, arrayOf(element), dataContext);
}
}
}
@@ -0,0 +1,147 @@
/*
* Copyright 2010-2015 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 org.jetbrains.kotlin.android.navigation;
import com.android.resources.ResourceType;
import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.xml.XmlElement;
import org.jetbrains.android.dom.AndroidAttributeValue;
import org.jetbrains.android.dom.manifest.Manifest;
import org.jetbrains.android.dom.manifest.ManifestElementWithRequiredName;
import org.jetbrains.android.dom.resources.Attr;
import org.jetbrains.android.dom.resources.DeclareStyleable;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.resourceManagers.LocalResourceManager;
import org.jetbrains.android.resourceManagers.ResourceManager;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.android.AndroidUtilKt;
import org.jetbrains.kotlin.psi.KtSimpleNameExpression;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// TODO: ask for extension point
// this class is mostly copied from org.jetbrains.android.AndroidGotoDeclarationHandler
public class KotlinAndroidGotoDeclarationHandler implements GotoDeclarationHandler {
@Override
public PsiElement[] getGotoDeclarationTargets(@Nullable PsiElement sourceElement, int offset, Editor editor) {
KtSimpleNameExpression referenceExpression = GotoResourceHelperKt.getReferenceExpression(sourceElement);
if (referenceExpression == null) {
return null;
}
AndroidFacet facet = AndroidUtilKt.getAndroidFacetForFile(referenceExpression);
if (facet == null) {
return null;
}
AndroidResourceUtil.MyReferredResourceFieldInfo info = GotoResourceHelperKt.getInfo(referenceExpression, facet);
if (info == null) return null;
String nestedClassName = info.getClassName();
String fieldName = info.getFieldName();
List<PsiElement> resourceList = new ArrayList<PsiElement>();
if (info.isFromManifest()) {
collectManifestElements(nestedClassName, fieldName, facet, resourceList);
}
else {
ResourceManager manager = info.isSystem()
? facet.getSystemResourceManager(false)
: facet.getLocalResourceManager();
if (manager == null) {
return null;
}
manager.collectLazyResourceElements(nestedClassName, fieldName, false, referenceExpression, resourceList);
if (manager instanceof LocalResourceManager) {
LocalResourceManager lrm = (LocalResourceManager) manager;
if (nestedClassName.equals(ResourceType.ATTR.getName())) {
for (Attr attr : lrm.findAttrs(fieldName)) {
resourceList.add(attr.getName().getXmlAttributeValue());
}
}
else if (nestedClassName.equals(ResourceType.STYLEABLE.getName())) {
for (DeclareStyleable styleable : lrm.findStyleables(fieldName)) {
resourceList.add(styleable.getName().getXmlAttributeValue());
}
for (Attr styleable : lrm.findStyleableAttributesByFieldName(fieldName)) {
resourceList.add(styleable.getName().getXmlAttributeValue());
}
}
}
}
if (resourceList.size() > 1) {
// Sort to ensure the output is stable, and to prefer the base folders
Collections.sort(resourceList, AndroidResourceUtil.RESOURCE_ELEMENT_COMPARATOR);
}
return resourceList.toArray(new PsiElement[resourceList.size()]);
}
private static void collectManifestElements(
@NotNull String nestedClassName,
@NotNull String fieldName,
@NotNull AndroidFacet facet,
@NotNull List<PsiElement> result
) {
Manifest manifest = facet.getManifest();
if (manifest == null) {
return;
}
List<? extends ManifestElementWithRequiredName> list;
if ("permission".equals(nestedClassName)) {
list = manifest.getPermissions();
}
else if ("permission_group".equals(nestedClassName)) {
list = manifest.getPermissionGroups();
}
else {
return;
}
for (ManifestElementWithRequiredName domElement : list) {
AndroidAttributeValue<String> nameAttribute = domElement.getName();
String name = nameAttribute.getValue();
if (AndroidUtils.equal(name, fieldName, false)) {
XmlElement psiElement = nameAttribute.getXmlAttributeValue();
if (psiElement != null) {
result.add(psiElement);
}
}
}
}
@Override
public String getActionText(DataContext context) {
return null;
}
}
@@ -0,0 +1,111 @@
/*
* Copyright 2010-2015 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 org.jetbrains.kotlin.android.navigation
import org.jetbrains.android.util.AndroidResourceUtil
import com.intellij.psi.PsiElement
import org.jetbrains.android.facet.AndroidFacet
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.PsiClass
import org.jetbrains.android.util.AndroidUtils
import com.android.SdkConstants
import org.jetbrains.android.augment.AndroidPsiElementFinder
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
import org.jetbrains.kotlin.psi.KtExpression
fun getReferenceExpression(element: PsiElement?): KtSimpleNameExpression? {
return PsiTreeUtil.getParentOfType<KtSimpleNameExpression>(element, KtSimpleNameExpression::class.java)
}
// given 'R.a.b' returns info for all three parts of the expression 'a', 'b', 'R'
fun getInfo(
referenceExpression: KtSimpleNameExpression,
facet: AndroidFacet
): AndroidResourceUtil.MyReferredResourceFieldInfo? {
val info = getReferredInfo(referenceExpression, facet)
if (info != null) return info
val topMostQualified = referenceExpression.getParentQualified().getParentQualified() ?: return null
val selectorCandidate = topMostQualified.selectorExpression as? KtSimpleNameExpression ?: return null
return getReferredInfo(selectorCandidate, facet)
}
private fun KtExpression?.getParentQualified(): KtDotQualifiedExpression? {
return this?.parent as? KtDotQualifiedExpression
}
// returns info if passed expression is 'b' in 'R.a.b'
private fun getReferredInfo(
lastPart: KtSimpleNameExpression,
facet: AndroidFacet
): AndroidResourceUtil.MyReferredResourceFieldInfo? {
val resFieldName = lastPart.getReferencedName()
if (resFieldName.isEmpty()) return null
val middlePart = getReceiverAsSimpleNameExpression(lastPart) ?: return null
val resClassName = middlePart.getReferencedName()
if (resClassName.isEmpty()) return null
val firstPart = getReceiverAsSimpleNameExpression(middlePart) ?: return null
val resolvedClass = firstPart.mainReference.resolve() as? PsiClass ?: return null
//the following code is copied from
// org.jetbrains.android.util.AndroidResourceUtil.getReferredResourceOrManifestField
// (org.jetbrains.android.facet.AndroidFacet, com.intellij.psi.PsiReferenceExpression, java.lang.String, boolean)
val classShortName = resolvedClass.name
val fromManifest = AndroidUtils.MANIFEST_CLASS_NAME == classShortName
if (!fromManifest && AndroidUtils.R_CLASS_NAME != classShortName) {
return null
}
val qName = resolvedClass.qualifiedName
if (SdkConstants.CLASS_R == qName || AndroidPsiElementFinder.INTERNAL_R_CLASS_QNAME == qName) {
return AndroidResourceUtil.MyReferredResourceFieldInfo(resClassName, resFieldName, true, false)
}
val containingFile = resolvedClass.containingFile ?: return null
val isFromCorrectFile =
if (fromManifest) AndroidResourceUtil.isManifestJavaFile(facet, containingFile)
else AndroidResourceUtil.isRJavaFile(facet, containingFile)
if (!isFromCorrectFile) {
return null
}
return AndroidResourceUtil.MyReferredResourceFieldInfo(resClassName, resFieldName, false, fromManifest)
}
private fun getReceiverAsSimpleNameExpression(exp: KtSimpleNameExpression): KtSimpleNameExpression? {
val receiver = exp.getReceiverExpression()
return when (receiver) {
is KtSimpleNameExpression -> {
receiver
}
is KtDotQualifiedExpression -> {
receiver.selectorExpression as? KtSimpleNameExpression
}
else -> null
}
}
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2016 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 org.jetbrains.kotlin.android.configure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.configuration.AbstractConfigureProjectByChangingFileTest;
public abstract class AbstractConfigureProjectTest extends AbstractConfigureProjectByChangingFileTest {
public void doTestAndroidGradle(@NotNull String path) throws Exception {
doTest(path, path.replace("before", "after"), new KotlinAndroidGradleModuleConfigurator());
}
}
@@ -0,0 +1,166 @@
/*
* Copyright 2010-2016 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 org.jetbrains.kotlin.android.configure;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/configuration/android-gradle")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class ConfigureProjectTestGenerated extends AbstractConfigureProjectTest {
public void testAllFilesPresentInAndroid_gradle() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/configuration/android-gradle"), Pattern.compile("(\\w+)_before\\.gradle$"), true);
}
@TestMetadata("androidStudioDefault_before.gradle")
public void testAndroidStudioDefault() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/androidStudioDefault_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("androidStudioDefaultShapshot_before.gradle")
public void testAndroidStudioDefaultShapshot() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/androidStudioDefaultShapshot_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("buildConfigs_before.gradle")
public void testBuildConfigs() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/buildConfigs_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("emptyDependencyList_before.gradle")
public void testEmptyDependencyList() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/emptyDependencyList_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("emptyFile_before.gradle")
public void testEmptyFile() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/emptyFile_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("helloWorld_before.gradle")
public void testHelloWorld() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/helloWorld_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("libraryFile_before.gradle")
public void testLibraryFile() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/libraryFile_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("missedApplyAndroidStatement_before.gradle")
public void testMissedApplyAndroidStatement() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/missedApplyAndroidStatement_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("missedBuildscriptBlock_before.gradle")
public void testMissedBuildscriptBlock() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/missedBuildscriptBlock_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("missedRepositoriesInBuildscriptBlock_before.gradle")
public void testMissedRepositoriesInBuildscriptBlock() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/missedRepositoriesInBuildscriptBlock_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("productFlavor_before.gradle")
public void testProductFlavor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/productFlavor_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("idea/testData/configuration/android-gradle/gradleExamples")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class GradleExamples extends AbstractConfigureProjectTest {
public void testAllFilesPresentInGradleExamples() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/configuration/android-gradle/gradleExamples"), Pattern.compile("(\\w+)_before\\.gradle$"), true);
}
@TestMetadata("gradleExample0_before.gradle")
public void testGradleExample0() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/gradleExamples/gradleExample0_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("gradleExample18_before.gradle")
public void testGradleExample18() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/gradleExamples/gradleExample18_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("gradleExample22_before.gradle")
public void testGradleExample22() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/gradleExamples/gradleExample22_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("gradleExample44_before.gradle")
public void testGradleExample44() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/gradleExamples/gradleExample44_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("gradleExample5_before.gradle")
public void testGradleExample5() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/gradleExamples/gradleExample5_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("gradleExample50_before.gradle")
public void testGradleExample50() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/gradleExamples/gradleExample50_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("gradleExample58_before.gradle")
public void testGradleExample58() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/gradleExamples/gradleExample58_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("gradleExample65_before.gradle")
public void testGradleExample65() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/gradleExamples/gradleExample65_before.gradle");
doTestAndroidGradle(fileName);
}
@TestMetadata("gradleExample8_before.gradle")
public void testGradleExample8() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/configuration/android-gradle/gradleExamples/gradleExample8_before.gradle");
doTestAndroidGradle(fileName);
}
}
}