Java tutorial
/* * Copyright 2010-2012 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.jet.compiler; import com.google.common.io.Closeables; import com.google.common.io.Files; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.psi.PsiFileFactory; import com.intellij.psi.impl.PsiFileFactoryImpl; import com.intellij.testFramework.LightVirtualFile; import junit.framework.Test; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.JetTestCaseBuilder; import org.jetbrains.jet.JetTestUtils; import org.jetbrains.jet.codegen.ClassBuilderFactories; import org.jetbrains.jet.codegen.ClassBuilderFactory; import org.jetbrains.jet.codegen.ClassFileFactory; import org.jetbrains.jet.codegen.GenerationState; import org.jetbrains.jet.lang.psi.JetFile; import org.jetbrains.jet.lang.resolve.AnalyzingUtils; import org.jetbrains.jet.lang.resolve.java.JvmStdlibNames; import org.jetbrains.jet.plugin.JetLanguage; import org.junit.Assert; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.commons.EmptyVisitor; import org.objectweb.asm.commons.Method; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.charset.Charset; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Test correctness of written JVM signature * * @author Stepan Koltsov * * @see CompileJavaAgainstKotlinTest */ public class WriteSignatureTest extends TestCaseWithTmpdir { private final File ktFile; private JetCoreEnvironment jetCoreEnvironment; public WriteSignatureTest(File ktFile) { this.ktFile = ktFile; } @Override public String getName() { return ktFile.getName(); } @Override protected void runTest() throws Throwable { jetCoreEnvironment = JetTestUtils.createEnvironmentWithMockJdk(myTestRootDisposable); String text = FileUtil.loadFile(ktFile); LightVirtualFile virtualFile = new LightVirtualFile(ktFile.getName(), JetLanguage.INSTANCE, text); virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET); JetFile psiFile = (JetFile) ((PsiFileFactoryImpl) PsiFileFactory .getInstance(jetCoreEnvironment.getProject())).trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false); GenerationState state = new GenerationState(jetCoreEnvironment.getProject(), ClassBuilderFactories.binaries(false)); AnalyzingUtils.checkForSyntacticErrors(psiFile); state.compile(psiFile); ClassFileFactory classFileFactory = state.getFactory(); CompileEnvironment.writeToOutputDirectory(classFileFactory, tmpdir.getPath()); Disposer.dispose(myTestRootDisposable); final Expectation expectation = parseExpectations(); ActualSignature actualSignature = readSignature(expectation.className, expectation.methodName); String template = "jvm signature: %s\n" + "generic signature: %s\n" + "kotlin signature: %s\n" + ""; String expected = String.format(template, expectation.jvmSignature, expectation.genericSignature, expectation.kotlinSignature); String actual = String.format(template, actualSignature.jvmSignature, actualSignature.genericSignature, actualSignature.kotlinSignature); Assert.assertEquals(expected, actual); } private static class ActualSignature { private final String jvmSignature; private final String genericSignature; private final String kotlinSignature; private ActualSignature(@NotNull String jvmSignature, @Nullable String genericSignature, @Nullable String kotlinSignature) { this.jvmSignature = jvmSignature; this.genericSignature = genericSignature; this.kotlinSignature = kotlinSignature; } } @NotNull private ActualSignature readSignature(String className, final String methodName) throws Exception { // ugly unreadable code begin FileInputStream classInputStream = new FileInputStream( tmpdir + "/" + className.replace('.', '/') + ".class"); try { class Visitor extends EmptyVisitor { ActualSignature readSignature; @Override public MethodVisitor visitMethod(int access, String name, final String desc, final String signature, String[] exceptions) { if (name.equals(methodName)) { final int parameterCount = new Method(name, desc).getArgumentTypes().length; return new EmptyVisitor() { String typeParameters = ""; String returnType; String[] parameterTypes = new String[parameterCount]; @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if (desc.equals(JvmStdlibNames.JET_METHOD.getDescriptor())) { return new EmptyVisitor() { @Override public void visit(String name, Object value) { if (name.equals(JvmStdlibNames.JET_METHOD_TYPE_PARAMETERS_FIELD)) { typeParameters = (String) value; } else if (name.equals(JvmStdlibNames.JET_METHOD_RETURN_TYPE_FIELD)) { returnType = (String) value; } } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return new EmptyVisitor(); } @Override public AnnotationVisitor visitArray(String name) { return new EmptyVisitor(); } }; } else { return new EmptyVisitor(); } } @Override public AnnotationVisitor visitParameterAnnotation(final int parameter, String desc, boolean visible) { if (desc.equals(JvmStdlibNames.JET_VALUE_PARAMETER.getDescriptor())) { return new EmptyVisitor() { @Override public void visit(String name, Object value) { if (name.equals(JvmStdlibNames.JET_VALUE_PARAMETER_TYPE_FIELD)) { parameterTypes[parameter] = (String) value; } } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return new EmptyVisitor(); } @Override public AnnotationVisitor visitArray(String name) { return new EmptyVisitor(); } }; } else { return new EmptyVisitor(); } } @Override public AnnotationVisitor visitAnnotationDefault() { return new EmptyVisitor(); } @Nullable private String makeKotlinSignature() { boolean allNulls = true; StringBuilder sb = new StringBuilder(); sb.append(typeParameters); if (typeParameters != null && typeParameters.length() > 0) { allNulls = false; } sb.append("("); for (String parameterType : parameterTypes) { sb.append(parameterType); if (parameterType != null) { allNulls = false; } } sb.append(")"); sb.append(returnType); if (returnType != null) { allNulls = false; } if (allNulls) { return null; } else { return sb.toString(); } } @Override public void visitEnd() { Assert.assertNull(readSignature); readSignature = new ActualSignature(desc, signature, makeKotlinSignature()); } }; } return super.visitMethod(access, name, desc, signature, exceptions); } } Visitor visitor = new Visitor(); new ClassReader(classInputStream).accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); Assert.assertNotNull("method not found: " + className + "::" + methodName, visitor.readSignature); return visitor.readSignature; } finally { Closeables.closeQuietly(classInputStream); } // ugly unreadable code end } private static class Expectation { private final String className; private final String methodName; private final String jvmSignature; private final String genericSignature; private final String kotlinSignature; private Expectation(@NotNull String className, @NotNull String methodName, @NotNull String jvmSignature, @Nullable String genericSignature, @Nullable String kotlinSignature) { this.className = className; this.methodName = methodName; this.jvmSignature = jvmSignature; this.genericSignature = genericSignature; this.kotlinSignature = kotlinSignature; } } @NotNull private static final Pattern methodPattern = Pattern.compile("^// method: *(.*)::(.*?) *(//.*)?"); private static final Pattern jvmSignaturePattern = Pattern.compile("^// jvm signature: *(.+?) *(//.*)?"); private static final Pattern genericSignaturePattern = Pattern .compile("^// generic signature: *(.+?) *(//.*)?"); private static final Pattern kotlinSignaturePattern = Pattern.compile("^// kotlin signature: *(.+?) *(//.*)?"); private Expectation parseExpectations() throws IOException { List<String> lines = Files.readLines(ktFile, Charset.forName("utf-8")); for (int i = 0; i < lines.size() - 3; ++i) { Matcher methodMatcher = methodPattern.matcher(lines.get(i)); if (methodMatcher.matches()) { Matcher jvmSignatureMatcher = jvmSignaturePattern.matcher(lines.get(i + 1)); Matcher genericSignatureMatcher = genericSignaturePattern.matcher(lines.get(i + 2)); Matcher kotlinSignatureMatcher = kotlinSignaturePattern.matcher(lines.get(i + 3)); if (!jvmSignatureMatcher.matches() || !genericSignatureMatcher.matches() || !kotlinSignatureMatcher.matches()) { throw new AssertionError("'method:' must be followed ... bla bla ... use the source luke"); } String className = methodMatcher.group(1); String methodName = methodMatcher.group(2); String jvmSignature = jvmSignatureMatcher.group(1); String genericSignature = genericSignatureMatcher.group(1); String kotlinSignature = kotlinSignatureMatcher.group(1); if (genericSignature.equals("null")) { genericSignature = null; } if (kotlinSignature.equals("null")) { kotlinSignature = null; } return new Expectation(className, methodName, jvmSignature, genericSignature, kotlinSignature); } } throw new AssertionError("test instructions not found in " + ktFile); } public static Test suite() { return JetTestCaseBuilder.suiteForDirectory(JetTestCaseBuilder.getTestDataPathBase(), "/writeSignature", true, new JetTestCaseBuilder.NamedTestFactory() { @NotNull @Override public Test createTest(@NotNull String dataPath, @NotNull String name, @NotNull File file) { return new WriteSignatureTest(file); } }); } }