Skip to content

Commit d7e71da

Browse files
committed
Added JNI signature formatter
1 parent 5ecd077 commit d7e71da

File tree

3 files changed

+301
-0
lines changed

3 files changed

+301
-0
lines changed

wpiformat/test/test_jni.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import os
2+
3+
from test.tasktest import *
4+
from wpiformat.jni import Jni
5+
6+
7+
def test_jni():
8+
test = TaskTest(Jni())
9+
10+
# Input args go to next line even if they fit on same line
11+
test.add_input("./TestJNI.cpp",
12+
"JNIEXPORT void JNICALL" + os.linesep + \
13+
"Java_TestJNI_testFunc(JNIEnv* env, jclass) {" + os.linesep)
14+
test.add_output(
15+
"/*" + os.linesep + \
16+
" * Class: TestJNI" + os.linesep + \
17+
" * Method: testFunc" + os.linesep + \
18+
" * Signature: ()V" + os.linesep + \
19+
" */" + os.linesep + \
20+
"JNIEXPORT void JNICALL" + os.linesep + \
21+
"Java_TestJNI_testFunc" + os.linesep + \
22+
" (JNIEnv* env, jclass)" + os.linesep + \
23+
"{" + os.linesep, True, True)
24+
25+
# Input aligned to "(" and args past end of line
26+
test.add_input("./TestJNI.cpp",
27+
"JNIEXPORT void JNICALL" + os.linesep + \
28+
"Java_edu_wpi_cscore_CameraServerJNI_setCameraExposureHoldCurrent(JNIEnv* env," + os.linesep + \
29+
" jclass," + os.linesep + \
30+
" jint source) {" + os.linesep)
31+
test.add_output(
32+
"/*" + os.linesep + \
33+
" * Class: edu_wpi_cscore_CameraServerJNI" + os.linesep + \
34+
" * Method: setCameraExposureHoldCurrent" + os.linesep + \
35+
" * Signature: (I)V" + os.linesep + \
36+
" */" + os.linesep + \
37+
"JNIEXPORT void JNICALL" + os.linesep + \
38+
"Java_edu_wpi_cscore_CameraServerJNI_setCameraExposureHoldCurrent" + os.linesep + \
39+
" (JNIEnv* env, jclass, jint source)" + os.linesep + \
40+
"{" + os.linesep, True, True)
41+
42+
# Args in input on line after "(" and args length > 80 characters
43+
test.add_input("./TestJNI.cpp",
44+
"JNIEXPORT void JNICALL Java_edu_wpi_cscore_CameraServerJNI_putSourceFrame(" + os.linesep + \
45+
" JNIEnv *env, jclass, jint source, jlong imageNativeObj) {" + os.linesep)
46+
test.add_output(
47+
"/*" + os.linesep + \
48+
" * Class: edu_wpi_cscore_CameraServerJNI" + os.linesep + \
49+
" * Method: putSourceFrame" + os.linesep + \
50+
" * Signature: (IJ)V" + os.linesep + \
51+
" */" + os.linesep + \
52+
"JNIEXPORT void JNICALL" + os.linesep + \
53+
"Java_edu_wpi_cscore_CameraServerJNI_putSourceFrame" + os.linesep + \
54+
" (JNIEnv *env, jclass, jint source, jlong imageNativeObj)" + os.linesep + \
55+
"{" + os.linesep, True, True)
56+
57+
# Args > 80 characters long
58+
test.add_input("./TestJNI.cpp",
59+
"JNIEXPORT jint JNICALL Java_edu_wpi_cscore_CameraServerJNI_createSourceProperty(" + os.linesep + \
60+
" JNIEnv *env, jclass, jint source, jstring name, jint kind, jint minimum," + os.linesep + \
61+
" jint maximum, jint step, jint defaultValue, jint value) {" + os.linesep)
62+
test.add_output(
63+
"/*" + os.linesep + \
64+
" * Class: edu_wpi_cscore_CameraServerJNI" + os.linesep + \
65+
" * Method: createSourceProperty" + os.linesep + \
66+
" * Signature: (ILjava/lang/String;IIIIII)I" + os.linesep + \
67+
" */" + os.linesep + \
68+
"JNIEXPORT jint JNICALL" + os.linesep + \
69+
"Java_edu_wpi_cscore_CameraServerJNI_createSourceProperty" + os.linesep + \
70+
" (JNIEnv *env, jclass, jint source, jstring name, jint kind, jint minimum," + os.linesep + \
71+
" jint maximum, jint step, jint defaultValue, jint value)" + os.linesep + \
72+
"{" + os.linesep, True, True)
73+
74+
# Ensure fixes clang-format output aligned with "("
75+
test.add_input("./TestJNI.cpp",
76+
"JNIEXPORT jint JNICALL" + os.linesep + \
77+
"Java_edu_wpi_first_networktables_NetworkTablesJNI_createInstance(JNIEnv*," + os.linesep + \
78+
" jclass) {" + os.linesep)
79+
test.add_output(
80+
"/*" + os.linesep + \
81+
" * Class: edu_wpi_first_networktables_NetworkTablesJNI" + os.linesep + \
82+
" * Method: createInstance" + os.linesep + \
83+
" * Signature: ()I" + os.linesep + \
84+
" */" + os.linesep + \
85+
"JNIEXPORT jint JNICALL" + os.linesep + \
86+
"Java_edu_wpi_first_networktables_NetworkTablesJNI_createInstance" + os.linesep + \
87+
" (JNIEnv*, jclass)" + os.linesep + \
88+
"{" + os.linesep, True, True)
89+
90+
# Idempotence for same code
91+
test.add_input("./TestJNI.cpp",
92+
"/*" + os.linesep + \
93+
" * Class: edu_wpi_first_networktables_NetworkTablesJNI" + os.linesep + \
94+
" * Method: createInstance" + os.linesep + \
95+
" * Signature: ()I" + os.linesep + \
96+
" */" + os.linesep + \
97+
"JNIEXPORT jint JNICALL" + os.linesep + \
98+
"Java_edu_wpi_first_networktables_NetworkTablesJNI_createInstance" + os.linesep + \
99+
" (JNIEnv*, jclass)" + os.linesep + \
100+
"{" + os.linesep)
101+
test.add_latest_input_as_output(True)
102+
103+
# Idempotence for same code with named jclass variable
104+
test.add_input("./TestJNI.cpp",
105+
"/*" + os.linesep + \
106+
" * Class: edu_wpi_first_networktables_NetworkTablesJNI" + os.linesep + \
107+
" * Method: createInstance" + os.linesep + \
108+
" * Signature: ()I" + os.linesep + \
109+
" */" + os.linesep + \
110+
"JNIEXPORT jint JNICALL" + os.linesep + \
111+
"Java_edu_wpi_first_networktables_NetworkTablesJNI_createInstance" + os.linesep + \
112+
" (JNIEnv*, jclass class)" + os.linesep + \
113+
"{" + os.linesep)
114+
test.add_latest_input_as_output(True)
115+
116+
# Ensure text before JNIEXPORT and after args and ")" is handled correctly
117+
# as well as two JNI functions in a row
118+
test.add_input("./TestJNI.cpp",
119+
"/**" + os.linesep + \
120+
" *" + os.linesep + \
121+
" */" + os.linesep + \
122+
"JNIEXPORT jint JNICALL" + os.linesep + \
123+
"Java_edu_wpi_first_networktables_NetworkTablesJNI_getDefaultInstance" + os.linesep + \
124+
" (JNIEnv *, jclass)" + os.linesep + \
125+
"{" + os.linesep + \
126+
" return nt::GetDefaultInstance();" + os.linesep + \
127+
"}" + os.linesep + \
128+
os.linesep + \
129+
"JNIEXPORT jint JNICALL" + os.linesep + \
130+
"Java_edu_wpi_first_networktables_NetworkTablesJNI_createInstance" + os.linesep + \
131+
" (JNIEnv *, jclass)" + os.linesep + \
132+
"{" + os.linesep + \
133+
" return nt::CreateInstance();" + os.linesep + \
134+
"}" + os.linesep)
135+
test.add_output(
136+
"/*" + os.linesep + \
137+
" * Class: edu_wpi_first_networktables_NetworkTablesJNI" + os.linesep + \
138+
" * Method: getDefaultInstance" + os.linesep + \
139+
" * Signature: ()I" + os.linesep + \
140+
" */" + os.linesep + \
141+
"JNIEXPORT jint JNICALL" + os.linesep + \
142+
"Java_edu_wpi_first_networktables_NetworkTablesJNI_getDefaultInstance" + os.linesep + \
143+
" (JNIEnv *, jclass)" + os.linesep + \
144+
"{" + os.linesep + \
145+
" return nt::GetDefaultInstance();" + os.linesep + \
146+
"}" + os.linesep + \
147+
os.linesep + \
148+
"/*" + os.linesep + \
149+
" * Class: edu_wpi_first_networktables_NetworkTablesJNI" + os.linesep + \
150+
" * Method: createInstance" + os.linesep + \
151+
" * Signature: ()I" + os.linesep + \
152+
" */" + os.linesep + \
153+
"JNIEXPORT jint JNICALL" + os.linesep + \
154+
"Java_edu_wpi_first_networktables_NetworkTablesJNI_createInstance" + os.linesep + \
155+
" (JNIEnv *, jclass)" + os.linesep + \
156+
"{" + os.linesep + \
157+
" return nt::CreateInstance();" + os.linesep + \
158+
"}" + os.linesep, True, True)
159+
160+
test.run(OutputType.FILE)

