Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions macros/macros.jurand
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# java_remove - remove dependency statements from Java source files
#
# Usage: *%java_remove* [-n <simple class name>]... [-p|-m|-pm <pattern>]... [file path]...
#
# Removes import / module requires statements from Java source files by matching
# them against the lists of simple class names and patterns.
%java_remove %{_bindir}/jurand -i

# java_remove_imports - remove import statements from Java source files
#
# Usage: *%java_remove_imports* [-n <simple class name>]... [-p <pattern>]... [file path]...
Expand Down
10 changes: 8 additions & 2 deletions manpages/jurand.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
jurand - Java removal of annotations

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

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

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

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

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

*-m*, *--pattern*=_<pattern>_::
Regex pattern to match module name requires fields used in `module-info.java` files.

*-pm*, *--pattern*=_<pattern>_::
Same as specifying *-p* and *-m* options with the same pattern

*-a*::
Also remove annotations used in code.

Expand Down
185 changes: 161 additions & 24 deletions src/java_symbols.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ struct Mutex
struct Parameters
{
std::vector<Named_regex> patterns_;
std::vector<Named_regex> module_patterns_;
String_view_set names_;
bool also_remove_annotations_ = false;
bool in_place_ = false;
Expand All @@ -123,6 +124,7 @@ struct Strict_mode
{
std::atomic<bool> any_annotation_removed_ = false;
Mutex<std::map<std::string_view, bool, std::less<>>> patterns_matched_;
Mutex<std::map<std::string_view, bool, std::less<>>> module_patterns_matched_;
Mutex<std::map<std::string_view, bool, std::less<>>> names_matched_;
Mutex<std::map<std::string_view, bool>> files_truncated_;
};
Expand Down Expand Up @@ -418,6 +420,25 @@ inline bool name_matches(std::string_view name, std::span<const Named_regex> pat
return false;
}

/*!
* Iterates over @p content to find newline starting from position @p pos.
*
* @return The position of the newline or `-1` if not found.
*/
inline std::ptrdiff_t find_newline(std::string_view content, std::ptrdiff_t pos)
{
while (pos != std::ssize(content) and std::isspace(static_cast<unsigned char>(content[pos])))
{
if (content[pos] == '\n')
{
return pos;
}
++pos;
}

return -1;
}

/*!
* Iterates over @p content to remove all import statements provided
* as @p patterns and @p names. Patterns match the string representation
Expand Down Expand Up @@ -472,20 +493,9 @@ inline std::tuple<std::string, String_map> remove_imports(
std::tie(symbol, end_pos) = next_symbol(content, end_pos);
}

// Skip whitespace until one newline but only if a newline is found
if (auto skip_space = find_newline(content, end_pos); skip_space != -1)
{
auto skip_space = end_pos;

while (skip_space != std::ssize(content) and std::isspace(static_cast<unsigned char>(content[skip_space])))
{
++skip_space;

if (content[skip_space - 1] == '\n')
{
end_pos = skip_space;
break;
}
}
end_pos = skip_space + 1;
}

copy_end = end_pos;
Expand Down Expand Up @@ -579,24 +589,136 @@ inline std::string remove_annotations(std::string_view content, std::span<const
return result;
}

inline std::string remove_jpms_requires(std::string_view content, std::span<const Named_regex> module_patterns)
{
auto new_content = std::string(content);
new_content.reserve(content.size());

auto pos = std::ptrdiff_t(0);
pos = find_token(content, "module");
pos = find_token(content, "{", pos);

if (pos != std::ssize(content))
{
++pos;
new_content.clear();
new_content.append(content, 0, pos);
while (pos != std::ssize(content))
{
auto symbol = std::string_view();
auto end_pos = std::ptrdiff_t(0);
auto old_pos = pos;
std::tie(symbol, end_pos) = next_symbol(content, pos);

if (symbol == "requires")
{
pos = end_pos;
}
else
{
pos = find_token(content, ";", pos);
if (pos != std::ssize(content))
{
++pos;
}
new_content.append(content, old_pos, pos - old_pos);
continue;
}

std::tie(symbol, end_pos) = next_symbol(content, pos);
if (symbol == "transitive")
{
pos = end_pos;
}
else if (symbol == "static")
{
pos = end_pos;
std::tie(symbol, end_pos) = next_symbol(content, pos);
if (symbol == "transitive")
{
pos = end_pos;
}
}

auto module_name = std::string();

std::tie(symbol, end_pos) = next_symbol(content, pos);
while (symbol != ";")
{
if (symbol.empty())
{
new_content.clear();
new_content.append(content);
return new_content;
}

module_name += symbol;
std::tie(symbol, end_pos) = next_symbol(content, end_pos);
}

pos = end_pos;

bool matched = false;
for (const auto& pattern : module_patterns)
{
if (std::regex_search(module_name.begin(), module_name.end(), pattern))
{
if (strict_mode)
{
strict_mode->module_patterns_matched_.lock().get().at(pattern) = true;
}

matched = true;
break;
}
}

if (matched)
{
if (auto skip_space = find_newline(content, pos); skip_space != -1)
{
pos = skip_space;
}
}
else
{
new_content.append(content, old_pos, pos - old_pos);
}
}
}

return new_content;
}

////////////////////////////////////////////////////////////////////////////////

inline std::string handle_content(std::string_view content, const Parameters& parameters)
inline std::string handle_content(const Path_origin_entry& path, std::string_view content, const Parameters& parameters)
{
auto [new_content, removed_classes] = remove_imports(content, parameters.patterns_, parameters.names_);
auto result = std::string();

if (parameters.also_remove_annotations_)
if (path.filename() == "module-info.java")
{
auto content_size = new_content.size();
new_content = remove_annotations(new_content, parameters.patterns_, parameters.names_, removed_classes);
result = remove_jpms_requires(content, parameters.module_patterns_);
}
else
{
auto [new_content, removed_classes] = remove_imports(content, parameters.patterns_, parameters.names_);

if (strict_mode and new_content.size() < content_size)
if (parameters.also_remove_annotations_)
{
strict_mode->any_annotation_removed_.store(true, std::memory_order_release);
auto content_size = new_content.size();
new_content = remove_annotations(new_content, parameters.patterns_, parameters.names_, removed_classes);

if (strict_mode and new_content.size() < content_size)
{
strict_mode->any_annotation_removed_.store(true, std::memory_order_release);
}
}

result = new_content;
}

return new_content;
return result;
}

inline std::string handle_file(const Path_origin_entry& path, const Parameters& parameters)
Expand All @@ -621,7 +743,7 @@ try
original_content = std::string(std::istreambuf_iterator<char>(ifs), {});
}

auto content = handle_content(original_content, parameters);
auto content = handle_content(path, original_content, parameters);

if (not parameters.in_place_)
{
Expand Down Expand Up @@ -706,14 +828,29 @@ inline Parameters interpret_args(const Parameter_dict& parameters)

if (auto it = parameters.find("-p"); it != parameters.end())
{
result.patterns_.reserve(it->second.size());

for (const auto& pattern : it->second)
{
result.patterns_.emplace_back(pattern, std::regex_constants::extended);
}
}

if (auto it = parameters.find("-m"); it != parameters.end())
{
for (const auto& pattern : it->second)
{
result.module_patterns_.emplace_back(pattern, std::regex_constants::extended);
}
}

if (auto it = parameters.find("-pm"); it != parameters.end())
{
for (const auto& pattern : it->second)
{
result.patterns_.emplace_back(pattern, std::regex_constants::extended);
result.module_patterns_.emplace_back(pattern, std::regex_constants::extended);
}
}

if (auto it = parameters.find("-n"); it != parameters.end())
{
for (const auto& name : it->second)
Expand Down
20 changes: 19 additions & 1 deletion src/jurand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ Usage: jurand [optional flags] <matcher>... [file path]...
simple (not fully-qualified) class name
-p <pattern>
regex pattern to match names used in code
-m <pattern>
regex pattern to match module name requires fields used in 'module-info.java' files
-pm <pattern>
same as specifying '-p' and '-m' options with the same pattern

Optional flags:
-a also remove annotations used in code
Expand All @@ -38,7 +42,7 @@ Usage: jurand [optional flags] <matcher>... [file path]...

const auto parameters = interpret_args(parameter_dict);

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

for (const auto& pattern : parameters.module_patterns_)
{
strict_mode->module_patterns_matched_.lock().get().try_emplace(pattern);
}

for (const auto& name : parameters.names_)
{
strict_mode->names_matched_.lock().get().try_emplace(name);
Expand Down Expand Up @@ -196,6 +205,15 @@ Usage: jurand [optional flags] <matcher>... [file path]...
}
}

for (const auto& pattern_entry : strict_mode->module_patterns_matched_.lock().get())
{
if (not pattern_entry.second)
{
std::cout << "jurand: strict mode: module pattern " << pattern_entry.first << " did not match anything" << "\n";
exit_code = 3;
}
}

if (parameters.also_remove_annotations_ and not strict_mode->any_annotation_removed_.load(std::memory_order_acquire))
{
std::cout << "jurand: strict mode: -a was specified but no annotation was removed" << "\n";
Expand Down
4 changes: 2 additions & 2 deletions src/jurand_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

using namespace java_symbols;

template <typename T1, typename T2>
static std::ostream &operator<<(std::ostream &os, const std::tuple<T1, T2> &t)
template<typename T1, typename T2>
static std::ostream& operator<<(std::ostream& os, const std::tuple<T1, T2>& t)
{
return os << "(" << std::get<0>(t) << ", " << std::get<1>(t) << ")";
}
Expand Down
7 changes: 7 additions & 0 deletions test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ test_file()
{
local filename="${1}"; shift
local expected="${1}"; shift
if [ -d "test_resources/${expected%/*}" ]; then
mkdir -p "target/test_resources/${expected%/*}"
fi
cp "test_resources/${expected}" "target/test_resources/${expected}"
run_tool "${filename}" "${@}"
diff -u "target/test_resources/${filename}" "target/test_resources/${expected}"
Expand Down Expand Up @@ -152,6 +155,10 @@ test_file "Array.java" "Array.5.java" -a -n C -n D -n E -n F

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

################################################################################
# Tests for module-info handling
test_file "simple_module/module-info.java" "simple_module/module-info.1.java" -m "java[.]base"

################################################################################
# Tests for tool termination on invalid sources, result is irrelevant

Expand Down
Loading