Skip to content

Commit

Permalink
first draft version in a hurry
Browse files Browse the repository at this point in the history
  • Loading branch information
CharlesZ-Chen committed Dec 10, 2016
0 parents commit 0c6fbbd
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 0 deletions.
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# multiDeclRefactor

Simple and a bit ugly tool for refactoring multiple declarations in Java:

e.g. Given a java file has multiple declarations:

```java
public class MultiDecl {
private int a, b, c;

public List<String> d = new ArrayList<>(),
e,
f;
}
```

it will refactor all multiple declarations in one statements to each declaration per statement:
```java
public class MultiDecl {
private int a;
private int b;
private int c;

public List<String> d = new ArrayList<>();
public List<String> e;
public List<String> f;
}
```

**Note** : this tool behaviour as a pre-processor for code analysis, so that the produced result is a bit unfriendly to human-readability:

1. it will remove all comments appeared inside a statement of multiple declarations

2. it will not keep the origin identation of a refactor statements

## usage

```bash
./run-refactor.sh a.java b.java ...
```

## dependency

This tool depends on a hacked version of [checkstyle](https://github.com/CharlesZ-Chen/checkstyle) in my git repo.

The hacked version of `checkstyle` is the backend of this tool, i.e. it detects the multiple declarations in a source file and then propage the diagnostic result to this tool.

I currently still not have time to write build system and test framework for this tool, but hopefully I will create one soon.
31 changes: 31 additions & 0 deletions multiDeclChecks.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">

<!--
Checkstyle configuration that checks the Google coding conventions from Google Java Style
that can be found at https://google.github.io/styleguide/javaguide.html.
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.sf.net (or in your downloaded distribution).
To completely disable a check, just comment it out or delete it from the file.
Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
-->

<module name = "Checker">
<property name="charset" value="UTF-8"/>

<property name="severity" value="warning"/>

<property name="fileExtensions" value="java, properties, xml"/>

<module name="TreeWalker">
<module name="HackedMultipleVariableDeclarationsCheck">
<message key="multiple.variable.declarations" value=""/>
<message key="multiple.variable.declarations.comma" value=""/>
</module>
</module>
</module>
162 changes: 162 additions & 0 deletions multiDeclRefactor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import sys, os
import shutil, tempfile
import json

REFACTOR_ISSUE_NAME = "HackedMultipleVariableDeclarations"

def main():
try:
data = json.load(sys.stdin)
total_multi_decls = data[REFACTOR_ISSUE_NAME]
file_based_multi_decls = collect_by_file(total_multi_decls)
for refactor_file, multi_decls in file_based_multi_decls.iteritems():
refactor_decls(refactor_file, multi_decls)
except ValueError:
for line in sys.stdin:
print line
sys.exit(1)

def refactor_decls(refactor_file, multi_decls):
# print multi_decls
backup = refactor_file + ".origin"
shutil.copyfile(refactor_file, backup)
origin_line_no = 1;
with open(backup, 'r') as origin_file:
with tempfile.NamedTemporaryFile(mode='w', suffix='.java') as out_file:
ORIGIN_LINES = origin_file.readlines()
refactor_list = produce_refactor_list(multi_decls, ORIGIN_LINES)

# print refactor_list
## ASSUMPTION: refactors are sorted by start line (first element in a tuple element in the list)
for (start_line, end_line, decl_type, decls) in refactor_list:
while origin_line_no < start_line:
out_file.write(ORIGIN_LINES[origin_line_no - 1])
origin_line_no += 1;
origin_line_no = end_line + 1

decl_list = list()
for decl in decls:
decl_list.append(decl_type + ' ' + decl + ';')

for refactor_decl in decl_list:
out_file.write(refactor_decl + '\n')


while origin_line_no <= len(ORIGIN_LINES):
out_file.write(ORIGIN_LINES[origin_line_no - 1])
origin_line_no += 1;

out_file.flush()
shutil.copyfile(out_file.name, refactor_file)

def produce_refactor_list(multi_decls, ORIGIN_LINES):
refactor_list = list()
for multi_decl in multi_decls:
refactor = produce_refactor(multi_decl, ORIGIN_LINES)
refactor_list.append(refactor)
return refactor_list

def produce_refactor(multi_decl, ORIGIN_LINES):
start_line_no = multi_decl['line']
declared_type = multi_decl['declared_type']
(end_line_no, multi_decl_str) = get_multi_decls_as_str(multi_decl, ORIGIN_LINES)

decl_list = get_decl_list(multi_decl_str, declared_type)
return (start_line_no, end_line_no, declared_type, decl_list)

def get_multi_decls_as_str(multi_decl, ORIGIN_LINES):
"""This function will remove comments in multiple declarations statement."""
decl_str_list = list()
cur_line_no = multi_decl['line']
is_end_line = False
has_unclosed_comment = False

while not is_end_line:
cur_line = ORIGIN_LINES[cur_line_no - 1]
(processed_line, is_end_line, has_unclosed_comment) = process_line(cur_line, has_unclosed_comment)
decl_str_list.append(processed_line)
cur_line_no += 1

reformat_multi_decls_str = " ".join("".join(decl_str_list).split());
return (cur_line_no - 1, reformat_multi_decls_str)

def process_line(line, has_unclosed_comment):
if has_unclosed_comment:
i = 0
while i < len(line):
if line[i] == '*' and i < len(line) - 1:
i += 1
if line[i] == '/':
return process_line(line[i + 1:], False)
i += 1
return ("", False, True) # empty line, not end line, has unclosed comment

i = 0
while i < len(line):
# if meet a '/' then is must be '//' or '/*' for a well-grammered line
if line[i] == '/' and i < len(line) - 1:
cmt_start_idx = i;
i += 1
if line[i] == '/':
## this line is a single line comment
return (line[:i - 1], False, False) #(remaining line, not end line, no unclosed comment)
elif line[i] == '*':
i += 1
while i < len(line):
if line[i] == '*' and i < len(line) - 1:
i += 1
if line[i] == '/':
# recursive produce line: (line before cmt + line after cmt)
return process_line(line[:cmt_start_idx] + line[i + 1:], False)
i += 1
return (line[:cmt_start_idx], False, True)

if line[i] == ';':
return (line[:i], True, False)
i += 1

return (line, False, False)

def get_decl_list(decls_str, declared_type):
""" public int[] a = {1,2,3}, b = new int[0], c = Test.<Object, Object>m(a, b), d = new int [2], e = {1,2,3}
"""
#remove type first
decls_str = decls_str.replace(declared_type, '').strip()
decls_list = list()
LEFT_SET = set(['(', '<', '{'])
RIGHT_SET = set([')', '>', '}'])
# indicate whether current iterating in a parameter decalration like "<x, x>" or "{x, x}" or "(x, x)"
in_param_level = 0

i = 0
start_idx = i
while i < len(decls_str):
if decls_str[i] in LEFT_SET:
in_param_level += 1
elif decls_str[i] in RIGHT_SET:
in_param_level -= 1
assert in_param_level >= 0
elif decls_str[i] == ',' and in_param_level == 0:
decls_list.append(str(decls_str[start_idx:i]).strip())
start_idx = i + 1
i += 1

if start_idx < len(decls_str):
decls_list.append(str(decls_str[start_idx:]).strip())

return decls_list


def collect_by_file(multi_decls):
file_based_multi_decls = dict()

for decl in multi_decls:
file_path = decl['file']
if not file_path in file_based_multi_decls:
file_based_multi_decls[file_path] = list()
decl.pop('file')
file_based_multi_decls[file_path].append(decl)
return file_based_multi_decls

if __name__ == '__main__':
main()
5 changes: 5 additions & 0 deletions run-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash
SCRIPT_DIR=$(cd $(dirname "$0") && pwd)
ROOT_DIR=$(cd $SCRIPT_DIR/.. && pwd)
java -cp $ROOT_DIR/checkstyle/target/classes:$(ls $ROOT_DIR/checkstyle/target/dependency/*.jar | tr '\n' : | rev | cut -c 2- | rev) com.puppycrawl.tools.checkstyle.Main -c multiDeclChecks.xml -f json "$@"
# java -cp $ROOT_DIR/checkstyle/target/classes:$(ls $ROOT_DIR/checkstyle/target/dependency/*.jar | tr '\n' : | rev | cut -c 2- | rev) com.puppycrawl.tools.checkstyle.Main -t "$@"
5 changes: 5 additions & 0 deletions run-refactor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash
SCRIPT_DIR=$(cd $(dirname "$0") && pwd)
ROOT_DIR=$(cd $SCRIPT_DIR/.. && pwd)
java -cp $ROOT_DIR/checkstyle/target/classes:$(ls $ROOT_DIR/checkstyle/target/dependency/*.jar | tr '\n' : | rev | cut -c 2- | rev) com.puppycrawl.tools.checkstyle.Main -c $SCRIPT_DIR/multiDeclChecks.xml -f json "$@" | python $SCRIPT_DIR/multiDeclRefactor.py
# java -cp $ROOT_DIR/checkstyle/target/classes:$(ls $ROOT_DIR/checkstyle/target/dependency/*.jar | tr '\n' : | rev | cut -c 2- | rev) com.puppycrawl.tools.checkstyle.Main -t "$@"
20 changes: 20 additions & 0 deletions test/Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import java.util.*;
public class Test {
protected int f;
protected int g;

public int[] a = {1,2,3};
public int[] b = new int[0];
public int[] c = Test.<Object, Object>m(a, b);
public int[] d = new int [2];
public int[] e = {1,2,3};

public int i;
public int j;

static <T1, T2> int[] m(T1 t1, T2 t2) {
return new int[0];
}
}

class MyType {}

0 comments on commit 0c6fbbd

Please sign in to comment.