1

I'm compiling Java classes from a string and for some reason however I'm doing it works fine on Windows but throws a NullPointerException on Linux.

I've tried Googling to find a solution but I can't find anything even remotely related to this.

Here is the stacktrace:

java.lang.RuntimeException: java.lang.NullPointerException
    at com.sun.tools.javac.main.Main.compile(Main.java:559)
    at com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:129)
    at com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:138) //This is where the compiler task gets called
    at mekanism.common.network.PacketSecurityUpdate.invalidUsername(PacketSecurityUpdate.java:140)
    at mekanism.common.network.PacketSecurityUpdate$SecurityUpdateMessage.fromBytes(PacketSecurityUpdate.java:89)
    at cpw.mods.fml.common.network.simpleimpl.SimpleIndexedCodec.decodeInto(SimpleIndexedCodec.java:17)
    at cpw.mods.fml.common.network.simpleimpl.SimpleIndexedCodec.decodeInto(SimpleIndexedCodec.java:7)
    at cpw.mods.fml.common.network.FMLIndexedMessageToMessageCodec.decode(FMLIndexedMessageToMessageCodec.java:77)
    at cpw.mods.fml.common.network.FMLIndexedMessageToMessageCodec.decode(FMLIndexedMessageToMessageCodec.java:17)
    at io.netty.handler.codec.MessageToMessageCodec$2.decode(MessageToMessageCodec.java:81)
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:89)
    at io.netty.handler.codec.MessageToMessageCodec.channelRead(MessageToMessageCodec.java:111)
    at io.netty.channel.DefaultChannelHandlerContext.invokeChannelRead(DefaultChannelHandlerContext.java:337)
    at io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:323)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:785)
    at io.netty.channel.embedded.EmbeddedChannel.writeInbound(EmbeddedChannel.java:169)
    at cpw.mods.fml.common.network.internal.FMLProxyPacket.func_148833_a(FMLProxyPacket.java:77)
    at net.minecraft.network.NetworkManager.func_74428_b(NetworkManager.java:245)
    at net.minecraft.network.NetworkSystem.func_151269_c(NetworkSystem.java:181)
    at net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:1023)
    at net.minecraft.server.dedicated.DedicatedServer.func_71190_q(DedicatedServer.java:432)
    at net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:841)
    at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:693)
    at java.lang.Thread.run(Thread.java:748)
  Caused by: java.lang.NullPointerException
    at com.sun.tools.javac.api.ClientCodeWrapper.isTrusted(ClientCodeWrapper.java:189)
    at com.sun.tools.javac.api.ClientCodeWrapper.wrap(ClientCodeWrapper.java:133)
    at com.sun.tools.javac.api.ClientCodeWrapper$WrappedJavaFileManager.getJavaFileForOutput(ClientCodeWrapper.java:309)
    at com.sun.tools.javac.jvm.ClassWriter.writeClass(ClassWriter.java:1615)
    at com.sun.tools.javac.main.JavaCompiler.genCode(JavaCompiler.java:746)
    at com.sun.tools.javac.main.JavaCompiler.generate(JavaCompiler.java:1572)
    at com.sun.tools.javac.main.JavaCompiler.generate(JavaCompiler.java:1536)
    at com.sun.tools.javac.main.JavaCompiler.compile2(JavaCompiler.java:901)
    at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:860)
    at com.sun.tools.javac.main.Main.compile(Main.java:523)
    ... 23 more

