IntelliJ IDEA – remote debug of java code inside docker container

Since I do Java development on windows and IntelliJ, sometimes I need to do remote debugging of files inside docker image that is running inside VM. The problem that I have here is that each time I want to debug, I would need to pack project as .jar (long process), then go to shell of Linux running inside docker and run java -jar in server mode (waiting for my connection), and then go to IntelliJ and run app with Remote config.

This plugin that I created (Remote Debug Plugin), when installed is automatically communicating with remote docker process, and all output of this remote process is redirected directly to IntelliJ console – so there is no need to go to two places – just press DEBUG icon. Prerequisites are that you need to start my image (https://hub.docker.com/r/bojanv55/rds/) that has this java remote process running. Also, in order to run your files, all your windows drives (C:, D:, …) needs to be mounted inside docker image.

Hare is a walk-trough of installation

Inside IntelliJ you will need this plugging installed:

Currently plugin is being approved by Jetbrains, so in case it is not already in their repo, you can download it manually from this address https://github.com/bojanv55/remote-debug/blob/master/remote-debug.jar?raw=true and isntall it manually from disk.

Since my docker is running inside linux which is running as virtual machine inside VirtualBox, I had to share all drives where my Java code is on (C: drive is usually needed, since java SDK and maven repositories are there).

   

Or, if you are using Vagrant, this is how you share your C: drive as /c:

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/trusty64"
  config.vm.network "private_network", ip: "192.168.50.50"
  config.vm.synced_folder "C:\\", "/c"
  config.vm.provider "virtualbox" do |v|
    v.memory = 2048
    v.cpus = 2
  end
end

On the docker side, you will need to pull my image (bojanv55/rds), and since I’m using minikube (Kubernetes) in my demonstration, I will show pod configuration for this specific case (inside file named rds.yaml):

apiVersion: v1
kind: Pod
metadata:
  name: remotedbg
  labels:
    app: remotedbg
spec:
  hostNetwork: true
  volumes:
  - name: drive-c
    hostPath:
      path: /media/sf_C_DRIVE
  - name: drive-d
    hostPath:
      path: /media/sf_D_DRIVE
  containers:
  - image: bojanv55/rds
    name: remotedbg
    volumeMounts:
    - name: drive-c
      mountPath: /c
    - name: drive-d
      mountPath: /d
    ports:
    - containerPort: 55004
    - containerPort: 55005
    - containerPort: 55006

Here we are mounting C: and D: drives shared inside VirtualBox to docker image, so we will have full access to host disk (where java source is). It is important that those drives are mounted as “/c”, “/d” … “/z” folders inside docker. Container ports are used for communication with my server (55004) and (55005-55015) for communication with java remote debug server (multiple ports in order to support multiple parallel process debugging).

Now we can start this pod:

kubectl create -f rds.yaml

And if we list all the pods, we will now see our remote server running:

kubectl get pods

NAME                                READY     STATUS    RESTARTS   AGE
remotedbg                           1/1       Running   0          28m

Or, if you don’t use minikube, you can run docker image inside VM with this simple command:

docker run -d -v /c:/c -p 55004:55004 -p 55005:55005 -p 55006:55006 bojanv55/rds

Now we are ready to setup our project for remote debugging. I just created new project that outputs “HI RDS”, and run it locally (this is needed in order to create local Application run configuration that will be used for remote debugging also).

Then we can go to “Edit Configurations” window, and setup our “Remote Debug” configuration:

As you can see, we are using local Application configuration as base for our remote config (this is where we pickup working directory, all needed libraries, etc…). Host is address of my Linux running inside VirtualBox in this case (since we are using “hostNetwork: true” inside kubernetes configuration, docker image will also have access to this IP).

Finally, we can run this Remote debug session:

As you can see we have our app running on remote system with 1 click of a button. If we need to add new code, and test again, all we need to do is add code to Intellij and hit that debug button inside IDEA. Much easier… And maybe someone will find it useful…

In case that you want to modify project, code is on github:

Server part: https://github.com/bojanv55/remote-debug-server
Plugin: https://github.com/bojanv55/remote-debug

Advertisements

Java 8 – Functional Interfaces

Types and methods

  • Predicate<T> – test
  • Consumer<T> – accept
  • Supplier<T> – get / getAs[RETURN_PRIMITIVE_TYPE]
  • Function<T,R> – apply / applyAs[RETURN_PRIMITIVE_TYPE]
    • UnaryOperator<T> extends Function<T,T> – apply / applyAs[RETURN_PRIMITIVE_TYPE]
    • BinaryOperator<T> extends Function<T,T,T> – apply / applyAs[RETURN_PRIMITIVE_TYPE]

 

java8FunctionalInterfaces

Walking filesystem from Java console

This is small example of walking over filesystem from console:

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Scanner;
import java.util.Spliterator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

class DirectoryList{


	public static void main(String[] args) throws IOException {

		Scanner scanner = new Scanner(System.in);
		Path selectedPath = null;

		while (true){

			//get all paths in current selected directory
			List<Path> selectFromPath = getDirList(selectedPath).collect(Collectors.toList());

			System.out.println("\n--------");

			for(int i=0; i<selectFromPath.size(); i++){
				System.out.println(i + " : " + selectFromPath.get(i));
			}

			System.out.print("\nEnter choice (ENTER TO EXIT) > ");
			//select path matching line number
			String choice = scanner.nextLine();

			if(choice.isEmpty()) break;

			//load that path inside selectFromPath using line number as index
			selectedPath = selectFromPath.get(Integer.parseInt(choice));

		}

	}

	private static Stream<Path> getDirList(Path path) throws IOException {
		if(path == null){
			//if path is null, get initial drive list in windows (or root in linux)
			Spliterator<Path> pathSpliterator = FileSystems.getDefault().getRootDirectories().spliterator();
			return StreamSupport.stream(pathSpliterator, false);
		}
		//include path for walking one level up from current "path"
		Path previousPath = path.resolve(Paths.get("..")).normalize();
		//include path for previous level (..) and all paths in the current "path" directory
		return Stream.concat(Stream.of(previousPath.equals(path) ? null : previousPath), Files.walk(path, 1));
	}

}

Better database backed-up enum

After the code that uses javassist to generate bytecode for the enum that can be loaded from the database, I created full java solution for enum that is partially defined in code and partially read from the database. So here is the code:

import java.util.HashMap;
import java.util.Map;

public class Sport {

    private int id;
    private SportName name;
    private String additionalProp;

    public int getId(){
        return this.id;
    }

    public SportName getName(){
        return this.name;
    }

    public String getAdditionalProp(){
        return this.additionalProp;
    }

    public Sport(int id, String name, String additionalProp){
        this.id = id;
        this.name = Name.getOrCreateSportName(id, name);
        this.additionalProp = additionalProp;
    }

    private interface SportName{}

    private static class SportNameImpl implements SportName{
        private final String name;

        protected SportNameImpl(String name){
            this.name = name;
        }

        @Override
        public String toString() {
            return this.name;
        }
    }

    public enum Name implements SportName{
        SOCCER(1, "Soccer"),
        BASKETBALL(2, "Basketball"),
        TENNIS_CLAY(3, "Tennis"),   // 1.GroupClay -> 3.Tennis (in case 2 sports can have same name)
        TENNIS_GRASS(4, "Tennis");  // 2.GroupGrass -> 4.Tennis (in case 2 sports can have same name)

        private static final Map<Integer, Name> sportIdToSportNames = new HashMap<>();

        static{
            for(Name sportName : Name.values()){
                sportIdToSportNames.put(sportName.id, sportName);
            }
        }

        private final int id;
        private final String name;

        Name(int id, String name){
            this.id = id;
            this.name = name;
        }

        private static SportName getOrCreateSportName(int id, String name){
            SportName sportName = sportIdToSportNames.get(id);
            if(sportName == null){
                sportName = new SportNameImpl(name);
            }
            return sportName;
        }

        @Override
        public String toString() {
            return this.name;
        }
    }

}

import java.util.Arrays;
import java.util.List;

public class noviEnumProg {

    public static void main(String[] args) {
        Sport sport = new Sport(1, "Soccer", "additionalpro");
        Sport s4 = new Sport(100, "badminton", "badmintondodatni");
        Sport s2 = new Sport(2, "BASKABALLAAA", "dodatnozabasket");
        Sport s3 = new Sport(3, "HOKEJ", "HOKEJD");

        List<Sport> sports = Arrays.asList(sport, s2, s3, s4);

        if(sport.getName() == Sport.Name.SOCCER){
            System.out.println("this is soccer!");
        }

        if(s2.getName() == Sport.Name.BASKETBALL){
            System.out.println("this is basket since id is same");
        }

        if(s3.getName() == Sport.Name.SOCCER){
            System.out.println("this is never true");
        }

        for(Sport sp : sports){
            System.out.println(sp.getName() + " + " + sp.getAdditionalProp());
        }
    }

}

Java – Dynamic Enums (Database supported Enums)

Since I needed a good implementation of dynamic enum feature in Java, is tried to find a way to do it. I found this post that explained how to do it using reflection, but since it was too complicated, I wanted to find a better solution. After few days, I have something that can be used in projects… It is implemented using javassist library, and this is something that you have to run after compilation is done. So, after compilation of your Java project, you have to run “DynamicEnumGenerator.java” with correct parameters in its main method (enum full name and path of generated classes) in order for javassist o modify default compiled code.

If you want to automatize this part, you can use one of many Maven or Gradle plugins avaliable. For testing purposes, you can just start main method on the DynamicEnumGenerator class, but be sure to modify this line

generator.makeDynamic("me.vukas.enumeration.DynamicEnum", "./target/classes/");

– it tells what enum to modify and where are the original classes that needs to be modified before final usage.

Here is the description of the classes. First, lets talk about enum that needs to be write-enabled:

public enum DynamicEnum {
    ONE(1, "one"),
    TWO(2, "two"),
    THREE(3, "three");

    private int id;
    private String alternativeName;

    DynamicEnum(int id){
        this(id, null);
    }

    DynamicEnum(int id, String alternativeName){
        this.id = id;
        this.alternativeName = alternativeName;
    }

    public int getId() {
        return id;
    }

    public String getAlternativeName() {
        return alternativeName;
    }

    //must have methods per constructor that will be used in runtime.
    //first parameter is name of the enum that wants to be added
    //body will be replaced in the compile time
    public static boolean add(String value, int id, String alternativeName){ throw new IllegalStateException(); }
    public static boolean add(String value, int id){ throw new IllegalStateException(); }

    //must have this single method per enum. allows removal of dynamic enum in runtime
    //body will be replaced in the compile time
    public static boolean remove(String value){ throw new IllegalStateException(); }
}

In runtime, you cannot delete defined enums ONE, TWO and THREE. You can remove only dynamically added values.

In order to add values, you have to define methods using signature as stated above – first param must be String, and other parameters must be equal with type to Enum constructors.

In order to remove values, you need only single remove method (you can copy it from above). This method receives name of dynamically created enum that should be deleted in runtime.

Now, let’s see code that is used by javassist in order to modify original enum and enable run-time write operations:

import javassist.*;

import java.io.IOException;

public class DynamicEnumGenerator {
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
        DynamicEnumGenerator generator = new DynamicEnumGenerator();
        generator.makeDynamic("me.vukas.enumeration.DynamicEnum", "./target/classes/");
    }

    private void makeDynamic(String className, String targetDirectory) throws NotFoundException, CannotCompileException, IOException {
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.get(className);

        int hardcodedEnumsCount = 0;
        for(CtField ctField : ctClass.getFields()){
            if(ctField.getType().equals(ctClass)){
                hardcodedEnumsCount++;
            }
        }

        CtField ctSizeField = CtField.make(String.format("private final static int $SIZE = %d;", hardcodedEnumsCount), ctClass);
        ctClass.addField(ctSizeField);
        CtField ctConstructorsField = CtField.make("private static java.util.Map $CONSTRUCTORS = new java.util.concurrent.ConcurrentHashMap();", ctClass);
        ctClass.addField(ctConstructorsField);

        int constructorsCount = 0;
        CtClass ctEmbeddedClass = ctClass.makeNestedClass("Embedded", true);
        CtField ctCallableField = CtField.make("public java.util.concurrent.Callable kon;", ctEmbeddedClass);
        ctEmbeddedClass.addField(ctCallableField);
        CtConstructor ctc12 = new CtConstructor(new CtClass[]{ classPool.get("java.util.concurrent.Callable") }, ctEmbeddedClass);
        ctc12.setBody("{ this.kon = $1; }");
        ctEmbeddedClass.addConstructor(ctc12);
        ctEmbeddedClass.toClass();
        ctEmbeddedClass.writeFile(targetDirectory);

        CtClass ctBasicConstructorClass = ctClass.makeNestedClass("BasicConstructor", true);
        CtConstructor ctConstructor = new CtConstructor(null, ctBasicConstructorClass);
        ctConstructor.setBody("{}");
        ctBasicConstructorClass.addConstructor(ctConstructor);
        CtField ctOrdinalField = new CtField(CtPrimitiveType.intType, "ordinal", ctBasicConstructorClass);
        ctOrdinalField.setModifiers(Modifier.PUBLIC);
        ctBasicConstructorClass.addField(ctOrdinalField);
        ctBasicConstructorClass.toClass();
        ctBasicConstructorClass.writeFile(targetDirectory);

        for(CtMethod method : ctClass.getDeclaredMethods()){
            if(method.getName().equals("add")
                    && method.getReturnType().getName().equals("boolean")
                    && Modifier.isStatic(method.getModifiers())
                    && Modifier.isPublic(method.getModifiers())
                    && method.getParameterTypes().length > 0
                    && method.getParameterTypes()[0].getName().equals("java.lang.String")
                    ){
                CtClass constructorClass = ctClass.makeNestedClass("Constructor" + constructorsCount, true);
                constructorClass.setSuperclass(ctBasicConstructorClass);
                CtClass ctCallableClass = classPool.get("java.util.concurrent.Callable");
                constructorClass.addInterface(ctCallableClass);
                constructorClass.addField(new CtField(classPool.get("java.lang.String"), "name", constructorClass));
                CtClass[] constructorTypes = new CtClass[method.getParameterTypes().length+1];

                constructorTypes[0] = classPool.get("java.lang.String");
                constructorTypes[1] = CtPrimitiveType.intType;

                StringBuilder constructorBody = new StringBuilder();
                constructorBody.append("{ this.name=$1; this.ordinal=$2; ");

                for(int i=2; i<=method.getParameterTypes().length; i++){
                    constructorTypes[i] = method.getParameterTypes()[i-1];
                    constructorClass.addField(new CtField(constructorTypes[i], "param" + i, constructorClass));
                    constructorBody.append("this.param");
                    constructorBody.append(i);
                    constructorBody.append("=$");
                    constructorBody.append(i+1);
                    constructorBody.append("; ");
                }
                constructorBody.append("} ");

                CtConstructor constructor = new CtConstructor(constructorTypes, constructorClass);
                constructor.setBody(constructorBody.toString());
                constructorClass.addConstructor(constructor);

                CtClass objectClass = classPool.getCtClass("java.lang.Object");

                CtMethod callMethod = new CtMethod(objectClass, "call", null, constructorClass);
                callMethod.setExceptionTypes(new CtClass[]{classPool.getCtClass("java.lang.Exception")});

                StringBuilder callableBody = new StringBuilder();
                callableBody.append("{return new ");
                callableBody.append(className);
                callableBody.append("(name,ordinal");

                for(int i =2; i<=method.getParameterTypes().length; i++){
                    callableBody.append(",param");
                    callableBody.append(i);
                }
                callableBody.append(");}");

                callMethod.setBody(callableBody.toString());
                constructorClass.addMethod(callMethod);

                constructorClass.toClass();
                constructorClass.writeFile("./target/classes/");

                StringBuilder methodBody = new StringBuilder("{ for(int i=0; i<$VALUES.length; i++){ if($VALUES[i].name().equals($1)){ return false; }} int currentNumOfEnums = $VALUES.length; %1$s[] temp = new %1$s[currentNumOfEnums + 1]; System.arraycopy($VALUES, 0, temp, 0, currentNumOfEnums); $CONSTRUCTORS.put($1, new ");
                methodBody.append(className);
                methodBody.append(".Embedded(new ");
                methodBody.append(className);
                methodBody.append(".Constructor");
                methodBody.append(constructorsCount);
                methodBody.append("($1, currentNumOfEnums");

                for(int i =2; i<=method.getParameterTypes().length; i++){
                    methodBody.append(", $");
                    methodBody.append(i);
                }

                methodBody.append("))); try { temp[currentNumOfEnums] = ((");
                methodBody.append(className);
                methodBody.append(".Constructor");
                methodBody.append(constructorsCount);
                methodBody.append(")((");
                methodBody.append(className);
                methodBody.append(".Embedded)$CONSTRUCTORS.get($1)).kon).call(); } catch (Exception e) { e.printStackTrace(); } $VALUES = temp; return true;}");

                method.setBody(String.format(methodBody.toString(), className));

                constructorsCount++;
            }

            if(method.getName().equals("remove")
                    && method.getReturnType().getName().equals("boolean")
                    && Modifier.isStatic(method.getModifiers())
                    && Modifier.isPublic(method.getModifiers())
                    && method.getParameterTypes().length > 0
                    && method.getParameterTypes()[0].getName().equals("java.lang.String")
                    ){
                method.setBody(String.format("{ for(int i=0; i<$VALUES.length; i++){ if($VALUES[i].name().equals($1)){ if(i < %1$s.$SIZE){ return false; } int currentNumOfEnums = $VALUES.length; %1$s[] temp = new %1$s[currentNumOfEnums - 1]; System.arraycopy($VALUES, 0, temp, 0, i); System.arraycopy($VALUES, i+1, temp, i, currentNumOfEnums-i-1); $CONSTRUCTORS.remove($VALUES[i].name()); for(int j=i; j<currentNumOfEnums-1; j++){ try { ((" + className + ".BasicConstructor)((" + className + ".Embedded)$CONSTRUCTORS.get(temp[j].name())).kon).ordinal = j; temp[j] = (((" + className + ".Embedded)$CONSTRUCTORS.get(temp[j].name())).kon).call(); } catch (Exception e) { e.printStackTrace(); } } $VALUES = temp; return true; } } return false; }", className));
            }
        }
        ctClass.writeFile(targetDirectory);
    }
}

And finally, the test code that shows up how this can be used (you should run this only after full compilation, and after execution of DynamicEnumGenerator – otherwise you’ll get IllegalStateException.

import org.junit.Test;

public class DynamicEnumTest {

    @Test
    public void DemoEnumUsage(){
        System.out.println("==DEMO ENUM TEST ==");
        for(DynamicEnum dynamicEnum : DynamicEnum.values()){
            System.out.println("Name: " + dynamicEnum.name() + "; Id:" + dynamicEnum.getId() + "; AlternativeName: " + dynamicEnum.getAlternativeName() + "; Ordinal: " + dynamicEnum.ordinal());
        }

        System.out.println("Adding 4,5,6 in runtime...");

        DynamicEnum.add("FOUR", 4);
        DynamicEnum.add("FIVE", 5, "five");
        DynamicEnum.add("SIX", 6, "six");

        System.out.println("Values after update:");
        for(DynamicEnum dynamicEnum : DynamicEnum.values()){
            System.out.println("Name: " + dynamicEnum.name() + "; Id:" + dynamicEnum.getId() + "; AlternativeName: " + dynamicEnum.getAlternativeName() + "; Ordinal: " + dynamicEnum.ordinal());
        }

        System.out.println("Deleting 5...");

        DynamicEnum.remove("FIVE");

        System.out.println("Values after removal of enum 5 (Six got new ordinal):");
        for(DynamicEnum dynamicEnum : DynamicEnum.values()){
            System.out.println("Name: " + dynamicEnum.name() + "; Id:" + dynamicEnum.getId() + "; AlternativeName: " + dynamicEnum.getAlternativeName() + "; Ordinal: " + dynamicEnum.ordinal());
        }

        System.out.println("Readding 5 and adding 10...");

        DynamicEnum.add("FIVE", 5);
        DynamicEnum.add("TEN", 10, "ten");

        System.out.println("Values after update:");
        for(DynamicEnum dynamicEnum : DynamicEnum.values()){
            System.out.println("Name: " + dynamicEnum.name() + "; Id:" + dynamicEnum.getId() + "; AlternativeName: " + dynamicEnum.getAlternativeName() + "; Ordinal: " + dynamicEnum.ordinal());
        }

        System.out.println("Trying to remove 1 (cannot because it is hardcoded) and remove 6...");

        DynamicEnum.remove("ONE");
        DynamicEnum.remove("SIX");

        System.out.println("Values after update:");
        for(DynamicEnum dynamicEnum : DynamicEnum.values()){
            System.out.println("Name: " + dynamicEnum.name() + "; Id:" + dynamicEnum.getId() + "; AlternativeName: " + dynamicEnum.getAlternativeName() + "; Ordinal: " + dynamicEnum.ordinal());
        }
    }

}

This is the output of this program:

 

==DEMO ENUM TEST ==
Name: ONE; Id:1; AlternativeName: one; Ordinal: 0
Name: TWO; Id:2; AlternativeName: two; Ordinal: 1
Name: THREE; Id:3; AlternativeName: three; Ordinal: 2
Adding 4,5,6 in runtime…
Values after update:
Name: ONE; Id:1; AlternativeName: one; Ordinal: 0
Name: TWO; Id:2; AlternativeName: two; Ordinal: 1
Name: THREE; Id:3; AlternativeName: three; Ordinal: 2
Name: FOUR; Id:4; AlternativeName: null; Ordinal: 3
Name: FIVE; Id:5; AlternativeName: five; Ordinal: 4
Name: SIX; Id:6; AlternativeName: six; Ordinal: 5
Deleting 5…
Values after removal of enum 5 (Six got new ordinal):
Name: ONE; Id:1; AlternativeName: one; Ordinal: 0
Name: TWO; Id:2; AlternativeName: two; Ordinal: 1
Name: THREE; Id:3; AlternativeName: three; Ordinal: 2
Name: FOUR; Id:4; AlternativeName: null; Ordinal: 3
Name: SIX; Id:6; AlternativeName: six; Ordinal: 4
Readding 5 and adding 10…
Values after update:
Name: ONE; Id:1; AlternativeName: one; Ordinal: 0
Name: TWO; Id:2; AlternativeName: two; Ordinal: 1
Name: THREE; Id:3; AlternativeName: three; Ordinal: 2
Name: FOUR; Id:4; AlternativeName: null; Ordinal: 3
Name: SIX; Id:6; AlternativeName: six; Ordinal: 4
Name: FIVE; Id:5; AlternativeName: null; Ordinal: 5
Name: TEN; Id:10; AlternativeName: ten; Ordinal: 6
Trying to remove 1 (cannot because it is hardcoded) and remove 6…
Values after update:
Name: ONE; Id:1; AlternativeName: one; Ordinal: 0
Name: TWO; Id:2; AlternativeName: two; Ordinal: 1
Name: THREE; Id:3; AlternativeName: three; Ordinal: 2
Name: FOUR; Id:4; AlternativeName: null; Ordinal: 3
Name: FIVE; Id:5; AlternativeName: null; Ordinal: 4
Name: TEN; Id:10; AlternativeName: ten; Ordinal: 5

Process finished with exit code 0

Please be aware that remove() method updates ordinal values on each update/delete operation. Hardcoded enum values cannot be removed from the enum. add returns true if it successfully added the value, false otherwise. Similar situation is with remove – if removal is successful true is returned.

In case that somebody needs full IntelliJ IDEA Project, here is the link: DynamicEnum.zip