In this post we dive deeper into the the architectual overview of the code base. We will start with the architectural style and patterns used in the IntelliJ IDEA code base. Next we will dive deeper into the containers, components, connectors, development and run time view to get a better understanding of the code base. Afterwards, we will dive deeper into the SDK IntelliJ provides in order to allow developers to further extend the functionality. Lastly, we will evaluate the architecutal decision JetBrains has made to achieve the key quality attributes we defined in our previous post.
Architectural style and Patterns
IntelliJ IDEA is based on the IntelliJ Platform, which is not an IDE on itself, but rather a platform or framework to develop custom IDEs. For example, it’s the basis of: CLion, IntelliJ IDEA, PyCharm, Andriod Studio and much more IDEs1.
Extending the base platform is done so via plugins, for example, IntelliJ IDEA comes with 45 plugins pre-installed by default! Moreover, there are no limits on what a plugin can do. For example, common plugins include: UI Themes, Custom language support, Framework integration, Tool integration and User Interface add-ons.
Since the IntelliJ platform, and by extension, IntelliJ IDEA are written in Java, most patterns present in the codebase are typical Java patterns, such as: builders, static factories and singletons. However, the IntelliJ contribution guidelines do not mention or enforce any patterns and practices in particular, but it’s of no suprise that they do recommend all practices mentioned in the book Effective Java by Joshua Bloch2.
Even though the main platform and plugins are written in Java, JetBrains recommends that plugins are developed in Kotlin, which is 100% compatible with Java3. This is due to the fact that Kotlin offers features such as null safety and type-safe builders4.
IntelliJ IDEA does not specifically have an containers view, if we look at the definition of a container from C45:
A “container” is something like a web application, mobile app, desktop application, database, file system, etc. Essentially, a container is a separately deployable unit that executes code or stores data.
Then it appears that IntelliJ IDEA only has one single container: the program itself, since there are no other separately deployable units that work in conjunction with it.
|Virtual File System (VFS)||The VFS provides an API that allows interaction with files regardless of their actual location, the VFS also tracks changed to files and provides both the old and the new file content, and supports associating additional persistent data with files.|
|Text Editor||The text editor has a lot of functionality, it allows for single or multiline text fields, read-only and editable text fields, syntax highlighting, code completion, code folding, and much more.|
|UI Framework||The UI framework consists of multiple custom Swing components with different functions allowing for consistent looks when making new plugins|
|Debugger||The debugger has the expected functionality, such as breakpoints, but can also be expanded to function for other languages that are not java through plugins that use its API.|
|Test Runner||The test runner can be used to test automatically, rerun specific (or all) tests, and even activate the debugger on failed tests to examine their state.|
|Plugins||The plugins use the APIs provided by the components above to turn IntelliJ into the full-fledged Java IDE that is IntelliJ IDEA Community Edition.|
As mentioned earlier, IntelliJ is a platform on which JetBrains builds their IDEs. To make it easier for themselves, they made APIs for the components in the IntelliJ platform. Consequently, the main connectors in the IntelliJ IDEA Community Edition are APIs.
As mentioned before, IntelliJ IDEA is based on the IntelliJ platform, which makes it obvious that this is it’s main dependency. The platform is developed in Java, while IntelliJ IDEA expands this platform with various plugins, which are mostly developed in Kotlin3.
Even though the IntelliJ platform is a seperate entity, there is no repository for it. Instead, the InteliJ IDEA repository also contains the complete platform. Moreover, since all expansions are done via plugins, developing on the IntelliJ platform is done by simply changing the plugins1 - creating a new look and feel.
Run time view
IntelliJ is a standalone application which can be run from any desktop environment. In order to provide this runnable application IntelliJ contains a
Build package. This package contains all the code needed to make the application runnable, this includes defining the dependencies, tasks which should be run at startup and build and the actual launch code itself. The dependencies are defined through a gradle file and contains the dependencies for the Java part of the code as well as the Kotlin part. The tasks to be run at startup and build include, detecting broken files, methods to allow Gradle to create a runnable jar, and a logger implementation for the application. The launcher is responsible for making sure the executable has the correct canonical and class paths and attaching to a debug port or launching as a docker instance if neccecary.
API design principles
JetBrains provides an SDK which allows users to develop plugins, create custom language support and build a custom IDE for the IntelliJ IDEA platform7. Below we will discuss a number of design principles which stand out in the code base, either because they are implemented well, or because they could use some improvement.
When designing a plugin for the IntelliJ system, each plugin will need to provide their dedicated class-loader. This allows each plugin to use a different library version. Furthermore, this class loader also allows plugins to specify their dependencies. A developer can specify in the class loader other plugins which must be loaded, in case he/she is referencing any of the classes in that plugin8.
Principle of least surprise
When browsing through the code base, it becomes obvious that many packages contain an
openapi directory. Therefore, we were expecting that all the API code for their respective package is located in this directory. However, we could not be more wrong. After going through the documentation, we found out that there are many more important interfaces which are not located in the
openapi directory. An example of this is the
PsiParser, an interface which allows the developer to create a custom language parser, which is located in the
com.intellij.lang directory in the
core package9. There are several more of these examples, and we feel that there should be more consistency in the location of these important interfaces for creating plugins in order to make it easier for developers to create plugins.
Make it easy to learn
IntelliJ IDEA comes by default with several plugins already installed, including the Maven, Git, etc. These plugins are created by JetBrains themselves using their own SDK and are part of the open source code base published on GitHub. These plugins provide an excellent example on how plugins should be developed. This way, IntelliJ, together with their documentation, provide an easy way to learn about the possibilites their SDK provides.
How does the architecture realize the key quality attributes?
|Quality Attribute||Affectected by architecture|
|Usability||In order to guarantee usability, IntelliJ offer a very extensive SDK to create plugins for IntelliJ. This gives each developer the freedom to, via a (new) plugin, alter the default behaviour and appearcence of the IntelliJ. A great example of this is how IntelliJ uses this SDK to add quality of life features for, among other thing: Git, Gradle, Maven, etc.|
|Reliability||In order to provide the ability to use different environments to develop code, Jetbrains allows developers to create their own plugins for IntelliJ IDEA. IntelliJ IDEA already ships with several default installed plugins, developed by Jetbrains themselves. These plugins allow developers to extend the IntelliJ environment set by a) Adding new language support and b) Adding quality of life features for that language. (Consider testing frameworks, static analysis, etc.)|
|Correctness||Proving that an application or program is correct can be very tricky. In order to do this for the IntelliJ IDEA platform, jetbrains has created their own testing framework. This testing framework should allow the developers to verify the correct workings of their code, however there is no guarantee that there is no bug in the testing framework that JetBrains provides.|
|Efficiency||In order to make the IDE as efficient as possible, IntelliJ stores each file they open in a
Contributors to key quality attributes
|Maintainability||The api provides an excellent way of guaranteeing maintainability. As described in the compatability section IntelliJ provides a way of allowing backwards compatability for a certain amount of time. This allows the developer ample time to update their plugins in order to support newer versions of IntelliJ IDEA|
|Testability||IntelliJ does not provide any way of testing the UI, they expect the UI to be tested manually. Instead, IntelliJ has focused more on being able to test the backend code. IntelliJ provides a wide variety of test frameworks including: JUnit, TestNG and Cucumber. Furthermore, IntelliJ also provides a way to create a whole test project environment to run tests against. In order to create such tests, the test class must extend the