Skip to content

Commit 9469f3a

Browse files
committed
Implement module requires removal
Fixes: #37
1 parent 4bf7886 commit 9469f3a

7 files changed

Lines changed: 287 additions & 14 deletions

File tree

macros/macros.jurand

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# java_remove - remove dependency statements from Java source files
2+
#
3+
# Usage: *%java_remove* [-n <simple class name>]... [-p|-m|-pm <pattern>]... [file path]...
4+
#
5+
# Removes import / module requires statements from Java source files by matching
6+
# them against the lists of simple class names and patterns.
7+
%java_remove %{_bindir}/jurand -i
8+
19
# java_remove_imports - remove import statements from Java source files
210
#
311
# Usage: *%java_remove_imports* [-n <simple class name>]... [-p <pattern>]... [file path]...

manpages/jurand.1.adoc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
jurand - Java removal of annotations
88

99
== SYNOPSIS
10-
*jurand* [*-a*] [*-i*] [*-s*] [*-n*=_<name>_] [*-p*=<pattern>] [_<paths>_...]
10+
*jurand* [*-a*] [*-i*] [*-s*] [*-n*=_<name>_] [*-p|-m|-pm*=_<pattern>_] [_<paths>_...]
1111

1212
== DESCRIPTION
1313
A tool for manipulating symbols present in `.java` source files.
1414

1515
The tool can be used for patching `.java` sources in cases where using sed is insufficient due to Java language syntax.
1616
The tool follows Java language rules rather than applying simple regular expressions on the source code.
1717

18-
Currently the tool is able to remove `import` statements and annotations.
18+
Currently the tool is able to remove `import` statements, annotations and module `requires` statements.
1919

2020
== OPTIONS
2121
*-n*, *--name*=_<name>_::
@@ -24,6 +24,12 @@ Simple (not fully-qualified) class name.
2424
*-p*, *--pattern*=_<pattern>_::
2525
Regex pattern to match names used in code.
2626

27+
*-m*, *--pattern*=_<pattern>_::
28+
Regex pattern to match module name requires fields used in `module-info.java` files.
29+
30+
*-pm*, *--pattern*=_<pattern>_::
31+
Same as specifying *-p* and *-m* options with the same pattern
32+
2733
*-a*::
2834
Also remove annotations used in code.
2935

src/java_symbols.hpp

