Java Web IDE implemented by Vue codemirror + java compiler

background

Recently, my colleague told me a very interesting requirement: let the user (in the application scenario, generally for other developers) fill in the Java code snippet. The content of the code snippet is the inheritance class of the template class that has been specified, and implement the method of template class definition. Our project is to realize dynamic compilation of code fragments, store the mapping relationship between code fragments and user operation records, and be able to load code fragments in the business for execution.

This is a bit like we provide a template pattern architecture, except that the implementation class of the template class is dynamically implemented by the external interface filled with code snippets. Compared with letting other developers directly participate in project development, there is no doubt that:

  1. Reduced risk of intrusion
  2. Hide most of the implementation from other developers
  3. Reduce operation difficulty and development threshold
  4. Easy to manage

......
This is equivalent to implementing a simple online Java development environment, providing basic code filling, compiling and saving functions.

Effect demonstration

Based on the dynamic compilation of Vue codemirror and Java Compiler, the above requirements are realized. The main functions of the Web IDE completed at present include:

  • Java code block is displayed on the page (code highlights, line numbers, brackets can be filled automatically, etc.)
  • Get the template class code from the server and provide an example
  • Compile dynamically in real time and get compilation results (pass / fail todo: return compilation error information)
  • Load the input string into Java Class
    And small function points: Auto indent, complete brackets, switch topics, fill in class name and so on.
    The technologies and implementation methods involved are given below.

CodeMirror

CodeMirror is a JS library, which can support the implementation of rich additional functions and multi language support. The front-end of our project uses the Vue framework, which can easily integrate and use the plug-ins provided by CodeMirror to realize various features of our online IDE.
reference resources: CodeMirror website

introduce

Installation dependency: "Vue codemirror": "^ 4.0.6"
In the src directory main.js Introduced in:

import VueCodeMirror from 'vue-codemirror'
import 'codemirror/lib/codemirror.css'
Vue.use(VueCodeMirror)

use

New component JavaIDE.vue

<template>
    <codemirror ref="codeMirrorEditor" :value="code" :options="cmOptions" @changes="onChange">
  </codemirror>
  </template>  
  <script>
      import codemirror from "codemirror/lib/codemirror";
      require("codemirror/mode/clike/clike.js");
      require("codemirror/addon/edit/closebrackets.js");
      components: {
          codemirror;
      }
      export default{
          data(){
              return{
                code: "",
                cmOptions:{
                    mode: "text/x-java",  //Java language
                    theme: "darcula", // Default theme
                    autofocus: true,  
                    lineNumbers: true,   //set number 
                    smartIndent: true, // Auto indent
                    autoCloseBrackets: true// Autocomplete parentheses
                }
              }
          }
  </script>

Using it componently, we can easily manipulate its bound value (code) and other additional options (cmOption).
The template code can be loaded by assigning a value to the code when the component is created.

According to the official website, we can directly use the default constructor of CodeMirror, or provide a textarea DOM element as the parameter to construct the CodeMirror object.

You can use the readOnly parameter to set the code block to read-only.

Function of linkage filling in class name

Hope to achieve: fill in the class name in the top column above, and fill in the code in linkage.
Implementation: replace the code snippet with regular matching, and then replace it
Using the same method, we can also achieve the function of dynamic completion of class names

See more Regular expression of JavaScript

Add listening function @ input="changeClassName" to input box

 changeClassName(className) {
    var reg = new RegExp(/public class .*? extends ActionParamBuilder/);
    this.code = this.code.replace(reg,
                    "public class " + className + " extends ActionParamBuilder"
   );
 }

Switch theme

Import theme css style file

 import "codemirror/theme/eclipse.css";
 import "codemirror/theme/darcula.css";
 import "codemirror/theme/blackboard.css";

Use String array to define supported topics, and Select component provided by element UI to support topic switching:

<el-select v-model="cmOptions.theme" placeholder="Switch theme" @change="changeTheme">
          <span slot="prefix">
             <el-tooltip content="Change theme">
              <a-icon type="skin" style="fontSize:16px;line-height=50px;"/>
      </el-tooltip>
      </span>
 <el-option v-for="(item,index) in supportThemes" :key="index" :label="item" :value="item">
   </el-option>
</el-select>
  • Use slot to embed icons in selectors, and support the tool IP function, making the toolbar more compact. Slot means slot, which is the space reserved by the encapsulated component for customization. We can use slot to put DOM elements into the component, which is very flexible.

Style override

Use the! important keyword to override the original CodeMirror style. Note that the style is placed in a global rather than local scoped style sheet.

.CodeMirror {
     height: 500px !important;
 }

JavaCompiler

Instead of saving the incoming code as a. java file and writing it to disk, you can directly use the JavaCompiler tool to compile strings.

In order to realize the function of real-time dynamic compilation, I searched for the method of how to compile strings into class es, and also looked at the implementation ideas of some dynamic agents. Later, I read this article:
The method of dynamically generating class in Java runtime
, found that this is what I want!

Use the java compiler tool provided by Java SDK (since 1.6). The tool provides compilation methods:

  CompilationTask getTask(Writer out,
                            JavaFileManager fileManager,
                            DiagnosticListener<? super JavaFileObject> diagnosticListener,
                            Iterable<String> options,
                            Iterable<String> classes,
                            Iterable<? extends JavaFileObject> compilationUnits);
  • JavaFileManager
    Customize memory javafilemanager, inherit forwarding javafilemanager < javafilemanager >, and read JavaFileObject from memory string
    The key point is the following method:

	JavaFileObject makeStringSource(String name, String code) {
		return new MemoryInputJavaFileObject(name, code);
	}
	static class MemoryInputJavaFileObject extends SimpleJavaFileObject {
		final String code;
		MemoryInputJavaFileObject(String name, String code) {
			super(URI.create("string:///" + name), Kind.SOURCE);
			this.code = code;
		}
		@Override
		public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
			return CharBuffer.wrap(code);
		}
	}
  • options, optional parameter list, can increase external Jar package dependency
    Because the dependent classes in the code we need to compile are from external Jar packages, we need to use option to add these dependencies. I stepped on the pit in this step, because I didn't use it before, I don't know how to write Finally, we found the right way to write:
    List<String> optionList =Arrays.asList("-extdirs",extLib);
    extLib is the path (directory address) of the external jar package. Multiple paths can be filled with path separators.


  • Diagnostic listener
    By adding the diagnostic information listener, we can get the compiled error information and feed it back to the front end to realize the function of real-time compiling and error reporting.
    DiagnosticCollector diagnosticCollector = new DiagnosticCollector();

  • JavaFileObject the Java object to be compiled calls the makeStringSource method of the custom class MemoryJavaFileManager. You can pass in a set of compilation units.
    The complete method is as follows:
