/*
* 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.test;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.io.ZipUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.common.ExitCode;
import org.jetbrains.kotlin.cli.js.K2JSCompiler;
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
import org.jetbrains.kotlin.preloading.ClassPreloadingUtils;
import org.jetbrains.kotlin.preloading.Preloader;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import org.jetbrains.kotlin.utils.PathUtil;
import java.io.*;
import java.lang.ref.SoftReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.zip.ZipOutputStream;
import static org.junit.Assert.assertEquals;
public class MockLibraryUtil {
private static SoftReference<ClassLoader> compilerClassLoader = new SoftReference<>(null);
@NotNull
public static File compileLibraryToJar(
@NotNull String sourcesPath,
@NotNull String jarName,
boolean addSources,
boolean isJsLibrary,
boolean allowKotlinPackage,
@NotNull String... extraClasspath
) {
if (isJsLibrary) {
return compileJsLibraryToJar(sourcesPath, jarName, addSources);
}
else {
return compileLibraryToJar(sourcesPath, jarName, addSources, allowKotlinPackage, extraClasspath);
}
}
@NotNull
public static File compileLibraryToJar(
@NotNull String sourcesPath,
@NotNull String jarName,
boolean addSources,
boolean allowKotlinPackage,
@NotNull String... extraClasspath
) {
try {
return compileLibraryToJar(
sourcesPath,
KotlinTestUtils.tmpDir("testLibrary-" + jarName),
jarName,
addSources,
allowKotlinPackage,
extraClasspath);
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
@NotNull
public static File compileLibraryToJar(
@NotNull String sourcesPath,
@NotNull File contentDir,
@NotNull String jarName,
boolean addSources,
boolean allowKotlinPackage,
@NotNull String... extraClasspath
) {
try {
File classesDir = new File(contentDir, "classes");
File srcFile = new File(sourcesPath);
List<File> kotlinFiles = FileUtil.findFilesByMask(Pattern.compile(".*\\.kt"), srcFile);
if (srcFile.isFile() || !kotlinFiles.isEmpty()) {
compileKotlin(sourcesPath, classesDir, allowKotlinPackage, extraClasspath);
}
List<File> javaFiles = FileUtil.findFilesByMask(Pattern.compile(".*\\.java"), srcFile);
if (!javaFiles.isEmpty()) {
List<String> classpath = new ArrayList<>();
classpath.add(ForTestCompileRuntime.runtimeJarForTests().getPath());
classpath.add(KotlinTestUtils.getAnnotationsJar().getPath());
Collections.addAll(classpath, extraClasspath);
// Probably no kotlin files were present, so dir might not have been created after kotlin compiler
if (classesDir.exists()) {
classpath.add(classesDir.getPath());
}
else {
FileUtil.createDirectory(classesDir);
}
List<String> options = Arrays.asList(
"-classpath", StringUtil.join(classpath, File.pathSeparator),
"-d", classesDir.getPath()
);
KotlinTestUtils.compileJavaFiles(javaFiles, options);
}
return createJarFile(contentDir, classesDir, sourcesPath, jarName, addSources);
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
@NotNull
private static File compileJsLibraryToJar(
@NotNull String sourcesPath,
@NotNull String jarName,
boolean addSources
) {
try {
File contentDir = KotlinTestUtils.tmpDir("testLibrary-" + jarName);
File outDir = new File(contentDir, "out");
File outputFile = new File(outDir, jarName + ".js");
compileKotlin2JS(sourcesPath, outputFile);
return createJarFile(contentDir, outDir, sourcesPath, jarName, addSources);
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
public static File createJarFile(File contentDir, File dirToAdd, String sourcesPath, String jarName, boolean addSources) throws IOException {
File jarFile = new File(contentDir, jarName + ".jar");
ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(jarFile));
ZipUtil.addDirToZipRecursively(zip, jarFile, dirToAdd, "", null, null);
if (addSources) {
ZipUtil.addDirToZipRecursively(zip, jarFile, new File(sourcesPath), "src", null, null);
}
zip.close();
return jarFile;
}
private static void runJvmCompiler(@NotNull List<String> args) {
runCompiler(getCompiler2JVMClass(), args);
}
private static void runJsCompiler(@NotNull List<String> args) {
runCompiler(getCompiler2JSClass(), args);
}
// Runs compiler in custom class loader to avoid effects caused by replacing Application with another one created in compiler.
private static void runCompiler(@NotNull Class<?> compilerClass, @NotNull List<String> args) {
try {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
Object compiler = compilerClass.newInstance();
Method execMethod = compilerClass.getMethod("exec", PrintStream.class, String[].class);
Enum<?> invocationResult = (Enum<?>) execMethod.invoke(compiler, new PrintStream(outStream), ArrayUtil.toStringArray(args));
assertEquals(new String(outStream.toByteArray()), ExitCode.OK.name(), invocationResult.name());
}
catch (Throwable e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
public static void compileKotlin(@NotNull String sourcesPath, @NotNull File outDir, @NotNull String... extraClasspath) {
compileKotlin(sourcesPath, outDir, false, extraClasspath);
}
public static void compileKotlin(
@NotNull String sourcesPath,
@NotNull File outDir,
boolean allowKotlinPackage,
@NotNull String... extraClasspath
) {
List<String> classpath = new ArrayList<>();
if (new File(sourcesPath).isDirectory()) {
classpath.add(sourcesPath);
}
Collections.addAll(classpath, extraClasspath);
List<String> args = new ArrayList<>();
args.add(sourcesPath);
args.add("-d");
args.add(outDir.getAbsolutePath());
args.add("-classpath");
args.add(StringUtil.join(classpath, File.pathSeparator));
if (allowKotlinPackage) {
args.add("-Xallow-kotlin-package");
}
runJvmCompiler(args);
}
private static void compileKotlin2JS(@NotNull String sourcesPath, @NotNull File outputFile) {
List<String> args = new ArrayList<>();
args.add("-meta-info");
args.add("-output");
args.add(outputFile.getAbsolutePath());
args.add(sourcesPath);
runJsCompiler(args);
}
public static void compileKotlinModule(@NotNull String modulePath) {
runJvmCompiler(Arrays.asList("-no-stdlib", "-module", modulePath));
}
@NotNull
private static synchronized Class<?> getCompiler2JVMClass() {
return loadCompilerClass(K2JVMCompiler.class.getName());
}
@NotNull
private static synchronized Class<?> getCompiler2JSClass() {
return loadCompilerClass(K2JSCompiler.class.getName());
}
private static synchronized Class<?> loadCompilerClass(String compilerClassName) {
try {
ClassLoader classLoader = compilerClassLoader.get();
if (classLoader == null) {
classLoader = createCompilerClassLoader();
compilerClassLoader = new SoftReference<>(classLoader);
}
return classLoader.loadClass(compilerClassName);
}
catch (Throwable e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
@NotNull
private static synchronized ClassLoader createCompilerClassLoader() {
try {
File kotlinCompilerJar = new File(PathUtil.getKotlinPathsForDistDirectory().getLibPath(), "kotlin-compiler.jar");
return ClassPreloadingUtils.preloadClasses(
Collections.singletonList(kotlinCompilerJar), Preloader.DEFAULT_CLASS_NUMBER_ESTIMATE, null, null, null
);
}
catch (Throwable e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
private MockLibraryUtil() {
}
}