JS: add idea-js module: KotlinJavaScriptLibraryManager and KotlinJavaScriptLibraryContentsTreeStructureProvider

This commit is contained in:
Michael Nedzelsky
2015-04-30 16:00:06 +03:00
parent 06af6bbd82
commit 044497fab6
8 changed files with 425 additions and 0 deletions
+1
View File
@@ -43,6 +43,7 @@
<element id="module-output" name="idea-completion" />
<element id="module-output" name="idea-core" />
<element id="module-output" name="annotation-collector" />
<element id="module-output" name="idea-js" />
</element>
<element id="library" level="project" name="javax.inject" />
<element id="directory" name="jps">
+1
View File
@@ -33,6 +33,7 @@
<module fileurl="file://$PROJECT_DIR$/idea/idea-completion/idea-completion.iml" filepath="$PROJECT_DIR$/idea/idea-completion/idea-completion.iml" group="ide" />
<module fileurl="file://$PROJECT_DIR$/idea/idea-core/idea-core.iml" filepath="$PROJECT_DIR$/idea/idea-core/idea-core.iml" group="ide" />
<module fileurl="file://$PROJECT_DIR$/idea/idea-jps-common/idea-jps-common.iml" filepath="$PROJECT_DIR$/idea/idea-jps-common/idea-jps-common.iml" group="ide" />
<module fileurl="file://$PROJECT_DIR$/idea/idea-js/idea-js.iml" filepath="$PROJECT_DIR$/idea/idea-js/idea-js.iml" group="ide" />
<module fileurl="file://$PROJECT_DIR$/idea-runner/idea-runner.iml" filepath="$PROJECT_DIR$/idea-runner/idea-runner.iml" group="ide" />
<module fileurl="file://$PROJECT_DIR$/idea/idea-test-framework/idea-test-framework.iml" filepath="$PROJECT_DIR$/idea/idea-test-framework/idea-test-framework.iml" group="ide" />
<module fileurl="file://$PROJECT_DIR$/generators/injector-generator/injector-generator.iml" filepath="$PROJECT_DIR$/generators/injector-generator/injector-generator.iml" group="infrastructure" />
+19
View File
@@ -0,0 +1,19 @@
<?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" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" scope="PROVIDED" name="intellij-core" level="project" />
<orderEntry type="module" module-name="frontend" />
<orderEntry type="module" module-name="js.dart-ast" exported="" />
<orderEntry type="module" module-name="js.parser" exported="" />
<orderEntry type="module" module-name="util" />
<orderEntry type="module" module-name="js.serializer" />
<orderEntry type="library" name="idea-full" level="project" />
<orderEntry type="module" module-name="idea-analysis" />
</component>
</module>
@@ -0,0 +1,70 @@
/*
* 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.idea.js
import com.intellij.openapi.vfs.impl.ArchiveHandler
import com.intellij.openapi.vfs.impl.ArchiveHandler.EntryInfo
import com.intellij.util.ArrayUtil
import gnu.trove.THashMap
import org.jetbrains.kotlin.serialization.js.forEachFile
import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils
public open class KotlinJavaScriptHandler(path: String) : ArchiveHandler(path) {
override fun createEntriesMap(): Map<String, EntryInfo> {
val map = THashMap<String, EntryInfo>()
map.put("", createRootEntry())
for(metadata in KotlinJavascriptMetadataUtils.loadMetadata(getFile())) {
val moduleName = metadata.moduleName
metadata.forEachFile {
filePath, fileContent -> getOrCreate(moduleName + "/" + filePath, false, map, fileContent)
}
}
return map
}
private fun getOrCreate(
entryPath: String,
isDirectory: Boolean,
map: MutableMap<String, EntryInfo>,
content: ByteArray = ArrayUtil.EMPTY_BYTE_ARRAY
): EntryInfo {
val info = map[entryPath]
if (info != null) return info
val path = splitPath(entryPath)
val parentInfo = getOrCreate(path.first, true, map)
val newInfo = JsMetaEntryInfo(parentInfo, path.second, isDirectory, content)
map[entryPath] = newInfo
return newInfo
}
override fun contentsToByteArray(relativePath: String): ByteArray {
val entryInfo = getEntryInfo(relativePath)
return if (entryInfo is JsMetaEntryInfo) entryInfo.content else ArrayUtil.EMPTY_BYTE_ARRAY
}
private class JsMetaEntryInfo(
parent: EntryInfo,
shortName: String,
isDirectory: Boolean,
val content: ByteArray
) : EntryInfo(parent, shortName, isDirectory, ArchiveHandler.DEFAULT_LENGTH, ArchiveHandler.DEFAULT_TIMESTAMP)
}
@@ -0,0 +1,36 @@
/*
* 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.idea.js
import com.intellij.ide.projectView.TreeStructureProvider
import com.intellij.ide.projectView.ViewSettings
import com.intellij.ide.projectView.impl.nodes.ExternalLibrariesNode
import com.intellij.ide.projectView.impl.nodes.NamedLibraryElementNode
import com.intellij.ide.util.treeView.AbstractTreeNode
public class KotlinJavaScriptLibraryContentsTreeStructureProvider : TreeStructureProvider {
override fun modify(parent: AbstractTreeNode<*>, children: Collection<AbstractTreeNode<*>>, settings: ViewSettings): Collection<AbstractTreeNode<*>> =
if (parent.getProject() == null || parent !is ExternalLibrariesNode) children else filterLibraryNodes(children)
private fun filterLibraryNodes(children: Collection<AbstractTreeNode<*>>): Collection<AbstractTreeNode<*>> {
val filteredChildren = children.filterNot { it is NamedLibraryElementNode && KotlinJavaScriptLibraryManager.LIBRARY_NAME == it.getName() }
return if (filteredChildren.size() == children.size()) children else filteredChildren
}
override fun getData(selected: MutableCollection<AbstractTreeNode<*>>?, dataName: String?): Any? = null
}
@@ -0,0 +1,226 @@
/*
* 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.idea.js
import com.intellij.ProjectTopics
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.components.ProjectComponent
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.module.ModuleType
import com.intellij.openapi.module.ModuleTypeId
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.*
import com.intellij.openapi.roots.impl.OrderEntryUtil
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.roots.libraries.LibraryTable
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.PathUtil
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.idea.framework.KotlinJavaScriptLibraryDetectionUtil
import org.jetbrains.kotlin.idea.project.ProjectStructureUtil.isJsKotlinModule
import org.jetbrains.kotlin.utils.LibraryUtils
import java.io.File
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.platform.platformStatic
public class KotlinJavaScriptLibraryManager private constructor(private var myProject: Project?) : ProjectComponent, ModuleRootListener {
private val myMuted = AtomicBoolean(false)
override fun getComponentName(): String = "KotlinJavascriptLibraryManager"
override fun projectOpened() {
val project = myProject!!
project.getMessageBus().connect(project).subscribe<ModuleRootListener>(ProjectTopics.PROJECT_ROOTS, this)
DumbService.getInstance(project).smartInvokeLater() { updateProjectLibrary() }
}
override fun projectClosed() {
}
override fun initComponent() {
}
override fun disposeComponent() {
myProject = null
}
override fun beforeRootsChange(event: ModuleRootEvent) {
}
override fun rootsChanged(event: ModuleRootEvent) {
if (myMuted.get()) return
ApplicationManager.getApplication().invokeLater({
DumbService.getInstance(myProject!!).runWhenSmart() { updateProjectLibrary() }
}, ModalityState.NON_MODAL, myProject!!.getDisposed())
}
TestOnly
public fun syncUpdateProjectLibrary(): Unit = updateProjectLibrary(true)
/**
* @param synchronously may be true only in tests.
*/
private fun updateProjectLibrary(synchronously: Boolean = false) {
val project = myProject
if (project == null || project.isDisposed()) return
ApplicationManager.getApplication().assertReadAccessAllowed()
for (module in ModuleManager.getInstance(project).getModules()) {
if (!isModuleApplicable(module)) continue
if (!isJsKotlinModule(module)) {
findLibraryByName(module, LIBRARY_NAME)?.let { resetLibraries(module, ChangesToApply(), LIBRARY_NAME, synchronously) }
continue
}
val clsRootFiles = arrayListOf<VirtualFile>()
val srcRootFiles = arrayListOf<VirtualFile>()
ModuleRootManager.getInstance(module).orderEntries().librariesOnly().forEachLibrary() { library ->
if (KotlinJavaScriptLibraryDetectionUtil.isKotlinJavaScriptLibrary(library)) {
var addSources = false
for (clsRootFile in library.getFiles(OrderRootType.CLASSES)) {
val path = PathUtil.getLocalPath(clsRootFile)
assert(path != null, "expected not-null path for ${clsRootFile.getName()}")
if (LibraryUtils.isKotlinJavascriptLibraryWithMetadata(File(path))) {
val classRoot = KotlinJavaScriptMetaFileSystem.getInstance().refreshAndFindFileByPath(path!! + "!/")
clsRootFiles.add(classRoot)
addSources = true
}
}
if (addSources) {
srcRootFiles.addAll(arrayListOf(*library.getFiles(OrderRootType.SOURCES)))
}
}
true
}
val clsRootUrls = clsRootFiles.map { it.getUrl() }
val srcRootUrls = srcRootFiles.map { it.getUrl() }
val changesToApply = ChangesToApply(clsRootUrls, srcRootUrls)
resetLibraries(module, changesToApply, LIBRARY_NAME, synchronously)
}
}
private fun resetLibraries(module: Module, changesToApply: ChangesToApply, libraryName: String, synchronously: Boolean) =
applyChange(module, changesToApply, synchronously, libraryName)
private fun applyChange(module: Module, changesToApply: ChangesToApply, synchronously: Boolean, libraryName: String) {
if (synchronously) {
//for test only
val application = ApplicationManager.getApplication()
if (!application.isUnitTestMode()) {
throw IllegalStateException("Synchronous library update may be done only in test mode")
}
val token = application.acquireWriteActionLock(javaClass<KotlinJavaScriptLibraryManager>())
try {
applyChangeImpl(module, changesToApply, libraryName)
}
finally {
token.finish()
}
}
else {
val commit = Runnable { applyChangeImpl(module, changesToApply, libraryName) }
val commitInWriteAction = Runnable { ApplicationManager.getApplication().runWriteAction(commit) }
ApplicationManager.getApplication().invokeLater(commitInWriteAction, myProject!!.getDisposed())
}
}
synchronized private fun applyChangeImpl(module: Module, changesToApply: ChangesToApply, libraryName: String) {
if (module.isDisposed()) return
val model = ModuleRootManager.getInstance(module).getModifiableModel()
val libraryTableModel = model.getModuleLibraryTable().getModifiableModel()
var library = findLibraryByName(libraryTableModel, libraryName)
if (library == null) {
if (changesToApply.clsUrlsToAdd.isEmpty()) {
model.dispose()
return
}
library = libraryTableModel.createLibrary(libraryName)!!
}
if (changesToApply.clsUrlsToAdd.isEmpty()) {
libraryTableModel.removeLibrary(library)
commitLibraries(null, libraryTableModel, model)
return
}
val libraryModel = library.getModifiableModel()
val existingClsUrls = library.getUrls(OrderRootType.CLASSES).toSet()
val existingSrcUrls = library.getUrls(OrderRootType.SOURCES).toSet()
if (existingClsUrls == changesToApply.clsUrlsToAdd.toSet() && existingSrcUrls == changesToApply.srcUrlsToAdd.toSet()) {
model.dispose()
Disposer.dispose(libraryModel)
return
}
existingClsUrls.forEach { libraryModel.removeRoot(it, OrderRootType.CLASSES) }
existingSrcUrls.forEach { libraryModel.removeRoot(it, OrderRootType.SOURCES) }
changesToApply.clsUrlsToAdd.forEach { libraryModel.addRoot(it, OrderRootType.CLASSES) }
changesToApply.srcUrlsToAdd.forEach { libraryModel.addRoot(it, OrderRootType.SOURCES) }
commitLibraries(libraryModel, libraryTableModel, model)
}
private fun commitLibraries(libraryModel: Library.ModifiableModel?, tableModel: LibraryTable.ModifiableModel, model: ModifiableRootModel) {
try {
myMuted.set(true)
libraryModel?.commit()
tableModel.commit()
model.commit()
}
finally {
myMuted.set(false)
}
}
private fun isModuleApplicable(module: Module) = ModuleTypeId.JAVA_MODULE == ModuleType.get(module).getId()
private fun findLibraryByName(libraryTableModel: LibraryTable.ModifiableModel, libraryName: String) =
libraryTableModel.getLibraries().firstOrNull { libraryName == it.getName() }
private fun findLibraryByName(module: Module, libraryName: String) =
OrderEntryUtil.findLibraryOrderEntry(ModuleRootManager.getInstance(module), libraryName)?.getLibrary()
private class ChangesToApply(val clsUrlsToAdd: List<String> = listOf(), val srcUrlsToAdd: List<String> = listOf())
companion object {
public val LIBRARY_NAME: String = "<Kotlin JavaScript library>"
platformStatic
public fun getInstance(project: Project): KotlinJavaScriptLibraryManager =
project.getComponent<KotlinJavaScriptLibraryManager>(javaClass<KotlinJavaScriptLibraryManager>())
}
}
@@ -0,0 +1,62 @@
/*
* 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.idea.js
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.JarFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.newvfs.ArchiveFileSystem
import com.intellij.openapi.vfs.newvfs.VfsImplUtil
import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils
import kotlin.platform.platformStatic
public class KotlinJavaScriptMetaFileSystem : ArchiveFileSystem() {
companion object {
platformStatic
public fun getInstance(): KotlinJavaScriptMetaFileSystem = VirtualFileManager.getInstance().getFileSystem(KotlinJavascriptMetadataUtils.VFS_PROTOCOL) as KotlinJavaScriptMetaFileSystem
}
private val ARCHIVE_SUFFIX = ".kjsm_archive"
override fun getProtocol(): String = KotlinJavascriptMetadataUtils.VFS_PROTOCOL
override fun extractRootPath(path: String): String {
val jarSeparatorIndex = path.indexOf(JarFileSystem.JAR_SEPARATOR)
assert(jarSeparatorIndex >= 0) { "Path passed to KotlinJavascriptMetaFileSystem must have separator '!/': " + path }
return path.substring(0, jarSeparatorIndex + JarFileSystem.JAR_SEPARATOR.length())
}
override fun getHandler(entryFile: VirtualFile): KotlinJavaScriptHandler {
val pathToRoot = extractLocalPath(this.extractRootPath(entryFile.getPath()))
return VfsImplUtil.getHandler<KotlinJavaScriptHandler>(this, pathToRoot + ARCHIVE_SUFFIX) {
KotlinJavaScriptHandler(it.substringBeforeLast(ARCHIVE_SUFFIX))
}
}
override fun extractLocalPath(rootPath: String): String = StringUtil.trimEnd(rootPath, JarFileSystem.JAR_SEPARATOR)
override fun composeRootPath(localPath: String): String = localPath + JarFileSystem.JAR_SEPARATOR
override fun findFileByPath(path: String): VirtualFile? = VfsImplUtil.findFileByPath(this, path)
override fun findFileByPathIfCached(path: String): VirtualFile? = VfsImplUtil.findFileByPathIfCached(this, path)
override fun refreshAndFindFileByPath(path: String): VirtualFile? = VfsImplUtil.refreshAndFindFileByPath(this, path)
override fun refresh(asynchronous: Boolean) = VfsImplUtil.refresh(this, asynchronous)
}
+10
View File
@@ -36,6 +36,9 @@
<component>
<implementation-class>org.jetbrains.kotlin.idea.configuration.ui.AbsentJdkAnnotationsComponent</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.js.KotlinJavaScriptLibraryManager</implementation-class>
</component>
</project-components>
<application-components>
@@ -46,6 +49,12 @@
<component>
<implementation-class>org.jetbrains.kotlin.idea.versions.KotlinUpdatePluginComponent</implementation-class>
</component>
<component>
<interface-class>org.jetbrains.kotlin.idea.js.KotlinJavaScriptMetaFileSystem</interface-class>
<implementation-class>org.jetbrains.kotlin.idea.js.KotlinJavaScriptMetaFileSystem</implementation-class>
</component>
</application-components>
<module-components>
@@ -995,6 +1004,7 @@
<project.converterProvider implementation="org.jetbrains.kotlin.idea.converters.JetRunConfigurationSettingsFormatConverterProvider"/>
<treeStructureProvider implementation="org.jetbrains.kotlin.idea.js.KotlinJavaScriptLibraryContentsTreeStructureProvider"/>
</extensions>
<xi:include href="extensions/ide.xml" xpointer="xpointer(/idea-plugin/*)"/>