public Map<String, byte[]> compile(String fileName, String source,String extLib) throws IOException {
		try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
			JavaFileObject javaFileObject = manager.makeStringSource(fileName, source);    
            // The size of the incoming diagnostic listener is the same as that of the incoming javaObject
            DiagnosticCollector diagnosticCollector = new DiagnosticCollector();
			List<String> optionList =Arrays.asList("-extdirs",extLib);
			CompilationTask task = compiler.getTask(null, manager,diagnosticCollector, optionList, null, Arrays.asList(javaFileObject));
			Boolean result = task.call();
			if (result == null || !result.booleanValue()) {
				throw new RuntimeException("Compilation failed.");
			}
			return manager.getClassBytes();
		}
	}

Call code:

 Map<String, byte[]> results = javaStringCompiler.compile(className + ".java", CODE_TO_COMPILE, libDir);

Custom ClassLoader

reference resources Logic of Java programming In 24.5, we can use the user-defined ClassLoader to load user code fragments and become callable Class objects.

  • Inherit URLClassLoader
  • Override the findClass method
class MemoryClassLoader extends URLClassLoader {

	// class name to class bytes:
	Map<String, byte[]> classBytes = new HashMap<String, byte[]>();

	public MemoryClassLoader(Map<String, byte[]> classBytes) {
		super(new URL[0], MemoryClassLoader.class.getClassLoader());
		this.classBytes.putAll(classBytes);
	}

	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		byte[] buf = classBytes.get(name);
		if (buf == null) {
			return super.findClass(name);
		}
		classBytes.remove(name);
		return defineClass(name, buf, 0, buf.length);
	}

}

The custom class loader has the following advantages:

  • You can customize the method and form of reading class file bytecode, such as from memory, specified jar package, database / network, etc
  • Implement isolation, and different versions of the same class can be used
  • Implement hot deployment and dynamically update class content

summary

This part mainly involves knowledge points:

  • Vue codemirror integration and use
  • The use of JavaCompiler
  • Slots in JavaScript regular and Vue
  • Custom ClassLoader for dynamic loading

Tags: Java Vue Javascript Eclipse

Posted on Sat, 23 May 2020 21:44:37 -0700 by jh21236