wpiformat/wpiformat/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from wpiformat.includeguard import IncludeGuard
1616
from wpiformat.includeorder import IncludeOrder
1717
from wpiformat.javaclass import JavaClass
18+
from wpiformat.jni import Jni
1819
from wpiformat.licenseupdate import LicenseUpdate
1920
from wpiformat.lint import Lint
2021
from wpiformat.newline import Newline
@@ -343,6 +344,9 @@ def main():
343344
]
344345
run_pipeline(task_pipeline, args, files)
345346

347+
task_pipeline = [Jni()]
348+
run_pipeline(task_pipeline, args, files)
349+
346350
# Lint is run last since previous tasks can affect its output.
347351
task_pipeline = [ClangFormat(args.clang_version), PyFormat(), Lint()]
348352
run_batch(task_pipeline, args, file_batches)

wpiformat/wpiformat/jni.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""This task formats JNI signatures according to javah's output.
2+
3+
The format is of the form:
4+
5+
JNIEXPORT <return type> JNICALL
6+
Java_blah
7+
(args, more args)
8+
{
9+
// code here
10+
11+
If string of arguments exceeds 80 characters in length, it wraps as follows:
12+
13+
(args,
14+
more args)
15+
{
16+
// code here
17+
18+
The preceding comment containing the Class, Method, and Signature is also
19+
automatically generated based on the function's return type and arguments.
20+
"""
21+
22+
import regex
23+
24+
from wpiformat.task import Task
25+
26+
27+
class Jni(Task):
28+
29+
def should_process_file(self, config_file, name):
30+
return config_file.is_cpp_src_file(name)
31+
32+
def map_jni_type(self, type_name):
33+
ret = ""
34+
if type_name.endswith("*"):
35+
ret += "["
36+
37+
if type_name == "jboolean":
38+
return ret + "Z"
39+
elif type_name == "jbyte":
40+
return ret + "B"
41+
elif type_name == "jchar":
42+
return ret + "C"
43+
elif type_name == "jshort":
44+
return ret + "S"
45+
elif type_name == "jint":
46+
return ret + "I"
47+
elif type_name == "jlong":
48+
return ret + "J"
49+
elif type_name == "jfloat":
50+
return ret + "F"
51+
elif type_name == "jdouble":
52+
return ret + "D"
53+
elif type_name == "void":
54+
return ret + "V"
55+
elif type_name == "jstring":
56+
return ret + "Ljava/lang/String;"
57+
elif type_name == "jobject":
58+
return ret + "Ljava/lang/Object;"
59+
else:
60+
return ret + "?"
61+
62+
def run_pipeline(self, config_file, name, lines):
63+
linesep = Task.get_linesep(lines)
64+
65+
regex_str_sig = "(/\*[\w\*:\s\(\)]+\*/\s+)?" + \
66+
"JNIEXPORT\s+ (?P<ret>\w+)\s+ JNICALL\s+ " + \
67+
"(?P<func>Java_\w+)\s* \(\s* " + \
68+
"(?P<env_type>JNIEnv\s*\*\s*)" + \
69+
"(?P<env_name>\w+)?,\s* jclass\s*(?P<jclass_name>\w*)?"
70+
regex_sig = regex.compile(regex_str_sig, regex.VERBOSE)
71+
72+
regex_str_func = "Java_(?P<class>\w+)_(?P<method>[^_]+)$"
73+
regex_func = regex.compile(regex_str_func)
74+
75+
regex_str_arg = ", \s* (?P<arg>(?P<arg_type>[\w\*]+) \s+ \w+)|\)\s*{"
76+
regex_arg = regex.compile(regex_str_arg, regex.VERBOSE)
77+
78+
output = ""
79+
pos = 0
80+
for match_sig in regex_sig.finditer(lines):
81+
comment = ""
82+
signature = ""
83+
84+
if match_sig.start() > 0:
85+
output += lines[pos:match_sig.start()]
86+
87+
# Add JNI-specific args
88+
jni_args = " ("
89+
if match_sig.group("env_type"):
90+
jni_args += match_sig.group("env_type")
91+
if match_sig.group("env_name"):
92+
jni_args += match_sig.group("env_name")
93+
jni_args += ", jclass"
94+
if match_sig.group("jclass_name"):
95+
jni_args += " " + match_sig.group("jclass_name")
96+
97+
# Write JNI function comment
98+
match = regex_func.search(match_sig.group("func"))
99+
comment += "/*" + linesep + \
100+
" * Class: " + match.group("class") + linesep + \
101+
" * Method: " + match.group("method") + linesep + \
102+
" * Signature: ("
103+
104+
signature += "JNIEXPORT " + match_sig.group("ret") + " JNICALL" + \
105+
linesep + match_sig.group("func") + linesep + jni_args
106+
107+
# Add other args
108+
line_length = len(jni_args)
109+
for match_arg in regex_arg.finditer(lines[match_sig.end():]):
110+
if ")" in match_arg.group():
111+
break
112+
# If args going past 80 characters
113+
elif line_length + len(", ") + len(
114+
match_arg.group("arg")) + len(")") > 80:
115+
# Put current arg on next line and set line_length to
116+
# reflect that
117+
signature += "," + linesep + " " + match_arg.group("arg")
118+
line_length = len(" " + match_arg.group("arg"))
119+
else:
120+
signature += ", " + match_arg.group("arg")
121+
line_length += len(", ") + len(match_arg.group("arg"))
122+
comment += self.map_jni_type(match_arg.group("arg_type"))
123+
comment += ")" + self.map_jni_type(match_sig.group("ret")) + linesep + \
124+
" */" + linesep
125+
signature += ")" + linesep + "{"
126+
output += comment + signature
127+
128+
pos = match_sig.end() + match_arg.end()
129+
130+
# Write rest of file
131+
if pos < len(lines):
132+
output += lines[pos:]
133+
134+
if output == "" or output == lines:
135+
return (lines, False, True)
136+
else:
137+
return (output, True, True)

0 commit comments

Comments
 (0)