Here is the code that I'm using

    public static final JavaCompiler JAVAC;

    static {
        String h = System.getProperty("java.home");
        if (!h.endsWith("jre")) {
            h = h.replace("jre", "jdk") + File.separator + "jre";
            System.setProperty("java.home", h);
        }
        System.out.println(System.getProperty("java.home"));
        JAVAC = ToolProvider.getSystemJavaCompiler();
    }

    public static class JavaClass extends SimpleJavaFileObject {

        private ByteArrayOutputStream baos = new ByteArrayOutputStream();

        public JavaClass(String className) {
            super(getUri(className), Kind.CLASS);
        }

        private static final URI getUri(String className) {
            try {
                return new URI(className);
            } catch (URISyntaxException e) {
                throw new RuntimeException("Could not parse URI", e);
            }
        }

        @Override
        public OutputStream openOutputStream() throws IOException {
            return baos;
        }

        public byte[] getByteCode() {
            return baos.toByteArray();
        }

    }

    public static class JavaClassLoader extends ClassLoader {

        private final Map<String, JavaClass> classes = new HashMap<>();

        public JavaClassLoader(ClassLoader classLoader) {
            super(classLoader);
        }

        public JavaClass getClass(String name) {
            return classes.get(name);
        }

        public JavaClassLoader addClass(JavaClass cc) {
            classes.put(cc.getName(), cc);
            return this;
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            JavaClass cc = classes.get(name);
            if (cc == null) {
                return super.findClass(name);
            }
            byte[] byteCode = cc.getByteCode();
            return defineClass(name, byteCode, 0, byteCode.length);
        }

    }

    public static class FileManager extends ForwardingJavaFileManager<JavaFileManager> {

        private static final StandardJavaFileManager STANDARD_JAVA_FILE_MANAGER = JAVAC.getStandardFileManager(null, null, null);

        private JavaClassLoader classLoader = new JavaClassLoader(Test.class.getClassLoader());

        public FileManager(String className) {
            super(STANDARD_JAVA_FILE_MANAGER);
            this.classLoader.addClass(new JavaClass(className));
        }

        public JavaClassLoader getClassLoader() {
            return classLoader;
        }

        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
            return classLoader.getClass(className);
        }

        public byte[] getBytecode(String className) {
            return classLoader.getClass(className).getByteCode();
        }

        public Class<?> loadClass(String className) throws ClassNotFoundException {
            return classLoader.loadClass(className);
        }

    }

    public static class JavaSource extends SimpleJavaFileObject {

        private final String code;

        public JavaSource(String name, String code) {
            super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
            this.code = code;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return code;
        }

    }


    public static void main(String[] args) throws Exception {
        String className = //Class name
        String code = //Code

        JavaSource sourceCode = new JavaSource(className, code);
        FileManager fileManager = new FileManager(className);
        JAVAC.getTask(null, fileManager, null, null, null, Arrays.asList(sourceCode)).call();
    }
kmecpp
  • 2,371
  • 1
  • 23
  • 38

1 Answers1

0

@MadProgrammer's comment allowed me to realize what I forgot. The java home fix is only relevant on Windows

    static {
        if (System.getProperty("os.name").toLowerCase().contains("windows")) { //Added this check
            String h = System.getProperty("java.home");
            if (!h.endsWith("jre")) {
                h = h.replace("jre", "jdk") + File.separator + "jre";
                System.setProperty("java.home", h);
            }
        }
        JAVAC = ToolProvider.getSystemJavaCompiler();
    }
kmecpp
  • 2,371
  • 1
  • 23
  • 38
  • This `java.home` manipulation is not relevant to any platform. – Holger Jan 21 '19 at 09:24
  • @Holger I was having an issue compiling on Java versions and that code fixed it. I'm not sure this is the SO question where I found the answer but I think its the same or problem I was having: https://stackoverflow.com/questions/15513330/toolprovider-getsystemjavacompiler-returns-null-usable-with-only-jre-install – kmecpp Jan 21 '19 at 14:58
  • But this code is not sufficient to redirect from a JRE to a JDK; it’s just pure luck if the file system paths happen to be aligned in such a way that this code does the intended thing. Since even that lucky incident requires a prerequisite, i.e. that a JDK has been actually installed on that system (and in the right place), the much better solution always was to deselect the installation of another JRE within the installer. Thankfully, the most recent JDK versions do not have such a JRE anymore. The only thing to fix, is to remove this property manipulation which will break on Java 9 or newer. – Holger Jan 21 '19 at 15:22