aws_fully_qualified_names/languages/java/
mod.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4mod builtins;
5mod traverse;
6
7use super::*;
8use crate::languages::ast_visitor::{DeclUseVisitor, RelevantName, ScopeVisitor};
9use traverse::traverse_ast;
10#[cfg(target_arch = "wasm32")]
11use wasm_bindgen::prelude::*;
12
13#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
14pub struct Java;
15
16impl TreeSitterLanguage for Java {
17    fn find_symbol_declarations_and_usages<'code>(
18        #[cfg(not(target_arch = "wasm32"))] cursor: &mut TreeCursor<'code>,
19        #[cfg(target_arch = "wasm32")] cursor: &mut TreeCursor,
20        code: &'code [u8],
21    ) -> ScopedDeclarationsAndUsages {
22        traverse_ast(cursor, code, DeclUseVisitor::new())
23    }
24
25    #[cfg(not(target_arch = "wasm32"))]
26    fn grammar() -> Grammar {
27        tree_sitter_java::language()
28    }
29}
30
31#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
32impl Java {
33    #[cfg(target_arch = "wasm32")]
34    #[wasm_bindgen(js_name = "findNames")]
35    pub async fn find_names(code: String) -> JsValue {
36        Parser::init().await;
37        let grammar =
38            crate::wasm::tree_sitter::load(include_bytes!(std::env!("JAVA_LANGUAGE"))).await;
39        let parser = Parser::new();
40        parser.set_language(grammar);
41        let ast = parser.parse(&code);
42        let names = <Self as Language>::find_names(&ast, &code);
43        serde_wasm_bindgen::to_value(&names).unwrap()
44    }
45
46    #[cfg(target_arch = "wasm32")]
47    #[wasm_bindgen(js_name = "findNamesWithInExtent")]
48    pub async fn find_names_within_extent(code: String, extent: Extent) -> JsValue {
49        Parser::init().await;
50        let grammar =
51            crate::wasm::tree_sitter::load(include_bytes!(std::env!("JAVA_LANGUAGE"))).await;
52        let parser = Parser::new();
53        parser.set_language(grammar);
54        let ast = parser.parse(&code);
55        let names = <Self as Language>::find_names(&ast, &code);
56        let extent_names = names.within_extent(&extent);
57        serde_wasm_bindgen::to_value(&extent_names).unwrap()
58    }
59
60    #[cfg(not(target_arch = "wasm32"))]
61    pub fn find_relevant_scope(
62        code: &str,
63        relevant_names: Vec<RelevantName>,
64    ) -> Result<RelevantScope> {
65        let code = code.as_bytes();
66        let ast = Java::parse(code)?;
67        let mut cursor = ast.walk();
68        let node = cursor.node();
69        let byte_range = node.byte_range();
70        let extent = node.into();
71        let (relevant_scope, snippet_extent, relevant_names_occurrences) = traverse_ast(
72            &mut cursor,
73            code,
74            ScopeVisitor::new(
75                Scope {
76                    extent,
77                    byte_range,
78                    name: None,
79                },
80                relevant_names,
81            ),
82        );
83        Ok(RelevantScope {
84            snippet: String::from_utf8_lossy(&code[relevant_scope]).to_string(),
85            snippet_extent,
86            relevant_names_occurrences,
87        })
88    }
89}
90
91#[cfg(all(test, not(target_arch = "wasm32")))]
92mod test {
93    use super::*;
94    use crate::languages::test_macros::*;
95
96    #[test]
97    fn finds_names_in_import_declaration() {
98        let code = r#"import java.lang.String;
99        String a = "lala";"#;
100        let names = Java::find_names(code).expect("Cannot find names in test code");
101
102        assert_eq!(
103            names,
104            Names {
105                fully_qualified: DeclUse {
106                    declared_symbols: vec![FullyQualifiedName {
107                        source: sym!(java.lang),
108                        symbol: sym!(String),
109                        extent: extent!(0:7-0:23),
110                    },],
111                    used_symbols: vec![FullyQualifiedName {
112                        source: sym!(java.lang),
113                        symbol: sym!(String),
114                        extent: extent!(1:8-1:14),
115                    }]
116                },
117                simple: DeclUse {
118                    declared_symbols: vec![
119                        SimpleName {
120                            symbol: "java".to_string(),
121                            extent: optional_extent!(0:7-0:11),
122                        },
123                        SimpleName {
124                            symbol: "lang".to_string(),
125                            extent: optional_extent!(0:12-0:16),
126                        },
127                        SimpleName {
128                            symbol: "String".to_string(),
129                            extent: optional_extent!(0:17-0:23),
130                        },
131                        SimpleName {
132                            symbol: "a".to_string(),
133                            extent: optional_extent!(1:15-1:16),
134                        },
135                    ],
136                    used_symbols: vec![
137                        SimpleName {
138                            symbol: "java".to_string(),
139                            extent: None,
140                        },
141                        SimpleName {
142                            symbol: "lang".to_string(),
143                            extent: None,
144                        },
145                        SimpleName {
146                            symbol: "String".to_string(),
147                            extent: optional_extent!(1:8-1:14),
148                        },
149                    ]
150                },
151                external_simple: DeclUse {
152                    declared_symbols: vec![
153                        SimpleName {
154                            symbol: "java".to_string(),
155                            extent: optional_extent!(0:7-0:11),
156                        },
157                        SimpleName {
158                            symbol: "lang".to_string(),
159                            extent: optional_extent!(0:12-0:16),
160                        },
161                        SimpleName {
162                            symbol: "String".to_string(),
163                            extent: optional_extent!(0:17-0:23),
164                        },
165                    ],
166                    used_symbols: vec![
167                        SimpleName {
168                            symbol: "java".to_string(),
169                            extent: None,
170                        },
171                        SimpleName {
172                            symbol: "lang".to_string(),
173                            extent: None,
174                        },
175                        SimpleName {
176                            symbol: "String".to_string(),
177                            extent: optional_extent!(1:8-1:14),
178                        },
179                    ]
180                }
181            }
182        );
183    }
184
185    #[test]
186    fn finds_names_in_unscoped_import_declaration() {
187        let code = r#"import Example;"#;
188        let names = Java::find_names(code).expect("Cannot find names in test code");
189
190        assert_eq!(
191            names,
192            Names {
193                fully_qualified: DeclUse {
194                    declared_symbols: vec![],
195                    used_symbols: vec![]
196                },
197                simple: DeclUse {
198                    declared_symbols: vec![SimpleName {
199                        symbol: "Example".to_string(),
200                        extent: optional_extent!(0:7-0:14),
201                    },],
202                    used_symbols: vec![]
203                },
204                external_simple: DeclUse {
205                    declared_symbols: vec![SimpleName {
206                        symbol: "Example".to_string(),
207                        extent: optional_extent!(0:7-0:14),
208                    },],
209                    used_symbols: vec![]
210                }
211            }
212        );
213    }
214
215    #[test]
216    fn finds_names_with_asterisk_import_declaration() {
217        let code = r#"import java.lang.*;
218class Example {}"#;
219        let names = Java::find_names(code).expect("Cannot find names in test code");
220
221        assert_eq!(
222            names,
223            Names {
224                fully_qualified: DeclUse {
225                    declared_symbols: vec![],
226                    used_symbols: vec![]
227                },
228                simple: DeclUse {
229                    declared_symbols: vec![SimpleName {
230                        symbol: "Example".to_string(),
231                        extent: optional_extent!(1:6-1:13)
232                    }],
233                    used_symbols: vec![]
234                },
235                external_simple: DeclUse {
236                    declared_symbols: vec![],
237                    used_symbols: vec![]
238                }
239            },
240        );
241    }
242
243    #[test]
244    fn finds_names_in_main() {
245        let code = r#"public class Main {
246    public static void main(String[] args) {
247        int i = 42;
248    }
249}"#;
250        let names = Java::find_names(code).expect("Cannot find names in test code");
251        assert_eq!(
252            names,
253            Names {
254                fully_qualified: DeclUse {
255                    declared_symbols: vec![],
256                    used_symbols: vec![]
257                },
258                simple: DeclUse {
259                    declared_symbols: vec![
260                        SimpleName {
261                            symbol: "Main".to_string(),
262                            extent: optional_extent!(0:13-0:17),
263                        },
264                        SimpleName {
265                            symbol: "main".to_string(),
266                            extent: optional_extent!(1:23-1:27),
267                        },
268                        SimpleName {
269                            symbol: "args".to_string(),
270                            extent: optional_extent!(1:37-1:41),
271                        },
272                        SimpleName {
273                            symbol: "i".to_string(),
274                            extent: optional_extent!(2:12-2:13),
275                        }
276                    ],
277                    used_symbols: vec![
278                        SimpleName {
279                            symbol: "String".to_string(),
280                            extent: optional_extent!(1:28-1:34),
281                        },
282                        SimpleName {
283                            symbol: "int".to_string(),
284                            extent: optional_extent!(2:8-2:11),
285                        }
286                    ]
287                },
288                external_simple: DeclUse {
289                    declared_symbols: vec![],
290                    used_symbols: vec![]
291                }
292            }
293        );
294    }
295
296    #[test]
297    fn finds_names_via_super() {
298        let code = r#"import restaurant.Food;
299public class Pretzel extends Food {
300    public taste() {
301        super.taste(Taste.SALTY);
302    }
303}"#;
304        let names = Java::find_names(code).expect("Cannot find names in test code");
305        assert_eq!(
306            names,
307            Names {
308                fully_qualified: DeclUse {
309                    declared_symbols: vec![FullyQualifiedName {
310                        source: vec!["restaurant".to_string()],
311                        symbol: vec!["Food".to_string()],
312                        extent: Extent {
313                            start: Location {
314                                line: 0,
315                                character: 7
316                            },
317                            end: Location {
318                                line: 0,
319                                character: 22
320                            }
321                        }
322                    }],
323                    used_symbols: vec![
324                        FullyQualifiedName {
325                            source: vec!["restaurant".to_string()],
326                            symbol: vec!["Food".to_string()],
327                            extent: Extent {
328                                start: Location {
329                                    line: 1,
330                                    character: 29
331                                },
332                                end: Location {
333                                    line: 1,
334                                    character: 33
335                                }
336                            }
337                        },
338                        FullyQualifiedName {
339                            source: vec!["restaurant".to_string()],
340                            symbol: vec!["Food".to_string(), "taste".to_string()],
341                            extent: Extent {
342                                start: Location {
343                                    line: 3,
344                                    character: 14
345                                },
346                                end: Location {
347                                    line: 3,
348                                    character: 19
349                                }
350                            }
351                        }
352                    ]
353                },
354                simple: DeclUse {
355                    declared_symbols: vec![
356                        SimpleName {
357                            symbol: "restaurant".to_string(),
358                            extent: optional_extent!(0:7-0:17),
359                        },
360                        SimpleName {
361                            symbol: "Food".to_string(),
362                            extent: optional_extent!(0:18-0:22),
363                        },
364                        SimpleName {
365                            symbol: "Pretzel".to_string(),
366                            extent: optional_extent!(1:13-1:20),
367                        },
368                        SimpleName {
369                            symbol: "taste".to_string(),
370                            extent: optional_extent!(2:11-2:16),
371                        }
372                    ],
373                    used_symbols: vec![
374                        SimpleName {
375                            symbol: "restaurant".to_string(),
376                            extent: None
377                        },
378                        SimpleName {
379                            symbol: "Food".to_string(),
380                            extent: optional_extent!(1:29-1:33),
381                        },
382                        SimpleName {
383                            symbol: "restaurant".to_string(),
384                            extent: None
385                        },
386                        SimpleName {
387                            symbol: "Food".to_string(),
388                            extent: None
389                        },
390                        SimpleName {
391                            symbol: "taste".to_string(),
392                            extent: optional_extent!(3:14-3:19),
393                        },
394                        SimpleName {
395                            symbol: "Taste".to_string(),
396                            extent: optional_extent!(3:20-3:25),
397                        },
398                        SimpleName {
399                            symbol: "SALTY".to_string(),
400                            extent: optional_extent!(3:26-3:31),
401                        }
402                    ]
403                },
404                external_simple: DeclUse {
405                    declared_symbols: vec![
406                        SimpleName {
407                            symbol: "restaurant".to_string(),
408                            extent: optional_extent!(0:7-0:17),
409                        },
410                        SimpleName {
411                            symbol: "Food".to_string(),
412                            extent: optional_extent!(0:18-0:22),
413                        }
414                    ],
415                    used_symbols: vec![
416                        SimpleName {
417                            symbol: "restaurant".to_string(),
418                            extent: None
419                        },
420                        SimpleName {
421                            symbol: "Food".to_string(),
422                            extent: optional_extent!(1:29-1:33),
423                        },
424                        SimpleName {
425                            symbol: "restaurant".to_string(),
426                            extent: None
427                        },
428                        SimpleName {
429                            symbol: "Food".to_string(),
430                            extent: None
431                        },
432                        SimpleName {
433                            symbol: "taste".to_string(),
434                            extent: optional_extent!(3:14-3:19),
435                        },
436                        SimpleName {
437                            symbol: "Taste".to_string(),
438                            extent: optional_extent!(3:20-3:25),
439                        },
440                        SimpleName {
441                            symbol: "SALTY".to_string(),
442                            extent: optional_extent!(3:26-3:31),
443                        }
444                    ]
445                }
446            }
447        );
448    }
449
450    #[test]
451    fn finds_names_in_assignment_expression() {
452        let code = r#"text += "and also...";"#;
453        let names = Java::find_names(code).expect("Cannot find names in test code");
454        assert_eq!(
455            names,
456            Names {
457                fully_qualified: DeclUse {
458                    declared_symbols: vec![],
459                    used_symbols: vec![]
460                },
461                simple: DeclUse {
462                    declared_symbols: vec![],
463                    used_symbols: vec![SimpleName {
464                        symbol: "text".to_string(),
465                        extent: optional_extent!(0:0-0:4),
466                    }]
467                },
468                external_simple: DeclUse {
469                    declared_symbols: vec![],
470                    used_symbols: vec![SimpleName {
471                        symbol: "text".to_string(),
472                        extent: optional_extent!(0:0-0:4),
473                    }]
474                }
475            },
476        );
477    }
478
479    #[test]
480    fn finds_names_in_basic_control_flow() {
481        let code = r#"int i = 0;
482while (i < 10) {
483    i += 3;
484    if (i % 2 == 0) break;
485}"#;
486        let names = Java::find_names(code).expect("Cannot find names in test code");
487        assert_eq!(
488            names,
489            Names {
490                fully_qualified: DeclUse {
491                    declared_symbols: vec![],
492                    used_symbols: vec![]
493                },
494                simple: DeclUse {
495                    declared_symbols: vec![SimpleName {
496                        symbol: "i".to_string(),
497                        extent: optional_extent!(0:4-0:5),
498                    }],
499                    used_symbols: vec![
500                        SimpleName {
501                            symbol: "int".to_string(),
502                            extent: optional_extent!(0:0-0:3),
503                        },
504                        SimpleName {
505                            symbol: "i".to_string(),
506                            extent: optional_extent!(1:7-1:8),
507                        },
508                        SimpleName {
509                            symbol: "i".to_string(),
510                            extent: optional_extent!(2:4-2:5),
511                        },
512                        SimpleName {
513                            symbol: "i".to_string(),
514                            extent: optional_extent!(3:8-3:9),
515                        }
516                    ]
517                },
518                external_simple: DeclUse {
519                    declared_symbols: vec![],
520                    used_symbols: vec![]
521                }
522            }
523        );
524    }
525
526    #[test]
527    fn finds_names_in_switch() {
528        let code = r#"switch (i > 5? 6 : i) {
529    case 1:
530    case 2:
531        i++;
532        break;
533    case 3:
534        --i;
535        break;
536    default:
537        throw new SwitchException();
538}
539"#;
540        let names = Java::find_names(code).expect("Cannot find names in test code");
541        assert_eq!(
542            names,
543            Names {
544                fully_qualified: DeclUse {
545                    declared_symbols: vec![],
546                    used_symbols: vec![]
547                },
548                simple: DeclUse {
549                    declared_symbols: vec![],
550                    used_symbols: vec![
551                        SimpleName {
552                            symbol: "i".to_string(),
553                            extent: optional_extent!(0:8-0:9),
554                        },
555                        SimpleName {
556                            symbol: "i".to_string(),
557                            extent: optional_extent!(0:19-0:20),
558                        },
559                        SimpleName {
560                            symbol: "i".to_string(),
561                            extent: optional_extent!(3:8-3:9),
562                        },
563                        SimpleName {
564                            symbol: "i".to_string(),
565                            extent: optional_extent!(6:10-6:11),
566                        },
567                        SimpleName {
568                            symbol: "SwitchException".to_string(),
569                            extent: optional_extent!(9:18-9:33),
570                        }
571                    ]
572                },
573                external_simple: DeclUse {
574                    declared_symbols: vec![],
575                    used_symbols: vec![
576                        SimpleName {
577                            symbol: "i".to_string(),
578                            extent: optional_extent!(0:8-0:9),
579                        },
580                        SimpleName {
581                            symbol: "i".to_string(),
582                            extent: optional_extent!(0:19-0:20),
583                        },
584                        SimpleName {
585                            symbol: "i".to_string(),
586                            extent: optional_extent!(3:8-3:9),
587                        },
588                        SimpleName {
589                            symbol: "i".to_string(),
590                            extent: optional_extent!(6:10-6:11),
591                        },
592                        SimpleName {
593                            symbol: "SwitchException".to_string(),
594                            extent: optional_extent!(9:18-9:33),
595                        }
596                    ]
597                }
598            }
599        );
600    }
601
602    #[test]
603    fn finds_names_in_lambda() {
604        let code = r#"(i) -> System.out.println(!(boolean)i);"#;
605        let names = Java::find_names(code).expect("Cannot find names in test code");
606        assert_eq!(
607            names,
608            Names {
609                fully_qualified: DeclUse {
610                    declared_symbols: vec![],
611                    used_symbols: vec![]
612                },
613                simple: DeclUse {
614                    declared_symbols: vec![SimpleName {
615                        symbol: "i".to_string(),
616                        extent: optional_extent!(0:1-0:2),
617                    }],
618                    used_symbols: vec![
619                        SimpleName {
620                            symbol: "System".to_string(),
621                            extent: optional_extent!(0:7-0:13),
622                        },
623                        SimpleName {
624                            symbol: "System".to_string(),
625                            extent: None
626                        },
627                        SimpleName {
628                            symbol: "out".to_string(),
629                            extent: optional_extent!(0:14-0:17),
630                        },
631                        SimpleName {
632                            symbol: "println".to_string(),
633                            extent: optional_extent!(0:18-0:25),
634                        },
635                        SimpleName {
636                            symbol: "boolean".to_string(),
637                            extent: optional_extent!(0:28-0:35),
638                        },
639                        SimpleName {
640                            symbol: "i".to_string(),
641                            extent: optional_extent!(0:36-0:37),
642                        }
643                    ]
644                },
645                external_simple: DeclUse {
646                    declared_symbols: vec![],
647                    used_symbols: vec![
648                        SimpleName {
649                            symbol: "println".to_string(),
650                            extent: optional_extent!(0:18-0:25),
651                        },
652                        SimpleName {
653                            symbol: "boolean".to_string(),
654                            extent: optional_extent!(0:28-0:35),
655                        }
656                    ]
657                }
658            }
659        );
660    }
661
662    #[test]
663    fn finds_names_in_try() {
664        let code = r#"try (Reader reader = new Reader(), marker = nextMarker()) {
665    reader.readUpTo(marker);
666} catch (ReadException e) {
667    log(e.message);
668    return false;
669} finally {
670    return true;
671}"#;
672
673        let names = Java::find_names(code).expect("Cannot find names in test code");
674        assert_eq!(
675            names,
676            Names {
677                fully_qualified: DeclUse {
678                    declared_symbols: vec![],
679                    used_symbols: vec![]
680                },
681                simple: DeclUse {
682                    declared_symbols: vec![
683                        SimpleName {
684                            symbol: "reader".to_string(),
685                            extent: optional_extent!(0:12-0:18),
686                        },
687                        SimpleName {
688                            symbol: "e".to_string(),
689                            extent: optional_extent!(2:23-2:24),
690                        }
691                    ],
692                    used_symbols: vec![
693                        SimpleName {
694                            symbol: "Reader".to_string(),
695                            extent: optional_extent!(0:5-0:11),
696                        },
697                        SimpleName {
698                            symbol: "marker".to_string(),
699                            extent: optional_extent!(0:35-0:41),
700                        },
701                        SimpleName {
702                            symbol: "reader".to_string(),
703                            extent: optional_extent!(1:4-1:10),
704                        },
705                        SimpleName {
706                            symbol: "readUpTo".to_string(),
707                            extent: optional_extent!(1:11-1:19),
708                        },
709                        SimpleName {
710                            symbol: "marker".to_string(),
711                            extent: optional_extent!(1:20-1:26),
712                        },
713                        SimpleName {
714                            symbol: "ReadException".to_string(),
715                            extent: optional_extent!(2:9-2:22),
716                        },
717                        SimpleName {
718                            symbol: "e".to_string(),
719                            extent: optional_extent!(3:8-3:9),
720                        },
721                        SimpleName {
722                            symbol: "message".to_string(),
723                            extent: optional_extent!(3:10-3:17),
724                        }
725                    ]
726                },
727                external_simple: DeclUse {
728                    declared_symbols: vec![],
729                    used_symbols: vec![
730                        SimpleName {
731                            symbol: "Reader".to_string(),
732                            extent: optional_extent!(0:5-0:11),
733                        },
734                        SimpleName {
735                            symbol: "marker".to_string(),
736                            extent: optional_extent!(0:35-0:41),
737                        },
738                        SimpleName {
739                            symbol: "readUpTo".to_string(),
740                            extent: optional_extent!(1:11-1:19),
741                        },
742                        SimpleName {
743                            symbol: "marker".to_string(),
744                            extent: optional_extent!(1:20-1:26),
745                        },
746                        SimpleName {
747                            symbol: "ReadException".to_string(),
748                            extent: optional_extent!(2:9-2:22),
749                        },
750                        SimpleName {
751                            symbol: "message".to_string(),
752                            extent: optional_extent!(3:10-3:17),
753                        }
754                    ]
755                }
756            }
757        );
758    }
759
760    #[test]
761    fn finds_name_in_annotation() {
762        let code = r#"class Dog extends Animal {
763            @Override
764            void eatSomething() {}
765            }"#;
766
767        let names = Java::find_names(code).expect("Cannot find names in test code");
768        assert_eq!(
769            names,
770            Names {
771                fully_qualified: DeclUse {
772                    declared_symbols: vec![],
773                    used_symbols: vec![]
774                },
775                simple: DeclUse {
776                    declared_symbols: vec![
777                        SimpleName {
778                            symbol: "Dog".to_string(),
779                            extent: optional_extent!(0:6-0:9),
780                        },
781                        SimpleName {
782                            symbol: "eatSomething".to_string(),
783                            extent: optional_extent!(2:17-2:29),
784                        }
785                    ],
786                    used_symbols: vec![
787                        SimpleName {
788                            symbol: "Animal".to_string(),
789                            extent: optional_extent!(0:18-0:24),
790                        },
791                        SimpleName {
792                            symbol: "Override".to_string(),
793                            extent: optional_extent!(1:13-1:21),
794                        },
795                    ]
796                },
797                external_simple: DeclUse {
798                    declared_symbols: vec![],
799                    used_symbols: vec![SimpleName {
800                        symbol: "Animal".to_string(),
801                        extent: optional_extent!(0:18-0:24),
802                    }]
803                }
804            },
805        );
806    }
807
808    #[test]
809    fn finds_name_in_annotation_with_parameters() {
810        let code = r#"class TestAnnotation2 {
811        @SuppressWarnings("unchecked")
812        public static void main(String args[]) {
813        }"#;
814
815        let names = Java::find_names(code).expect("Cannot find names in test code");
816        assert_eq!(
817            names,
818            Names {
819                fully_qualified: DeclUse {
820                    declared_symbols: vec![],
821                    used_symbols: vec![]
822                },
823                simple: DeclUse {
824                    declared_symbols: vec![
825                        SimpleName {
826                            symbol: "TestAnnotation2".to_string(),
827                            extent: optional_extent!(0:6-0:21),
828                        },
829                        SimpleName {
830                            symbol: "main".to_string(),
831                            extent: optional_extent!(2:27-2:31),
832                        },
833                        SimpleName {
834                            symbol: "args".to_string(),
835                            extent: optional_extent!(2:39-2:43),
836                        },
837                    ],
838                    used_symbols: vec![
839                        SimpleName {
840                            symbol: "SuppressWarnings".to_string(),
841                            extent: optional_extent!(1:9-1:25),
842                        },
843                        SimpleName {
844                            symbol: "String".to_string(),
845                            extent: optional_extent!(2:32-2:38),
846                        },
847                    ]
848                },
849                external_simple: DeclUse {
850                    declared_symbols: vec![],
851                    used_symbols: vec![]
852                }
853            },
854        );
855    }
856
857    #[test]
858    fn finds_name_in_annotation_with_key_value_parameters() {
859        let code = r#"class TestAnnotation2 {
860        @AnnotationName(element1 = "value1", element2 = "value2")
861        public static void main(String args[]) {
862        }"#;
863
864        let names = Java::find_names(code).expect("Cannot find names in test code");
865        assert_eq!(
866            names,
867            Names {
868                fully_qualified: DeclUse {
869                    declared_symbols: vec![],
870                    used_symbols: vec![]
871                },
872                simple: DeclUse {
873                    declared_symbols: vec![
874                        SimpleName {
875                            symbol: "TestAnnotation2".to_string(),
876                            extent: optional_extent!(0:6-0:21),
877                        },
878                        SimpleName {
879                            symbol: "main".to_string(),
880                            extent: optional_extent!(2:27-2:31),
881                        },
882                        SimpleName {
883                            symbol: "args".to_string(),
884                            extent: optional_extent!(2:39-2:43),
885                        },
886                    ],
887                    used_symbols: vec![
888                        SimpleName {
889                            symbol: "AnnotationName".to_string(),
890                            extent: optional_extent!(1:9-1:23),
891                        },
892                        SimpleName {
893                            symbol: "element1".to_string(),
894                            extent: optional_extent!(1:24-1:32),
895                        },
896                        SimpleName {
897                            symbol: "element2".to_string(),
898                            extent: optional_extent!(1:45-1:53),
899                        },
900                        SimpleName {
901                            symbol: "String".to_string(),
902                            extent: optional_extent!(2:32-2:38),
903                        },
904                    ]
905                },
906                external_simple: DeclUse {
907                    declared_symbols: vec![],
908                    used_symbols: vec![
909                        SimpleName {
910                            symbol: "AnnotationName".to_string(),
911                            extent: optional_extent!(1:9-1:23),
912                        },
913                        SimpleName {
914                            symbol: "element1".to_string(),
915                            extent: optional_extent!(1:24-1:32),
916                        },
917                        SimpleName {
918                            symbol: "element2".to_string(),
919                            extent: optional_extent!(1:45-1:53),
920                        },
921                    ]
922                }
923            },
924        );
925    }
926
927    #[test]
928    fn finds_names_in_enum_declaration() {
929        let code = r#"enum HotDrink implements Drink {
930    COFFEE(Ingredient.BEANS),
931    TEA(Ingredient.LEAVES);
932    private final Ingredient[] ingredients;
933    HotDrink(Ingredient... ingredients) {
934        for (var ingredient : ingredients)
935            assert ingredient instanceof AvailableIngredients;
936        this.ingredients = ingredients;
937    }
938}"#;
939
940        let names = Java::find_names(code).expect("Cannot find names in test code");
941        assert_eq!(
942            names,
943            Names {
944                fully_qualified: DeclUse {
945                    declared_symbols: vec![],
946                    used_symbols: vec![]
947                },
948                simple: DeclUse {
949                    declared_symbols: vec![
950                        SimpleName {
951                            symbol: "HotDrink".to_string(),
952                            extent: optional_extent!(0:5-0:13),
953                        },
954                        SimpleName {
955                            symbol: "COFFEE".to_string(),
956                            extent: optional_extent!(1:4-1:10),
957                        },
958                        SimpleName {
959                            symbol: "TEA".to_string(),
960                            extent: optional_extent!(2:4-2:7),
961                        },
962                        SimpleName {
963                            symbol: "ingredients".to_string(),
964                            extent: optional_extent!(3:31-3:42),
965                        },
966                        SimpleName {
967                            symbol: "HotDrink".to_string(),
968                            extent: optional_extent!(4:4-4:12),
969                        },
970                        SimpleName {
971                            symbol: "ingredients".to_string(),
972                            extent: optional_extent!(4:27-4:38),
973                        },
974                        SimpleName {
975                            symbol: "ingredient".to_string(),
976                            extent: optional_extent!(5:17-5:27),
977                        }
978                    ],
979                    used_symbols: vec![
980                        SimpleName {
981                            symbol: "Drink".to_string(),
982                            extent: optional_extent!(0:25-0:30),
983                        },
984                        SimpleName {
985                            symbol: "Ingredient".to_string(),
986                            extent: optional_extent!(1:11-1:21),
987                        },
988                        SimpleName {
989                            symbol: "BEANS".to_string(),
990                            extent: optional_extent!(1:22-1:27),
991                        },
992                        SimpleName {
993                            symbol: "Ingredient".to_string(),
994                            extent: optional_extent!(2:8-2:18),
995                        },
996                        SimpleName {
997                            symbol: "LEAVES".to_string(),
998                            extent: optional_extent!(2:19-2:25),
999                        },
1000                        SimpleName {
1001                            symbol: "Ingredient".to_string(),
1002                            extent: optional_extent!(3:18-3:28),
1003                        },
1004                        SimpleName {
1005                            symbol: "Ingredient".to_string(),
1006                            extent: optional_extent!(4:13-4:23),
1007                        },
1008                        SimpleName {
1009                            symbol: "var".to_string(),
1010                            extent: optional_extent!(5:13-5:16),
1011                        },
1012                        SimpleName {
1013                            symbol: "ingredients".to_string(),
1014                            extent: optional_extent!(5:30-5:41),
1015                        },
1016                        SimpleName {
1017                            symbol: "ingredient".to_string(),
1018                            extent: optional_extent!(6:19-6:29),
1019                        },
1020                        SimpleName {
1021                            symbol: "AvailableIngredients".to_string(),
1022                            extent: optional_extent!(6:41-6:61),
1023                        },
1024                        SimpleName {
1025                            symbol: "HotDrink".to_string(),
1026                            extent: None
1027                        },
1028                        SimpleName {
1029                            symbol: "ingredients".to_string(),
1030                            extent: optional_extent!(7:13-7:24),
1031                        },
1032                        SimpleName {
1033                            symbol: "ingredients".to_string(),
1034                            extent: optional_extent!(7:27-7:38),
1035                        }
1036                    ]
1037                },
1038                external_simple: DeclUse {
1039                    declared_symbols: vec![],
1040                    used_symbols: vec![
1041                        SimpleName {
1042                            symbol: "Drink".to_string(),
1043                            extent: optional_extent!(0:25-0:30),
1044                        },
1045                        SimpleName {
1046                            symbol: "Ingredient".to_string(),
1047                            extent: optional_extent!(1:11-1:21),
1048                        },
1049                        SimpleName {
1050                            symbol: "BEANS".to_string(),
1051                            extent: optional_extent!(1:22-1:27),
1052                        },
1053                        SimpleName {
1054                            symbol: "Ingredient".to_string(),
1055                            extent: optional_extent!(2:8-2:18),
1056                        },
1057                        SimpleName {
1058                            symbol: "LEAVES".to_string(),
1059                            extent: optional_extent!(2:19-2:25),
1060                        },
1061                        SimpleName {
1062                            symbol: "Ingredient".to_string(),
1063                            extent: optional_extent!(3:18-3:28),
1064                        },
1065                        SimpleName {
1066                            symbol: "Ingredient".to_string(),
1067                            extent: optional_extent!(4:13-4:23),
1068                        },
1069                        SimpleName {
1070                            symbol: "var".to_string(),
1071                            extent: optional_extent!(5:13-5:16),
1072                        },
1073                        SimpleName {
1074                            symbol: "AvailableIngredients".to_string(),
1075                            extent: optional_extent!(6:41-6:61),
1076                        }
1077                    ]
1078                }
1079            }
1080        );
1081    }
1082
1083    #[test]
1084    fn finds_names_in_array_initializer() {
1085        let code = r#"String[] c = {"a", "b"};"#;
1086
1087        let names = Java::find_names(code).expect("Cannot find names in test code");
1088        assert_eq!(
1089            names,
1090            Names {
1091                fully_qualified: DeclUse {
1092                    declared_symbols: vec![],
1093                    used_symbols: vec![]
1094                },
1095                simple: DeclUse {
1096                    declared_symbols: vec![SimpleName {
1097                        symbol: "c".to_string(),
1098                        extent: optional_extent!(0:9-0:10),
1099                    },],
1100                    used_symbols: vec![SimpleName {
1101                        symbol: "String".to_string(),
1102                        extent: optional_extent!(0:0-0:6),
1103                    },]
1104                },
1105                external_simple: DeclUse {
1106                    declared_symbols: vec![],
1107                    used_symbols: vec![]
1108                }
1109            }
1110        );
1111    }
1112
1113    #[test]
1114    fn finds_names_in_double_array_initializer() {
1115        let code = r#"int[][] matrix = {{1,2}, {3,4}};"#;
1116
1117        let names = Java::find_names(code).expect("Cannot find names in test code");
1118        assert_eq!(
1119            names,
1120            Names {
1121                fully_qualified: DeclUse {
1122                    declared_symbols: vec![],
1123                    used_symbols: vec![]
1124                },
1125                simple: DeclUse {
1126                    declared_symbols: vec![SimpleName {
1127                        symbol: "matrix".to_string(),
1128                        extent: optional_extent!(0:8-0:14),
1129                    },],
1130                    used_symbols: vec![SimpleName {
1131                        symbol: "int".to_string(),
1132                        extent: optional_extent!(0:0-0:3),
1133                    },]
1134                },
1135                external_simple: DeclUse {
1136                    declared_symbols: vec![],
1137                    used_symbols: vec![]
1138                }
1139            }
1140        );
1141    }
1142
1143    #[test]
1144    fn finds_names_in_field_declaration() {
1145        let code = r#"public class Customer {
1146            String customerType = "OnlineCustomer";
1147        }"#;
1148
1149        let names = Java::find_names(code).expect("Cannot find names in test code");
1150        assert_eq!(
1151            names,
1152            Names {
1153                fully_qualified: DeclUse {
1154                    declared_symbols: vec![],
1155                    used_symbols: vec![]
1156                },
1157                simple: DeclUse {
1158                    declared_symbols: vec![
1159                        SimpleName {
1160                            symbol: "Customer".to_string(),
1161                            extent: optional_extent!(0:13-0:21),
1162                        },
1163                        SimpleName {
1164                            symbol: "customerType".to_string(),
1165                            extent: optional_extent!(1:19-1:31),
1166                        },
1167                    ],
1168                    used_symbols: vec![SimpleName {
1169                        symbol: "String".to_string(),
1170                        extent: optional_extent!(1:12-1:18),
1171                    },]
1172                },
1173                external_simple: DeclUse {
1174                    declared_symbols: vec![],
1175                    used_symbols: vec![]
1176                }
1177            }
1178        );
1179    }
1180
1181    #[test]
1182    fn finds_names_in_record_declaration() {
1183        let code = r#"record Rectangle(float length, float width) { }"#;
1184
1185        let names = Java::find_names(code).expect("Cannot find names in test code");
1186        assert_eq!(
1187            names,
1188            Names {
1189                fully_qualified: DeclUse {
1190                    declared_symbols: vec![],
1191                    used_symbols: vec![]
1192                },
1193                simple: DeclUse {
1194                    declared_symbols: vec![
1195                        SimpleName {
1196                            symbol: "Rectangle".to_string(),
1197                            extent: optional_extent!(0:7-0:16),
1198                        },
1199                        SimpleName {
1200                            symbol: "length".to_string(),
1201                            extent: optional_extent!(0:23-0:29),
1202                        },
1203                        SimpleName {
1204                            symbol: "width".to_string(),
1205                            extent: optional_extent!(0:37-0:42),
1206                        },
1207                    ],
1208                    used_symbols: vec![
1209                        SimpleName {
1210                            symbol: "float".to_string(),
1211                            extent: optional_extent!(0:17-0:22),
1212                        },
1213                        SimpleName {
1214                            symbol: "float".to_string(),
1215                            extent: optional_extent!(0:31-0:36),
1216                        },
1217                    ]
1218                },
1219                external_simple: DeclUse {
1220                    declared_symbols: vec![],
1221                    used_symbols: vec![],
1222                }
1223            }
1224        );
1225    }
1226
1227    #[test]
1228    fn finds_names_in_interface_declaration() {
1229        let code = r#"interface MyInterface
1230        {
1231           int constantVar = 5;
1232           public int method1();
1233           public void method2();
1234        }"#;
1235
1236        let names = Java::find_names(code).expect("Cannot find names in test code");
1237        assert_eq!(
1238            names,
1239            Names {
1240                fully_qualified: DeclUse {
1241                    declared_symbols: vec![],
1242                    used_symbols: vec![]
1243                },
1244                simple: DeclUse {
1245                    declared_symbols: vec![
1246                        SimpleName {
1247                            symbol: "MyInterface".to_string(),
1248                            extent: optional_extent!(0:10-0:21),
1249                        },
1250                        SimpleName {
1251                            symbol: "constantVar".to_string(),
1252                            extent: optional_extent!(2:15-2:26),
1253                        },
1254                        SimpleName {
1255                            symbol: "method1".to_string(),
1256                            extent: optional_extent!(3:22-3:29),
1257                        },
1258                        SimpleName {
1259                            symbol: "method2".to_string(),
1260                            extent: optional_extent!(4:23-4:30),
1261                        },
1262                    ],
1263                    used_symbols: vec![
1264                        SimpleName {
1265                            symbol: "int".to_string(),
1266                            extent: optional_extent!(2:11-2:14),
1267                        },
1268                        SimpleName {
1269                            symbol: "int".to_string(),
1270                            extent: optional_extent!(3:18-3:21),
1271                        },
1272                    ]
1273                },
1274                external_simple: DeclUse {
1275                    declared_symbols: vec![],
1276                    used_symbols: vec![]
1277                }
1278            }
1279        );
1280    }
1281
1282    #[test]
1283    fn finds_names_in_static_initalizer() {
1284        let code = r#"public class Demo
1285        {
1286            static double percentage;
1287            static
1288            {
1289                percentage = 44.6;
1290            }
1291        }"#;
1292
1293        let names = Java::find_names(code).expect("Cannot find names in test code");
1294        assert_eq!(
1295            names,
1296            Names {
1297                fully_qualified: DeclUse {
1298                    declared_symbols: vec![],
1299                    used_symbols: vec![]
1300                },
1301                simple: DeclUse {
1302                    declared_symbols: vec![
1303                        SimpleName {
1304                            symbol: "Demo".to_string(),
1305                            extent: optional_extent!(0:13-0:17),
1306                        },
1307                        SimpleName {
1308                            symbol: "percentage".to_string(),
1309                            extent: optional_extent!(2:26-2:36),
1310                        },
1311                    ],
1312                    used_symbols: vec![
1313                        SimpleName {
1314                            symbol: "double".to_string(),
1315                            extent: optional_extent!(2:19-2:25),
1316                        },
1317                        SimpleName {
1318                            symbol: "percentage".to_string(),
1319                            extent: optional_extent!(5:16-5:26),
1320                        },
1321                    ]
1322                },
1323                external_simple: DeclUse {
1324                    declared_symbols: vec![],
1325                    used_symbols: vec![]
1326                }
1327            }
1328        );
1329    }
1330
1331    #[test]
1332    fn finds_names_in_annotation_type_declaration() {
1333        let code = r#"public @interface RequestForEnhancement {
1334            int    id();
1335            String synopsis();
1336        }"#;
1337
1338        let names = Java::find_names(code).expect("Cannot find names in test code");
1339        assert_eq!(
1340            names,
1341            Names {
1342                fully_qualified: DeclUse {
1343                    declared_symbols: vec![],
1344                    used_symbols: vec![]
1345                },
1346                simple: DeclUse {
1347                    declared_symbols: vec![
1348                        SimpleName {
1349                            symbol: "RequestForEnhancement".to_string(),
1350                            extent: optional_extent!(0:18-0:39),
1351                        },
1352                        SimpleName {
1353                            symbol: "id".to_string(),
1354                            extent: optional_extent!(1:19-1:21),
1355                        },
1356                        SimpleName {
1357                            symbol: "synopsis".to_string(),
1358                            extent: optional_extent!(2:19-2:27),
1359                        },
1360                    ],
1361                    used_symbols: vec![
1362                        SimpleName {
1363                            symbol: "int".to_string(),
1364                            extent: optional_extent!(1:12-1:15),
1365                        },
1366                        SimpleName {
1367                            symbol: "String".to_string(),
1368                            extent: optional_extent!(2:12-2:18),
1369                        },
1370                    ]
1371                },
1372                external_simple: DeclUse {
1373                    declared_symbols: vec![],
1374                    used_symbols: vec![]
1375                }
1376            }
1377        );
1378    }
1379
1380    #[test]
1381    fn finds_names_in_generic_type() {
1382        let code = r#"Box<Integer> integerBox = new Box<>();"#;
1383
1384        let names = Java::find_names(code).expect("Cannot find names in test code");
1385        assert_eq!(
1386            names,
1387            Names {
1388                fully_qualified: DeclUse {
1389                    declared_symbols: vec![],
1390                    used_symbols: vec![]
1391                },
1392                simple: DeclUse {
1393                    declared_symbols: vec![SimpleName {
1394                        symbol: "integerBox".to_string(),
1395                        extent: optional_extent!(0:13-0:23),
1396                    },],
1397                    used_symbols: vec![
1398                        SimpleName {
1399                            symbol: "Box".to_string(),
1400                            extent: optional_extent!(0:0-0:3),
1401                        },
1402                        SimpleName {
1403                            symbol: "Integer".to_string(),
1404                            extent: optional_extent!(0:4-0:11),
1405                        },
1406                        SimpleName {
1407                            symbol: "Box".to_string(),
1408                            extent: optional_extent!(0:30-0:33),
1409                        },
1410                    ]
1411                },
1412                external_simple: DeclUse {
1413                    declared_symbols: vec![],
1414                    used_symbols: vec![
1415                        SimpleName {
1416                            symbol: "Box".to_string(),
1417                            extent: optional_extent!(0:0-0:3),
1418                        },
1419                        SimpleName {
1420                            symbol: "Box".to_string(),
1421                            extent: optional_extent!(0:30-0:33),
1422                        },
1423                    ]
1424                }
1425            }
1426        );
1427    }
1428
1429    #[test]
1430    fn finds_names_in_for_statement() {
1431        let code = r#"for(int i=1;i<=10;i++) {}"#;
1432
1433        let names = Java::find_names(code).expect("Cannot find names in test code");
1434        assert_eq!(
1435            names,
1436            Names {
1437                fully_qualified: DeclUse {
1438                    declared_symbols: vec![],
1439                    used_symbols: vec![]
1440                },
1441                simple: DeclUse {
1442                    declared_symbols: vec![SimpleName {
1443                        symbol: "i".to_string(),
1444                        extent: optional_extent!(0:8-0:9),
1445                    }],
1446                    used_symbols: vec![
1447                        SimpleName {
1448                            symbol: "int".to_string(),
1449                            extent: optional_extent!(0:4-0:7),
1450                        },
1451                        SimpleName {
1452                            symbol: "i".to_string(),
1453                            extent: optional_extent!(0:12-0:13),
1454                        },
1455                        SimpleName {
1456                            symbol: "i".to_string(),
1457                            extent: optional_extent!(0:18-0:19),
1458                        },
1459                    ]
1460                },
1461                external_simple: DeclUse {
1462                    declared_symbols: vec![],
1463                    used_symbols: vec![]
1464                }
1465            }
1466        );
1467    }
1468
1469    #[test]
1470    fn finds_names_in_synchronized_statement() {
1471        let code = r#"public class MyCounter {
1472            public void increment(int value) {
1473            synchronized(this) {
1474                this.count += value;
1475            }
1476            }
1477        }"#;
1478
1479        let names = Java::find_names(code).expect("Cannot find names in test code");
1480        assert_eq!(
1481            names,
1482            Names {
1483                fully_qualified: DeclUse {
1484                    declared_symbols: vec![],
1485                    used_symbols: vec![]
1486                },
1487                simple: DeclUse {
1488                    declared_symbols: vec![
1489                        SimpleName {
1490                            symbol: "MyCounter".to_string(),
1491                            extent: optional_extent!(0:13-0:22),
1492                        },
1493                        SimpleName {
1494                            symbol: "increment".to_string(),
1495                            extent: optional_extent!(1:24-1:33),
1496                        },
1497                        SimpleName {
1498                            symbol: "value".to_string(),
1499                            extent: optional_extent!(1:38-1:43),
1500                        },
1501                    ],
1502                    used_symbols: vec![
1503                        SimpleName {
1504                            symbol: "int".to_string(),
1505                            extent: optional_extent!(1:34-1:37),
1506                        },
1507                        SimpleName {
1508                            symbol: "MyCounter".to_string(),
1509                            extent: None,
1510                        },
1511                        SimpleName {
1512                            symbol: "count".to_string(),
1513                            extent: optional_extent!(3:21-3:26),
1514                        },
1515                        SimpleName {
1516                            symbol: "value".to_string(),
1517                            extent: optional_extent!(3:30-3:35),
1518                        },
1519                    ]
1520                },
1521                external_simple: DeclUse {
1522                    declared_symbols: vec![],
1523                    used_symbols: vec![]
1524                }
1525            }
1526        );
1527    }
1528
1529    #[test]
1530    fn finds_names_in_unqualified_object_creation() {
1531        let code = r#"public class MyCounter {
1532            public void increment(int value) {
1533            synchronized(this) {
1534                this.count += value;
1535            }
1536            }
1537        }"#;
1538
1539        let names = Java::find_names(code).expect("Cannot find names in test code");
1540        assert_eq!(
1541            names,
1542            Names {
1543                fully_qualified: DeclUse {
1544                    declared_symbols: vec![],
1545                    used_symbols: vec![]
1546                },
1547                simple: DeclUse {
1548                    declared_symbols: vec![
1549                        SimpleName {
1550                            symbol: "MyCounter".to_string(),
1551                            extent: optional_extent!(0:13-0:22),
1552                        },
1553                        SimpleName {
1554                            symbol: "increment".to_string(),
1555                            extent: optional_extent!(1:24-1:33),
1556                        },
1557                        SimpleName {
1558                            symbol: "value".to_string(),
1559                            extent: optional_extent!(1:38-1:43),
1560                        },
1561                    ],
1562                    used_symbols: vec![
1563                        SimpleName {
1564                            symbol: "int".to_string(),
1565                            extent: optional_extent!(1:34-1:37),
1566                        },
1567                        SimpleName {
1568                            symbol: "MyCounter".to_string(),
1569                            extent: None,
1570                        },
1571                        SimpleName {
1572                            symbol: "count".to_string(),
1573                            extent: optional_extent!(3:21-3:26),
1574                        },
1575                        SimpleName {
1576                            symbol: "value".to_string(),
1577                            extent: optional_extent!(3:30-3:35),
1578                        },
1579                    ]
1580                },
1581                external_simple: DeclUse {
1582                    declared_symbols: vec![],
1583                    used_symbols: vec![]
1584                }
1585            }
1586        );
1587    }
1588
1589    #[test]
1590    fn finds_names_in_labeled_statement() {
1591        let code = r#"int a = 1;
1592                            block1: {
1593                                if (a < 0) {
1594                                break block1;
1595                                }
1596                            }"#;
1597
1598        let names = Java::find_names(code).expect("Cannot find names in test code");
1599        assert_eq!(
1600            names,
1601            Names {
1602                fully_qualified: DeclUse {
1603                    declared_symbols: vec![],
1604                    used_symbols: vec![]
1605                },
1606                simple: DeclUse {
1607                    declared_symbols: vec![SimpleName {
1608                        symbol: "a".to_string(),
1609                        extent: optional_extent!(0:4-0:5),
1610                    },],
1611                    used_symbols: vec![
1612                        SimpleName {
1613                            symbol: "int".to_string(),
1614                            extent: optional_extent!(0:0-0:3),
1615                        },
1616                        SimpleName {
1617                            symbol: "block1".to_string(),
1618                            extent: optional_extent!(1:28-1:34),
1619                        },
1620                        SimpleName {
1621                            symbol: "a".to_string(),
1622                            extent: optional_extent!(2:36-2:37),
1623                        },
1624                    ]
1625                },
1626                external_simple: DeclUse {
1627                    declared_symbols: vec![],
1628                    used_symbols: vec![SimpleName {
1629                        symbol: "block1".to_string(),
1630                        extent: optional_extent!(1:28-1:34),
1631                    },]
1632                }
1633            }
1634        );
1635    }
1636
1637    //TODO: double check the behaviour of System.out method
1638    #[test]
1639    fn finds_names_in_method_reference() {
1640        let code = r#"list.forEach(System.out::println);"#;
1641
1642        let names = Java::find_names(code).expect("Cannot find names in test code");
1643        assert_eq!(
1644            names,
1645            Names {
1646                fully_qualified: DeclUse {
1647                    declared_symbols: vec![],
1648                    used_symbols: vec![]
1649                },
1650                simple: DeclUse {
1651                    declared_symbols: vec![],
1652                    used_symbols: vec![
1653                        SimpleName {
1654                            symbol: "list".to_string(),
1655                            extent: optional_extent!(0:0-0:4),
1656                        },
1657                        SimpleName {
1658                            symbol: "forEach".to_string(),
1659                            extent: optional_extent!(0:5-0:12),
1660                        },
1661                        SimpleName {
1662                            symbol: "System".to_string(),
1663                            extent: optional_extent!(0:13-0:19),
1664                        },
1665                        SimpleName {
1666                            symbol: "System".to_string(),
1667                            extent: None,
1668                        },
1669                        SimpleName {
1670                            symbol: "out".to_string(),
1671                            extent: optional_extent!(0:20-0:23),
1672                        },
1673                        SimpleName {
1674                            symbol: "println".to_string(),
1675                            extent: optional_extent!(0:25-0:32),
1676                        },
1677                    ]
1678                },
1679                external_simple: DeclUse {
1680                    declared_symbols: vec![],
1681                    used_symbols: vec![
1682                        SimpleName {
1683                            symbol: "list".to_string(),
1684                            extent: optional_extent!(0:0-0:4),
1685                        },
1686                        SimpleName {
1687                            symbol: "forEach".to_string(),
1688                            extent: optional_extent!(0:5-0:12),
1689                        },
1690                        SimpleName {
1691                            symbol: "println".to_string(),
1692                            extent: optional_extent!(0:25-0:32),
1693                        },
1694                    ]
1695                }
1696            }
1697        );
1698    }
1699
1700    #[test]
1701    fn finds_names_in_method_reference_2() {
1702        let code = r#"ComparisonProvider comparator = new ComparisonProvider();
1703                            Collections.sort(personList, comparator::compareByName);"#;
1704
1705        let names = Java::find_names(code).expect("Cannot find names in test code");
1706        assert_eq!(
1707            names,
1708            Names {
1709                fully_qualified: DeclUse {
1710                    declared_symbols: vec![],
1711                    used_symbols: vec![]
1712                },
1713                simple: DeclUse {
1714                    declared_symbols: vec![SimpleName {
1715                        symbol: "comparator".to_string(),
1716                        extent: optional_extent!(0:19-0:29),
1717                    },],
1718                    used_symbols: vec![
1719                        SimpleName {
1720                            symbol: "ComparisonProvider".to_string(),
1721                            extent: optional_extent!(0:0-0:18),
1722                        },
1723                        SimpleName {
1724                            symbol: "ComparisonProvider".to_string(),
1725                            extent: optional_extent!(0:36-0:54),
1726                        },
1727                        SimpleName {
1728                            symbol: "Collections".to_string(),
1729                            extent: optional_extent!(1:28-1:39),
1730                        },
1731                        SimpleName {
1732                            symbol: "sort".to_string(),
1733                            extent: optional_extent!(1:40-1:44),
1734                        },
1735                        SimpleName {
1736                            symbol: "personList".to_string(),
1737                            extent: optional_extent!(1:45-1:55),
1738                        },
1739                        SimpleName {
1740                            symbol: "comparator".to_string(),
1741                            extent: optional_extent!(1:57-1:67),
1742                        },
1743                        SimpleName {
1744                            symbol: "compareByName".to_string(),
1745                            extent: optional_extent!(1:69-1:82),
1746                        },
1747                    ]
1748                },
1749                external_simple: DeclUse {
1750                    declared_symbols: vec![],
1751                    used_symbols: vec![
1752                        SimpleName {
1753                            symbol: "ComparisonProvider".to_string(),
1754                            extent: optional_extent!(0:0-0:18),
1755                        },
1756                        SimpleName {
1757                            symbol: "ComparisonProvider".to_string(),
1758                            extent: optional_extent!(0:36-0:54),
1759                        },
1760                        SimpleName {
1761                            symbol: "Collections".to_string(),
1762                            extent: optional_extent!(1:28-1:39),
1763                        },
1764                        SimpleName {
1765                            symbol: "sort".to_string(),
1766                            extent: optional_extent!(1:40-1:44),
1767                        },
1768                        SimpleName {
1769                            symbol: "personList".to_string(),
1770                            extent: optional_extent!(1:45-1:55),
1771                        },
1772                        SimpleName {
1773                            symbol: "compareByName".to_string(),
1774                            extent: optional_extent!(1:69-1:82),
1775                        },
1776                    ]
1777                }
1778            }
1779        );
1780    }
1781
1782    #[test]
1783    fn finds_names_in_array_creation_expression() {
1784        let code = r#"int[] myArray = new int[10];"#;
1785
1786        let names = Java::find_names(code).expect("Cannot find names in test code");
1787        assert_eq!(
1788            names,
1789            Names {
1790                fully_qualified: DeclUse {
1791                    declared_symbols: vec![],
1792                    used_symbols: vec![]
1793                },
1794                simple: DeclUse {
1795                    declared_symbols: vec![SimpleName {
1796                        symbol: "myArray".to_string(),
1797                        extent: optional_extent!(0:6-0:13),
1798                    },],
1799                    used_symbols: vec![
1800                        SimpleName {
1801                            symbol: "int".to_string(),
1802                            extent: optional_extent!(0:0-0:3),
1803                        },
1804                        SimpleName {
1805                            symbol: "int".to_string(),
1806                            extent: optional_extent!(0:20-0:23),
1807                        },
1808                    ]
1809                },
1810                external_simple: DeclUse {
1811                    declared_symbols: vec![],
1812                    used_symbols: vec![]
1813                }
1814            }
1815        );
1816    }
1817
1818    #[test]
1819    fn finds_names_in_module_declaration() {
1820        let code = r#"module demo.hello_modules {
1821            requires java.base;
1822            exports demo.modules;
1823        }"#;
1824
1825        let names = Java::find_names(code).expect("Cannot find names in test code");
1826        assert_eq!(
1827            names,
1828            Names {
1829                fully_qualified: DeclUse {
1830                    declared_symbols: vec![FullyQualifiedName {
1831                        source: sym!(java),
1832                        symbol: sym!(base),
1833                        extent: extent!(1:21-1:30),
1834                    },],
1835                    used_symbols: vec![FullyQualifiedName {
1836                        source: sym!(demo),
1837                        symbol: sym!(modules),
1838                        extent: extent!(2:20-2:32),
1839                    },]
1840                },
1841                simple: DeclUse {
1842                    declared_symbols: vec![
1843                        SimpleName {
1844                            symbol: "demo".to_string(),
1845                            extent: optional_extent!(0:7-0:11),
1846                        },
1847                        SimpleName {
1848                            symbol: "hello_modules".to_string(),
1849                            extent: optional_extent!(0:12-0:25),
1850                        },
1851                        SimpleName {
1852                            symbol: "java".to_string(),
1853                            extent: optional_extent!(1:21-1:25),
1854                        },
1855                        SimpleName {
1856                            symbol: "base".to_string(),
1857                            extent: optional_extent!(1:26-1:30),
1858                        },
1859                    ],
1860                    used_symbols: vec![
1861                        SimpleName {
1862                            symbol: "demo".to_string(),
1863                            extent: optional_extent!(2:20-2:24),
1864                        },
1865                        SimpleName {
1866                            symbol: "modules".to_string(),
1867                            extent: optional_extent!(2:25-2:32),
1868                        },
1869                    ]
1870                },
1871                external_simple: DeclUse {
1872                    declared_symbols: vec![
1873                        SimpleName {
1874                            symbol: "java".to_string(),
1875                            extent: optional_extent!(1:21-1:25),
1876                        },
1877                        SimpleName {
1878                            symbol: "base".to_string(),
1879                            extent: optional_extent!(1:26-1:30),
1880                        },
1881                    ],
1882                    used_symbols: vec![
1883                        SimpleName {
1884                            symbol: "demo".to_string(),
1885                            extent: optional_extent!(2:20-2:24),
1886                        },
1887                        SimpleName {
1888                            symbol: "modules".to_string(),
1889                            extent: optional_extent!(2:25-2:32),
1890                        },
1891                    ]
1892                }
1893            }
1894        );
1895    }
1896
1897    #[test]
1898    fn finds_names_in_package_declaration() {
1899        let code = r#"package graphics;"#;
1900
1901        let names = Java::find_names(code).expect("Cannot find names in test code");
1902        assert_eq!(
1903            names,
1904            Names {
1905                fully_qualified: DeclUse {
1906                    declared_symbols: vec![],
1907                    used_symbols: vec![]
1908                },
1909                simple: DeclUse {
1910                    declared_symbols: vec![],
1911                    used_symbols: vec![]
1912                },
1913                external_simple: DeclUse {
1914                    declared_symbols: vec![],
1915                    used_symbols: vec![]
1916                }
1917            }
1918        );
1919    }
1920
1921    #[test]
1922    fn find_names_in_for_variable_assignment() {
1923        let code = r#"for (i = x1Index; i < x2Index; ++i) {}"#;
1924
1925        let names = Java::find_names(code).expect("Cannot find names in test code");
1926        assert_eq!(
1927            names,
1928            Names {
1929                fully_qualified: DeclUse {
1930                    declared_symbols: vec![],
1931                    used_symbols: vec![]
1932                },
1933                simple: DeclUse {
1934                    declared_symbols: vec![],
1935                    used_symbols: vec![
1936                        SimpleName {
1937                            symbol: "i".to_string(),
1938                            extent: optional_extent!(0:5-0:6),
1939                        },
1940                        SimpleName {
1941                            symbol: "x1Index".to_string(),
1942                            extent: optional_extent!(0:9-0:16),
1943                        },
1944                        SimpleName {
1945                            symbol: "i".to_string(),
1946                            extent: optional_extent!(0:18-0:19),
1947                        },
1948                        SimpleName {
1949                            symbol: "x2Index".to_string(),
1950                            extent: optional_extent!(0:22-0:29),
1951                        },
1952                        SimpleName {
1953                            symbol: "i".to_string(),
1954                            extent: optional_extent!(0:33-0:34),
1955                        },
1956                    ]
1957                },
1958                external_simple: DeclUse {
1959                    declared_symbols: vec![],
1960                    used_symbols: vec![
1961                        SimpleName {
1962                            symbol: "i".to_string(),
1963                            extent: optional_extent!(0:5-0:6),
1964                        },
1965                        SimpleName {
1966                            symbol: "x1Index".to_string(),
1967                            extent: optional_extent!(0:9-0:16),
1968                        },
1969                        SimpleName {
1970                            symbol: "i".to_string(),
1971                            extent: optional_extent!(0:18-0:19),
1972                        },
1973                        SimpleName {
1974                            symbol: "x2Index".to_string(),
1975                            extent: optional_extent!(0:22-0:29),
1976                        },
1977                        SimpleName {
1978                            symbol: "i".to_string(),
1979                            extent: optional_extent!(0:33-0:34),
1980                        },
1981                    ]
1982                }
1983            }
1984        );
1985    }
1986
1987    #[test]
1988    fn finds_names_in_annotation_argument_list() {
1989        let code = r#"@RunWith(RobolectricTestRunner.class)
1990        public class FileReceiverActivityTest {
1991        }"#;
1992
1993        let names = Java::find_names(code).expect("Cannot find names in test code");
1994        assert_eq!(
1995            names,
1996            Names {
1997                fully_qualified: DeclUse {
1998                    declared_symbols: vec![],
1999                    used_symbols: vec![]
2000                },
2001                simple: DeclUse {
2002                    declared_symbols: vec![SimpleName {
2003                        symbol: "FileReceiverActivityTest".to_string(),
2004                        extent: optional_extent!(1:21-1:45),
2005                    },],
2006                    used_symbols: vec![
2007                        SimpleName {
2008                            symbol: "RunWith".to_string(),
2009                            extent: optional_extent!(0:1-0:8),
2010                        },
2011                        SimpleName {
2012                            symbol: "RobolectricTestRunner".to_string(),
2013                            extent: optional_extent!(0:9-0:30),
2014                        },
2015                    ]
2016                },
2017                external_simple: DeclUse {
2018                    declared_symbols: vec![],
2019                    used_symbols: vec![
2020                        SimpleName {
2021                            symbol: "RunWith".to_string(),
2022                            extent: optional_extent!(0:1-0:8),
2023                        },
2024                        SimpleName {
2025                            symbol: "RobolectricTestRunner".to_string(),
2026                            extent: optional_extent!(0:9-0:30),
2027                        },
2028                    ]
2029                }
2030            }
2031        );
2032    }
2033
2034    #[test]
2035    fn finds_names_in_method_invocation_with_type_arguments() {
2036        let code =
2037            r#"Stream.Builder<RECIPE_ID> recipeIdsEnumBuilder = Stream.<RECIPE_ID>builder();"#;
2038
2039        let names = Java::find_names(code).expect("Cannot find names in test code");
2040        assert_eq!(
2041            names,
2042            Names {
2043                fully_qualified: DeclUse {
2044                    declared_symbols: vec![],
2045                    used_symbols: vec![]
2046                },
2047                simple: DeclUse {
2048                    declared_symbols: vec![SimpleName {
2049                        symbol: "recipeIdsEnumBuilder".to_string(),
2050                        extent: optional_extent!(0:26-0:46),
2051                    },],
2052                    used_symbols: vec![
2053                        SimpleName {
2054                            symbol: "Stream".to_string(),
2055                            extent: optional_extent!(0:0-0:6),
2056                        },
2057                        SimpleName {
2058                            symbol: "Builder".to_string(),
2059                            extent: optional_extent!(0:7-0:14),
2060                        },
2061                        SimpleName {
2062                            symbol: "RECIPE_ID".to_string(),
2063                            extent: optional_extent!(0:15-0:24),
2064                        },
2065                        SimpleName {
2066                            symbol: "Stream".to_string(),
2067                            extent: optional_extent!(0:49-0:55),
2068                        },
2069                        SimpleName {
2070                            symbol: "RECIPE_ID".to_string(),
2071                            extent: optional_extent!(0:57-0:66),
2072                        },
2073                        SimpleName {
2074                            symbol: "builder".to_string(),
2075                            extent: optional_extent!(0:67-0:74),
2076                        },
2077                    ]
2078                },
2079                external_simple: DeclUse {
2080                    declared_symbols: vec![],
2081                    used_symbols: vec![
2082                        SimpleName {
2083                            symbol: "Stream".to_string(),
2084                            extent: optional_extent!(0:0-0:6),
2085                        },
2086                        SimpleName {
2087                            symbol: "Builder".to_string(),
2088                            extent: optional_extent!(0:7-0:14),
2089                        },
2090                        SimpleName {
2091                            symbol: "RECIPE_ID".to_string(),
2092                            extent: optional_extent!(0:15-0:24),
2093                        },
2094                        SimpleName {
2095                            symbol: "Stream".to_string(),
2096                            extent: optional_extent!(0:49-0:55),
2097                        },
2098                        SimpleName {
2099                            symbol: "RECIPE_ID".to_string(),
2100                            extent: optional_extent!(0:57-0:66),
2101                        },
2102                        SimpleName {
2103                            symbol: "builder".to_string(),
2104                            extent: optional_extent!(0:67-0:74),
2105                        },
2106                    ]
2107                }
2108            }
2109        );
2110    }
2111
2112    #[test]
2113    fn finds_names_in_for_with_expression() {
2114        let code = r#"
2115            private static void missedForBlock() {
2116                for (nop(); f(); nop()) {
2117                    nop();
2118                }
2119            }"#;
2120
2121        let names = Java::find_names(code).expect("Cannot find names in test code");
2122        assert_eq!(
2123            names,
2124            Names {
2125                fully_qualified: DeclUse {
2126                    declared_symbols: vec![],
2127                    used_symbols: vec![]
2128                },
2129                simple: DeclUse {
2130                    declared_symbols: vec![SimpleName {
2131                        symbol: "missedForBlock".to_string(),
2132                        extent: optional_extent!(1:32-1:46),
2133                    },],
2134                    used_symbols: vec![]
2135                },
2136                external_simple: DeclUse {
2137                    declared_symbols: vec![],
2138                    used_symbols: vec![]
2139                }
2140            }
2141        );
2142    }
2143
2144    #[test]
2145    fn finds_names_in_requires_with_modifiers() {
2146        let code = r#"
2147            module r2dbc.mariadb {
2148                requires transitive io.netty.buffer;
2149            }"#;
2150
2151        let names = Java::find_names(code).expect("Cannot find names in test code");
2152        assert_eq!(
2153            names,
2154            Names {
2155                fully_qualified: DeclUse {
2156                    declared_symbols: vec![FullyQualifiedName {
2157                        source: vec!["io".to_string(), "netty".to_string()],
2158                        symbol: sym!(buffer),
2159                        extent: extent!(2:36-2:51),
2160                    },],
2161                    used_symbols: vec![]
2162                },
2163                simple: DeclUse {
2164                    declared_symbols: vec![
2165                        SimpleName {
2166                            symbol: "r2dbc".to_string(),
2167                            extent: optional_extent!(1:19-1:24),
2168                        },
2169                        SimpleName {
2170                            symbol: "mariadb".to_string(),
2171                            extent: optional_extent!(1:25-1:32),
2172                        },
2173                        SimpleName {
2174                            symbol: "io".to_string(),
2175                            extent: optional_extent!(2:36-2:38),
2176                        },
2177                        SimpleName {
2178                            symbol: "netty".to_string(),
2179                            extent: optional_extent!(2:39-2:44),
2180                        },
2181                        SimpleName {
2182                            symbol: "buffer".to_string(),
2183                            extent: optional_extent!(2:45-2:51),
2184                        },
2185                    ],
2186                    used_symbols: vec![]
2187                },
2188                external_simple: DeclUse {
2189                    declared_symbols: vec![
2190                        SimpleName {
2191                            symbol: "io".to_string(),
2192                            extent: optional_extent!(2:36-2:38),
2193                        },
2194                        SimpleName {
2195                            symbol: "netty".to_string(),
2196                            extent: optional_extent!(2:39-2:44),
2197                        },
2198                        SimpleName {
2199                            symbol: "buffer".to_string(),
2200                            extent: optional_extent!(2:45-2:51),
2201                        },
2202                    ],
2203                    used_symbols: vec![]
2204                }
2205            }
2206        );
2207    }
2208
2209    #[test]
2210    fn finds_names_in_annotation_module() {
2211        let code = r#"
2212        @ModuleAnnotation("typeannotationtest")
2213        module org.jboss.jandex.typeannotationtest {
2214        }"#;
2215
2216        let names = Java::find_names(code).expect("Cannot find names in test code");
2217        assert_eq!(
2218            names,
2219            Names {
2220                fully_qualified: DeclUse {
2221                    declared_symbols: vec![],
2222                    used_symbols: vec![]
2223                },
2224                simple: DeclUse {
2225                    declared_symbols: vec![
2226                        SimpleName {
2227                            symbol: "org".to_string(),
2228                            extent: optional_extent!(2:15-2:18),
2229                        },
2230                        SimpleName {
2231                            symbol: "jboss".to_string(),
2232                            extent: optional_extent!(2:19-2:24),
2233                        },
2234                        SimpleName {
2235                            symbol: "jandex".to_string(),
2236                            extent: optional_extent!(2:25-2:31),
2237                        },
2238                        SimpleName {
2239                            symbol: "typeannotationtest".to_string(),
2240                            extent: optional_extent!(2:32-2:50),
2241                        },
2242                    ],
2243                    used_symbols: vec![SimpleName {
2244                        symbol: "ModuleAnnotation".to_string(),
2245                        extent: optional_extent!(1:9-1:25),
2246                    },]
2247                },
2248                external_simple: DeclUse {
2249                    declared_symbols: vec![],
2250                    used_symbols: vec![SimpleName {
2251                        symbol: "ModuleAnnotation".to_string(),
2252                        extent: optional_extent!(1:9-1:25),
2253                    },]
2254                }
2255            }
2256        );
2257    }
2258
2259    #[test]
2260    fn finds_names_in_method_declaration_annotation() {
2261        let code =
2262            r#"static <T> @Nullable ArrayList<T> listToArrayList(@Nullable List<T> list) {}"#;
2263
2264        let names = Java::find_names(code).expect("Cannot find names in test code");
2265        assert_eq!(
2266            names,
2267            Names {
2268                fully_qualified: DeclUse {
2269                    declared_symbols: vec![],
2270                    used_symbols: vec![]
2271                },
2272                simple: DeclUse {
2273                    declared_symbols: vec![
2274                        SimpleName {
2275                            symbol: "listToArrayList".to_string(),
2276                            extent: optional_extent!(0:34-0:49),
2277                        },
2278                        SimpleName {
2279                            symbol: "list".to_string(),
2280                            extent: optional_extent!(0:68-0:72),
2281                        }
2282                    ],
2283                    used_symbols: vec![
2284                        SimpleName {
2285                            symbol: "T".to_string(),
2286                            extent: optional_extent!(0:8-0:9),
2287                        },
2288                        SimpleName {
2289                            symbol: "Nullable".to_string(),
2290                            extent: optional_extent!(0:12-0:20),
2291                        },
2292                        SimpleName {
2293                            symbol: "ArrayList".to_string(),
2294                            extent: optional_extent!(0:21-0:30),
2295                        },
2296                        SimpleName {
2297                            symbol: "T".to_string(),
2298                            extent: optional_extent!(0:31-0:32),
2299                        },
2300                        SimpleName {
2301                            symbol: "Nullable".to_string(),
2302                            extent: optional_extent!(0:51-0:59),
2303                        },
2304                        SimpleName {
2305                            symbol: "List".to_string(),
2306                            extent: optional_extent!(0:60-0:64),
2307                        },
2308                        SimpleName {
2309                            symbol: "T".to_string(),
2310                            extent: optional_extent!(0:65-0:66),
2311                        },
2312                    ]
2313                },
2314                external_simple: DeclUse {
2315                    declared_symbols: vec![],
2316                    used_symbols: vec![
2317                        SimpleName {
2318                            symbol: "T".to_string(),
2319                            extent: optional_extent!(0:8-0:9),
2320                        },
2321                        SimpleName {
2322                            symbol: "Nullable".to_string(),
2323                            extent: optional_extent!(0:12-0:20),
2324                        },
2325                        SimpleName {
2326                            symbol: "ArrayList".to_string(),
2327                            extent: optional_extent!(0:21-0:30),
2328                        },
2329                        SimpleName {
2330                            symbol: "T".to_string(),
2331                            extent: optional_extent!(0:31-0:32),
2332                        },
2333                        SimpleName {
2334                            symbol: "Nullable".to_string(),
2335                            extent: optional_extent!(0:51-0:59),
2336                        },
2337                        SimpleName {
2338                            symbol: "List".to_string(),
2339                            extent: optional_extent!(0:60-0:64),
2340                        },
2341                        SimpleName {
2342                            symbol: "T".to_string(),
2343                            extent: optional_extent!(0:65-0:66),
2344                        },
2345                    ]
2346                }
2347            }
2348        );
2349    }
2350
2351    #[test]
2352    fn finds_relevant_snippet() {
2353        let code = r#"/*
2354 * SPDX-License-Identifier: Apache-2.0
2355 *
2356 * Copyright 2018-2022 Agorapulse.
2357 *
2358 * Licensed under the Apache License, Version 2.0 (the "License");
2359 * you may not use this file except in compliance with the License.
2360 * You may obtain a copy of the License at
2361 *
2362 *     https://www.apache.org/licenses/LICENSE-2.0
2363 *
2364 * Unless required by applicable law or agreed to in writing, software
2365 * distributed under the License is distributed on an "AS IS" BASIS,
2366 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2367 * See the License for the specific language governing permissions and
2368 * limitations under the License.
2369 */
2370package com.agorapulse.micronaut.amazon.awssdk.ses;
2371
2372import io.micronaut.core.util.StringUtils;
2373import org.slf4j.Logger;
2374import org.slf4j.LoggerFactory;
2375import software.amazon.awssdk.awscore.exception.AwsServiceException;
2376import software.amazon.awssdk.core.SdkBytes;
2377import software.amazon.awssdk.services.ses.SesClient;
2378import software.amazon.awssdk.services.ses.model.SendEmailRequest;
2379import software.amazon.awssdk.services.ses.model.SendRawEmailRequest;
2380
2381import javax.activation.DataHandler;
2382import javax.activation.DataSource;
2383import javax.mail.BodyPart;
2384import javax.mail.MessagingException;
2385import javax.mail.Session;
2386import javax.mail.internet.InternetAddress;
2387import javax.mail.internet.MimeBodyPart;
2388import javax.mail.internet.MimeMessage;
2389import javax.mail.internet.MimeMultipart;
2390import javax.mail.util.ByteArrayDataSource;
2391import java.io.ByteArrayOutputStream;
2392import java.io.File;
2393import java.io.FileInputStream;
2394import java.io.IOException;
2395import java.nio.charset.StandardCharsets;
2396import java.util.Optional;
2397import java.util.Properties;
2398
2399import static javax.mail.Message.RecipientType.TO;
2400
2401/**
2402 * Default implementation of simple email service.
2403 */
2404public class DefaultSimpleEmailService implements SimpleEmailService {
2405
2406    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultSimpleEmailService.class);
2407
2408    private final SesClient client;
2409    private final SimpleEmailServiceConfiguration configuration;
2410
2411    DefaultSimpleEmailService(SesClient client, SimpleEmailServiceConfiguration configuration) {
2412        this.client = client;
2413        this.configuration = configuration;
2414    }
2415
2416    public EmailDeliveryStatus send(TransactionalEmail email) {
2417        if (!email.getAttachments().isEmpty()) {
2418            try {
2419                return sendEmailWithAttachment(email);
2420            } catch (IOException | MessagingException exception) {
2421                LOGGER.error(
2422                    String.format(
2423                        "An exception was caught while sending email: destinationEmails=%s, from=%s, replyTo=%s, subject=%s",
2424                        email.getRecipients(),
2425                        email.getFrom(),
2426                        email.getReplyTo(),
2427                        email.getSubject()
2428                    ),
2429                    exception
2430                );
2431                return EmailDeliveryStatus.STATUS_NOT_DELIVERED;
2432            }
2433        }
2434
2435        return sendWithoutAttachments(email);
2436    }
2437
2438    private static EmailDeliveryStatus handleSend(TransactionalEmail email, Runnable c) {
2439        try {
2440            c.run();
2441            return EmailDeliveryStatus.STATUS_DELIVERED;
2442        } catch (AwsServiceException exception) {
2443            if (exception.getMessage().contains("blacklisted")) {
2444                LOGGER.warn(String.format("Address blacklisted destinationEmails=%s)", email.getRecipients()));
2445                return EmailDeliveryStatus.STATUS_BLACKLISTED;
2446            }
2447            LOGGER.error(
2448                String.format(
2449                    "An amazon service exception was caught while sending email: destinationEmails=%s, from=%s, replyTo=%s, subject=%s",
2450                    email.getRecipients(),
2451                    email.getFrom(),
2452                    email.getReplyTo(),
2453                    email.getSubject()
2454                ),
2455                exception
2456            );
2457            return EmailDeliveryStatus.STATUS_NOT_DELIVERED;
2458        }
2459    }
2460
2461    private EmailDeliveryStatus sendEmailWithAttachment(TransactionalEmail email) throws MessagingException, IOException {
2462        Session session = Session.getInstance(new Properties());
2463        MimeMessage mimeMessage = new MimeMessage(session);
2464        mimeMessage.setSubject(configuration.getSubjectPrefix().isPresent()
2465            ? configuration.getSubjectPrefix().get() + " " + email.getSubject()
2466            : email.getSubject()
2467        );
2468
2469        if (!StringUtils.isEmpty(email.getFrom())) {
2470            mimeMessage.setFrom(new InternetAddress(email.getFrom()));
2471        } else if (configuration.getSourceEmail().isPresent()) {
2472            mimeMessage.setFrom(new InternetAddress(configuration.getSourceEmail().get()));
2473        }
2474
2475        if (!StringUtils.isEmpty(email.getReplyTo())) {
2476            mimeMessage.setReplyTo(new InternetAddress[] {new InternetAddress(email.getReplyTo())});
2477        }
2478
2479        for (String r : email.getRecipients()) {
2480            mimeMessage.addRecipients(TO, r);
2481        }
2482
2483        MimeMultipart mimeMultipart = new MimeMultipart();
2484
2485        BodyPart p = new MimeBodyPart();
2486        p.setContent(email.getHtmlBody(), "text/html");
2487        mimeMultipart.addBodyPart(p);
2488
2489        for (TransactionalEmailAttachment attachment : email.getAttachments()) {
2490            if (!MimeType.isMimeTypeSupported(attachment.getMimeType())) {
2491                throw new UnsupportedAttachmentTypeException("Attachment type not supported for " + attachment.getFilename());
2492            }
2493
2494            MimeBodyPart mimeBodyPart = new MimeBodyPart();
2495            mimeBodyPart.setFileName(attachment.getFilename());
2496            mimeBodyPart.setDescription(attachment.getDescription(), StandardCharsets.UTF_8.name());
2497            DataSource ds = new ByteArrayDataSource(new FileInputStream(new File(attachment.getFilepath())), attachment.getMimeType());
2498            mimeBodyPart.setDataHandler(new DataHandler(ds));
2499            mimeMultipart.addBodyPart(mimeBodyPart);
2500        }
2501
2502        mimeMessage.setContent(mimeMultipart);
2503
2504        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
2505        mimeMessage.writeTo(outputStream);
2506
2507        SendRawEmailRequest rawEmailRequest = SendRawEmailRequest.builder()
2508            .rawMessage(b -> b.data(SdkBytes.fromByteArray(outputStream.toByteArray())))
2509            .destinations(email.getRecipients())
2510            .source(Optional.ofNullable(email.getFrom()).orElseGet(() -> configuration.getSourceEmail().orElse(null))).build();
2511
2512        return handleSend(email, () -> client.sendRawEmail(rawEmailRequest));
2513    }
2514
2515    private EmailDeliveryStatus sendWithoutAttachments(TransactionalEmail email) {
2516        SendEmailRequest.Builder builder = SendEmailRequest.builder()
2517            .destination(b -> b.toAddresses(email.getRecipients()))
2518            .message(b -> {
2519                b.subject(c -> c.data(
2520                        configuration.getSubjectPrefix().isPresent()
2521                        ? configuration.getSubjectPrefix().get() + " " + email.getSubject()
2522                        : email.getSubject()
2523                    )
2524                );
2525                b.body(body -> body.html(c -> c.data(email.getHtmlBody())));
2526            })
2527            .source(Optional.ofNullable(email.getFrom()).orElseGet(() -> configuration.getSourceEmail().orElse(null)));
2528
2529        if (email.getReplyTo() != null && email.getReplyTo().length() > 0) {
2530            builder.replyToAddresses(email.getReplyTo());
2531        }
2532
2533        return handleSend(email, () -> client.sendEmail(builder.build()));
2534    }
2535
2536}"#;
2537
2538        let snippet = Java::find_relevant_scope(
2539            code,
2540            vec![RelevantName::FullyQualified {
2541                source: sym!(software.amazon.awssdk.services.ses.model),
2542                symbol: sym!(SendRawEmailRequest.builder),
2543            }],
2544        )
2545        .expect("Cannot find snippet in test code")
2546        .snippet;
2547
2548        assert_eq!(
2549            snippet,
2550            r#"private EmailDeliveryStatus sendEmailWithAttachment(TransactionalEmail email) throws MessagingException, IOException {
2551        Session session = Session.getInstance(new Properties());
2552        MimeMessage mimeMessage = new MimeMessage(session);
2553        mimeMessage.setSubject(configuration.getSubjectPrefix().isPresent()
2554            ? configuration.getSubjectPrefix().get() + " " + email.getSubject()
2555            : email.getSubject()
2556        );
2557
2558        if (!StringUtils.isEmpty(email.getFrom())) {
2559            mimeMessage.setFrom(new InternetAddress(email.getFrom()));
2560        } else if (configuration.getSourceEmail().isPresent()) {
2561            mimeMessage.setFrom(new InternetAddress(configuration.getSourceEmail().get()));
2562        }
2563
2564        if (!StringUtils.isEmpty(email.getReplyTo())) {
2565            mimeMessage.setReplyTo(new InternetAddress[] {new InternetAddress(email.getReplyTo())});
2566        }
2567
2568        for (String r : email.getRecipients()) {
2569            mimeMessage.addRecipients(TO, r);
2570        }
2571
2572        MimeMultipart mimeMultipart = new MimeMultipart();
2573
2574        BodyPart p = new MimeBodyPart();
2575        p.setContent(email.getHtmlBody(), "text/html");
2576        mimeMultipart.addBodyPart(p);
2577
2578        for (TransactionalEmailAttachment attachment : email.getAttachments()) {
2579            if (!MimeType.isMimeTypeSupported(attachment.getMimeType())) {
2580                throw new UnsupportedAttachmentTypeException("Attachment type not supported for " + attachment.getFilename());
2581            }
2582
2583            MimeBodyPart mimeBodyPart = new MimeBodyPart();
2584            mimeBodyPart.setFileName(attachment.getFilename());
2585            mimeBodyPart.setDescription(attachment.getDescription(), StandardCharsets.UTF_8.name());
2586            DataSource ds = new ByteArrayDataSource(new FileInputStream(new File(attachment.getFilepath())), attachment.getMimeType());
2587            mimeBodyPart.setDataHandler(new DataHandler(ds));
2588            mimeMultipart.addBodyPart(mimeBodyPart);
2589        }
2590
2591        mimeMessage.setContent(mimeMultipart);
2592
2593        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
2594        mimeMessage.writeTo(outputStream);
2595
2596        SendRawEmailRequest rawEmailRequest = SendRawEmailRequest.builder()
2597            .rawMessage(b -> b.data(SdkBytes.fromByteArray(outputStream.toByteArray())))
2598            .destinations(email.getRecipients())
2599            .source(Optional.ofNullable(email.getFrom()).orElseGet(() -> configuration.getSourceEmail().orElse(null))).build();
2600
2601        return handleSend(email, () -> client.sendRawEmail(rawEmailRequest));
2602    }"#
2603        );
2604    }
2605}