Lines changed: 140 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ struct Mutex
113113
struct Parameters
114114
{
115115
std::vector<Named_regex> patterns_;
116+
std::vector<Named_regex> module_patterns_;
116117
String_view_set names_;
117118
bool also_remove_annotations_ = false;
118119
bool in_place_ = false;
@@ -123,6 +124,7 @@ struct Strict_mode
123124
{
124125
std::atomic<bool> any_annotation_removed_ = false;
125126
Mutex<std::map<std::string_view, bool, std::less<>>> patterns_matched_;
127+
Mutex<std::map<std::string_view, bool, std::less<>>> module_patterns_matched_;
126128
Mutex<std::map<std::string_view, bool, std::less<>>> names_matched_;
127129
Mutex<std::map<std::string_view, bool>> files_truncated_;
128130
};
@@ -587,24 +589,136 @@ inline std::string remove_annotations(std::string_view content, std::span<const
587589
return result;
588590
}
589591

592+
inline std::string remove_jpms_requires(std::string_view content, std::span<const Named_regex> module_patterns)
593+
{
594+
auto new_content = std::string(content);
595+
new_content.reserve(content.size());
596+
597+
auto pos = std::ptrdiff_t(0);
598+
pos = find_token(content, "module");
599+
pos = find_token(content, "{", pos);
600+
601+
if (pos != std::ssize(content))
602+
{
603+
++pos;
604+
new_content.clear();
605+
new_content.append(content, 0, pos);
606+
while (pos != std::ssize(content))
607+
{
608+
auto symbol = std::string_view();
609+
auto end_pos = std::ptrdiff_t(0);
610+
auto old_pos = pos;
611+
std::tie(symbol, end_pos) = next_symbol(content, pos);
612+
613+
if (symbol == "requires")
614+
{
615+
pos = end_pos;
616+
}
617+
else
618+
{
619+
pos = find_token(content, ";", pos);
620+
if (pos != std::ssize(content))
621+
{
622+
++pos;
623+
}
624+
new_content.append(content, old_pos, pos - old_pos);
625+
continue;
626+
}
627+
628+
std::tie(symbol, end_pos) = next_symbol(content, pos);
629+
if (symbol == "transitive")
630+
{
631+
pos = end_pos;
632+
}
633+
else if (symbol == "static")
634+
{
635+
pos = end_pos;
636+
std::tie(symbol, end_pos) = next_symbol(content, pos);
637+
if (symbol == "transitive")
638+
{
639+
pos = end_pos;
640+
}
641+
}
642+
643+
auto module_name = std::string();
644+
645+
std::tie(symbol, end_pos) = next_symbol(content, pos);
646+
while (symbol != ";")
647+
{
648+
if (symbol.empty())
649+
{
650+
new_content.clear();
651+
new_content.append(content);
652+
return new_content;
653+
}
654+
655+
module_name += symbol;
656+
std::tie(symbol, end_pos) = next_symbol(content, end_pos);
657+
}
658+
659+
pos = end_pos;
660+
661+
bool matched = false;
662+
for (const auto& pattern : module_patterns)
663+
{
664+
if (std::regex_search(module_name.begin(), module_name.end(), pattern))
665+
{
666+
if (strict_mode)
667+
{
668+
strict_mode->module_patterns_matched_.lock().get().at(pattern) = true;
669+
}
670+
671+
matched = true;
672+
break;
673+
}
674+
}
675+
676+
if (matched)
677+
{
678+
if (auto skip_space = find_newline(content, pos); skip_space != -1)
679+
{
680+
pos = skip_space;
681+
}
682+
}
683+
else
684+
{
685+
new_content.append(content, old_pos, pos - old_pos);
686+
}
687+
}
688+
}
689+
690+
return new_content;
691+
}
692+
590693
////////////////////////////////////////////////////////////////////////////////
591694

592-
inline std::string handle_content(std::string_view content, const Parameters& parameters)
695+
inline std::string handle_content(const Path_origin_entry& path, std::string_view content, const Parameters& parameters)
593696
{
594-
auto [new_content, removed_classes] = remove_imports(content, parameters.patterns_, parameters.names_);
697+
auto result = std::string();
595698

596-
if (parameters.also_remove_annotations_)
699+
if (path.filename() == "module-info.java")
700+
{
701+
result = remove_jpms_requires(content, parameters.module_patterns_);
702+
}
703+
else
597704
{
598-
auto content_size = new_content.size();
599-
new_content = remove_annotations(new_content, parameters.patterns_, parameters.names_, removed_classes);
705+
auto [new_content, removed_classes] = remove_imports(content, parameters.patterns_, parameters.names_);
600706

601-
if (strict_mode and new_content.size() < content_size)
707+
if (parameters.also_remove_annotations_)
602708
{
603-
strict_mode->any_annotation_removed_.store(true, std::memory_order_release);
709+
auto content_size = new_content.size();
710+
new_content = remove_annotations(new_content, parameters.patterns_, parameters.names_, removed_classes);
711+
712+
if (strict_mode and new_content.size() < content_size)
713+
{
714+
strict_mode->any_annotation_removed_.store(true, std::memory_order_release);
715+
}
604716
}
717+
718+
result = new_content;
605719
}
606720

607-
return new_content;
721+
return result;
608722
}
609723

610724
inline std::string handle_file(const Path_origin_entry& path, const Parameters& parameters)
@@ -629,7 +743,7 @@ try
629743
original_content = std::string(std::istreambuf_iterator<char>(ifs), {});
630744
}
631745

632-
auto content = handle_content(original_content, parameters);
746+
auto content = handle_content(path, original_content, parameters);
633747

