Java tutorial
/** * Copyright 2012 Alex Jones * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. * * @author unclealex72 * */ package uk.co.unclealex.executable.generator; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.co.unclealex.executable.Executable; import uk.co.unclealex.executable.generator.jar.JarService; import uk.co.unclealex.executable.generator.scan.exception.ExecutableScanException; import uk.co.unclealex.process.ProcessRequest; import uk.co.unclealex.process.ProcessResult; import uk.co.unclealex.process.builder.ProcessRequestBuilder; import uk.co.unclealex.process.inject.ProcessRequestBuilderModule; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.inject.Guice; import com.google.inject.Injector; import com.lexicalscope.jewel.cli.CommandLineInterface; /** * @author alex * */ public class ScripterImplTest implements Opcodes { private static final Logger log = LoggerFactory.getLogger(ScripterImplTest.class); private Path tempDir; @Before public void setUp() throws IOException { tempDir = Files.createTempDirectory("scripterimpl-"); } @Test public void testScript() throws IOException, URISyntaxException, ExecutableScanException { Path classesDir = tempDir.resolve("classes"); Path scripterClassesDir = classesDir.resolve("scripter"); final Path workDir = tempDir.resolve("work"); Path homeDir = tempDir.resolve("home"); for (Path dir : new Path[] { scripterClassesDir, workDir, homeDir }) { Files.createDirectories(dir); } generateClasses("First", "first", false, scripterClassesDir); generateClasses("Second", "second", true, scripterClassesDir); Injector injector = Guice.createInjector(new CodeGeneratorModule()); Scripter scripter = injector.getInstance(Scripter.class); final JarService jarService = injector.getInstance(JarService.class); Path scriptPath = homeDir.resolve("script.sh"); Function<Class<?>, Path> jarFunction = new Function<Class<?>, Path>() { @Override public Path apply(Class<?> clazz) { try { return jarService.findJarFileOrDirectoryDefiningClass(getClass().getClassLoader(), clazz.getName()); } catch (IOException e) { e.printStackTrace(System.err); Assert.fail("Could not find a jar file for class " + clazz); return null; } } }; @SuppressWarnings("unchecked") List<Class<?>> classes = Lists.newArrayList(CommandLineInterface.class, Lists.class, Executable.class); Iterable<Path> jarFiles = Iterables.transform(classes, jarFunction); scripter.generate(scriptPath, classesDir, homeDir.toString(), "999", workDir, jarFiles); Assert.assertTrue("The generated script was not executable.", Files.isExecutable(scriptPath)); checkRun(homeDir, scriptPath, "[One, Two, Three]\n", "", 0, "--runas", "first", "One", "Two", "Three"); checkRun(homeDir, scriptPath, "[Three, Two, One]\n", "", 0, "--runas", "second", "One", "Two", "Three"); checkRun(homeDir, scriptPath, "", "Exactly one of --links, --show or --runas must be specified.\n", 2); } protected void checkRun(Path homeDir, Path scriptPath, String expectedStdout, String expectedStderr, int expectedReturnValue, String... arguments) throws IOException { ProcessRequestBuilder processRequestBuilder = Guice.createInjector(new ProcessRequestBuilderModule()) .getInstance(ProcessRequestBuilder.class); String command = scriptPath.toAbsolutePath().toString(); final ProcessRequest processRequest = processRequestBuilder.forCommand(command).withArguments(arguments); Callable<ProcessResult> callable = new Callable<ProcessResult>() { @Override public ProcessResult call() throws IOException { return processRequest.execute(); } }; List<String> commandParts = Lists.newArrayList(); commandParts.add(command); commandParts.addAll(Arrays.asList(arguments)); String fullCommand = Joiner.on(' ').join(commandParts); try { ProcessResult processResult = Executors.newSingleThreadExecutor().submit(callable).get(10, TimeUnit.SECONDS); Assert.assertTrue("The executable jar was not copied.", Files.isReadable(homeDir.resolve(".executable").resolve("executable-999.jar"))); Assert.assertEquals("Command " + fullCommand + " produced the wrong stderr.", expectedStderr, processResult.getError()); Assert.assertEquals("Command " + fullCommand + " produced the wrong stdout.", expectedStdout, processResult.getOutput()); Assert.assertEquals("Command " + fullCommand + " returned with the wrong value.", expectedReturnValue, processResult.getReturnValue()); } catch (InterruptedException | ExecutionException | TimeoutException e) { log.error("Running command " + fullCommand + " failed.", e); Assert.fail("Running command " + fullCommand + " failed."); } } protected void generateClasses(String classPrefixName, String commandName, boolean reverse, Path targetDirectory) throws IOException { generateCommandClass(classPrefixName, reverse, targetDirectory); generateCommandLineClass(classPrefixName, commandName, targetDirectory); } protected void generateCommandClass(String classPrefixName, boolean reverse, Path targetDirectory) throws IOException { ClassWriter cw = new ClassWriter(0); FieldVisitor fv; MethodVisitor mv; cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, "scripter/" + classPrefixName + "Command", null, "java/lang/Object", new String[] { "uk/co/unclealex/executable/streams/HasStdout" }); fv = cw.visitField(ACC_PRIVATE, "stdout", "Ljava/io/PrintStream;", null, null); fv.visitEnd(); mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); mv.visitInsn(RETURN); mv.visitMaxs(1, 1); mv.visitEnd(); mv = cw.visitMethod(ACC_PUBLIC, "run", "(Lscripter/" + classPrefixName + "CommandLine;)V", null, null); AnnotationVisitor av0 = mv.visitAnnotation("Luk/co/unclealex/executable/Executable;", true); av0.visitEnd(); mv.visitCode(); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEINTERFACE, "scripter/" + classPrefixName + "CommandLine", "getArguments", "()Ljava/util/List;"); mv.visitMethodInsn(INVOKESTATIC, "com/google/common/collect/Lists", "newArrayList", "(Ljava/lang/Iterable;)Ljava/util/ArrayList;"); mv.visitVarInsn(ASTORE, 2); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKEVIRTUAL, "scripter/" + classPrefixName + "Command", "reverse", "()Z"); Label l0 = new Label(); mv.visitJumpInsn(IFEQ, l0); mv.visitVarInsn(ALOAD, 2); mv.visitMethodInsn(INVOKESTATIC, "java/util/Collections", "reverse", "(Ljava/util/List;)V"); mv.visitLabel(l0); mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { "java/util/List" }, 0, null); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, "scripter/" + classPrefixName + "Command", "stdout", "Ljava/io/PrintStream;"); mv.visitVarInsn(ALOAD, 2); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/Object;)V"); mv.visitInsn(RETURN); mv.visitMaxs(2, 3); mv.visitEnd(); mv = cw.visitMethod(ACC_PROTECTED, "reverse", "()Z", null, null); mv.visitCode(); mv.visitInsn(reverse ? ICONST_1 : ICONST_0); mv.visitInsn(IRETURN); mv.visitMaxs(1, 1); mv.visitEnd(); mv = cw.visitMethod(ACC_PUBLIC, "getStdout", "()Ljava/io/PrintStream;", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, "scripter/" + classPrefixName + "Command", "stdout", "Ljava/io/PrintStream;"); mv.visitInsn(ARETURN); mv.visitMaxs(1, 1); mv.visitEnd(); mv = cw.visitMethod(ACC_PUBLIC, "setStdout", "(Ljava/io/PrintStream;)V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitFieldInsn(PUTFIELD, "scripter/" + classPrefixName + "Command", "stdout", "Ljava/io/PrintStream;"); mv.visitInsn(RETURN); mv.visitMaxs(2, 2); mv.visitEnd(); cw.visitEnd(); Files.write(targetDirectory.resolve(classPrefixName + "Command.class"), cw.toByteArray()); } protected void generateCommandLineClass(String classPrefixName, String commandName, Path targetDirectory) throws IOException { ClassWriter cw = new ClassWriter(0); MethodVisitor mv; AnnotationVisitor av0; cw.visit(V1_7, ACC_ABSTRACT + ACC_INTERFACE, "scripter/" + classPrefixName + "CommandLine", null, "java/lang/Object", null); av0 = cw.visitAnnotation("Lcom/lexicalscope/jewel/cli/CommandLineInterface;", true); av0.visit("application", commandName); av0.visitEnd(); mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "getArguments", "()Ljava/util/List;", "()Ljava/util/List<Ljava/lang/String;>;", null); av0 = mv.visitAnnotation("Lcom/lexicalscope/jewel/cli/Unparsed;", true); av0.visitEnd(); mv.visitEnd(); cw.visitEnd(); Files.write(targetDirectory.resolve(classPrefixName + "CommandLine.class"), cw.toByteArray()); } @After public void tearDown() throws IOException { FileUtils.deleteDirectory(tempDir.toFile()); } }