mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
test framework: add DSL to generate class-files in 'directoryContent'
So we can produce simple *.class files dynamically instead of placing them in testData directories.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.util.io.java
|
||||
|
||||
import com.intellij.testFramework.UsefulTestCase.assertEmpty
|
||||
import com.intellij.testFramework.UsefulTestCase.assertOneElement
|
||||
import com.intellij.util.io.directoryContent
|
||||
import junit.framework.TestCase.assertSame
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* @author nik
|
||||
*/
|
||||
class ClassFileBuilderTest {
|
||||
@Test
|
||||
fun `empty class`() {
|
||||
val dir = directoryContent {
|
||||
classFile("A") {}
|
||||
}.generateInTempDir()
|
||||
val aClass = loadClass("A", File(dir, "A.class"))
|
||||
assertSame(Object::class.java, aClass.superclass)
|
||||
assertEmpty(aClass.interfaces)
|
||||
assertEmpty(aClass.declaredFields)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `class with extends and implements`() {
|
||||
val dir = directoryContent {
|
||||
classFile("A") {
|
||||
superclass = ArrayList::class.java.name
|
||||
interfaces = listOf(Serializable::class.java.name)
|
||||
}
|
||||
}.generateInTempDir()
|
||||
val aClass = loadClass("A", File(dir, "A.class"))
|
||||
assertSame(ArrayList::class.java, aClass.superclass)
|
||||
assertSame(Serializable::class.java, assertOneElement(aClass.interfaces))
|
||||
assertEmpty(aClass.declaredFields)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `class with fields`() {
|
||||
val dir = directoryContent {
|
||||
classFile("A") {
|
||||
field("foo", Int::class, AccessModifier.PUBLIC)
|
||||
field("bar", Object::class.java.name)
|
||||
}
|
||||
}.generateInTempDir()
|
||||
val aClass = loadClass("A", File(dir, "A.class"))
|
||||
val foo = aClass.getDeclaredField("foo")
|
||||
assertSame(Int::class.java, foo.type)
|
||||
assertTrue(Modifier.isPublic(foo.modifiers))
|
||||
val bar = aClass.getDeclaredField("bar")
|
||||
assertSame(Object::class.java, bar.type)
|
||||
assertTrue(Modifier.isPrivate(bar.modifiers))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `class in non-default package`() {
|
||||
val dir = directoryContent {
|
||||
classFile("p.A") {}
|
||||
}.generateInTempDir()
|
||||
val aClass = loadClass("p.A", File(dir, "p/A.class"))
|
||||
assertEquals("p.A", aClass.name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadClass(name: String, file: File): Class<*> {
|
||||
return MyClassLoader(ClassFileBuilderTest::class.java.classLoader).doDefineClass(name, file.readBytes())
|
||||
}
|
||||
|
||||
private class MyClassLoader(parent: ClassLoader) : ClassLoader(parent) {
|
||||
fun doDefineClass(name: String, data: ByteArray): Class<*> {
|
||||
return defineClass(name, data, 0, data.size)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.util.io.java
|
||||
|
||||
import com.intellij.util.io.DirectoryContentBuilder
|
||||
import com.intellij.util.io.java.impl.ClassFileBuilderImpl
|
||||
import org.jetbrains.jps.model.java.LanguageLevel
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Produces class-file with qualified name [name] in a place specified by [content]. If the class is not from the default package,
|
||||
* the produced file will be placed in a sub-directory according to its package.
|
||||
*
|
||||
* @author nik
|
||||
*/
|
||||
inline fun DirectoryContentBuilder.classFile(name: String, content: ClassFileBuilder.() -> Unit) {
|
||||
val builder = ClassFileBuilderImpl(name)
|
||||
builder.content()
|
||||
builder.generate(this)
|
||||
}
|
||||
|
||||
abstract class ClassFileBuilder {
|
||||
var javaVersion: LanguageLevel = LanguageLevel.JDK_1_8
|
||||
var superclass: String = "java.lang.Object"
|
||||
var interfaces: List<String> = emptyList()
|
||||
var access: AccessModifier = AccessModifier.PUBLIC
|
||||
|
||||
abstract fun field(name: String, type: KClass<*>, access: AccessModifier = AccessModifier.PRIVATE)
|
||||
|
||||
/**
|
||||
* Adds a field which type is a class with qualified name [type]
|
||||
*/
|
||||
abstract fun field(name: String, type: String, access: AccessModifier = AccessModifier.PRIVATE)
|
||||
}
|
||||
|
||||
enum class AccessModifier { PRIVATE, PUBLIC, PROTECTED, PACKAGE_LOCAL }
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.util.io.java.impl
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.util.PathUtil
|
||||
import com.intellij.util.io.DirectoryContentBuilder
|
||||
import com.intellij.util.io.java.AccessModifier
|
||||
import com.intellij.util.io.java.ClassFileBuilder
|
||||
import org.jetbrains.jps.model.java.LanguageLevel
|
||||
import org.jetbrains.org.objectweb.asm.ClassWriter
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* @author nik
|
||||
*/
|
||||
class ClassFileBuilderImpl(private val name: String) : ClassFileBuilder() {
|
||||
private val writer = ClassWriter(ClassWriter.COMPUTE_FRAMES)
|
||||
|
||||
override fun field(name: String, type: String, access: AccessModifier) {
|
||||
addField(name, "L" + toJvmName(type) + ";", access)
|
||||
}
|
||||
|
||||
override fun field(name: String, type: KClass<*>, access: AccessModifier) {
|
||||
addField(name, Type.getDescriptor(type.java), access)
|
||||
}
|
||||
|
||||
private fun addField(name: String, typeDescriptor: String, access: AccessModifier) {
|
||||
writer.visitField(access.toAsmCode(), name, typeDescriptor, null, null).visitEnd()
|
||||
}
|
||||
|
||||
fun generate(targetRoot: DirectoryContentBuilder) {
|
||||
writer.visit(javaVersion.toAsmCode(), access.toAsmCode(), toJvmName(name), null,
|
||||
superclass.replace('.', '/'),
|
||||
interfaces.map(::toJvmName).toTypedArray())
|
||||
writer.visitEnd()
|
||||
|
||||
targetRoot.directories(StringUtil.getPackageName(name).replace('.', '/')) {
|
||||
file("${StringUtil.getShortName(name)}.class", writer.toByteArray())
|
||||
}
|
||||
}
|
||||
|
||||
private fun DirectoryContentBuilder.directories(relativePath: String, content: DirectoryContentBuilder.() -> Unit) {
|
||||
if (relativePath.isEmpty()) {
|
||||
content()
|
||||
}
|
||||
else {
|
||||
directories(PathUtil.getParentPath(relativePath)) {
|
||||
dir(PathUtil.getFileName(relativePath)) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toJvmName(className: String) = className.replace('.', '/')
|
||||
|
||||
private fun LanguageLevel.toAsmCode() = when (this) {
|
||||
LanguageLevel.JDK_1_3 -> Opcodes.V1_3
|
||||
LanguageLevel.JDK_1_4 -> Opcodes.V1_4
|
||||
LanguageLevel.JDK_1_5 -> Opcodes.V1_5
|
||||
LanguageLevel.JDK_1_6 -> Opcodes.V1_6
|
||||
LanguageLevel.JDK_1_7 -> Opcodes.V1_7
|
||||
LanguageLevel.JDK_1_8 -> Opcodes.V1_8
|
||||
LanguageLevel.JDK_1_9 -> Opcodes.V1_9
|
||||
LanguageLevel.JDK_X -> throw UnsupportedOperationException("Java 10 isn't supported yet")
|
||||
}
|
||||
|
||||
private fun AccessModifier.toAsmCode() = when (this) {
|
||||
AccessModifier.PROTECTED -> Opcodes.ACC_PROTECTED
|
||||
AccessModifier.PRIVATE -> Opcodes.ACC_PRIVATE
|
||||
AccessModifier.PUBLIC -> Opcodes.ACC_PUBLIC
|
||||
AccessModifier.PACKAGE_LOCAL -> 0
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
<orderEntry type="module" module-name="java-runtime" scope="RUNTIME" />
|
||||
<orderEntry type="library" name="Log4J" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
|
||||
<orderEntry type="module" module-name="testFramework" scope="TEST" />
|
||||
<orderEntry type="module" module-name="testFramework-java" scope="TEST" />
|
||||
<orderEntry type="library" scope="TEST" name="Eclipse" level="project" />
|
||||
<orderEntry type="module" module-name="jps-model-api" />
|
||||
<orderEntry type="module" module-name="jps-model-serialization" />
|
||||
|
||||
+19
-16
@@ -20,6 +20,10 @@ import com.intellij.openapi.util.io.FileUtilRt
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import com.intellij.util.io.EnumeratorStringDescriptor
|
||||
import com.intellij.util.io.directoryContent
|
||||
import com.intellij.util.io.java.AccessModifier
|
||||
import com.intellij.util.io.java.ClassFileBuilder
|
||||
import com.intellij.util.io.java.classFile
|
||||
import gnu.trove.THashSet
|
||||
import org.jetbrains.jps.ModuleChunk
|
||||
import org.jetbrains.jps.builders.DirtyFilesHolder
|
||||
@@ -30,9 +34,8 @@ import org.jetbrains.jps.incremental.ModuleBuildTarget
|
||||
import org.jetbrains.jps.incremental.ModuleLevelBuilder
|
||||
import org.jetbrains.jps.incremental.storage.AbstractStateStorage
|
||||
import org.jetbrains.jps.incremental.storage.PathStringDescriptor
|
||||
import org.jetbrains.jps.model.java.LanguageLevel
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.ClassWriter
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
@@ -75,19 +78,19 @@ class MockPackageFacadeGenerator : ModuleLevelBuilder(BuilderCategory.SOURCE_PRO
|
||||
val callback = JavaBuilderUtil.getDependenciesRegistrar(context)
|
||||
|
||||
fun generateClass(packageName: String, className: String, target: ModuleBuildTarget, sources: Collection<String>,
|
||||
allSources: Collection<String>, generate: (ClassWriter.() -> Unit)? = null) {
|
||||
val writer = ClassWriter(ClassWriter.COMPUTE_FRAMES)
|
||||
val fullClassName = StringUtil.getQualifiedName(packageName, className).replace('.', '/')
|
||||
writer.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, fullClassName, null, "java/lang/Object", null)
|
||||
if (generate != null) {
|
||||
writer.generate()
|
||||
}
|
||||
writer.visitEnd()
|
||||
val outputFile = File(target.outputDir, "$fullClassName.class")
|
||||
val classBytes = writer.toByteArray()
|
||||
FileUtil.writeToFile(outputFile, classBytes)
|
||||
allSources: Collection<String>, content: (ClassFileBuilder.() -> Unit)? = null) {
|
||||
val fullClassName = StringUtil.getQualifiedName(packageName, className)
|
||||
directoryContent {
|
||||
classFile(fullClassName) {
|
||||
javaVersion = LanguageLevel.JDK_1_6
|
||||
if (content != null) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}.generate(target.outputDir!!)
|
||||
val outputFile = File(target.outputDir, "${fullClassName.replace('.', '/')}.class")
|
||||
outputConsumer.registerOutputFile(target, outputFile, sources)
|
||||
callback.associate(fullClassName.replace('/', '.'), allSources, ClassReader(classBytes))
|
||||
callback.associate(fullClassName, allSources, ClassReader(outputFile.readBytes()))
|
||||
}
|
||||
|
||||
for (target in chunk.targets) {
|
||||
@@ -130,8 +133,8 @@ class MockPackageFacadeGenerator : ModuleLevelBuilder(BuilderCategory.SOURCE_PRO
|
||||
|
||||
generateClass(packageName, "PackageFacade", target, dirtySource, allSources) {
|
||||
for (fileName in classNames) {
|
||||
val fieldClass = StringUtil.getQualifiedName(packageName, fileName).replace('.', '/')
|
||||
visitField(Opcodes.ACC_PUBLIC, StringUtil.decapitalize(fileName), "L$fieldClass;", null, null).visitEnd()
|
||||
val fieldClass = StringUtil.getQualifiedName(packageName, fileName)
|
||||
field(StringUtil.decapitalize(fileName), fieldClass, AccessModifier.PUBLIC)
|
||||
}
|
||||
}
|
||||
for (source in dirtySource) {
|
||||
|
||||
@@ -42,6 +42,8 @@ abstract class DirectoryContentBuilder {
|
||||
|
||||
abstract fun file(name: String, text: String)
|
||||
|
||||
abstract fun file(name: String, content: ByteArray)
|
||||
|
||||
inline fun dir(name: String, content: DirectoryContentBuilder.() -> Unit) {
|
||||
val dirDefinition = DirectorySpec()
|
||||
DirectoryContentBuilderImpl(dirDefinition).content()
|
||||
|
||||
@@ -104,7 +104,11 @@ class DirectoryContentBuilderImpl(val result: DirectorySpecBase) : DirectoryCont
|
||||
}
|
||||
|
||||
override fun file(name: String, text: String) {
|
||||
addChild(name, FileSpec(text.toByteArray()))
|
||||
file(name, text.toByteArray())
|
||||
}
|
||||
|
||||
override fun file(name: String, content: ByteArray) {
|
||||
addChild(name, FileSpec(content))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user