634748
if (not parameters.in_place_)
635749
{
@@ -714,14 +828,29 @@ inline Parameters interpret_args(const Parameter_dict& parameters)
714828

715829
if (auto it = parameters.find("-p"); it != parameters.end())
716830
{
717-
result.patterns_.reserve(it->second.size());
718-
719831
for (const auto& pattern : it->second)
720832
{
721833
result.patterns_.emplace_back(pattern, std::regex_constants::extended);
722834
}
723835
}
724836

837+
if (auto it = parameters.find("-m"); it != parameters.end())
838+
{
839+
for (const auto& pattern : it->second)
840+
{
841+
result.module_patterns_.emplace_back(pattern, std::regex_constants::extended);
842+
}
843+
}
844+
845+
if (auto it = parameters.find("-pm"); it != parameters.end())
846+
{
847+
for (const auto& pattern : it->second)
848+
{
849+
result.patterns_.emplace_back(pattern, std::regex_constants::extended);
850+
result.module_patterns_.emplace_back(pattern, std::regex_constants::extended);
851+
}
852+
}
853+
725854
if (auto it = parameters.find("-n"); it != parameters.end())
726855
{
727856
for (const auto& name : it->second)

src/jurand.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ Usage: jurand [optional flags] <matcher>... [file path]...
2121
simple (not fully-qualified) class name
2222
-p <pattern>
2323
regex pattern to match names used in code
24+
-m <pattern>
25+
regex pattern to match module name requires fields used in 'module-info.java' files
26+
-pm <pattern>
27+
same as specifying '-p' and '-m' options with the same pattern
2428
2529
Optional flags:
2630
-a also remove annotations used in code
@@ -38,7 +42,7 @@ Usage: jurand [optional flags] <matcher>... [file path]...
3842

3943
const auto parameters = interpret_args(parameter_dict);
4044

41-
if (parameters.names_.empty() and parameters.patterns_.empty())
45+
if (parameters.names_.empty() and parameters.patterns_.empty() and parameters.module_patterns_.empty())
4246
{
4347
std::cout << "jurand: no matcher specified" << "\n";
4448
return 1;
@@ -113,6 +117,11 @@ Usage: jurand [optional flags] <matcher>... [file path]...
113117
strict_mode->patterns_matched_.lock().get().try_emplace(pattern);
114118
}
115119

120+
for (const auto& pattern : parameters.module_patterns_)
121+
{
122+
strict_mode->module_patterns_matched_.lock().get().try_emplace(pattern);
123+
}
124+
116125
for (const auto& name : parameters.names_)
117126
{
118127
strict_mode->names_matched_.lock().get().try_emplace(name);
@@ -196,6 +205,15 @@ Usage: jurand [optional flags] <matcher>... [file path]...
196205
}
197206
}
198207

208+
for (const auto& pattern_entry : strict_mode->module_patterns_matched_.lock().get())
209+
{
210+
if (not pattern_entry.second)
211+
{
212+
std::cout << "jurand: strict mode: module pattern " << pattern_entry.first << " did not match anything" << "\n";
213+
exit_code = 3;
214+
}
215+
}
216+
199217
if (parameters.also_remove_annotations_ and not strict_mode->any_annotation_removed_.load(std::memory_order_acquire))
200218
{
201219
std::cout << "jurand: strict mode: -a was specified but no annotation was removed" << "\n";

test.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ test_file()
1919
{
2020
local filename="${1}"; shift
2121
local expected="${1}"; shift
22+
if [ -d "test_resources/${expected%/*}" ]; then
23+
mkdir -p "target/test_resources/${expected%/*}"
24+
fi
2225
cp "test_resources/${expected}" "target/test_resources/${expected}"
2326
run_tool "${filename}" "${@}"
2427
diff -u "target/test_resources/${filename}" "target/test_resources/${expected}"
@@ -152,6 +155,10 @@ test_file "Array.java" "Array.5.java" -a -n C -n D -n E -n F
152155

153156
test_file "Package_info.java" "Package_info.1.java" -a -n MyAnn
154157

158+
################################################################################
159+
# Tests for module-info handling
160+
test_file "simple_module/module-info.java" "simple_module/module-info.1.java" -m "java[.]base"
161+
155162
################################################################################
156163
# Tests for tool termination on invalid sources, result is irrelevant
157164

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Sample module-info.java demonstrating all JPMS constructs
3+
*/
4+
@Deprecated
5+
module com.example.full.module {
6+
7+
// Requires with modifiers
8+
requires transitive java.sql;
9+
requires static java.logging;
10+
11+
// Combined modifiers
12+
requires static transitive org.jetbrains.annotations;
13+
14+
/*
15+
* ---- EXPORTS ----
16+
*/
17+
18+
// Unqualified export
19+
exports com.example.api;
20+
21+
// Qualified export
22+
exports com.example.internal
23+
to com.example.friend,
24+
com.example.another.friend;
25+
26+
/*
27+
* ---- OPENS ----
28+
*/
29+
30+
// Unqualified open
31+
opens com.example.model;
32+
33+
// Qualified open
34+
opens com.example.secret
35+
to com.fasterxml.jackson.databind,
36+
com.google.gson;
37+
38+
/*
39+
* ---- SERVICES ----
40+
*/
41+
42+
// Service usage
43+
uses com.example.spi.Plugin;
44+
45+
// Service provision
46+
provides com.example.spi.Plugin
47+
with com.example.impl.PluginImpl,
48+
com.example.impl.AnotherPluginImpl;
49+
}

0 commit comments

Comments
 (0)