@@ -99,27 +99,44 @@ pub enum ClassMatch {
9999
100100impl Default for ClassMatch {
101101 fn default ( ) -> Self {
102- Self :: One ( "" . to_string ( ) )
102+ Self :: One ( "** " . to_string ( ) )
103103 }
104104}
105105impl ClassMatch {
106106 fn matches ( & self , class : & str ) -> bool {
107+ let options = glob:: MatchOptions {
108+ case_sensitive : true ,
109+ require_literal_separator : true ,
110+ require_literal_leading_dot : false ,
111+ } ;
112+
107113 match self {
108- Self :: One ( p) => class. starts_with ( p) ,
109- Self :: Many ( pp) => pp. iter ( ) . any ( |p| class. starts_with ( p) ) ,
114+ Self :: One ( p) => {
115+ let pattern = glob:: Pattern :: new ( p) . unwrap_or_else ( |e| panic ! ( "Invalid glob pattern '{}': {}" , p, e) ) ;
116+ pattern. matches_with ( class, options)
117+ }
118+ Self :: Many ( pp) => pp. iter ( ) . any ( |p| {
119+ let pattern = glob:: Pattern :: new ( p) . unwrap_or_else ( |e| panic ! ( "Invalid glob pattern '{}': {}" , p, e) ) ;
120+ pattern. matches_with ( class, options)
121+ } ) ,
110122 }
111123 }
112124}
113125
114126#[ derive( Debug , Clone , Deserialize , Default ) ]
115127pub struct Rule {
116- /// What java class(es) to match against. This takes the form of a simple prefix to a JNI path with no wildcards .
128+ /// What java class(es) to match against. This takes the form of a glob pattern matching JNI paths .
117129 ///
118- /// | To Match: | Use a JNI Prefix: |
130+ /// Glob patterns are case-sensitive and require literal path separators (/ cannot be matched by *).
131+ /// Use ** to match across directory boundaries.
132+ ///
133+ /// | To Match: | Use a glob pattern: |
119134 /// | ------------------------- | ------------------------------------- |
120- /// | * | jni_prefix = ""
121- /// | java.lang.* | jni_prefix = "java/lang/"
122- /// | name.spaces.OuterClass.* | jni_prefix = "name/spaces/OuterClass$"
135+ /// | * | "*"
136+ /// | java.lang.* | "java/lang/**"
137+ /// | name.spaces.OuterClass.* | "name/spaces/OuterClass$*"
138+ /// | Specific class | "com/example/MyClass"
139+ /// | Multiple specific classes | ["com/example/Class1", "com/example/Class2"]
123140 #[ serde( rename = "match" ) ]
124141 pub matches : ClassMatch ,
125142
@@ -386,4 +403,57 @@ mod tests {
386403 fn test_expand_vars_panic_on_missing_var_dollar ( ) {
387404 expand_vars ( "$NONEXISTENT" ) ;
388405 }
406+
407+ #[ test]
408+ fn test_class_match_glob_patterns ( ) {
409+ // Test exact match
410+ let match_exact = ClassMatch :: One ( "com/example/MyClass" . to_string ( ) ) ;
411+ assert ! ( match_exact. matches( "com/example/MyClass" ) ) ;
412+ assert ! ( !match_exact. matches( "com/example/MyOtherClass" ) ) ;
413+
414+ // Test wildcard patterns
415+ let match_wildcard = ClassMatch :: One ( "com/example/*" . to_string ( ) ) ;
416+ assert ! ( match_wildcard. matches( "com/example/MyClass" ) ) ;
417+ assert ! ( match_wildcard. matches( "com/example/MyOtherClass" ) ) ;
418+ assert ! ( !match_wildcard. matches( "com/other/MyClass" ) ) ;
419+
420+ // Test question mark pattern
421+ let match_question = ClassMatch :: One ( "com/example/MyClass?" . to_string ( ) ) ;
422+ assert ! ( match_question. matches( "com/example/MyClass1" ) ) ;
423+ assert ! ( match_question. matches( "com/example/MyClassA" ) ) ;
424+ assert ! ( !match_question. matches( "com/example/MyClass" ) ) ;
425+ assert ! ( !match_question. matches( "com/example/MyClass12" ) ) ;
426+
427+ // Test multiple patterns
428+ let match_many = ClassMatch :: Many ( vec ! [ "com/example/*" . to_string( ) , "org/test/specific/Class" . to_string( ) ] ) ;
429+ assert ! ( match_many. matches( "com/example/MyClass" ) ) ;
430+ assert ! ( match_many. matches( "org/test/specific/Class" ) ) ;
431+ assert ! ( !match_many. matches( "org/other/MyClass" ) ) ;
432+ }
433+
434+ #[ test]
435+ #[ should_panic( expected = "Invalid glob pattern" ) ]
436+ fn test_class_match_invalid_pattern_panics ( ) {
437+ let match_invalid = ClassMatch :: One ( "[invalid" . to_string ( ) ) ;
438+ match_invalid. matches ( "any_class" ) ;
439+ }
440+ #[ test]
441+ fn test_class_match_literal_separator ( ) {
442+ // Test that require_literal_separator: true prevents * from matching /
443+ let match_pattern = ClassMatch :: One ( "com/example*" . to_string ( ) ) ;
444+ assert ! ( match_pattern. matches( "com/example" ) ) ;
445+ assert ! ( match_pattern. matches( "com/exampleClass" ) ) ;
446+ assert ! ( !match_pattern. matches( "com/example/SubClass" ) ) ; // * should not match /
447+
448+ // Test that we can use ** to match across directories
449+ let match_recursive = ClassMatch :: One ( "com/**/MyClass" . to_string ( ) ) ;
450+ assert ! ( match_recursive. matches( "com/example/MyClass" ) ) ;
451+ assert ! ( match_recursive. matches( "com/deep/nested/path/MyClass" ) ) ;
452+ assert ! ( match_recursive. matches( "com/MyClass" ) ) ; // ** can match zero directories too
453+
454+ // Test that * within a directory works
455+ let match_single_dir = ClassMatch :: One ( "com/*/MyClass" . to_string ( ) ) ;
456+ assert ! ( match_single_dir. matches( "com/example/MyClass" ) ) ;
457+ assert ! ( !match_single_dir. matches( "com/deep/nested/MyClass" ) ) ; // single * doesn't cross /
458+ }
389459}
0 commit comments