diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a0a7f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,148 @@ +#################################### Java.gitignore + +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +#################################### Scala.gitignore + +*.class +*.log + +# sbt specific +.cache +.history +.lib/ +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ + +# Scala-IDE specific +.scala_dependencies +.worksheet + +#################################### Eclipse.gitignore + +*.pydevproject +.metadata +.gradle +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath + +# Eclipse Core +.project + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Java annotation processor (APT) +.factorypath + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse + +#################################### Linux.gitignore + +*~ + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +#################################### JetBrains.gitignore + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws +*.iml + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + +#################################### Mac + +.DS_Store + +#################################### Maven + +log/ +target/ + + + diff --git a/pom.xml b/pom.xml index f3f0cab..639f926 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,18 @@ 2.2.5 test + + org.apache.commons + commons-io + 1.3.2 + + + junit + junit + 4.10 + test + + @@ -131,7 +143,7 @@ - com.github.scalatojava.Main + com.github.scalatojava.MainExtended diff --git a/src/main/java/com/github/scalatojava/Constants.java b/src/main/java/com/github/scalatojava/Constants.java new file mode 100644 index 0000000..59341fe --- /dev/null +++ b/src/main/java/com/github/scalatojava/Constants.java @@ -0,0 +1,14 @@ +package com.github.scalatojava; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class Constants +{ + public static final String LS = System.getProperty("line.separator"); + public static final String FS = System.getProperty("file.separator"); + public static final String PS = System.getProperty("path.separator"); + + public static final String S2J = "scala2java_"; +} diff --git a/src/main/java/com/github/scalatojava/MainExtended.java b/src/main/java/com/github/scalatojava/MainExtended.java new file mode 100644 index 0000000..4139df6 --- /dev/null +++ b/src/main/java/com/github/scalatojava/MainExtended.java @@ -0,0 +1,41 @@ +package com.github.scalatojava; + +import com.github.scalatojava.inputs.ScalaToJavaFromSourceTree; +import com.github.scalatojava.inputs.ScalaToJavaFromStandaloneFile; +import com.strobel.core.ArrayUtilities; +import com.strobel.core.StringUtilities; + +import java.io.File; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class MainExtended +{ + public static void main(String[] args) throws Exception + { + String pathToTranslate = System.getProperty("path"); + String repo = System.getProperty("repo"); + boolean slim = ArrayUtilities.contains(args, "--slim"); + + + if (StringUtilities.isNullOrEmpty(pathToTranslate)) + { + new ScalaToJavaFromStandardInput().run(args); + return; + } + + File file = new File(pathToTranslate); + if (file.isDirectory()) + { + new ScalaToJavaFromSourceTree().run(pathToTranslate, repo, slim); + } + else if (file.isFile()) + { + new ScalaToJavaFromStandaloneFile().run(pathToTranslate, repo, slim); + } + + System.out.println("done"); + } +} diff --git a/src/main/java/com/github/scalatojava/compilation/ScalaToJavaWithClasspath.java b/src/main/java/com/github/scalatojava/compilation/ScalaToJavaWithClasspath.java new file mode 100644 index 0000000..b87f294 --- /dev/null +++ b/src/main/java/com/github/scalatojava/compilation/ScalaToJavaWithClasspath.java @@ -0,0 +1,106 @@ +package com.github.scalatojava.compilation; + +import com.github.scalatojava.Constants; +import com.github.scalatojava.NoRetryMetadataSystem; +import com.github.scalatojava.files.FileExtension; +import com.github.scalatojava.files.filters.ClassFileFilter; +import com.strobel.assembler.InputTypeLoader; +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.decompiler.DecompilationOptions; +import com.strobel.decompiler.DecompilerSettings; +import com.strobel.decompiler.PlainTextOutput; +import com.strobel.decompiler.languages.Languages; +import com.strobel.decompiler.languages.java.JavaFormattingOptions; +import com.strobel.decompiler.languages.java.JavaLanguage; +import scala.collection.JavaConversions; +import scala.tools.nsc.Global; +import scala.tools.nsc.Settings; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.file.Files; +import java.util.Arrays; + +public class ScalaToJavaWithClasspath +{ + protected String classpath; + protected boolean slim; + + public ScalaToJavaWithClasspath(String classpath, boolean slim) + { + this.classpath = classpath; + this.slim = slim; + } + + public String decompile(final File file) throws IOException + { + final InputTypeLoader typeLoader = new InputTypeLoader(); + typeLoader.tryLoadType(file.getPath(), new Buffer(Files.readAllBytes(file.toPath()))); + final JavaLanguage javaLanguage = Languages.java(); + final NoRetryMetadataSystem metadataSystem = new NoRetryMetadataSystem(typeLoader); + final TypeDefinition resolvedType = metadataSystem.resolveType(FileExtension.remove(file.getName()), false); + + final DecompilerSettings settings = new DecompilerSettings(); + settings.setLanguage(javaLanguage); + settings.setTypeLoader(typeLoader); + settings.setFormattingOptions(JavaFormattingOptions.createDefault()); + final DecompilationOptions options = new DecompilationOptions(); + options.setSettings(settings); + options.setFullDecompilation(true); + final StringWriter writer = new StringWriter(); + final PlainTextOutput output = new PlainTextOutput(writer); + javaLanguage.decompileType(resolvedType, output, options); + writer.flush(); + String java = writer.toString(); + if (slim) + java = java.replaceFirst("(?s)public final class _\\$\\s+.*", ""); + return java; + } + + public File[] compile(final File scalaSourceFile, final File containerDirForCompiledFiles) + { + final Global.Run runner = getCompilerRun(containerDirForCompiledFiles); + runner.compile(JavaConversions.asScalaBuffer(Arrays.asList(scalaSourceFile.getPath())).toList()); + return containerDirForCompiledFiles.listFiles(new ClassFileFilter()); + } + + private Global.Run getCompilerRun(File dir) + { + final Settings settings = new Settings(); + settings.processArgumentString(prepareCompileArgs(dir)); + final Global compiler = new Global(settings); + return compiler.new Run(); + } + + private String prepareCompileArgs(File dir) + { + StringBuilder args = new StringBuilder(); + args.append(" -target:jvm-1.8") + .append(" -explaintypes") + .append(" -Xscript _") + .append(" -usejavacp") + .append(" -d ").append(dir.getPath()); + if (classpath != null && classpath.trim().length() > 0) + { + args.append(" -classpath \"").append(classpath).append("\""); + } + return args.toString(); + } + + public String apply(final String value) throws IOException + { + final File tempDir = Files.createTempDirectory("s2j").toFile(); + final File scalaFile = new File(tempDir, "_.scala"); + Files.write(scalaFile.toPath(), value.getBytes()); + File[] compileds = compile(scalaFile, tempDir); + StringBuilder javaSource = new StringBuilder(); + for (File compiled : compileds) + { + String s = decompile(compiled); + javaSource.append(s).append(Constants.LS); + } + return javaSource.toString(); + } +} diff --git a/src/main/java/com/github/scalatojava/files/FileExtension.java b/src/main/java/com/github/scalatojava/files/FileExtension.java new file mode 100644 index 0000000..590f27e --- /dev/null +++ b/src/main/java/com/github/scalatojava/files/FileExtension.java @@ -0,0 +1,54 @@ +package com.github.scalatojava.files; + +import com.strobel.core.StringUtilities; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class FileExtension +{ + public static String replace(String filename, String extensionReplacement) + { + guardUndefined(filename); + if (filename.lastIndexOf(".") == -1) { + filename += "."; + } + if (StringUtilities.isNullOrEmpty(extensionReplacement)) { + return filename; + } + if (extensionReplacement.startsWith(".")) { + extensionReplacement = extensionReplacement.substring(1); + } + + filename = filename.substring(0, filename.lastIndexOf(".") + 1) + extensionReplacement; + guardUndefined(filename); + return filename; + } + + public static String get(String filename) + { + guardUndefined(filename); + if (filename.lastIndexOf(".") == -1) { + return ""; + } + return filename.substring(filename.lastIndexOf(".") + 1, filename.length()); + } + + public static String remove(String filename) + { + guardUndefined(filename); + if (filename.lastIndexOf(".") == -1) { + return filename; + } + return filename.substring(0, filename.lastIndexOf(".")); + } + + private static void guardUndefined(String filename) + { + if (filename == null || filename.equals(".") || filename.equals("..")) + { + throw new IllegalArgumentException("the operation is undefined on this filename"); + } + } +} diff --git a/src/main/java/com/github/scalatojava/files/FileTreeTraversal.java b/src/main/java/com/github/scalatojava/files/FileTreeTraversal.java new file mode 100644 index 0000000..7466d3d --- /dev/null +++ b/src/main/java/com/github/scalatojava/files/FileTreeTraversal.java @@ -0,0 +1,46 @@ +package com.github.scalatojava.files; + +import java.io.File; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class FileTreeTraversal +{ + protected TraverseStrategy cs; + + public void traverse(File root, TraverseStrategy cs) + { + this.cs = cs; + if (root.isFile()) + { + cs.handleFile(root); + } + else + { + traverseRecursive(root); + } + cs.handleDone(); + } + + protected void traverseRecursive(File file) + { + if (file == null) + { + return; + } + else if (file.isFile()) + { + cs.handleFile(file); + } + else + { + cs.handleDir(file); + for (File f : file.listFiles()) + { + traverseRecursive(f); + } + } + } +} diff --git a/src/main/java/com/github/scalatojava/files/TraverseStrategy.java b/src/main/java/com/github/scalatojava/files/TraverseStrategy.java new file mode 100644 index 0000000..68ba80e --- /dev/null +++ b/src/main/java/com/github/scalatojava/files/TraverseStrategy.java @@ -0,0 +1,15 @@ +package com.github.scalatojava.files; + +import java.io.File; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public interface TraverseStrategy +{ + Object handleFile(File file); + Object handleDir(File dir); + Object result(); + Object handleDone(); +} diff --git a/src/main/java/com/github/scalatojava/files/filters/ClassFileFilter.java b/src/main/java/com/github/scalatojava/files/filters/ClassFileFilter.java new file mode 100644 index 0000000..17a930c --- /dev/null +++ b/src/main/java/com/github/scalatojava/files/filters/ClassFileFilter.java @@ -0,0 +1,15 @@ +package com.github.scalatojava.files.filters; + +import java.io.File; +import java.io.FilenameFilter; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public final class ClassFileFilter implements FilenameFilter { + @Override + public boolean accept(final File dir, final String name) { + return name.endsWith(".class"); + } +} diff --git a/src/main/java/com/github/scalatojava/files/filters/JavaFileFilter.java b/src/main/java/com/github/scalatojava/files/filters/JavaFileFilter.java new file mode 100644 index 0000000..ee66781 --- /dev/null +++ b/src/main/java/com/github/scalatojava/files/filters/JavaFileFilter.java @@ -0,0 +1,17 @@ +package com.github.scalatojava.files.filters; + +import java.io.File; +import java.io.FileFilter; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class JavaFileFilter implements FileFilter +{ + @Override + public boolean accept(File pathname) + { + return false; + } +} diff --git a/src/main/java/com/github/scalatojava/files/filters/ScalaFileFilter.java b/src/main/java/com/github/scalatojava/files/filters/ScalaFileFilter.java new file mode 100644 index 0000000..c67f8fc --- /dev/null +++ b/src/main/java/com/github/scalatojava/files/filters/ScalaFileFilter.java @@ -0,0 +1,17 @@ +package com.github.scalatojava.files.filters; + +import java.io.File; +import java.io.FilenameFilter; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public final class ScalaFileFilter implements FilenameFilter +{ + @Override + public boolean accept(final File dir, final String name) + { + return name.endsWith(".scala"); + } +} diff --git a/src/main/java/com/github/scalatojava/files/strategies/ConcatFiles.java b/src/main/java/com/github/scalatojava/files/strategies/ConcatFiles.java new file mode 100644 index 0000000..f46bca0 --- /dev/null +++ b/src/main/java/com/github/scalatojava/files/strategies/ConcatFiles.java @@ -0,0 +1,86 @@ +package com.github.scalatojava.files.strategies; + +import com.github.scalatojava.Constants; +import com.github.scalatojava.files.TraverseStrategy; +import com.strobel.core.StringUtilities; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.List; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class ConcatFiles implements TraverseStrategy +{ + protected final File destFile; + protected final StringBuilder concatenated; + + public ConcatFiles(File destFile) + { + if (destFile == null) + { + throw new IllegalArgumentException("dest file cannot be null"); + } + else if (!destFile.exists()) + { + try + { + destFile.createNewFile(); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + this.destFile = destFile; + concatenated = new StringBuilder(); + } + + @Override + public Object handleFile(File file) + { + try + { + if (file == null || file.getName().contains("package.scala")) { + return null; + } + final List lines = Files.readAllLines(file.toPath()); + String text = StringUtilities.join(Constants.LS, lines); + concatenated.append(text).append(Constants.LS); + } + catch (Exception e) + { + System.out.println("could not read file: " + file.toPath()); + } + return null; + } + + @Override + public Object handleDir(File dir) + { + return null; + } + + @Override + public Object result() { + return concatenated.toString(); + } + + @Override + public Object handleDone() + { + try + { + Files.write(destFile.toPath(), concatenated.toString().getBytes(), StandardOpenOption.WRITE); + } + catch (IOException e) + { + System.out.println("could not write concatenated content to: " + destFile.toPath()); + } + return null; + } +} diff --git a/src/main/java/com/github/scalatojava/files/strategies/CopyScalaFiles.java b/src/main/java/com/github/scalatojava/files/strategies/CopyScalaFiles.java new file mode 100644 index 0000000..9660da5 --- /dev/null +++ b/src/main/java/com/github/scalatojava/files/strategies/CopyScalaFiles.java @@ -0,0 +1,83 @@ +package com.github.scalatojava.files.strategies; + +import com.github.scalatojava.files.TraverseStrategy; +import com.github.scalatojava.files.filters.ScalaFileFilter; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class CopyScalaFiles implements TraverseStrategy +{ + protected final File destDir; + private final boolean overwrite; + + public CopyScalaFiles(File destDir, boolean overwrite) + { + if (destDir == null || !destDir.exists()) + { + throw new IllegalArgumentException("dest dir does not exist"); + } + this.destDir = destDir; + this.overwrite = overwrite; + } + + @Override + public Object handleFile(File file) + { + return null; + } + + @Override + public Object handleDir(File dir) + { + if (dir == null) + { + return null; + } + File[] scalaSources = dir.listFiles(new ScalaFileFilter()); + for (File file : scalaSources) + { + + final Path sourcePath = file.toPath(); + final Path destPath = destDir.toPath().resolve(file.getName()); + try + { + if (overwrite) + { + Files.copy(sourcePath, destPath, StandardCopyOption.REPLACE_EXISTING); + } + else if (!destPath.toFile().exists()) + { + Files.copy(sourcePath, destPath); + } + else + { + System.out.println(String.format("file already exists, could not copy %s to %s", sourcePath, destPath)); + } + } + catch (IOException e) + { + throw new IllegalArgumentException(String.format("could not copy %s to %s", sourcePath, destPath), e); + } + } + return null; + } + + @Override + public Object result() + { + return destDir; + } + + @Override + public Object handleDone() { + return null; + } +} diff --git a/src/main/java/com/github/scalatojava/files/strategies/CreateClasspath.java b/src/main/java/com/github/scalatojava/files/strategies/CreateClasspath.java new file mode 100644 index 0000000..981ad37 --- /dev/null +++ b/src/main/java/com/github/scalatojava/files/strategies/CreateClasspath.java @@ -0,0 +1,59 @@ +package com.github.scalatojava.files.strategies; + +import com.github.scalatojava.Constants; +import com.github.scalatojava.files.TraverseStrategy; + +import java.io.File; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class CreateClasspath implements TraverseStrategy +{ + protected final StringBuilder classpath; + + public CreateClasspath() + { + classpath = new StringBuilder(); + } + + @Override + public Object handleFile(File file) + { + if (isCompiledJar(file)) + { + classpath.append(file.getAbsolutePath()); + classpath.append(Constants.PS); + } + return null; + } + + private boolean isCompiledJar(File file) + { + return file != null && file.getName().endsWith(".jar") && + !file.getName().contains("dependencies.jar") && + !file.getName().contains("tests.jar") && + !file.getName().contains("javadoc.jar") && + !file.getName().contains("sources.jar"); + } + + @Override + public Object handleDir(File dir) + { + return null; + } + + @Override + public Object result() + { + return classpath.toString(); + } + + @Override + public Object handleDone() + { + classpath.append('.'); + return classpath; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/scalatojava/files/strategies/CreateImports.java b/src/main/java/com/github/scalatojava/files/strategies/CreateImports.java new file mode 100644 index 0000000..0c568c6 --- /dev/null +++ b/src/main/java/com/github/scalatojava/files/strategies/CreateImports.java @@ -0,0 +1,98 @@ +package com.github.scalatojava.files.strategies; + +import com.github.scalatojava.files.TraverseStrategy; + +import java.io.File; +import java.util.NavigableSet; +import java.util.TreeSet; + +import static com.github.scalatojava.Constants.FS; +import static com.github.scalatojava.Constants.LS; + +/** + * @author eitanraviv@github + * @since 9/8/15 + */ +public class CreateImports implements TraverseStrategy +{ + protected final NavigableSet importSet; + protected StringBuilder importStr; + + public CreateImports() + { + importSet = new TreeSet<>(); + importStr = new StringBuilder(); + } + + @Override + public Object handleFile(File file) + { + return null; + } + + @Override + public Object handleDir(File dir) + { + String packageName = parsePackage(dir); + if (packageName != null) + { + importSet.add("import " + packageName + "._"); + } + return null; + } + + private String parsePackage(File dir) + { + String lang; + if (dir == null) + { + return null; + } + else if (!hasFiles(dir)) + { + return null; + } + else if (isSourceDir(dir, "java")) + { + lang = "java"; + } + else if (isSourceDir(dir, "scala")) + { + lang = "scala"; + } + else + { + return null; + } + String dirPath = dir.getAbsolutePath(); + String packagePath = dirPath.substring(dirPath.lastIndexOf(lang) + lang.length() + FS.length(), dirPath.length()); + return packagePath.replace(FS, "."); + } + + private boolean isSourceDir(File dir, String lang) + { + final String p = dir.getAbsolutePath(); + return p.contains("src" + FS + "main" + FS + lang) && !p.endsWith(lang); + } + + private boolean hasFiles(File dir) + { + return dir != null && dir.listFiles().length > 0; + } + + @Override + public Object result() + { + return importStr.toString(); + } + + @Override + public Object handleDone() + { + for (String s : importSet) + { + importStr.append(s).append(LS); + } + return importSet; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/scalatojava/files/strategies/DeleteClassFiles.java b/src/main/java/com/github/scalatojava/files/strategies/DeleteClassFiles.java new file mode 100644 index 0000000..9807aa2 --- /dev/null +++ b/src/main/java/com/github/scalatojava/files/strategies/DeleteClassFiles.java @@ -0,0 +1,42 @@ +package com.github.scalatojava.files.strategies; + +import com.github.scalatojava.files.FileExtension; +import com.github.scalatojava.files.TraverseStrategy; + +import java.io.File; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class DeleteClassFiles implements TraverseStrategy +{ + protected boolean allDeleted = true; + + @Override + public Object handleFile(File file) + { + if (file != null && "class".equals(FileExtension.get(file.getName()))) + { + allDeleted &= file.delete(); + } + return null; + } + + @Override + public Object handleDir(File dir) + { + return null; + } + + @Override + public Object result() + { + return allDeleted; + } + + @Override + public Object handleDone() { + return allDeleted; + } +} diff --git a/src/main/java/com/github/scalatojava/files/strategies/PrependText.java b/src/main/java/com/github/scalatojava/files/strategies/PrependText.java new file mode 100644 index 0000000..b6b895f --- /dev/null +++ b/src/main/java/com/github/scalatojava/files/strategies/PrependText.java @@ -0,0 +1,98 @@ +package com.github.scalatojava.files.strategies; + +import com.github.scalatojava.files.TraverseStrategy; +import com.strobel.core.Closeables; +import org.apache.commons.io.IOUtils; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.util.List; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class PrependText implements TraverseStrategy +{ + protected static final String LS = System.getProperty("line.separator"); + protected String prepend; + protected StringBuilder newText; + + public PrependText(String prepend) + { + this.prepend = prepend; + } + + @Override + public Object handleFile(File file) + { + if (file == null) + { + return null; + } + FileReader rd = null; + FileWriter fw = null; + try + { + rd = new FileReader(file); + List lines = IOUtils.readLines(rd); + StringBuilder text = concatLines(lines); + newText = prepend(text); + fw = new FileWriter(file); + fw.write(newText.toString()); + return newText; + } + catch (Exception e) + { + System.out.println("could not read file: " + file.getAbsolutePath()); + return null; + } + finally + { + Closeables.close(rd, fw); + } + } + + private StringBuilder prepend(StringBuilder text) + { + if (prepend == null) + { + return text; + } + else + { + return new StringBuilder(prepend.length() + text.length() + LS.length()). + append(prepend). + append(LS). + append(text); + } + } + + private StringBuilder concatLines(List lines) + { + StringBuilder sb = new StringBuilder(); + for (String line: lines) + { + sb.append(line).append(LS); + } + return sb; + } + + @Override + public Object handleDir(File dir) + { + return null; + } + + @Override + public Object result() { + return newText; + } + + @Override + public Object handleDone() + { + return null; + } +} diff --git a/src/main/java/com/github/scalatojava/files/strategies/RemovePackageDeclaration.java b/src/main/java/com/github/scalatojava/files/strategies/RemovePackageDeclaration.java new file mode 100644 index 0000000..24fd6e8 --- /dev/null +++ b/src/main/java/com/github/scalatojava/files/strategies/RemovePackageDeclaration.java @@ -0,0 +1,102 @@ +package com.github.scalatojava.files.strategies; + +import com.github.scalatojava.files.TraverseStrategy; +import com.strobel.core.Closeables; +import org.apache.commons.io.IOUtils; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; +import java.util.function.Predicate; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015 + */ +public class RemovePackageDeclaration implements TraverseStrategy +{ + protected final File destFile; + protected List lines; + + public RemovePackageDeclaration(File destFile) + { + if (destFile == null || !destFile.exists()) + { + throw new IllegalArgumentException("dest file does not exist"); + } + this.destFile = destFile; + } + + @Override + public Object handleFile(File file) + { + if (file == null) + { + return null; + } + try + { + lines = IOUtils.readLines(new FileReader(file)); + } + catch (IOException e) + { + e.printStackTrace(); + } + + return null; + } + + @Override + public Object handleDir(File dir) + { + return null; + } + + @Override + public Object result() { + return destFile; + } + + @Override + public Object handleDone() + { + removePackageDeclarations(); + writeLines(); + return null; + } + + private void removePackageDeclarations() + { + lines.removeIf(new Predicate() + { + @Override + public boolean test(String s) + { + return !s.matches("^package[\\s]+object.*") && s.matches("^package[\\s]+.*"); + } + }); + } + + private void writeLines() + { + FileWriter writer = null; + try + { + writer = new FileWriter(destFile); + IOUtils.writeLines(lines, null, writer); + } + catch (IOException e) + { + System.out.println("could not write to: " + destFile.getAbsolutePath()); + } + finally + { + if (writer != null) + { + Closeables.close(writer); + } + } + } +} diff --git a/src/main/java/com/github/scalatojava/inputs/ScalaToJavaFromSourceTree.java b/src/main/java/com/github/scalatojava/inputs/ScalaToJavaFromSourceTree.java new file mode 100644 index 0000000..c58f2ff --- /dev/null +++ b/src/main/java/com/github/scalatojava/inputs/ScalaToJavaFromSourceTree.java @@ -0,0 +1,118 @@ +package com.github.scalatojava.inputs; + +import com.github.scalatojava.Constants; +import com.github.scalatojava.compilation.ScalaToJavaWithClasspath; +import com.github.scalatojava.files.FileExtension; +import com.github.scalatojava.files.FileTreeTraversal; +import com.github.scalatojava.files.TraverseStrategy; +import com.github.scalatojava.files.filters.ClassFileFilter; +import com.github.scalatojava.files.strategies.*; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class ScalaToJavaFromSourceTree +{ + public void run(String rootPath, String repoPath, boolean slim) throws IOException + { + /** + * Sources root dir is the root dir of the compilation unit. + * compilation unit - a collection of files which can produce a jar; + * for example, a project in eclipse or a module in intellij-idea + */ + final File sourcesRootDir = new File(rootPath); + + //The repository where all the dependencies of the above compilation unit reside + final File repo = new File(repoPath); + + //Temporary directory where the output of this program will be written to + final File containerDir = Files.createTempDirectory(Constants.S2J).toFile(); + System.out.println(String.format("all files written to %s", containerDir.toPath())); + + //output directories + final File collectDir = containerDir.toPath().resolve("collect").toFile(); + final File concatDir = containerDir.toPath().resolve("concat").toFile(); + final File compileDir = containerDir.toPath().resolve("compiled").toFile(); + + boolean success = collectDir.mkdir(); + success &= concatDir.mkdir(); + success &= compileDir.mkdir(); + + if (!success) { + throw new IllegalStateException("could not create output dirs"); + } + + final File concatenatedScala = concatDir.toPath().resolve("concat.scala").toFile(); + final File adjustedScala = concatDir.toPath().resolve("adjusted.scala").toFile(); + final FileTreeTraversal t = new FileTreeTraversal(); + + //copy all scala file in the source tree to a single directory for further handling + TraverseStrategy copy = new CopyScalaFiles(collectDir, true); + t.traverse(sourcesRootDir, copy); + + //the compiler requires a single file containing all the scala to be translated + TraverseStrategy concat = new ConcatFiles(concatenatedScala); + t.traverse(collectDir, concat); + + adjustedScala.createNewFile(); + //the compiler does not accept package declarations + TraverseStrategy remove = new RemovePackageDeclaration(adjustedScala); + t.traverse(concatenatedScala, remove); + + //if there are java files in the source tree, the corresponding imports need to + //be added to the scala file + TraverseStrategy imports = new CreateImports(); + t.traverse(sourcesRootDir, imports); + + TraverseStrategy prependImports = new PrependText((String) imports.result()); + t.traverse(adjustedScala, prependImports); + + //build the classpath from the repo + TraverseStrategy classpath = new CreateClasspath(); + t.traverse(repo, classpath); + + translate(compileDir, adjustedScala, (String) classpath.result(), slim); + cleanup(compileDir); + System.out.println(String.format("all files written to %s", containerDir.toPath())); + } + + /** + * compile the scala file to class files, and then decompile them one by one + * @param compileDir the directory that will contain the compiled .class files and the decompiled .java files + * @param adjustedScala the scala file to translate from + * @param classpath the classpath of the scala sources in the scala file + * @param slim directive to the decompiler about the output + * @throws IOException + */ + private void translate(File compileDir, File adjustedScala, String classpath, boolean slim) throws IOException + { + ScalaToJavaWithClasspath s2j = new ScalaToJavaWithClasspath(classpath, slim); + s2j.compile(adjustedScala, compileDir); + + for (File f: compileDir.listFiles(new ClassFileFilter())) + { + System.out.println("decompiling: " + f.getName()); + String java = s2j.decompile(f); + File jf = new File(FileExtension.replace(f.getAbsolutePath(), ".java")); + Files.write(jf.toPath(), java.getBytes()); + } + } + + /** + * delete the .class files from the output directory + * @param compileDir + */ + private void cleanup(File compileDir) + { + TraverseStrategy delete = new DeleteClassFiles(); + new FileTreeTraversal().traverse(compileDir, delete); + if (! (boolean)delete.result()) { + System.out.println("could not delete all class files in " + compileDir.getName()); + } + } +} diff --git a/src/main/java/com/github/scalatojava/inputs/ScalaToJavaFromStandaloneFile.java b/src/main/java/com/github/scalatojava/inputs/ScalaToJavaFromStandaloneFile.java new file mode 100644 index 0000000..81a4343 --- /dev/null +++ b/src/main/java/com/github/scalatojava/inputs/ScalaToJavaFromStandaloneFile.java @@ -0,0 +1,57 @@ +package com.github.scalatojava.inputs; + +import com.github.scalatojava.Constants; +import com.github.scalatojava.compilation.ScalaToJavaWithClasspath; +import com.github.scalatojava.files.FileExtension; +import com.github.scalatojava.files.FileTreeTraversal; +import com.github.scalatojava.files.TraverseStrategy; +import com.github.scalatojava.files.strategies.CreateClasspath; +import com.strobel.core.StringUtilities; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class ScalaToJavaFromStandaloneFile +{ + public void run(String filename, String repoPath, boolean slim) throws IOException + { + String scala = readScala(filename); + String classpath = buildClasspath(repoPath); + String java = translate(scala, classpath, slim); + writeJava(filename, java); + } + + private String buildClasspath(String repoPath) + { + final File repo = new File(repoPath); + final FileTreeTraversal t = new FileTreeTraversal(); + TraverseStrategy classpath = new CreateClasspath(); + t.traverse(repo, classpath); + return (String) classpath.result(); + } + + private String readScala(String filename) throws IOException + { + List lines = Files.readAllLines(new File(filename).toPath()); + return StringUtilities.join(Constants.LS, lines); + } + + private String translate(String scala, String classpath, boolean slim) throws IOException + { + return new ScalaToJavaWithClasspath(classpath, slim).apply(scala); + } + + private void writeJava(String filename, String java) throws IOException + { + Path outPath = new File(FileExtension.replace(filename, ".java")).toPath(); + Files.write(outPath, java.getBytes()); + System.out.println("file was written to: " + outPath); + } +} diff --git a/src/main/resources/scala-to-java.sh b/src/main/resources/scala-to-java.sh new file mode 100755 index 0000000..9b2eb93 --- /dev/null +++ b/src/main/resources/scala-to-java.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +#args +#path of files to translate +path=/.../module/root +#repository of jars that the path is dependent on +repo=/.../.m2/repository + + +echo "running java -jar scala-to-java.jar" +echo "path:" $path +echo "repo:" $repo + +#run +java -jar -Dpath=path -Drepo=$repo scala-to-java.jar --slim + +echo "done" diff --git a/src/main/scala/com/github/scalatojava/Main.scala b/src/main/scala/com/github/scalatojava/Main.scala index 406a2dd..919d1ba 100644 --- a/src/main/scala/com/github/scalatojava/Main.scala +++ b/src/main/scala/com/github/scalatojava/Main.scala @@ -5,11 +5,6 @@ package com.github.scalatojava * 2015-07-12 */ object Main extends App { - val source = Iterator.continually(scala.io.StdIn.readLine()).takeWhile( - _ != null).mkString("\n") - - println() - private val java = ScalaToJava(source) - - println(if (args.contains("--slim")) java.replaceFirst("(?s)public final class _\\$\\s+.*", "") else java) + val s2j: ScalaToJavaFromStandardInput = new ScalaToJavaFromStandardInput() + s2j.run(args) } diff --git a/src/main/scala/com/github/scalatojava/NoRetryMetadataSystem.scala b/src/main/scala/com/github/scalatojava/NoRetryMetadataSystem.scala new file mode 100644 index 0000000..81c6a78 --- /dev/null +++ b/src/main/scala/com/github/scalatojava/NoRetryMetadataSystem.scala @@ -0,0 +1,21 @@ +package com.github.scalatojava + +import com.strobel.assembler.metadata.{ITypeLoader, MetadataSystem, TypeDefinition} + +import scala.collection.mutable + +class NoRetryMetadataSystem(typeLoader: ITypeLoader) extends MetadataSystem(typeLoader) { + + val failedTypes = mutable.Set[String]() + + override def resolveType(descriptor: String, mightBePrimitive: Boolean): TypeDefinition = { + if (failedTypes.contains(descriptor)) { + return null + } + val r = super.resolveType(descriptor, mightBePrimitive) + if (r == null) { + failedTypes.add(descriptor) + } + r + } +} diff --git a/src/main/scala/com/github/scalatojava/ScalaToJava.scala b/src/main/scala/com/github/scalatojava/ScalaToJava.scala index 7d36193..851c006 100644 --- a/src/main/scala/com/github/scalatojava/ScalaToJava.scala +++ b/src/main/scala/com/github/scalatojava/ScalaToJava.scala @@ -1,17 +1,16 @@ package com.github.scalatojava -import java.io.{File, StringWriter} -import java.nio.file.{Paths, Files} +import java.io.{File, StringWriter, _} +import java.nio.file.{Files, Paths} +import java.util.Arrays import com.strobel.assembler.InputTypeLoader import com.strobel.assembler.metadata._ import com.strobel.decompiler.languages.Languages import com.strobel.decompiler.languages.java.JavaFormattingOptions -import com.strobel.decompiler.{PlainTextOutput, DecompilerSettings, DecompilationOptions} -import scala.collection.mutable +import com.strobel.decompiler.{DecompilationOptions, DecompilerSettings, PlainTextOutput} + import scala.tools.nsc._ -import java.io._ -import java.util.Arrays /** * Scala-to-Java translator @@ -42,39 +41,28 @@ object ScalaToJava { writer.toString } - def compile(input: File, output: File): Array[File] = { + def compile(scalaFile: File, containerDir: File): Array[File] = { val settings = new Settings settings processArgumentString "-target:jvm-1.8 -Xscript _ -usejavacp -d " + - output.getPath + containerDir.getPath val compiler = new Global(settings) val runner = new compiler.Run - runner.compile(List(input.getPath)) - output.listFiles(new FilenameFilter { - override def accept(dir: File, name: String): Boolean = name.endsWith(".class") - }) + runner.compile(List(scalaFile.getPath)) + val compiledFiles: Array[File] = containerDir.listFiles(new FilenameFilter { + override def accept(dir: File, name: String): Boolean = name.endsWith(".class") + } + ) + return compiledFiles } def apply(value: String): String = { - val output = Files.createTempDirectory("s2j").toFile - val input = new File(output, "_.scala") - Files.write(input.toPath, Arrays.asList(value)) - compile(input, output).map(decompile).mkString("\n\n") + val containerDir = Files.createTempDirectory("s2j").toFile + val scalaFile = new File(containerDir, "_.scala") + Files.write(scalaFile.toPath, Arrays.asList(value)) + val compiled: Array[File] = compile(scalaFile, containerDir) + val map: Array[String] = compiled.map(decompile) + map.mkString("\n\n") } - class NoRetryMetadataSystem(typeLoader: ITypeLoader) extends MetadataSystem(typeLoader) { - - val failedTypes = mutable.Set[String]() - - override def resolveType(descriptor: String, mightBePrimitive: Boolean): TypeDefinition = { - if (failedTypes.contains(descriptor)) { - return null - } - val r = super.resolveType(descriptor, mightBePrimitive) - if (r == null) { - failedTypes.add(descriptor) - } - r - } - } } diff --git a/src/main/scala/com/github/scalatojava/ScalaToJavaFromStandardInput.scala b/src/main/scala/com/github/scalatojava/ScalaToJavaFromStandardInput.scala new file mode 100644 index 0000000..a83c879 --- /dev/null +++ b/src/main/scala/com/github/scalatojava/ScalaToJavaFromStandardInput.scala @@ -0,0 +1,15 @@ +package com.github.scalatojava + +class ScalaToJavaFromStandardInput +{ + def run(args: Array[String]) + { + val source = Iterator.continually(scala.io.StdIn.readLine()).takeWhile( + _ != null).mkString("\n") + + println() + val java = ScalaToJava(source) + + println(if (args.contains("--slim")) java.replaceFirst("(?s)public final class _\\$\\s+.*", "") else java) + } +} diff --git a/src/test/java/com/github/scalatojava/files/FileExtensionTest.java b/src/test/java/com/github/scalatojava/files/FileExtensionTest.java new file mode 100644 index 0000000..3a953bb --- /dev/null +++ b/src/test/java/com/github/scalatojava/files/FileExtensionTest.java @@ -0,0 +1,337 @@ +package com.github.scalatojava.files; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.junit.Assert.assertEquals; + +/** + * @author eitanraviv@github + * @since 21 Sep 2015 + */ +public class FileExtensionTest +{ + @Rule + public ExpectedException exception = ExpectedException.none(); + + /********** remove ************/ + + @Test + public void testRemove0() + { + //arrange + act + final String filename = FileExtension.remove(""); + //assert + assertEquals("", filename); + } + + @Test + public void testRemove1() + { + //arrange + act + final String filename = FileExtension.remove("name"); + //assert + assertEquals("name", filename); + } + + @Test + public void testRemove2() + { + //arrange + act + final String filename = FileExtension.remove("name."); + //assert + assertEquals("name", filename); + } + + @Test + public void testRemove3() + { + //arrange + act + final String filename = FileExtension.remove("name.e"); + //assert + assertEquals("name", filename); + } + + @Test + public void testRemove4() + { + //arrange + act + final String filename = FileExtension.remove("name.ext"); + //assert + assertEquals("name", filename); + } + + @Test + public void testRemove5() + { + //arrange + act + final String filename = FileExtension.remove("first.last.ext"); + //assert + assertEquals("first.last", filename); + } + + @Test + public void testRemove6() + { + //arrange + exception.expect(IllegalArgumentException.class); + //act + FileExtension.remove("."); + } + + @Test + public void testRemove7() + { + //arrange + exception.expect(IllegalArgumentException.class); + //act + FileExtension.remove(".."); + } + + @Test + public void testRemove8() + { + //arrange + exception.expect(IllegalArgumentException.class); + //act + FileExtension.remove(null); + } + + /********** get ************/ + + @Test + public void testGet0() + { + //arrange + act + final String ext = FileExtension.get(""); + //assert + assertEquals("", ext); + } + + @Test + public void testGet1() + { + //arrange + act + final String ext = FileExtension.get("name"); + //assert + assertEquals("", ext); + } + + @Test + public void testGet2() + { + //arrange + act + final String ext = FileExtension.get("name."); + //assert + assertEquals("", ext); + } + + @Test + public void testGet3() + { + //arrange + act + final String ext = FileExtension.get("name.e"); + //assert + assertEquals("e", ext); + } + + @Test + public void testGet4() + { + //arrange + act + final String ext = FileExtension.get("name.ext"); + //assert + assertEquals("ext", ext); + } + + @Test + public void testGet5() + { + //arrange + act + final String ext = FileExtension.get("first.last.ext"); + //assert + assertEquals("ext", ext); + } + + @Test + public void testGet6() + { + //arrange + exception.expect(IllegalArgumentException.class); + //act + FileExtension.get("."); + } + + @Test + public void testGet7() + { + //arrange + exception.expect(IllegalArgumentException.class); + //act + FileExtension.get(".."); + } + + @Test + public void testGet8() + { + //arrange + exception.expect(IllegalArgumentException.class); + //act + FileExtension.get(null); + } + + /********** replace ************/ + + @Test + public void testReplace0() + { + //arrange + act + final String filename = FileExtension.replace("", ""); + //assert + assertEquals(".", filename); + } + + @Test + public void testReplace1() + { + //arrange + act + final String filename = FileExtension.replace("name", "txt"); + //assert + assertEquals("name.txt", filename); + } + + @Test + public void testReplace2() + { + //arrange + act + final String filename = FileExtension.replace("name.", "txt"); + //assert + assertEquals("name.txt", filename); + } + + @Test + public void testReplace3() + { + //arrange + act + final String filename = FileExtension.replace("name.e", "txt"); + //assert + assertEquals("name.txt", filename); + } + + @Test + public void testReplace4() + { + //arrange + act + final String filename = FileExtension.replace("name.ext", "txt"); + //assert + assertEquals("name.txt", filename); + } + + @Test + public void testReplace5() + { + //arrange + act + final String filename = FileExtension.replace("first.last.ext", "txt"); + //assert + assertEquals("first.last.txt", filename); + } + + @Test + public void testReplace6() + { + //arrange + exception.expect(IllegalArgumentException.class); + //act + FileExtension.replace(".", ""); + } + + @Test + public void testReplace7() + { + //arrange + exception.expect(IllegalArgumentException.class); + //act + FileExtension.replace("..", ""); + } + + @Test + public void testReplace8() + { + //arrange + exception.expect(IllegalArgumentException.class); + //act + FileExtension.replace(null, ""); + } + + @Test + public void testReplace9() + { + //act + final String filename = FileExtension.replace("", null); + assertEquals(".", filename); + } + + @Test + public void testReplace10() + { + //arrange + act + final String filename = FileExtension.replace("name", ".txt"); + //assert + assertEquals("name.txt", filename); + } + + @Test + public void testReplace11() + { + //arrange + act + final String filename = FileExtension.replace("name.", ".txt"); + //assert + assertEquals("name.txt", filename); + } + + @Test + public void testReplace12() + { + //arrange + act + final String filename = FileExtension.replace("name.e", ".txt"); + //assert + assertEquals("name.txt", filename); + } + + @Test + public void testReplace13() + { + //arrange + act + final String filename = FileExtension.replace("", ".txt"); + //assert + assertEquals(".txt", filename); + } + + @Test + public void testReplace14() + { + //arrange + act + final String filename = FileExtension.replace("", "txt"); + //assert + assertEquals(".txt", filename); + } + + @Test + public void testReplace15() + { + //arrange + exception.expect(IllegalArgumentException.class); + //act + FileExtension.replace("", "."); + } + + @Test + public void testReplace16() + { + //arrange + exception.expect(IllegalArgumentException.class); + //act + FileExtension.replace(".", ""); + } +} diff --git a/src/test/java/com/github/scalatojava/files/strategies/CopyScalaFilesTest.java b/src/test/java/com/github/scalatojava/files/strategies/CopyScalaFilesTest.java new file mode 100644 index 0000000..069e8fd --- /dev/null +++ b/src/test/java/com/github/scalatojava/files/strategies/CopyScalaFilesTest.java @@ -0,0 +1,43 @@ +package com.github.scalatojava.files.strategies; + +import com.github.scalatojava.Constants; +import com.github.scalatojava.files.FileTreeTraversal; +import com.github.scalatojava.files.TraverseStrategy; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class CopyScalaFilesTest +{ + @Test + public void test() throws IOException + { + final File destDir = Files.createTempDirectory(Constants.S2J + CopyScalaFilesTest.class.getSimpleName()).toFile(); + System.out.println(String.format("all files written to %s", destDir.toPath())); + destDir.delete(); + destDir.mkdir(); + + TraverseStrategy s = new CopyScalaFiles(destDir, false); + FileTreeTraversal t = new FileTreeTraversal(); + t.traverse(new File("path/to/module/to/translate"), s); + } + + @Test + public void testOverwrite() throws IOException + { + final File destDir = Files.createTempDirectory(Constants.S2J + CopyScalaFilesTest.class.getSimpleName()).toFile(); + System.out.println(String.format("all files written to %s", destDir.toPath())); + destDir.delete(); + destDir.mkdir(); + + TraverseStrategy s = new CopyScalaFiles(destDir, true); + FileTreeTraversal t = new FileTreeTraversal(); + t.traverse(new File("path/to/module/to/translate"), s); + } +} \ No newline at end of file diff --git a/src/test/java/com/github/scalatojava/files/strategies/CreateClasspathTest.java b/src/test/java/com/github/scalatojava/files/strategies/CreateClasspathTest.java new file mode 100644 index 0000000..6901d5e --- /dev/null +++ b/src/test/java/com/github/scalatojava/files/strategies/CreateClasspathTest.java @@ -0,0 +1,25 @@ +package com.github.scalatojava.files.strategies; + +import com.github.scalatojava.files.FileTreeTraversal; +import com.github.scalatojava.files.TraverseStrategy; +import org.junit.Test; + +import java.io.File; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class CreateClasspathTest +{ + @Test + public void test() + { + final File jarRepo = new File("/path/to/repository/with/jars"); + FileTreeTraversal t = new FileTreeTraversal(); + + TraverseStrategy s1 = new CreateClasspath(); + t.traverse(jarRepo, s1); + System.out.println(s1.result()); + } +} \ No newline at end of file diff --git a/src/test/java/com/github/scalatojava/files/strategies/CreateImportsTest.java b/src/test/java/com/github/scalatojava/files/strategies/CreateImportsTest.java new file mode 100644 index 0000000..47f3f55 --- /dev/null +++ b/src/test/java/com/github/scalatojava/files/strategies/CreateImportsTest.java @@ -0,0 +1,24 @@ +package com.github.scalatojava.files.strategies; + +import com.github.scalatojava.files.FileTreeTraversal; +import com.github.scalatojava.files.TraverseStrategy; +import org.junit.Test; + +import java.io.File; + +/** + * @author eitanraviv@github + * @since 21 Sept 2015. + */ +public class CreateImportsTest +{ + @Test + public void test() + { + FileTreeTraversal t = new FileTreeTraversal(); + final File sourceDir = new File("path/to/module/to/translate"); + TraverseStrategy s = new CreateImports(); + t.traverse(sourceDir, s); + System.out.println(s.result()); + } +} \ No newline at end of file