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

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

Published in the Random EN group
"Don't reinvent the wheel" is one of the main rules of successful and efficient work. But what to do when you don’t want to reinvent your wheel, but someone else’s steering wheel turned out to be crooked and the wheels were square? This review is intended to be as brief as possible about the method of fixing in other people's libraries "in case of emergency" and how to spread this matter further than your computer.
IntelliJ Idea : Decompile, Compile, Substitution (or how to fix other people's mistakes) - 1

Introduction

We all use some kind of tool. But sometimes the tools do not fit completely or have errors in them. Thanks to the features of the Java language, we can fix the behavior of tools where we need it. It's good when we contribute projects and send pull requests (read more here: " GitHub - Contributing Your Own Projects "). But they may not be accepted immediately, or they may not even be accepted. But for the needs of the project, you need it now. And here, I hope, this article will show the tools available to us, as a developer. We will need to perform the following steps, which we will talk about:
  • Prepare an application under test for an example (on the example of a Hibernate project)
  • Finding a place to change
  • Making a change
  • Deploying a repository
All of the following actions are given for Windows, but have analogues for nix systems. So if necessary, you can repeat them.

Subject preparation

So we need a pilot project. Hibernate is ideal for us, because. it is "stylish, fashionable, modern". I will not go into details too much, because. the article is not about Hibernate. We will do everything quickly and to the point. And we will, like the right developers, 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 you know. Create a directory for the project, go to it and execute the command:

mkdir codegym 
cd codegym 
gradle init --type java-application
IntelliJ Idea : Decompile, Compile, Substitution (or how to fix 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 performed 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. This describes all the third party jars that we will be using. 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, 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? Hibernate needs to be configured. Hibernate has a " Getting Started Guide " but it's stupid and more of a hindrance than a help. Therefore, let's go as the right people right away to the " User Guide ". In the table of contents we see the section " Bootstrap ", which translates as "Bootstrap". What you need. A lot of buzzwords are 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 “resources” directory falls on the classpath. Therefore, we create the specified directory:mkdir src\main\resources\META-INF We create the persistence.xml file there and open it. There is also an example in the documentation "Example 268. META-INF/persistence.xml configuration file" from which we will take the contents and paste 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 essence. 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. We take it to ourselves, slightly reducing:
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 fix one place there: Where it is specified, classspecify our class entity.Contact. Alright, let's get started. Back to the Bootstrap chapter . Since we do not have an application server that will provide us with a special EE environment (that is, an environment that implements certain system behavior for us), we work in an SE environment. For him, 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 test subject is ready. I did not want to omit this part , because. for the following chapters, it is desirable to understand how our subject came to be.

Finding Changeable Behavior

Let's jump to the place where the BigInteger type count field is initialized and put breakpoints there ( BreakPoint ). Pasting 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 debug (Run -> Debug):
IntelliJ Idea : Decompile, Compile, Substitution (or how to fix 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. Press Ctrl+N, write the name of the class, go to it. So that when we go to the class, we are transferred to the place where this class lies, turn on the auto-scroll:
IntelliJ Idea : Decompile, Compile, Substitution (or how to fix other people's mistakes) - 4
Immediately, we note that Idea does not know now where you can find the source code of the program (source codes, that is). Therefore, she graciously decompiled the contents for us from the class file:
IntelliJ Idea : Decompile, Compile, Substitution (or how to fix other people's mistakes) - 5
We also note 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 lies:
IntelliJ Idea : Decompile, Compile, Substitution (or how to fix other people's mistakes) - 6
Change to this directory on the command line using the command cd way to каталогу. I will immediately make a remark: if it is possible to build a project from source, it is 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 build using the build scripts that are specified in the project. I cite the most terrible option in the article - there is a jar, but there is 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 one that Idea decompiled for us). Change the required lines. For example:
IntelliJ Idea : Decompile, Compile, Substitution (or how to fix other people's mistakes) - 7
Now let's compile the file. We execute: javac org\hibernate\query\internal\NativeQueryImpl.java. Wow, you can’t just take and compile without errors. Got a bunch of Cannot Find Symbol errors. the mutable class is tied to other classes that IntelliJ Idea usually adds to the classpath for us. Do you feel the usefulness of our IDEs? =) Well, let's add it ourselves, we can too. Copy the paths for:
  • [1] - hibernate-core-5.2.17.Final.jar
  • [2] - hibernate-jpa-2.1-api-1.0.0.Final.jar
Just as we did: In the "Project" view in the "External libraries" find the required jar and press Ctrl+Shift+C. Now let's form and execute the following command: javac -cp [1];[2] org\hibernate\query\internal\NativeQueryImpl.java As a result, next to the java file, new class files will appear that need to be updated in the jar file:
IntelliJ Idea : Decompile, Compile, Substitution (or how to fix other people's mistakes) - 8
Hooray, jar update can now be done. We can be guided by official materials : jar uf hibernate-core-5.2.17.Final.jar org\hibernate\query\internal\*.class Open IntelliJ Idea, most likely, will not allow you to change files. Therefore, before performing jar update, most likely, you will have to close Idea, and after the update - open it. After that, you can reopen the IDE, run dubug again. Break Points are not reset between IDE restarts. Therefore, the execution of the program will stop where it was before. Voila, we see how our changes work:
IntelliJ Idea : Decompile, Compile, Substitution (or how to fix other people's mistakes) - 9
Great. But here the question arises - thanks to what? Simply due to the fact that when gradle builds the project, it parses the dependencies and repositories block. Gradle has a certain build cache, which lies in a certain place (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 doesn't download anything.But any cache clearing will cause our changes to be lost.Plus, no one but us can just take and get them.What an inconvenience, isn't it?What to do. Hmm, downloads from the repository? So we need our repository, with preference and poetesses. This is the next step.

Deploying a repository

There are various 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's not as pretty, but it works reliably. On the download page , look for the Standalone version, unpack it. They have their own " Quick Start ". After starting, you need to wait until the address http://127.0.0.1:8080/#repositorylist. After that, select "Upload Artifact":
IntelliJ Idea : Decompile, Compile, Substitution (or how to fix other people's mistakes) - 10
Click "Start Upload" and then "Save Files". After that, a green success message will appear and the artifact will become available in the "Browse" section. This is how it should be done for jar and pom files:
IntelliJ Idea : Decompile, Compile, Substitution (or how to fix other people's mistakes) - 11
This is due to the fact that additional hibernate dependencies are written in the pom file. And we have only 1 step left - to 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-CODEGYM'. That's all, now our project uses the version we corrected, and not the original one.

Conclusion

This is how we got to know each other. 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 of what can be corrected like this:
  • There is such a web server Undertow. Until some time, there was a bug that, when using a proxy, did not let you know the IP of the end user.
  • For the time being, WildFly JPA in a certain way handled one moment not taken into account by the specification, because of this, Exception was thrown. And it didn't set up.
#Viacheslav
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION