JavaRush /Java Blog /Random EN /IntelliJ Idea: Decompilation, Compilation, Substitution (...
Viacheslav
Level 3

IntelliJ Idea: Decompilation, Compilation, Substitution (or how to correct other people's mistakes)

Published in the Random EN group
“Don’t reinvent the wheel” is one of the main rules for successful and efficient work. But what to do when you don’t want to reinvent your own wheel, but someone else’s steering wheel turns out to be crooked and the wheels are square? This review is intended to provide as brief an introduction as possible to the technique of fixing other people’s libraries “as a last resort” and how to extend this to your computer.
IntelliJ Idea: Decompilation, Compilation, Substitution (or how to correct other people's mistakes) - 1

Introduction

We all use one tool or another. But sometimes the tools are not completely suitable or have errors. Thanks to the features of the Java language, we can correct the behavior of tools where we need it. It’s good when we contribute to projects and send pull requests (you can read more here: “ GitHub - Contributing to projects ”). But they may not be accepted immediately, or they may not even be accepted. But for the needs of the project it is necessary now. And here, I hope, this article will show the tools available to us as developers. We will need to perform the following steps that we will talk about:
  • Prepare a test application for example (using the example of a Hibernate project)
  • Finding a changeable location
  • Making a change
  • Deploying the repository
All the steps below are given for Windows OS, but have analogues for nix systems. So you can repeat them if necessary.

Subject Preparation

So, we need a test project. Hibernate is ideal for us, because... it's "stylish, fashionable, modern." I won’t go into too much detail, because... The article is not about Hibernate. We will do everything quickly and to the point. And we, like proper developers, will use the build system. For example, Gradle is also suitable for us, which must be installed for this article ( https://gradle.org/install/ ). First, we need to create a project. Maven has archetypes for this , and Gradle has a special plugin for this: Gradle Init . So, open the command line in any way known to you. Create a directory for the project, go to it and execute the command:

mkdir javarush 
cd javarush 
gradle init --type java-application
IntelliJ Idea: Decompilation, Compilation, Substitution (or how to correct other people's mistakes) - 2
Before importing the project, let's make some changes to the file that describes how to build. This file is called the build script and is named build.gradle. It is located in the directory in which we executed gradle init. Therefore, we simply open it (for example, in Windows with the start build.gradle command). We find the “ dependencies ” block there, i.e. dependencies. All third-party jars that we will use are described here. Now we need to understand what to describe here. Let's go to the Hibernate website ( http://hibernate.org/ ). We are interested in Hibernate ORM . We need the latest release. In the menu on the left there is a subsection “Releases”. Select “latest stable”. Scroll down and find “Core implementation (includes JPA)”. Previously, it was necessary to connect JPA support separately, but now everything has become simpler and only one dependency is enough. We will also need to work with the database using Hibernate. To do this, let's take the simplest option - H2 Database . The choice is made, here are our dependencies:

dependencies {
    // Базовая зависимость для Hibernate (новые версии включают и JPA)
    compile 'org.hibernate:hibernate-core:5.2.17.Final'
    // База данных, к которой мы будем подключаться
    compile 'com.h2database:h2:1.4.197'
    // Use JUnit test framework
    testCompile 'junit:junit:4.12'
}
Great, what's next? We need to configure Hibernate. Hibernate has a " Getting Started Guide ", but it's stupid and more of a hindrance than a help. Therefore, let’s go straight to the “ User Guide ” like the right people. In the table of contents we see the section “ Bootstrap ”, which translates as “Bootstrapping”. Just what you need. There are a lot of smart words written there, but the point is that there should be a META-INF directory on the classpath, and there should be a persistence.xml file. According to the standard, the classpath contains the “resources” directory. Therefore, we create the specified directory: mkdir src\main\resources\META-INF Create the persistence.xml file there and open it. There in the documentation there is an example “Example 268. META-INF/persistence.xml configuration file” from which we will take the contents and insert it into the persistence.xml file. Next, launch the IDE and import our created project into it. Now we need to save something to the database. This is something called an entity. Entities represent something from the so-called domain model. And in the table of contents, lo and behold, we see “ 2. Domain Model ”. We go down the text and see in the chapter “2.1. Mapping types” a simple example of an entity. Let’s take it to ourselves, shortening it a little:
package entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity(name = "Contact")
public class Contact {

    @Id
    @GeneratedValue
    private Integer id;

    private String name;

    public Contact(String name) {
        this.name = name;
    }
}
Now we have a class that represents an entity. Let's go back to persistence.xml and correct one place there: Where indicated, classwe will indicate our class entity.Contact. Great, all that remains is to launch. Let's return to the Bootstrap chapter . Since we do not have an application server that will provide us with a special EE environment (i.e., an environment that implements certain system behavior for us), we work in an SE environment. For this, only the example “Example 269. Application bootstrapped EntityManagerFactory” is suitable for us. For example, let's do this:
public class App {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("CRM");
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        Contact contact = new Contact("Vasya");
        em.persist(contact);
        em.getTransaction().commit();
        Query sqlQuery = em.createNativeQuery("select count(*) from contact");
        BigInteger count = (BigInteger) sqlQuery.getSingleResult();
        emf.close();
        System.out.println("Entiries count: " + count);
    }
}
Hurray, our subject is ready. I didn't want to omit this part , because... For the following chapters, it is advisable to understand how our subject came to be.

Finding Modifiable Behavior

Let's take the place of initialization of the count field of type BigInteger and set breakpoints there ( BreakPoint ). Having inserted on the desired line, this can be done using Ctrl+F8 or through the menu Run -> Toggle Line Breakpoint. Then we run our main method in the debug (Run -> Debug):
IntelliJ Idea: Decompilation, Compilation, Substitution (or how to correct other people's mistakes) - 3
A bit of a clumsy example, but let’s say we want to change the number of query spaces at startup. As we can see, our sqlQuery is NativeQueryImpl. Click Ctrl+N, write the name of the class, and go to it. So that when we go to a class, we will be transferred to the place where this class is located and turn on autoscroll:
IntelliJ Idea: Decompilation, Compilation, Substitution (or how to correct other people's mistakes) - 4
Let us immediately note that Idea does not currently know where to find the source code of the program (source code, that is). Therefore, she kindly decompiled the contents from the class file for us:
IntelliJ Idea: Decompilation, Compilation, Substitution (or how to correct other people's mistakes) - 5
Note also that in the title of the IntelliJ Idea window it is written where Gradle saves the artifact for us. Now, let’s get in Idea the path where our artifact is located:
IntelliJ Idea: Decompilation, Compilation, Substitution (or how to correct other people's mistakes) - 6
Let's go to this directory on the command line using the command cd way to каталогу. I’ll make a note right away: if it is possible to build a project from source, it’s better to build from source. For example, the Hibernate source code is available on the official website. It is better to pick it up for the desired version and make all the changes there and assemble using the build scripts that are specified in the project. I present in the article the most terrible option - there is a jar, but no source code. And note number 2: Gradle can get the source code using plugins. See How to download javadocs and sources for jar using Gradle for details .

Making a change

We need to recreate the directory structure in accordance with the package in which the class we are changing is located. In this case: mkdir org\hibernate\query\internal, after which we create a file in this directory NativeQueryImpl.java. Now we open this file and copy all the contents of the class from the IDE there (the same one that Idea decompiled for us). Change the necessary lines. For example:
IntelliJ Idea: Decompilation, Compilation, Substitution (or how to correct other people's mistakes) - 7
Now, let's compile the file. We do: javac org\hibernate\query\internal\NativeQueryImpl.java. Wow, you can’t just take it and compile it without errors. We received a bunch of Cannot Find Symbol errors, because... the mutable class is tied to other classes, which IntelliJ Idea usually adds to the classpath for us. Do you feel all the usefulness of our IDEs? =) Well, let's add it ourselves, we can do it too. Let's copy the paths for:
  • [1] - hibernate-core-5.2.17.Final.jar
  • [2] - hibernate-jpa-2.1-api-1.0.0.Final.jar
Just like we did: In the “Project” view in “External libraries” we find the required jar and click Ctrl+Shift+C. Now let’s create and execute the following command: javac -cp [1];[2] org\hibernate\query\internal\NativeQueryImpl.java As a result, new class files will appear next to the java file, which need to be updated in the jar file:
IntelliJ Idea: Decompilation, Compilation, Substitution (or how to correct other people's mistakes) - 8
Hurray, now you can perform jar update. We can be guided by official materials : jar uf hibernate-core-5.2.17.Final.jar org\hibernate\query\internal\*.class Open IntelliJ Idea will most likely not allow you to change files. Therefore, before performing the jar update, you will most likely have to close Idea, and after the update, open it. After this, you can reopen the IDE and run dubug again. Break Points are not reset between IDE restarts. Therefore, program execution will stop where it was before. Voila, we see how our changes work:
IntelliJ Idea: Decompilation, Compilation, Substitution (or how to correct other people's mistakes) - 9
Great. But here the question arises - due to what? Simply due to the fact that when gradle builds a project, it analyzes the dependencies and repositories block. Gradle has a certain build cache, which is located in a certain location (see “ How to set gradle cache location? ” If there is no dependency in the cache, then Gradle will download it from the repository. Since we changed the jar in the cache itself, then Gradle thinks that the library is in the cache and does not pump out anything. But any clearing of the cache will lead to our changes being lost. Plus, no one but us can just go and get them. How much inconvenience, isn’t it? What to do. Hmm, downloads from the repository? So we need our repository, with preferences and poetesses. This is the next step.

Deploying the repository

There are different free solutions for deploying your repository: one of them is Artifactory , and the other is Apache Archive . Artifactory looks fashionable, stylish, modern, but I had difficulties with it, I didn’t want to place artifacts correctly and generated erroneous maven metadata. Therefore, unexpectedly for myself, the Apache version worked for me. It turned out to be not so beautiful, but it works reliably. On the download page , look for the Standalone version and unpack it. They have their own " Quick Start ". After launching, you need to wait until the address http://127.0.0.1:8080/#repositorylist. After that, select "Upload Artifact":
IntelliJ Idea: Decompilation, Compilation, Substitution (or how to correct other people's mistakes) - 10
Click "Start Upload", and then "Save Files". After this, a green success message will appear and the artifact will become available in the “Browse” section. This should be done for jar and pom files:
IntelliJ Idea: Decompilation, Compilation, Substitution (or how to correct other people's mistakes) - 11
This is due to the fact that additional hibernate dependencies are specified in the pom file. And we only have 1 step left - specify the repository in our build script:

repositories {
    jcenter()
    maven {
        url "http://127.0.0.1:8080/repository/internal/"
    }
}
And, accordingly, the version of our hibernate will become: compile 'org.hibernate:hibernate-core:5.2.17.Final-JAVARUSH'. That's all, now our project uses the version we corrected, and not the original one.

Conclusion

It seems like we got acquainted. I hope it was interesting. Such “tricks” are rarely done, but if suddenly your business requirements set conditions that the libraries you use cannot satisfy, you know what to do. And yes, here are a couple of examples that can be corrected this way:
  • There is a web server called Undertow. Until some time, there was a bug that, when using a proxy, did not allow us to find out the end user's IP.
  • For the time being, WildFly JPA handled in a certain way one moment not taken into account by the specification, because of this Exceptions were thrown. And it wasn't configurable.
#Viacheslav
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION