Next Article in Journal
A Survey of Robot Intelligence with Large Language Models
Previous Article in Journal
In Silico SwissADME Analysis of Antibacterial NHC–Silver Acetates and Halides Complexes
 
 
Font Type:
Arial Georgia Verdana
Font Size:
Aa Aa Aa
Line Spacing:
Column Width:
Background:
Article

Automatic Refactoring Approach for Asynchronous Mechanisms with CompletableFuture

1
School of Information Science and Engineering, Hebei University of Science and Technology, Shijiazhuang 050000, China
2
Shijiazhuang Meteorological Bureau, Shijiazhuang 050000, China
*
Authors to whom correspondence should be addressed.
Appl. Sci. 2024, 14(19), 8866; https://doi.org/10.3390/app14198866
Submission received: 28 August 2024 / Revised: 26 September 2024 / Accepted: 28 September 2024 / Published: 2 October 2024

Abstract

:
To address the inherent limitations of Future in asynchronous programming frameworks, JDK 1.8 introduced the CompletableFuture class, which features approximately 50 different methods for composing and executing asynchronous computations and handling exceptions. This paper proposes an automatic refactoring method that integrates multiple static analysis techniques, including visitor pattern analysis, alias analysis, and executor inheritance structure analysis, to conduct precondition checks. Distinct from existing Future refactoring methods, this approach considers custom executor types, thereby extending its applicability. Using this method, the ReFuture automatic refactoring plugin was implemented within the Eclipse JDT framework. The method was evaluated in terms of the number of refactorings, refactoring time, and error introduction, alongside a side-by-side comparison with the existing method. The refactoring outcomes for nine large applications, including ActiveMQ, Hadoop, and Elasticsearch, show that ReFuture successfully refactored 639 out of 813 potential code structures, achieving a refactoring success rate of 64.70% without introducing errors. This tool effectively facilitates the refactoring to CompletableFuture and enhances refactoring efficiency compared to manual methods.

1. Introduction

Java has supported asynchronous programming since its earliest versions, with continuous enhancements to its asynchronous capabilities as technology has evolved. A significant advancement came with the introduction of the java.util.concurrent (JUC) library in Java 5, which markedly improved asynchronous programming. A key enhancement in this library is the ExecutorService framework. This framework decouples the lifecycle management of threads from task execution and avoids the performance overhead associated with the frequent creation and destruction of threads by reusing them. It allows asynchronous tasks that implement the Callable or Runnable interfaces to be submitted for execution to an executor while returning a Future object as a handle for the asynchronous task. Through this handle, developers can access the task’s execution status and results. We refer to this mode of asynchronous programming as the “traditional asynchronous construct”. It has been well-received and widely used by developers, ranking as the second most popular asynchronous programming pattern after Thread, according to our empirical research results presented in Section 4.
As time has progressed, some challenges associated with using traditional asynchronous constructs in real-world applications have sparked extensive discussions in developer forums [1,2], particularly the lack of phased control in the ordinary Future returned by traditional constructs, which makes orchestrating asynchronous tasks exceptionally difficult. The introduction of the CompletableFuture class in Java 8’s JUC addresses these issues. CompletableFuture implements the Future interface and extends the CompletionStage interface. This extension provides callback mechanisms and chainable method invocations to compose and orchestrate asynchronous computations. Additionally, it integrates functional programming interfaces and lambda expressions, enhancing the readability of asynchronous code. It also introduces an exception-handling mechanism, simplifying exception management during asynchronous task execution. However, according to the empirical research results presented in Section 4 of this paper, only 3.53% of popular Java open-source projects on GitHub have adopted CompletableFuture. On one hand, this is related to the relatively late introduction of CompletableFuture. Traditional asynchronous constructs had already become deeply ingrained, and the existence of similar alternatives to CompletableFuture in popular asynchronous libraries within the Java community, such as ListenableFuture in Google’s Java core library Guava [3], has affected the popularity of CompletableFuture. On the other hand, due to CompletableFuture’s rich API, which results in a higher learning curve for programmers, many have missed the opportunity to utilize it.
Although refactoring traditional asynchronous constructs to CompletableFuture constructs can provide software developers with more powerful and flexible asynchronous programming capabilities, as discussed in developer forums [2], this transition is not without barriers. Firstly, there needs to be a method to assess whether the logic for submitting asynchronous tasks via executors is compatible with CompletableFuture. Various executor types in the JUC library meet refactoring criteria, but developers often use custom ExecutorService interfaces. This requires analyzing non-JUC executor types in projects to identify which are refactorable. Secondly, because CompletableFuture uses Supplier-type asynchronous tasks with return values, it is necessary to refactor Callable-type asynchronous tasks into Supplier-type. Furthermore, the cancellation mechanism of CompletableFuture differs from that of FutureTask, the Future object type used traditionally. This difference can cause issues if tasks depend on FutureTask’s cancellation feature. Lastly, refactoring changes the actual type of Future objects; if the code includes type comparisons or type conversions of Future objects, refactoring should be avoided. Considering that manual refactoring is labor-intensive, time-consuming, and prone to errors, the application of automated refactoring tools becomes particularly important. Through automation, we can more efficiently promote the migration of existing code to CompletableFuture, thereby fully leveraging its advanced asynchronous programming features.
In recent years, numerous studies have been conducted in the field of automated refactoring related to asynchronous constructs. Previous research has concentrated on (1) refactoring current constructs into more suitable alternatives [4,5,6,7,8] and (2) embedding asynchronous constructs within sequential code to enhance execution efficiency [9,10,11,12,13,14,15]. Our method expands the first area by diversifying asynchronous refactoring techniques, introducing a novel approach to transform traditional asynchronous constructs into CompletableFuture. Moreover, our strategy to identify non-JUC internal asynchronous constructs offers significant insights.
To address the aforementioned issues, this paper proposes an automated refactoring method for the CompletableFuture asynchronous mechanism. By employing program analysis techniques such as visitor pattern analysis, executor inheritance structure analysis, and points-to analysis, we identify refactorable executor types within software projects poised for refactoring. Based on these analyses, we apply refactoring templates to automatically transform four types of traditional asynchronous constructs into CompletableFuture constructs. Utilizing the Eclipse JDT framework [16] and Soot [17], an automated refactoring tool, ReFuture, has been implemented in the form of an Eclipse plugin. In experiments, ReFuture was evaluated based on the number of refactorings, the correctness of refactorings, and the refactoring time. The results demonstrate that among 813 instances of traditional asynchronous constructs in nine large-scale open-source projects, ReFuture successfully refactored 639 instances without introducing errors into the refactored programs. This tool effectively facilitates the automatic refactoring from traditional asynchronous constructs to CompletableFuture.
The main contributions of this paper are as follows.
  • Empirical research: We investigate the use of common asynchronous constructs in current open-source Java projects and analyze the complexity of asynchronous tasks in projects using traditional asynchronous constructs and CompletableFuture constructs.
  • Refactoring tool: We present an automated refactoring method from traditional asynchronous constructs to CompletableFuture constructs, specifically addressing executor types that exist outside of the JUC. We propose analysis methods from the perspectives of inheritance and composition, which are common in object-oriented program reuse, thereby enhancing the applicability of our refactoring method. Based on the Eclipse platform [18], we implement the ReFuture automatic refactoring tool, which has been made open-source [19].
  • Experimental evaluation: We assess the applicability, correctness, and efficiency and conduct a side-by-side comparison with the existing method, all through answering several research questions.
The rest of the paper is organized as follows. Section 2 discusses related work. Section 3 introduces the motivation of this study through an example. Section 4 presents the empirical research questions and results. Section 5 details the automated refactoring method proposed in this paper. Section 6 describes the experimental setup and evaluation results. The paper concludes with a summary of the entire work.

2. Related Work

In this section, we first introduce concurrency-related refactoring techniques in the Java domain, then extend the discussion to research on concurrency refactoring in other platforms, and finally discuss the connections between our approach and the related work.
A survey revealed that advanced concurrency libraries have not been widely applied [20]. Researchers hope to enhance the quality of existing code through software refactoring techniques, aiming for better utilization of the JUC library. Dig and colleagues [9] introduced an automatic refactoring method and tool, Concurrencer, which uses JUC to refactor sequential code into concurrent code, thereby speeding up program execution. They proposed three types of refactorings: transforming synchronized locks into atomic locks, refactoring HashMap into ConcurrentHashMap, and using the Fork/Join framework for recursive tasks. Subsequently, Ishizaki and others [10] focused on the first two refactorings mentioned above and proposed a refactoring method that combines inter-procedural points-to analysis, achieving better results. In studies related to locks in the JUC library, Zhang and his team performed a series of works where they proposed [11] an automated transformation method that combines side-effect analysis and other static analysis techniques at the bytecode level to refactor synchronized locks into ReentrantLock and ReentrantReadWriteLock. Later, they [12] continued their in-depth research and proposed five regular expressions to represent refactoring rules, which can refactor synchronized locks into fine-grained ReentrantReadWriteLocks. In terms of automatically refactoring sequential code into concurrent code, Midolo and colleagues [13] conducted further research by statically assessing the number of control flow graph nodes in the bodies of two invoked methods in sequential code; if the counts are similar and there are no dependencies, the two method invocations in the sequential code are submitted to a thread pool for asynchronous execution. As reactive programming concepts gained popularity, Köhler and colleagues [4] refactored SwingWorker and ExecutorService constructs to support the RxJava reactive programming library. Addressing the limitations of statically identifying inefficient concurrency patterns, Orton and others [5] combined dynamic analysis to find opportunities to refactor Executor pattern asynchronous code by inlining into synchronous code and using CompletableFuture compositions to release coordinator threads, thus improving the quality of asynchronous code.
In research related to concurrency refactoring outside the Java platform, in the C# domain, Okur and others [6] first explored the use of parallel abstractions in C# open-source programs, then presented a refactoring method and tool to transform lower-level parallel abstractions into higher-level ones, specifically converting Thread and ThreadPool abstractions into Task abstractions and optimizing the use of the Task abstraction. They later [7] researched refactoring C# code from callback-based asynchronous code to using async/await, implementing a refactoring tool. On the Android platform, Lin and colleagues [14] first conducted empirical research on the Android corpus, summarizing the underuse of AsyncTask constructs, leading to the proposal of an automatic refactoring method that extracts long-running operations into AsyncTask. Following this, he [8] addressed the misuse issues of AsyncTask, proposing a method and refactoring tool ASYNCDROID to transform incorrect use of AsyncTask into IntentServices constructs. On the JavaScript platform, Gokhale and others [15] proposed a method and tool using the async/await keywords combined with static analysis to migrate synchronous APIs to asynchronous APIs. Arteca and colleagues [21] demonstrated that there is room for optimization in the ordering of asynchronous I/O operations in existing JavaScript code, using static side-effect analysis to refactor asynchronous I/O operations to enhance program performance.
To better summarize the related work, as shown in Table 1, we present a comparison of studies on concurrency refactoring.
In Table 1, we initially showcase the study by Dig et al. [9] as it represents a significant work in the field of concurrency refactoring. This study aims to enhance code efficiency by automatically transforming sequential/synchronous code into concurrency/asynchronous code. Subsequently, the research by Okur et al. [6,7] focuses on refactoring concurrency programs that use outdated APIs to utilize updated APIs for code optimization. Lin et al.‘s study [8,14] addresses both these aspects. The research by Gokhale et al. [15] involves transforming synchronous APIs in JavaScript into asynchronous ones using the async/await keywords to boost performance.
The constructs of the refactorings in the last three rows of the table are related to those in our study. The research by Midolo et al. [13] resulted in refactoring that utilizes the CompletableFuture construct to asynchronously execute the original synchronous code. Köhler et al. [4] conducted refactoring of both the SwingWorker and ExecutorService frameworks to support the RxJava reactive programming library, using RxJava’s Observable to encapsulate the Future returned by traditional asynchronous constructs. However, their approach is limited to executor types within the JUC library, thus missing opportunities for broader refactoring. Orton et al. [5] explored using dynamic analysis to identify opportunities to enhance program performance through refactoring traditional asynchronous constructs. They discuss replacing a pattern where asynchronous tasks submitted to an executor are merely scheduling two other asynchronous tasks with CompletableFuture’s combinational capabilities to reduce scheduling overhead. However, their research focuses more on dynamic analysis and code patch generation technologies. The proposed refactoring pattern involves transitioning from traditional asynchronous constructs to CompletableFuture, but it does not provide specific methods for the refactoring.
Previous studies have primarily focused on two areas: (1) refactoring existing asynchronous constructs to more suitable alternatives [4,5,6,7,8] and (2) introducing asynchronous constructs into sequential code to improve program execution efficiency [9,10,11,12,13,14,15]. Our approach builds upon the first area by increasing the diversity of asynchronous refactoring methods, introducing a new method for refactoring traditional asynchronous constructs into CompletableFuture. Additionally, our approach for identifying non-JUC internal asynchronous construct types can provide valuable insights into expanding the applicability of automated refactoring methods.

3. Motivation

To visually demonstrate the advantages of CompletableFuture over traditional asynchronous constructs in asynchronous code, we illustrate this through a code example adapted from a classic concurrent programming case provided in Section 6.3 of “Java Concurrency in Practice” [22]. Our example code primarily involves two tasks: first, fetching images from a remote server based on a provided list of image information, and second, decrypting the fetched images, both performed asynchronously. In this section, we first show the shortcomings of traditional asynchronous constructs when handling multiple dependent asynchronous tasks, followed by a demonstration of how transitioning to CompletableFuture constructs allows developers to use its rich API to make asynchronous code concise and clear while also enhancing program performance.
In traditional asynchronous constructs, as shown in Figure 1, line 3 uses the submit method to submit getImageFile() to a download thread pool suitable for IO-intensive operations and returns a Future object, thus obtaining a List<Future<ImageFile>> type list called imageFileFutures. Subsequently, the fetched images are decrypted; line 7 uses the get method of the Future interface to synchronously block and obtain the results of the IO tasks, which are then submitted for decryption in the decryption thread pool at line 9.
This asynchronous approach has two drawbacks. In terms of code readability, the asynchronous logic (decrypting images depends on the completion of the image fetching task) is not evident in the code of Figure 1. Consider if there were more than these two asynchronous tasks; the complexity of dependencies between these tasks would significantly increase, further reducing the readability of asynchronous code and complicating future maintenance. Performance-wise, interaction between two or more asynchronous tasks requires the main thread to block and wait, as shown in Figure 1, line 7. During the waiting period for one asynchronous task in imageFileFutures, even if other IO asynchronous tasks are completed, they cannot be promptly submitted to the decryption tasks, failing to fully leverage the advantages of asynchronous programming. In other words, blocking waiting is a synchronous operation that affects asynchronous efficiency.
After transitioning from traditional asynchronous constructs to CompletableFuture, the changes to the code are minimal. As shown in Figure 2, unlike Figure 1, the modifications occur in lines 3 and 9, where developers can easily understand these changes. The ordinary Future initially returned by traditional asynchronous constructs has been converted to CompletableFuture, allowing developers to utilize its extensive API.
After a comprehensive assessment of the software project, if developers determine that removing the synchronous blocking get() method invocations will not affect the program’s normal operation, they can switch to completely asynchronous, streamlined code. As shown in Figure 3, developers use the CompletableFuture API to orchestrate these two interdependent asynchronous tasks. At line 4, they first use the static method supplyAsync() to submit the task of fetching images to the corresponding thread pool executor. Then, at line 5, the member method thenApplyAsync() is used to submit the decryption task to the corresponding thread pool executor. This demonstrates that CompletableFuture not only makes the dependencies between asynchronous tasks very clear but also allows interdependent asynchronous tasks to be executed completely asynchronously, overcoming the drawbacks of traditional asynchronous constructs.
To assess the impact of fully asynchronous execution of multiple asynchronous tasks with dependencies on program performance, we manually refactored the test case RunTest.timezoneOfID from the open-source project Jenkins and conducted measurements using the JMH benchmarking tool with 10 warm-ups followed by 10 measurements. The test results showed that the call time was reduced from 0.310 ms to 0.282 ms, enhancing execution speed by 9.03%.
From the above code example, it can be seen that refactoring traditional asynchronous constructs into the CompletableFuture form allows asynchronous code to replace the synchronous method get() with APIs provided by CompletableFuture during future maintenance. This not only enhances program performance but also improves the readability of asynchronous code.

4. Empirical Research

We conducted empirical research on asynchronous mechanisms to understand the current state of practice of asynchronous constructs like CompletableFuture in actual Java programs. This helps us to assess the potential opportunities for refactoring to CompletableFuture. On the other hand, we want to explore whether the CompletableFuture construct helps programmers write less complex asynchronous task code. Therefore, we addressed the following two research questions, introducing the corpus and methods first and then presenting the results for each question.
RQ1: How are the commonly used asynchronous executor APIs currently utilized in Java?
Through this research question, we aim to understand the usage of traditional asynchronous constructs and CompletableFuture constructs to assess the potential for our refactoring work.
The literature [4] has documented the use of asynchronous executor-related constructs; however, the dataset used was compiled in 2015 and may not accurately reflect the current state of open-source software implementations, particularly the use of the CompletableFuture construct introduced with Java in 2014. Using the same method as in the literature, we added 4 constructs to the 14 types of asynchronous executor constructs they analyzed. As shown in Table 2, we surveyed the use of 18 different asynchronous APIs. Among these, Executors serve as thread pool factory classes provided by JUC, FutureTask is a basic implementation class of the Future interface, the @Async annotation facilitates easy asynchronous method execution within the Spring framework, ListenableFuture and AsyncResult support callback-based asynchronous patterns, Publisher and Observable represent the reactive programming paradigm facilitating asynchronous data flow processing, SwingWorker and Task handle time-consuming tasks in Swing and JavaFX, respectively, without blocking the UI thread, AsyncResponse is used for asynchronous response handling in web services, and ManagedTask, Vert.x Future, and Akka Futures provide solutions for asynchronous operations in enterprise-level concurrent programming and event-driven architectures.
Corpus-1. Similar to the literature [4], we utilized BOA [23] to analyze open-source Java projects. BOA is a language and infrastructure designed for mining software repositories. We used its latest Java dataset (“2022 Jan/Java” in BOA), which includes 91,403 projects on GitHub with Java as the primary language (the highest proportion of Java code) and has been converted into AST (Abstract Syntax Tree).
Method. Using the BOA language, we accessed the AST of projects in the dataset through the visitor pattern to determine if the Import section contains any of the asynchronous APIs listed in Table 2. For Threads, since they belong to the java.lang package and do not require an import to be used, we traversed all Type nodes within the AST.
Result. Table 2 presents the usage statistics of the asynchronous APIs. Out of the 91,403 projects, 34,492 projects contained asynchronous APIs, accounting for 37.74% of all projects. The most frequently used API was Thread, accounting for 30.07% of the projects. Executors, being factory classes for thread pools within JUC, indicate the potential use of ExecutorService instances to execute asynchronous tasks. Therefore, projects using Executors, ExecutorService, FutureTask, and Future might all be employing traditional asynchronous constructs. However, CompletableFuture was only used in 3.53% of the projects, indicating it has not yet achieved widespread adoption.
RQ2: Does the CompletableFuture construct result in less complex asynchronous tasks compared to traditional asynchronous constructs?
As is well known, concurrent programming is a challenging mode of programming [24], and the CompletableFuture class has many member methods. By invoking these methods, multiple asynchronous tasks can be controlled and combined. We are interested in whether this approach helps programmers write less complex asynchronous tasks. We constructed two datasets: Corpus-2, which includes projects with numerous CompletableFuture calls and may also contain traditional asynchronous constructs, and Corpus-3, which solely contains projects with many traditional asynchronous constructs. Both datasets were selected based on the number of stars to ensure representativeness.
Corpus-2. On the BOA 2022 Jan/Java dataset, we traversed the AST’s method invocation nodes to identify the top 1000 projects with the most calls to CompletableFuture’s static methods for submitting asynchronous tasks. Next, we filtered the projects. Using the GitHub API, we obtained the star counts of projects and excluded those with fewer than 1000 stars. We utilized the CodeQL code analysis engine [25] for static analysis to measure asynchronous task metrics. Before analyzing with CodeQL, databases of the projects to be analyzed must be built. GitHub provides CodeQL databases for over 200,000 projects [26]; we prefer using these provided databases. If not available, we attempted to build databases locally from the source code, excluding projects that fail to build, such as Android-related projects that require additional compilation environments. Sorted by the number of CompletableFuture calls, we selected the top 20 projects that were not excluded to form corpus-2, which already contained a sufficient number of CompletableFuture calls.
Corpus-3. Using the same method, we identified the top 1000 projects from the BOA dataset that had not used CompletableFuture but had the highest number of calls to the traditional asynchronous constructs’ execute and submit methods. Unlike with corpus-2, we relaxed the star count requirement, excluding projects with fewer than 700 stars to include those with relatively more calls in our corpus. Ultimately, we still selected the top 20 un-excluded projects to form corpus-3.
Method. We conducted our analysis using CodeQL, which uses a global data flow analysis framework. We treated instantiation sites of asynchronous task types (including Callable, Runnable interface types, and CompletableFuture’s used types like Supplier, Function, Consumer, BiFunction, and BiConsumer) as sources and the method invocation sites where asynchronous tasks are submitted as sinks, based on the concept of taint analysis. Taint analysis determines whether objects at source sites flow into sink sites, identifying all potentially tainted program nodes. Through this method, we obtained the definition locations of all asynchronous tasks submitted in both manners, from which we derived metrics for the asynchronous tasks.
Result. As shown in Table 3 and Table 4, “SN” represents the number of stars, “CN” represents the number of CompletableFuture calls, “EN” represents the number of asynchronous tasks submitted via the Executor framework, “CC” represents the average cyclomatic complexity of the asynchronous tasks called by CompletableFuture, and “EC” represents the average cyclomatic complexity of the asynchronous tasks submitted by the Executor framework. The CC and EC columns of the “Overall average” in the last row are calculated by multiplying the complexity value of each item with its corresponding call count (CN or EN), summing up all items, and then dividing by the total number of calls. In Table 3, the overall average cyclomatic complexity of asynchronous tasks submitted by CompletableFuture is 1.71, while that of tasks submitted by the Executor framework is 2.20. In Table 4, projects in corpus-3, which do not use CompletableFuture, show an average cyclomatic complexity for tasks submitted by the Executor framework of 2.15. Whether in projects that contain only traditional asynchronous constructs or those where traditional asynchronous constructs coexist with CompletableFuture, the tasks submitted by CompletableFuture exhibit lower complexity. Therefore, we can conclude that the form of submitting asynchronous tasks via CompletableFuture can help developers write code for asynchronous tasks with lower complexity.

5. Refactoring Method

In this section, we detail the refactoring methods. We start by outlining the framework for automatic refactoring. Then, we explain how to map AST to JimpleIR and discuss the static program analysis methods necessary for executor inheritance structure analysis and refactoring precondition checks. We also describe four refactoring templates. Finally, we present the refactoring algorithms.

5.1. Method Framework

The framework diagram for the automated refactoring method aimed at asynchronous mechanisms is shown in Figure 4. In the automated refactoring process, the first step is to transform the source program into an AST (Abstract Syntax Tree) and Jimple IR (a three-address intermediate representation provided by the static analysis tool Soot). We use the visitor pattern to traverse the AST to obtain specific syntax nodes, which we then map to the corresponding Jimple IR model. Concurrently, we analyze the executor inheritance structure. Next, we perform program analysis on the Jimple IR to check preconditions, starting with verifying if the Future variable’s type matches the Future interface type. After obtaining the results of the executor inheritance structure analysis (executor types in the program that meet the refactoring conditions), it then determines whether the executor object type in the current traditional asynchronous construction statement satisfies the refactoring conditions through executor type analysis. Finally, the refactoring checks if the future object calls the cancel(true) method and if there are any type checks and operations on the future object in the program. Traditional asynchronous constructs that meet the precondition conditions are refactored by applying a refactoring template to modify the AST and complete the refactoring.

5.2. Mapping from AST to Jimple IR

The specific code refactoring process involves converting the source code into an AST, modifying AST nodes, and then converting the AST back into the refactored source code. Program analysis is based on Jimple IR because it has only 15 types of statements. A statement involves at most three local variables or constants, and the conversion process from bytecode to Jimple IR eliminates redundant code, making it more suitable for static program analysis. Therefore, specific nodes on the AST need to be mapped to Jimple IR before performing program analysis.
In our method, the syntax tree nodes that need mapping include MethodInvocation expressions, instanceof type comparison expressions, and CastExpression type casting expressions. On the AST, expressions might correspond to multiple Jimple IR statements. For example, in the method invocation a.b().c(), the invocation of c() includes b(), with the AST placing the c() node as the parent of the b() node. In the three-address code Jimple IR, this expression is represented as two expression statements. In our mapping method, we do not consider the child nodes of the current expression node on the AST. Below is our detailed mapping method.
“MethodInvocation” represents a method invocation. We traverse all method invocation statements in the corresponding block of Jimple IR code, comparing the names of the method invocations to identify the unique corresponding Jimple IR statement.
“Instanceof” type comparison expression is used to check whether a variable or expression’s type is the reference type or a subtype of the reference type on the right side of the instanceof operator. We traverse all Jimple statements in the corresponding block, matching whether they contain the string instanceof.
“CastExpression” forces the type conversion of a variable or expression to a subtype of its original type. By traversing all definition and assignment statements in the corresponding block, we determine whether the right side of the statement includes a type-casting expression to identify the corresponding statement.
Using the aforementioned methods for matching, if more than one statement is matched within a block, they are differentiated by line number. If the line numbers are also the same, the expressions in the AST are sorted from smallest to largest based on line and column numbers, following the left-to-right, top-to-bottom execution order of Java code. Simultaneously, the Jimple IR block is converted into an intraprocedural control flow graph, traversing from entry to exit, matching the corresponding Jimple IR statements by sequence number.

5.3. Executor Inheritance Structure Analysis

The JUC package contains 3 delegated executor types. Our source code review indicates that other executor types are also refactorable. However, software projects may use executor types from third-party libraries or custom-implemented executor types, the latter of which often appear in software projects that heavily utilize asynchronous programming techniques. For example, Jenkins has 6 custom executor types, while the JUC package has only 3 delegated types and 4 other executor types. The refactoring method mentioned in previous research [4] does not apply to subclassed asynchronous constructs, which means that the more widely asynchronous programming is used in a project, the more likely it is that the project cannot be fully refactored. Therefore, we propose an executor inheritance structure analysis method. Starting from the two most common object-oriented code reuse mechanisms of inheritance and delegation [27], we perform static analysis on the entire executor inheritance structure of the software project to obtain all the executor types that can be refactored, as well as the executor types that need further judgment due to the use of the delegation pattern. Finally, we exclude all executor types that have type checks and type casts in their `submit` and `execute` methods, as these executor types cannot be safely refactored.
Our static analysis method is based on points-to analysis, and we begin by defining points-to analysis.
Definition 1. 
Points-to analysis: points-to analysis is a static program analysis technique that computes the memory locations that references in a program can point to at runtime [28]. It is formally represented as:
  pointsTo ( r e f ) = { i 1 , i 2 , , i n }
Here, “ref” is a reference type variable, ik ∈ {i1, i2,…,in} (1 ≤ kn) is an abstract heap object identifier. We use the flow-insensitive, interprocedural context-insensitive points-to analysis provided by the Spark static analysis framework [29] in Soot. This points-to analysis maps object creation statements in the program (new statements) to corresponding abstract heap object identifiers.
Definition 2. 
Alias analysis: alias analysis is a static analysis technique used to determine whether two or more references might refer to the same memory location. It is a variant of points-to analysis, formally represented as [30]:
alias ( r e f 1 , r e f 2 ) = { true         if   pointsT o ( r e f 1 ) pointsTo ( r e f 2 ) false         otherwise
Definition 3. 
possibleTypes: Soot provides a method to obtain the possible actual types of reference variables through the results of points-to analysis, represented by abstract heap object identifiers (new statements). It is formally represented as:
possibleTypes ( r e f ) = { T |   T getType ( pointsTo ( r e f ) ) }  
Here, “getType” is used to obtain the type information of an abstract heap object identifier (new statement).

5.3.1. Executor Delegation Analysis

Executor delegation analysis aims to identify executor types within an executor inheritance hierarchy that delegate the concrete (partial) logic of their submit or execute methods, responsible for submitting asynchronous tasks, to another executor object. Whether such a type satisfies the refactoring conditions depends not only on the definition of its own submit or execute method but also on the execution logic of methods with the same name in the executor type it delegates to. In this section, we first define an interprocedural field access analysis within the class. Then, we formally define the delegated executor type and present a preliminary method for determining whether a delegated executor type can be refactored.
Definition 4. 
Field Access Analysis: This analysis determines whether a local reference type variable local within the body of a method m of a class t ultimately points to a field f of the given class t, if it points then class t belongs to the delegate class. The formal definition is as follows.
fieldAccessAnalysis ( local , f ) = { true ,         if   isFieldAccess ( stmt , f ) fieldAccessAnalysis ( getReturnLocal ( getMethod ( stmt ) ) , f ) ,         if   isMethodInvoc ( stmt ) false ,         otherwise , where   s t m t = getDefStmt ( l o c a l )
where
getDefStmt(Local): Retrieves the definition statement Stmt for a local reference type variable Local within the current method body via the use-def chain.
isFieldAccess(Stmt, f): Determines whether a definition statement Stmt within the method body defines a local reference type variable as field f.
isMethodInvoc(Stmt): Checks if a definition statement Stmt in the method body has a local reference type variable that serves as the return value of an intra-class method invocation.
getMethod(Stmt): Obtain the methods that contain class internal method calls within the current statement Stmt.
getReturnLocal(Method): Obtains the local reference type variable local returned in the return statement of the current method Method.
Following this, we provide the characteristic definition of delegate executor types.
Definition 5. 
Delegate Executor Type: A Delegate Executor Type (DT) is defined as a class that simultaneously satisfies the following characteristics.
  • DT is a concrete implementation class of the ExecutorService interface, formally represented as:
D T allSubTypes ( ExecutorService ) m getMethods ( D T ) , Abstract modiMethod ( m )
where
allSubTypes(T): Retrieves the set of all direct and indirect subclasses of type T.
getMethods(T): Obtains the collection of all methods defined in type T.
modiMethod(Method): Acquires the set of modifiers for the method Method.
2.
DT has an instance field f′, whose declared type is ExecutorService or its subclass, formally represented as:
f getFields ( DT ) : fieldType ( f ) allSubTypes ( ExecutorService ) Static modiField ( f )
where
getFields(T): Retrieves all fields defined in type T.
fieldType(F): Obtains the declared type of field F.
modiField(F): Acquires the set of modifiers for field F.
3.
The bodies of DT’s execute and submit methods both contain method invocations with the same sub-signature as the methods themselves, and the receiver object of these calls is field f′, formally represented as:
m { execute , submit } , m getMethods ( D T )     s t m t getBody ( m )   , m getMethod ( s t m t ) : ( sig ( m ) = sig ( m ) ) ( fieldAccessAnalysis ( receiver ( s t m t ) , f ) = true )
where
getBody(Method): Retrieves the body of a given method Method, a collection of statements Stmt.
sig(Method): Obtains the sub-signature of a given method Method, including the return type, method name, and the list of parameter types.
receiver(Stmt): Identifies the receiver object of the method invocation expression contained in a given statement Stmt, which is a reference type local variable.
Definition 6. 
Refactorable Delegate Executor Type: After identifying a delegate executor class, further judgments are necessary to determine if it could meet the refactoring conditions. More specifically, it involves assessing the differences between the three submit methods and the execute method, with the first step to eliminate known differences.
  • Submit statement: In Jimple IR, we use use-def chains to obtain the definition statement of local in the return statement, mapping the return statement to a separate return instruction. Additionally, we remove the equals sign and the local on its left side from the local definition statement and map it to a regular expression statement.
  • Method invocation differences: The method invocations within the submit and execute methods with the same sub-signature differ. We remove the method signature part from these method invocation statements.
  • Parameter differences: For submit(Runnable, Value), the initialization statement for the second parameter needs to be eliminated, and the locals in the method body are renamed sequentially. For submit(Callable), the parameter declaration of the Callable type is replaced with Runnable.
After eliminating known differences, we can compare the submit and execute methods. If no unknown differences exist, the corresponding types are added to the refactorable executor type set.
  • deleSubmitR set contains delegate executor types where submit(Runnable) method has no unknown differences.
  • deleSubmitRV set contains delegate executor types where submit(Runnable, Value) method has no unknown differences.
  • deleSubmitC set contains delegate executor types where submit(Callable) method has no unknown differences.

5.3.2. Executor Overriding Analysis

In addition to delegate executor types, the characteristic of code reuse through inheritance in object-oriented programming also provides a possibility for identifying refactorable executor types. If executor types internal to JUC meet the refactoring conditions, then executor types that inherit from those within JUC and do not override instance methods that could affect submit or execute still meet the refactoring conditions. This section first defines key methods, followed by the definition of refactorable executor types.
Definition 7. 
Key Methods: For the executor type RT that has been determined to be refactorable, its key methods are the submit method and the execute method, as well as all non-private and non-final instance methods that are directly or indirectly called within the class. The formal definition is:
  CM ( R T ) = { submit , execute } { m | m getMethods ( R T ) , ¬ ( private modiMethod ( m ) final modiMethod ( m ) static modiMethod ( m ) ) }
Definition 8. 
Refactorable Executor Type Set: We define the refactorable executor type set RTS as a specific group of executor classes. The method of judgment involves traversing the inheritance structure of AbstractExecutorService; if it is not part of JUC and its direct parent is a refactorable type, then by obtaining the key methods of the direct parent, we determine whether the current type overrides to identify refactorable executor types. The formal definition is as follows:
  R T S = ExecutorInJUC { R T | R T allSubTypes ( AbstractExecutorService ) R T E x e c u t o r I n J U C getSuperClass ( R T ) R T S declarMethods ( R T ) CM ( getSuperClass ( R T ) ) = }
where
ExecutorInJUC represents the set of executor types within JUC.
getSuperClass(T) returns the direct parent class of type T.
declarMethods(T) returns the set of methods declared in type T.

5.3.3. Executor Type Operation Check

In traditional asynchronous constructs utilizing Runnable or Callable type tasks, the actual workflow after refactoring involves wrapping the original asynchronous tasks with CompletableFuture’s internal asynchronous task types before submission to the executor. Therefore, if the executor’s methods contain instanceof expressions or type-casting operations related to asynchronous task types, it might prevent the refactored asynchronous tasks from being properly submitted and run.
Definition 9. 
Executor Type Operation Check: The executor type operation check executorTypeOperateCheck is a static analysis method that determines whether any statements in the executor’s method body check or transform the type of asynchronous tasks. It is formally represented as:
  executorTypeOperateCheck ( e x e c u t o r ) = { true         i f     alias ( r e f 1 , r e f 2 ) r e f 1 = g e t E x e c u t e P a r a ( e x e c u t o r ) r e f 2 a l l T y p e O p ( R u n n a b l e ) , false       otherwise
where
getExecutePara(Executor) is used to obtain the parameter variable ref1 of the execute method corresponding to the executor type.
allTypeOp(T) is obtained through the Visitor pattern, capturing all instanceof statements and type-casting statements in the AST and mapping them to Jimple IR by checking whether the type object is T or any of its subclasses or implementing classes.

5.4. Precondition Checks

Before refactoring, it is necessary to perform precondition checks using static program analysis techniques to ensure consistency before and after the refactoring process. We provide an example of a traditional asynchronous construct, as shown in Figure 5. The first step involves checking the type of the Future variable; it is essential to confirm that component “a” in Figure 5 is a Future and not another subtype. This is crucial because our refactoring changes the actual type of the variable the component points to; having “a” as a Future ensures that the type constraints of the program are satisfied both before and after refactoring. Subsequently, we analyze the executor type, specifically examining component “c” in Figure 5. We employ interprocedural point-to analysis techniques to identify all possible actual types of the “executor” variables. By combining these results with the executor inheritance structure analysis, we determine whether refactoring is feasible. The specific methodology will be discussed in detail in Section 5.4.1. Lastly, we assess the behavior of the object pointed to by component “b”. Through alias analysis, we determine whether it has invoked the cancel(true) method or used instanceof and type-casting operations. These operations could introduce program errors because our refactoring changes the actual type of the object that component “b” points to, with detailed methods discussed in Section 5.4.2.

5.4.1. Executor Type Analysis

The analysis first requires the results of executor inheritance structure analysis, namely the types that satisfy the refactoring conditions and the delegated executor types that need further judgment. Then, combined with points-to analysis, it determines whether the actual type of the currently analyzed executor variable satisfies the refactoring conditions. If the actual type includes a delegated executor type, points-to analysis is used to further determine the field type of this executor object.
Definition 10. 
(Executor Type Analysis) Given a reference type local variable local pointing to an executor object, executor type analysis determines whether the executor type can meet the refactoring conditions. It is formally represented as:
  executorTypeAnalysis ( local ) = {   true         i f   ¬ executorTypeOperateCheck ( ET ) ET possibleTypes ( local ) isExecuteInvoc ( local ) , true   i f   ( E S T RTS   getRealTypes ( local ) RTS ) EST possibleTypes ( local ) isSubmitInoc ( local ) ,   false       otherwise
where
isExecuteInvoc(Local) and isSubmitInvoc(Local) are used to determine whether the method invocation in the traditional asynchronous construct is an execute method invocation or a submit method invocation.
getRealTypes(Local) is used to obtain the actual delegate executor types within the delegate executor types, formally represented as:
  getRealTypes   ( local ) = {   possibleTypes ( getExecuteField   ( E S T ) )   |   EST     deleSubmitR   E S T   possibleTypes ( local )     isSubmitRInvoc ( local )   E S T   deleSubmitRV   E S T   possibleTypes ( local )     isSubmitRVInvoc ( local )   E S T   deleSubmitC   E S T   possibleTypes ( l o c a l )   isSubmitCInvoc ( l o c a l ) }
where
getExecuteField(DT) is used to obtain the field used for delegation within the delegate executor types.
isSubmitRInvoc(), isSubmitRCInvoc(), and isSubmitCInvoc() are used to determine if the method invocations are submit(Runnable), submit(Runnable, Value), and submit(Callable), respectively.

5.4.2. Cancel Call and Type Operation Checks

To ensure consistency before and after refactoring, it is necessary to analyze the behavior of the Future object returned by the submit method. Firstly, CompletableFuture differs from the FutureTask used in traditional asynchronous constructs in that its cancel method cannot cancel task execution via an interrupt exception. Therefore, if the receiver object of a cancel(true) method invocation aliases with the Future object returned by the traditional asynchronous construct, the refactoring should be canceled. Secondly, our refactoring changes the actual type of the Future object. If the program contains type checking instanceof statements and type-casting statements, it might lead to changes in the results of type checks or failures in type conversions. Therefore, refactoring should be canceled if the reference variable in instanceof statements and cast statements aliases with the Future object returned by the traditional asynchronous construct.
Definition 11. 
Cancel Call and Type Operation Check: This is a static analysis method to determine whether a future object has invoked the cancel(true) method or undergone type checking and type casting. It is formally represented as:
  cancelAndTypeCheck ( f u t u r e O b j ) = { true         i f   alias ( f u t u r e O b j , r e f )   (   r e f g e t C a n c e l R e c s ( ) r e f a l l T y p e O p ( F u t u r e ) ) , false       otherwise
Here, the getCancelRecs() method uses the visitor pattern to traverse the AST, searching for method call nodes with the method name “cancel” and a parameter of true. It maps these nodes to JimpleIR, collects the receiver object variables of the method calls on the IR, and returns them as a collection.

5.5. Refactoring Templates

We have summarized four refactoring patterns based on four methods of traditional asynchronous constructs, illustrated with templates. As shown in Table 5, the Source Code column represents the code before refactoring, and the After Refactoring column represents the code after refactoring, where the syntax elements that remain unchanged before and after refactoring of an expression are shown in bold. These include the execute method declared by the Executor interface and the three overloaded submit methods declared by the ExecutorService interface.
  • Pattern 1: Refactoring of execute(Runnable)
Converts execute(Runnable) from the Executor interface to CompletableFuture.runAsync, facilitating asynchronous task submission with Runnable.
  • Pattern 2: Refactoring of submit(Runnable)
Transforms submit(Runnable) from an ExecutorService interface instance into a CompletableFuture, using runAsync to handle tasks returning Future.
  • Pattern 3: Refactoring of submit(Runnable, Value)
The refactoring is divided into two parts. The first part draws from the source code of the FutureTask constructor in JUC. By calling the callable method from the Executors class in JUC, Runnable and Value are converted to Callable. This thus converts to the situation of Refactoring Pattern 4. To simplify the code in Table 5, the first part is shown in the third line of Table 5, and the second part is left for the reader to refer to in Pattern 4.
  • Pattern 4: Refactoring of submit(Callable)
Unlike Patterns 1 and 2, which use Runnable type asynchronous tasks, Callable type tasks have return values, requiring the use of CompletableFuture’s supplyAsync method. It is necessary to refactor the Callable type task into a Supplier type. In the first line of the code on the right side of Table 5, a new Callable type variable is defined because the Callable type asynchronous task might not be a simple variable but an expression, and placing it directly into the newly defined task could lead to data races. Since the Callable interface’s call method declares that it throws exceptions, whereas the Supplier interface’s get method does not, to maintain consistency in exception handling before and after refactoring, as shown on the right side of Table 5, in lines 2–6, a Supplier type asynchronous task object is defined using a lambda expression. By wrapping the exceptions thrown by the call method in a RuntimeException using a try...catch statement, in lines 8–9, the CompletableFuture’s handle method is used to process the exception and convert it to a normal result. Then, through the thenCompose method, in lines 11–13, a new CompletableFuture object is instantiated; if the result is an exception, it is unwrapped and set as the exception result of the new CompletableFuture object. In lines 15–17, if it is a normal result, it is directly set as the normal result of the new CompletableFuture object, which is then returned.

5.6. Refactoring Algorithm

This section presents the design of the refactoring algorithm. Initially, the source code is used to generate an AST and Jimple IR, on which the executor inheritance structure analysis is performed. Then, all method invocation expressions, type comparison expressions, and cast expression nodes are traversed. First, references needed for cancel call and type check analysis are obtained, followed by obtaining the method signature from the method invocation expressions to identify the traditional asynchronous construct’s Jimple IR statements. The preconditions for refactoring are evaluated, and the code that passes is refactored according to the method signature using the corresponding refactoring template. The detailed steps are presented in Algorithm 1.
Algorithm 1: Refactoring Algorithm
Input: P Source program
Output: P′ Refactored source program
1  MIS getTACENodes(P)// Get the traditional asynchronous construct expression nodes.
2  doEISA()//Run executor inheritance structure analysis.
3  foreach MI MIS do
4    JIR getJIRStmt(MI)// Get the corresponding Jimple IR statement.
5    receiverLocal getReceiLocal(JIR)// Get the receiver object.
6    futureLocal getReturnObj(JIR)// Get the return value of a method invocation.
7    if (executorTypeAnalysis(receiverLocal) && canceAndTypeAnalysis(futureLocal)) then
8      applyRefacTemplate(MI);// Apply the corresponding refactoring template.
9    end
10  end
11 return P′

6. Implementation and Evaluation

We first describe the implementation of our approach, the automatic refactoring tool ReFuture. We then evaluate our method by applying ReFuture to 9 selected large-scale open-source projects. In this evaluation, we investigate the following questions:
  • RQ1 (Applicability): How many instances can ReFuture refactor in real projects?
  • RQ2 (Correctness): Does ReFuture introduce errors into the code after refactoring?
  • RQ3 (Refactoring Efficiency): How much code does ReFuture need to change and how much time does it take to complete the refactoring?
  • RQ4 (Comparison): How does ReFuture compare to existing methods for refactoring traditional asynchronous constructs?

6.1. Refactoring Tool

ReFuture is an Eclipse plugin implemented under the Eclipse JDT framework, extending classes such as UserInputWizardPage and Refactoring. Figure 6 shows the refactoring interface, with the top section displaying the Java source files to be refactored within the software project. The bottom section presents a side-by-side comparison: the left side shows the original code, while the right side displays the corresponding refactored code.

6.2. Experimental Setup

All experiments were conducted on a computer equipped with an Intel Core i5-6300HQ CPU at 2.30 GHz (4 cores, 4 threads) and 16 GB of DDR4 2133 MHz RAM; the software environment consisted of the Ubuntu 22.04.3 operating system, JDK 1.8.32, with Eclipse 2020-6 used as the development and testing platform for the refactoring tool, and the Java optimization framework soot 4.4.0 for static program analysis.

6.3. Experimental Method

We selected 9 real-world open-source programs to evaluate ReFuture. The chosen open-source programs are mature and well-known to demonstrate the actual refactoring effects of our method on large-scale programs. These applications include ActiveMQ [31], Hadoop [32], Elasticsearch [33], Jenkins [34], Tomcat [35], JGroups [36], Flume [37], ZooKeeper [38], and Tika [39]. These programs cover multiple domains: ActiveMQ is an open-source message broker, Hadoop is a framework for distributed data and computation, Elasticsearch is an open-source search and analytics engine, Jenkins is an open-source continuous integration (CI) tool, Tomcat is a free web server, JGroups is a group communication tool, Flume is a distributed, reliable, and available log system, ZooKeeper is an open-source distributed coordination service, and Tika is an Apache open-source content analysis toolkit. It is worth noting that Jenkins is a highly renowned and actively maintained open-source program, Elasticsearch is mentioned in the referenced literature [4], and JGroups is a well-known asynchronous program. The remaining open-source projects are from the Java project list on the Apache Software Foundation’s website [40], which we randomly selected. Table 6 shows the version information, the number of asynchronous task submission statements containing ExecutorService constructs, the counts of execute and submit method invocations, and the total lines of source code. For RQ1 (applicability), we applied ReFuture to automatically refactor these projects and counted the number of occurrences of ExecutorService constructs for submitting asynchronous tasks, successful refactorings using the four patterns, as well as failed refactorings and reasons. For RQ2 (correctness), we assessed the correctness by running the project’s test cases before and after refactoring to ensure no errors were introduced. For RQ3 (refactoring efficiency), we measured the changes in lines of code and refactoring time after applying ReFuture. For RQ4 (comparison), how does ReFuture compare to existing methods for refactoring traditional asynchronous constructs?

6.4. Results and Analysis

  • RQ1: How many instances can ReFuture refactor in real projects?
To address the research questions, we assessed the success and failure counts of ReFuture’s refactoring across the four patterns and analyzed the reasons for any failures. Since the preconditions for refactoring execute and submit methods differ, the results are presented in two parts, as shown in Table 7 and Table 8.
Table 7 summarizes the refactoring results for the execute method across nine test programs. Out of 486 execute nodes, 384 were successfully refactored. The projects Hadoop, Flume, ZooKeeper, and Tika achieved complete success in their refactoring efforts. In contrast, other projects faced cancellations due to incompatible executor types. Notably, ActiveMQ, being a message middleware program, had the highest number of nodes requiring refactoring. Elasticsearch followed with the second highest number of nodes, with 70 instances canceled due to executor type mismatches.
Table 8 presents the results for the three submit method refactoring patterns. Refactoring submit methods is more complex and has stricter conditions compared to execute, resulting in 142 successful refactorings out of 327 asynchronous task submission nodes. Elasticsearch, Flume, ZooKeeper, and Tika included only patterns 2 and 3, while Tomcat had instances of all 3 patterns. Among the 3 patterns, pattern 4 had the fewest refactorings due to the lower number of submit(Runnable, Value) calls—only 4 instances across the 9 test programs. Refactoring successes were further enhanced by delegating analysis to help determine executor types, leading to 26 additional successful refactorings in ActiveMQ, Hadoop, Jenkins, Tomcat, and ZooKeeper. This approach increased the number of successful refactorings by 22.4% compared to without using delegation analysis.
Table 9 provides a detailed breakdown of the reasons for refactoring failures across the three submit method patterns. Out of 327 submit method invocations, 185 potential refactorings were excluded. The primary reason for exclusion was incompatible executor types, accounting for 107 cases. This issue was prevalent across multiple test programs, with Tika being the only program without this issue. The second major reason was the presence of cancel(true) method invocations, found in 45 cases. Additional issues included type cast mismatches and declaration type mismatches, with 16 and 17 occurrences, respectively. Unlike incompatible executor types, these issues were more localized to specific test programs.
Overall, ReFuture successfully refactored 639 out of 813 traditional asynchronous constructs in the nine test programs, achieving a refactoring success rate of 64.70%.
  • RQ2: Does ReFuture introduce errors into the code after refactoring?
Verification of the correctness of the refactorings was divided into two layers: syntactic and semantic correctness.
On the syntactic level, all refactored code could be compiled without errors. Semantically, similar to the assumptions in the literature [4], we presumed that if unit tests passed both before and after refactoring, the refactorings did not alter the functionality of the code, thus preserving the correct program semantics. Although the nine test programs selected are well-known open-source projects with mature test suites, there were still issues: 1. Some tests could not pass even before refactoring the original code, and 2. Some refactored code was not covered by existing test cases.
To address these issues, we used Randoop to generate supplementary test cases. Specifically, we first used automated tools to generate a list of methods affected by the refactorings in the test programs, not only the methods containing the refactored code but also methods calling the refactored methods, as determined by call graphs. We then excluded inaccessible methods, such as those declared private. Subsequently, Randoop was used to generate test cases for the methods in the list. Randoop automatically identifies methods that might have side effects (e.g., writing/reading files), leading to potentially different outcomes with each call; therefore, we discarded the test cases it generated for such methods and removed all test cases that failed. Finally, we retained all the Randoop-generated test cases that passed.
We combined the retained Randoop-generated test cases with those provided by the test programs that could pass the tests to form the final test suite. After executing the refactoring with ReFuture, we ran the final test suite and found no new test errors, confirming that our refactorings did not affect the semantic correctness of the program.
  • RQ3: How much code does ReFuture need to change and how much time does it take to complete the refactoring?
To address this question, we evaluated the efficiency of our automated refactoring method as well as the invasiveness of the refactoring on the code. Invasiveness refers to the impact of our refactoring on the refactored programs while achieving the set objectives; excessive invasiveness can increase the difficulty of understanding the code and reduce its maintainability.
As shown in Table 10, we used the popular code counting tool SLOCCount to tally the lines of code before and after refactoring. The nine test programs together comprised 3,693,417 lines of Java code. After refactoring, the total number of lines increased to 3,695,009, an increase of 1,592 lines. This increase was due to the need to add code to ensure that exception handling within Callable was unaffected by Refactoring Pattern 2, as well as the introduction of new types into the source files during refactoring. In the Hadoop program, given that it had the highest number of Refactoring Pattern 2 applications, it also showed the greatest change in lines of code, increasing by 726 lines. On the other hand, in Elasticsearch and JGroups, where there were no instances of Refactoring Pattern 2 and the refactoring was more concentrated within code files, the change in lines of code was minimal.
As illustrated in Table 11, the time consumed by ReFuture for refactoring is broken down into the time for building program call graphs and the time for code refactoring. Hadoop, having the highest number of Java lines, also had the longest refactoring time at 2621 s. ReFuture conducts refactoring at the Eclipse project level, and programs managed with Maven often contain multiple submodules, each represented as a project in Eclipse. Therefore, the refactoring tool needs to be run multiple times to complete refactoring tasks like in ActiveMQ, which significantly contributed to the longer duration consumed for Hadoop and ActiveMQ. Flume, ZooKeeper, and Tika required 415, 284, and 261 s, respectively, to complete refactoring. Although these are also managed using Maven, they have fewer lines of code and contain fewer traditional asynchronous constructs. For instance, in the ZooKeeper test program, only the zookeeper-serve submodule exists in traditional asynchronous constructs, and ReFuture skips building the call graph, saving time.
ReFuture refactored 639 traditional asynchronous constructs, adding a total of 1592 lines of code, an average increase of 2.49 lines per instance, indicating low invasiveness. This suggests that our refactoring has minimal impact on the programs beyond achieving the set objectives. Most of the time consumed was spent on building the full program call graphs. The total refactoring time for the nine test programs was 9520 s, with an average of 1057.78 s per test program.
  • RQ4: How does ReFuture compare to existing methods for refactoring traditional asynchronous constructs?
The method and refactoring tool 2RX proposed in the literature [4] refactors SwingWorker and traditional asynchronous constructs into reactive programming-related APIs. Although the refactoring purposes are different, the target code for refactoring includes traditional asynchronous constructs. Therefore, we applied ReFuture to the same test programs to conduct a comparison and demonstrate the differences between our work and existing work. The comparison results are shown in Table 12.
The 2RX part shows the results of its refactoring of the Future part. The refactoring result of ReFuture has already removed the refactoring of the execute method that 2RX does not support. The final result shows that except for Elasticsearch and AsyncHttpClient, the refactoring result of ReFuture is higher than 2RX. For Elasticsearch, the main reason is that the precision of our method using Spark pointer analysis is not enough, so the refactoring opportunity was missed. For AsyncHttpClient, ReFuture shows that there are 8 places of refactoring code in the entire project, and 7 of them were successfully refactored. 2RX refactored 105 places because the refactoring methods of ReFuture and 2RX are different. 2RX converts the method call expression whose return value type is Future and meets its pre-refactoring conditions as the parameter of the fromFuture method, while ReFuture refactors the traditional asynchronous construction, targeting the submit method of the executor. Their common point is that the return value type of the submit method may also be Future. However, there are many developer-defined methods with Future return values in the AsyncHttpClient project, and these methods do not belong to the executor type, so ReFuture did not perform the refactoring.

7. Conclusions and Future Work

This paper presents an automated refactoring method that combines various static analysis techniques such as visitor pattern analysis, alias analysis, and executor inheritance structure analysis to perform precondition checks before refactoring. Based on this method, an automated refactoring plugin, ReFuture, was implemented under the Eclipse JDT framework. In the experiments, ReFuture was evaluated based on the number of refactorings, refactoring time, and the introduction of errors, alongside a side-by-side comparison with the existing method. The refactoring results for nine large applications, including ActiveMQ, Hadoop, and Elasticsearch, show that ReFuture refactored 639 out of 813 code structures targeted for refactoring, achieving a success rate of 64.70% without introducing errors. The comparative experiment demonstrates that ReFuture not only excels in large programs but also performs well in medium- and small-scale programs. Compared to manual refactoring, it improves efficiency and provides a valuable learning opportunity for developers to master correct conversion techniques through its preview interface.
Our future research will primarily focus on the following. (1) ReFuture currently employs a context-insensitive pointer analysis technique from the Spark framework within Soot. While reliable, this method is outdated compared to newer pointer analysis technologies. Its efficacy is also limited by its dependence on the selection of the program’s entry method; inaccessible methods within the program can prevent accurate analysis of certain variables, consequently reducing the success rate of refactorings. To enhance our tool’s effectiveness and broaden its applicability, we plan to integrate more advanced pointer analysis techniques in future updates. (2) Exploring more rational uses of CompletableFuture to further enhance the quality of existing asynchronous program code, for example, automatically refactoring asynchronous logic that consists of multiple traditional asynchronous constructs combined in a synchronous manner into a fully asynchronous form using CompletableFuture, thereby fully leveraging its rich API to improve program execution efficiency. (3) continuing to improve the ReFuture tool, including testing on more programs to fix potential bugs and planning to develop refactoring plugins suitable for other IDEs to assist more programmers.

Author Contributions

Y.Z.: Proposing the idea, Writing—review and editing; Z.X.: Conducting experimentation, Writing—original draft, review and editing; Y.Y.: Experimental analysis, Writing—review and editing; L.Q.: Writing—review and editing. All authors have read and agreed to the published version of the manuscript.

Funding

This work is partially supported by the Hebei Provincial Natural Science Foundation under Grant No.F2023208001 and the Hebei Provincial Overseas High-level Talent Foundation under Grant No.C20230358. Foundation of Hebei Meteorological Service under Grant 22KY09.

Institutional Review Board Statement

Not applicable.

Informed Consent Statement

Not applicable.

Data Availability Statement

The raw data supporting the conclusions of this article will be made available by the authors on request.

Conflicts of Interest

The authors declare no conflicts of interest.

References

  1. Android—Java’s FutureTask Composability—Stack Overflow. Available online: https://stackoverflow.com/questions/14704812/javas-futuretask-composability (accessed on 21 March 2024).
  2. Java—Waiting on a List of Future—Stack Overflow. Available online: https://stackoverflow.com/questions/19348248/waiting-on-a-list-of-future (accessed on 21 March 2024).
  3. Google/Guava. Available online: https://github.com/google/guava (accessed on 19 September 2024).
  4. Kohler, M.; Salvaneschi, G. Automated Refactoring to Reactive Programming. In Proceedings of the 2019 34th IEEE/ACM International Conference on Automated Software Engineering (ASE), San Diego, CA, USA, 11–15 November 2019; IEEE: New York, NY, USA, 2019; pp. 835–846. [Google Scholar] [CrossRef]
  5. Orton, I. Alan Mycroft Refactoring Traces to Identify Concurrency Improvements. In Proceedings of the 23rd ACM International Workshop on Formal Techniques for Java-like Programs, Virtual Event, Denmark, 13 July 2021; ACM: New York, NY, USA, 2021; pp. 16–23. [Google Scholar] [CrossRef]
  6. Okur, S.; Erdogan, C.; Dig, D. Converting Parallel Code from Low-Level Abstractions to Higher-Level Abstractions. In ECOOP 2014—Object-Oriented Programming; Jones, R., Ed.; Lecture Notes in Computer Science; Springer: Berlin/Heidelberg, Germany, 2014; Volume 8586, pp. 515–540. ISBN 978-3-662-44201-2. [Google Scholar]
  7. Okur, S.; Hartveld, D.L.; Dig, D.; Deursen, A. A Study and Toolkit for Asynchronous Programming in C#. In Proceedings of the 36th International Conference on Software Engineering, Hyderabad, India, 31 May–7 June 2014. [Google Scholar] [CrossRef]
  8. Lin, Y.; Dig, D. Refactorings for Android Asynchronous Programming. In Proceedings of the 2015 30th IEEE/ACM International Conference on Automated Software Engineering (ASE), Lincoln, NE, USA, 9–13 November 2015; pp. 836–841. [Google Scholar] [CrossRef]
  9. Dig, D.; Marrero, J.; Ernst, M.D. Refactoring Sequential Java Code for Concurrency via Concurrent Libraries. In Proceedings of the 2009 IEEE 31st International Conference on Software Engineering, Vancouver, BC, Canada, 16–24 May 2009; IEEE: New York, NY, USA, 2009; pp. 397–407. [Google Scholar] [CrossRef]
  10. Ishizaki, K.; Daijavad, S.; Nakatani, T. Refactoring Java Programs Using Concurrent Libraries. In Proceedings of the Workshop on Parallel and Distributed Systems Testing, Analysis, and Debugging—PADTAD ’11, Toronto, ON, Canada, 17–21 July 2011; ACM Press: New York, NY, USA, 2011; p. 35. [Google Scholar] [CrossRef]
  11. Zhang, Y.; Shao, S.; Liu, H.; Qiu, J.; Zhang, D.; Zhang, G. Refactoring Java Programs for Customizable Locks Based on Bytecode Transformation. IEEE Access 2019, 7, 66292–66303. [Google Scholar] [CrossRef]
  12. Zhang, Y.; Shao, S.; Ji, M.; Qiu, J.; Tian, Z.; Du, X.; Guizani, M. An Automated Refactoring Approach to Improve IoT Software Quality. Appl. Sci. 2020, 10, 413. [Google Scholar] [CrossRef]
  13. Midolo, A.; Tramontana, E. An Automatic Transformer from Sequential to Parallel Java Code. Future Internet 2023, 15, 306. [Google Scholar] [CrossRef]
  14. Lin, Y.; Radoi, C.; Dig, D. Retrofitting Concurrency for Android Applications through Refactoring. In Proceedings of the 22nd ACM SIGSOFT International Symposium on Foundations of Software Engineering, Hong Kong, China, 11 November 2014; ACM: New York, NY, USA, 2014; pp. 341–352. [Google Scholar] [CrossRef]
  15. Gokhale, S.; Turcotte, A.; Tip, F. Automatic Migration from Synchronous to Asynchronous JavaScript APIs. Proc. ACM Program. Lang. 2021, 5, 1–27. [Google Scholar] [CrossRef]
  16. Eclipse Java Development Tools (JDT)|The Eclipse Foundation. Available online: https://eclipse.dev/jdt/ (accessed on 19 June 2024).
  17. Vallée-Rai, R.; Co, P.; Gagnon, E.; Hendren, L.; Lam, P.; Sundaresan, V. Soot: A Java Bytecode Optimization Framework. In Proceedings of the CASCON First Decade High Impact Papers on—CASCON ’10, Toronto, ON, Canada, 1–4 November 2010; ACM Press: New York, NY, USA, 2010; pp. 214–224. [Google Scholar] [CrossRef]
  18. Eclipse IDE|The Eclipse Foundation. Available online: https://eclipseide.org/ (accessed on 22 March 2024).
  19. Xzy0311/ReFuture. Available online: https://github.com/xzy0311/ReFuture (accessed on 27 August 2024).
  20. Pinto, G.; Torres, W.; Fernandes, B.; Castor, F.; Barros, R.S.M. A Large-Scale Study on the Usage of Java’s Concurrent Programming Constructs. J. Syst. Softw. 2015, 106, 59–81. [Google Scholar] [CrossRef]
  21. Arteca, E.; Tip, F.; Schäfer, M. Enabling Additional Parallelism in Asynchronous JavaScript Applications. In Proceedings of the 35th European Conference on Object-Oriented Programming (ECOOP 2021), Aarhus, Denmark, 11–17 July 2021; Schloss Dagstuhl—Leibniz-Zentrum für Informatik: Dagstuhl, Germany, 2021; Volume 194, pp. 7:1–7:28. [Google Scholar] [CrossRef]
  22. Goetz, B. Java Concurrency in Practice; Addison-Wesley: Upper Saddle River, NJ, USA, 2006; ISBN 978-0-321-34960-6. [Google Scholar]
  23. Dyer, R.; Nguyen, H.A.; Rajan, H.; Nguyen, T.N. Boa: A Language and Infrastructure for Analyzing Ultra-Large-Scale Software Repositories. In Proceedings of the 2013 35th International Conference on Software Engineering (ICSE), San Francisco, CA, USA, 18–26 May 2013; pp. 422–431. [Google Scholar] [CrossRef]
  24. Sutter, H. The Free Lunch Is over: A Fundamental Turn toward Concurrency in Software. Dr. Dobb’s J. 2005, 30, 202–210. [Google Scholar]
  25. CodeQL. Available online: https://codeql.github.com/ (accessed on 28 March 2024).
  26. Analyzing Your Projects—CodeQL. Available online: https://codeql.github.com/docs/codeql-for-visual-studio-code/analyzing-your-projects/#downloading-a-database-from-github (accessed on 28 March 2024).
  27. Giordano, G.; Fasulo, A.; Catolino, G.; Palomba, F.; Ferrucci, F.; Gravino, C. On the Evolution of Inheritance and Delegation Mechanisms and Their Impact on Code Quality. In Proceedings of the 2022 IEEE International Conference on Software Analysis, Evolution and Reengineering (SANER), Honolulu, HI, USA, 15–18 March 2022; IEEE: New York, NY, USA, 2022; pp. 947–958. [Google Scholar] [CrossRef]
  28. Tan, T.; Ma, X.; Xu, C.; Ma, C.; Li, Y. Survey on Java Pointer Analysis. J. Comput. Res. Dev. 2023, 60, 274–293. [Google Scholar] [CrossRef]
  29. Ondrej Lhoták. SPARK: A Flexible Points-to Analysis Framework for Java; National Library of Canada = Bibliothèque nationale du Canada: Ottawa, ON, Canada, 2004; ISBN 978-0-612-88247-8. [Google Scholar]
  30. Fan, G.; Xuan, Z. Design and Implementation of a Dependence-Based Taint Analysis. In Proceedings of the 2016 11th International Conference on Computer Science & Education (ICCSE), Nagoya, Japan, 23–25 August 2016; IEEE: New York, NY, USA, 2016; pp. 985–991. [Google Scholar] [CrossRef]
  31. ActiveMQ. Available online: https://activemq.apache.org/ (accessed on 19 June 2024).
  32. Apache Hadoop. Available online: https://hadoop.apache.org/ (accessed on 19 June 2024).
  33. Elasticsearch. Available online: https://www.elastic.co/cn/elasticsearch (accessed on 19 June 2024).
  34. Jenkins. Available online: https://www.jenkins.io/ (accessed on 19 June 2024).
  35. Apache Tomcat. Available online: https://tomcat.apache.org/ (accessed on 19 June 2024).
  36. JGroups. Available online: http://www.jgroups.org/ (accessed on 19 June 2024).
  37. Apache Flume—Apache Flume. Available online: https://flume.apache.org/ (accessed on 19 June 2024).
  38. Apache ZooKeeper. Available online: https://zookeeper.apache.org/ (accessed on 19 June 2024).
  39. Apache Tika. Available online: https://tika.apache.org/ (accessed on 19 June 2024).
  40. Apache Projects List. Available online: https://projects.apache.org/projects.html?language#Java (accessed on 22 September 2024).
Figure 1. Remote Acquisition and Decryption of Images.
Figure 1. Remote Acquisition and Decryption of Images.
Applsci 14 08866 g001
Figure 2. Traditional asynchronous remote acquisition and decryption of images.
Figure 2. Traditional asynchronous remote acquisition and decryption of images.
Applsci 14 08866 g002
Figure 3. CompletableFuture asynchronous remote acquisition and decryption of images.
Figure 3. CompletableFuture asynchronous remote acquisition and decryption of images.
Applsci 14 08866 g003
Figure 4. Refactoring Method Framework.
Figure 4. Refactoring Method Framework.
Applsci 14 08866 g004
Figure 5. Example of Traditional Asynchronous Constructs.
Figure 5. Example of Traditional Asynchronous Constructs.
Applsci 14 08866 g005
Figure 6. Interface of the refactoring tool ReFuture.
Figure 6. Interface of the refactoring tool ReFuture.
Applsci 14 08866 g006
Table 1. Comparison of Concurrency Refactoring Studies.
Table 1. Comparison of Concurrency Refactoring Studies.
Study PlatformSupported ConstructsRefactored Constructs Sync to Async
Dig et al. [9]JavaSynchronized lock,
HashMap,
Sequential recursive
Atomic lock,
ConcurrentHashMap,
Fork/Join framework
Yes
Okur et al. [6]C#Thread, ThreadPoolTaskNo
Okur et al. [7]C#Callback-based asynchronous codeasync, awaitNo
Lin et al. [14]AndroidLong-running codeAsyncTaskYes
Lin et al. [8]AndroidAsyncTaskIntentServiceNo
Gokhale et al. [15]JavaScriptSynchronous API functionsasync, awaitYes
Midolo et al. [13]JavaSequential method callsCompletableFutureYes
Köhler et al. [4]JavaFuture, SwingWorkerRxJava constructsNo
Orton et al. [5]JavaTraditional asynchronous constructsInline code,
CompletableFuture
No
Table 2. Asynchronously constructed statistics.
Table 2. Asynchronously constructed statistics.
Asynchronous ConstructsCount%
java.lang.Thread27,48930.07
java.util.concurrent.Executors13,89615.20
java.util.concurrent.ExecutorService11,66412.76
java.util.concurrent.Future71477.82
java.utilconcurrent.CompletableFuture32293.53
java.util.concurrent.FutureTask18332.01
org.springframework.scheduling.annotation.Async11041.21
com.google.common.util.concurrent.ListenableFuture8390.92
org.reactivestreams.Publisher7390.81
javax.swing.SwingWorker6130.67
javafx.concurrent.Task3770.41
java.util.concurrent.ForkJoinTask3330.36
io.vertx.core.Future2620.29
javax.ws.rs.container.AsyncResponse2540.17
io.reactivex.rxjava3.core.Observable1110.12
javax.ejb.AsyncResult 420.05
akka.dispatch.Futures340.04
javax.enterprise.concurrent.ManagedTask340.04
Projects with asynchronous constructs34,49237.74
Total number of projects91,403100.00
Table 3. Corpus-2 statistical results.
Table 3. Corpus-2 statistical results.
Project NameSNCNENCCEC
Peergos/Peergos17421833201.281.65
atomix/atomix234011721351.451.72
apache/pulsar13,26710921021.933.19
infinispan/infinispan10867651071.672.07
apache/flink22,1497123121.701.38
line/armeria44656362721.881.51
OpenLiberty/open-liberty10954714581.922.97
Azure/azure-sdk-for-java2058304622.171.58
camunda-cloud/zeebe2859294601.451.45
crate/crate3787259871.681.57
hyperledger/besu1301178221.531.68
apache/netbeans24181502173.143.76
apache/ratis108614271.561.14
lettuce-io/lettuce-core512812671.881.14
apache/hbase49941261361.352.68
ballerina-platform/ballerina-lang34668232.551.00
microsoft/CDM15647617.131.00
quarkusio/quarkus12,44874701.472.33
ben-manes/caffeine14,41748662.041.45
lucko/LuckPerms1840361081.811.25
Overall average5175.50428.80112.601.712.20
Table 4. Corpus-3 statistical results.
Table 4. Corpus-3 statistical results.
Project NameSNENEC
aws-amplify/aws-sdk-android99515301.53
opensourceBIM/BIMserver14374772.27
grpc/grpc-java10,9683161.93
apache/activemq21993093.13
MuntashirAkon/AppManager35132343.09
freenet/fred9411873.24
google/guava48,5781381.54
blazegraph/database8471133.70
sun0x00/redtorch740943.31
Docile-Alligator/Infinity-For-Reddit3654833.81
voldemort/voldemort2611744.96
tonikelope/megabasterd3819727.92
apache/iotdb4066673.45
hapifhir/hapi-fhir1797661.39
EdwardRaff/JSAT775564.43
open-telemetry/opentelemetry-java1693541.11
GoogleContainerTools/jib13,062281.96
apache/cloudstack1546262.08
JPressProjects/jpress2620251.32
KOHGYLW/kiftd-source1036252.24
Overall average5344.85198.702.15
Table 5. Refactoring Templates.
Table 5. Refactoring Templates.
Pattern NameSource CodeAfter Refactoring
Pattern 1executor.execute(runTask);CompletableFuture.runAsync(runTask,executor);
Pattern 2Future f = eService.submit(runTask);Future f = CompletableFuture.runAsync(runTask,eService);
Pattern 3Future f = eService.submit(runTask,value);Future f = eService.submit(Executors.callable(runTask, value));
Pattern 4Future f = eService.submit(callTask);1Callable newTask = callTask;
2Future f = CompletableFuture.supplyAsync(() -> {
3 try {
4  return newTask.call();
5 } catch (Exception e) {
6  throw new RuntimeException(e);
7 }
8 }, eService)
9 .handle((result, exception) ->
10 exception != null ? exception : result)
11 .thenCompose((resultOrException ) -> {
12 CompletableFuture newFuture = new CompletableFuture();
13 if (resultOrException instanceof CompletionException) {
14  CompletionException cptException = (CompletionException) resultOrException;
15  RuntimeException runException = (RuntimeException) cptException.getCause();
16  newFuture.completeExceptionally(runException.getCause());
17  return newFuture;
18 } else {
19  newFuture.complete(resultOrException);
20  return newFuture;}});
Table 6. Test Program Information.
Table 6. Test Program Information.
Project NameVersionNumber of Traditional Asynchronous ConstructsNumber of ExecuteNumber of SubmitNumber of Lines of Java Code
ActiveMQ5.16.634527372532,567
Hadoop2.10.2170431271,398,914
Elasticsearch6.1.4103976704,199
Jenkins2.356524481,195,314
JGroups4.2.21472621126,110
Tomcat9.0.80463313357,202
Flume1.10.12222094,180
ZooKeeper3.8.019415127,087
Tika2.8.0945164,885
total8134863274,700,458
Table 7. Statistics on execute Refactoring Outcomes.
Table 7. Statistics on execute Refactoring Outcomes.
Project NameNumber of ExecuteNumber of Refactorings for Pattern 1Executor Type Mismatch
ActiveMQ2732703
Hadoop43430
Elasticsearch972770
Jenkins431
JGroups26197
Tomcat331221
Flume220
ZooKeeper440
Tika440
total486384102
Table 8. Statistics on submit Refactoring Outcomes.
Table 8. Statistics on submit Refactoring Outcomes.
Project NameNumber of SubmitNumber of RefactoringsResults Obtained from Delegation Analysis
Pattern 2Pattern 3Pattern4TotalPattern 2Pattern 3Pattern 4Total
ActiveMQ131587210047577
Hadoop1020251273307405
Elasticsearch105610230
Jenkins23223485081310
JGroups15152100110
Tomcat3191300332
Flume201820208100
ZooKeeper501015109102
Tika203520350
total16651563275408814226
Table 9. Statistics on Reasons for submit Refactoring Failures.
Table 9. Statistics on Reasons for submit Refactoring Failures.
Project NameVariable Type CastingVariable Declaration Type Not FutureExecutor Type MismatchCancel (True)Total
ActiveMQ0011415
Hadoop010403787
Elasticsearch00303
Jenkins10025035
JGroups3710020
Tomcat306110
Flume0010010
ZooKeeper00235
Tika00000
total161710745185
Table 10. Code Line Changes Before and After ReFuture Refactoring.
Table 10. Code Line Changes Before and After ReFuture Refactoring.
Project Code Lines Before RefactoringCode Lines After RefactoringCode Lines Modified by Refactoring
ActiveMQ532,567532,965398
Hadoop1,398,9141,399,640726
Elasticsearch704,199704,23839
Jenkins188,273188,36188
JGroups126,110126,13929
Tomcat357,202357,29896
Flume94,18094,23454
ZooKeeper127,087127,17083
Tika164,885164,96479
total3,693,4173,695,0091592
Table 11. ReFuture Refactoring Time.
Table 11. ReFuture Refactoring Time.
Project NameTime to Build Program Call Graph (s)Time for Code Refactoring (s)Total (s)
ActiveMQ1416891505
Hadoop2534872621
Elasticsearch2281952376
Jenkins1306901396
JGroups15216168
Tomcat46430494
Flume4087415
ZooKeeper27113284
Tika2556261
total90874339520
Table 12. ReFuture VS 2RX.
Table 12. ReFuture VS 2RX.
Project Name2RXReFuture
Elasticsearch42
JUnit14
DropWizard22
Mockito25
Springside409
Titan11
AsyncHttpClient1057
Graylog2Server01
Java Websocket00
B3log00
Disclaimer/Publisher’s Note: The statements, opinions and data contained in all publications are solely those of the individual author(s) and contributor(s) and not of MDPI and/or the editor(s). MDPI and/or the editor(s) disclaim responsibility for any injury to people or property resulting from any ideas, methods, instructions or products referred to in the content.

Share and Cite

MDPI and ACS Style

Zhang, Y.; Xie, Z.; Yue, Y.; Qi, L. Automatic Refactoring Approach for Asynchronous Mechanisms with CompletableFuture. Appl. Sci. 2024, 14, 8866. https://doi.org/10.3390/app14198866

AMA Style

Zhang Y, Xie Z, Yue Y, Qi L. Automatic Refactoring Approach for Asynchronous Mechanisms with CompletableFuture. Applied Sciences. 2024; 14(19):8866. https://doi.org/10.3390/app14198866

Chicago/Turabian Style

Zhang, Yang, Zhaoyang Xie, Yanxia Yue, and Lin Qi. 2024. "Automatic Refactoring Approach for Asynchronous Mechanisms with CompletableFuture" Applied Sciences 14, no. 19: 8866. https://doi.org/10.3390/app14198866

Note that from the first issue of 2016, this journal uses article numbers instead of page numbers. See further details here.

Article Metrics

Article metric data becomes available approximately 24 hours after publication online.
Back to TopTop