Repository: leo-editor/leo-editor

Issue:
Java importer bugs with 'interface', (Java importer have regex without capture group, is it normal?)
See PR #3728.

The regex to capture blocks in java seem to have one for **interfaces** (the third one):
```java
    block_patterns = (
        ('class', re.compile(r'.*?\bclass\s+(\w+)')),
        ('func', re.compile(r'.*?\b(\w+)\s*\(.*?\)\s*{')),
        ('interface', re.compile(r'\w*\binterface\w*{')),
```

But, it looks for a word before 'interface', and no separation without spaces between that word and 'interface'.
Stepping in the code while debugging Leo, the sample below does not trigger a match. 

Neither does begenning the interface with:
abc interface Animal {
myInterface Animal

so the following file has the interface inserted at the beginning of the class Pig when doing and @auto node for this sample file: 

```java
interface Animal {
  public void animalSound(); // interface method (does not have a body)
  public void sleep(); // interface method (does not have a body)
}

class Pig implements Animal {
  public void animalSound() {
    // The body of animalSound() is provided here
    System.out.println("The pig says: wee wee");
  }
  public void sleep() {
    // The body of sleep() is provided here
    System.out.println("Zzz");
  }
}

class Main {
  public static void main(String[] args) {
    Pig myPig = new Pig();  // Create a Pig object
    myPig.animalSound();
    myPig.sleep();
  }
}
```

Also, when modifying the file on disk externally, when Leo detects the file change and we choose 'yes' to have it refresh the @auto file from disk, this message is printed in the log and the body does not refresh:
```
setCurrentPosition Invalid position <pos 2176939535872 childIndex: 0 lvl: 1 key: 2176872479232:1.2176872480288:0 class Pig> Commander 2176872740240: 'C:/prog/testmarkdown/testmd1.leo'     
Callers: [
     0: '<module>'
     1: 'main'
     2: 'run_file'
     3: 'run_path'
     4: '_run_module_code'    
     5: '_run_code'
     6: '<module>'
     7: 'run'
     8: 'load'
     9: 'runMainLoop'
    10: 'at_idle_time'        
    11: 'call_handler'        
    12: 'on_idle'
    13: 'on_idle'
    14: 'idle_check_commander'
    15: 'redraw'
    16: 'select'
    17: 'selectHelper'
    18: 'select_new_node'
    19: 'set_body_text_after_select'
]

setCurrentPosition Invalid position <pos 2176939535872 childIndex: 0 lvl: 1 key: 2176872479232:1.2176872480288:0 class Pig> Commander 2176872740240: 'C:/prog/testmarkdown/testmd1.leo'     
Callers: [
     0: '_run_module_as_main'
     1: '_run_code'
     2: '<module>'
     3: 'main'
     4: 'run_file'
     5: 'run_path'
     6: '_run_module_code'
     7: '_run_code'
     8: '<module>'
     9: 'run'
    10: 'load'
    11: 'runMainLoop'
    12: 'at_idle_time'
    13: 'call_handler'
    14: 'on_idle'
    15: 'on_idle'
    16: 'idle_check_commander'
    17: 'redraw'
    18: 'full_redraw'
]

setCurrentPosition Invalid position <pos 2176939535872 childIndex: 0 lvl: 1 key: 2176872479232:1.2176872480288:0 class Pig> Commander 2176872740240: 'C:/prog/testmarkdown/testmd1.leo'     
Callers: [
     0: 'main'
     1: 'run_file'
     2: 'run_path'
     3: '_run_module_code'
     4: '_run_code'
     5: '<module>'
     6: 'run'
     7: 'load'
     8: 'runMainLoop'
     9: 'at_idle_time'
    10: 'call_handler'
    11: 'on_idle'
    12: 'on_idle'
    13: 'idle_check_commander'
    14: 'redraw'
    15: 'selectPosition'
    16: 'select'
    17: 'selectHelper'
    18: 'select_new_node'
    19: 'set_body_text_after_select'
]

setCurrentPosition Invalid position <pos 2176939535872 childIndex: 0 lvl: 1 key: 2176872479232:1.2176872480288:0 class Pig> Commander 2176872740240: 'C:/prog/testmarkdown/testmd1.leo'     
Callers: [
     0: '_run_module_as_main'
     1: '_run_code'
     2: '<module>'
     3: 'main'
     4: 'run_file'
     5: 'run_path'
     6: '_run_module_code'
     7: '_run_code'
     8: '<module>'
     9: 'run'
    10: 'load'
    11: 'runMainLoop'
    12: 'at_idle_time'
    13: 'call_handler'
    14: 'on_idle'
    15: 'on_idle'
    16: 'idle_check_commander'
    17: 'redraw'
    18: 'selectPosition'
]
```

Lastly, Java and ~ini~ importers have regex without capture groups, is it normal?

Discussion:

Patch:
diff --git a/leo/plugins/importers/java.py b/leo/plugins/importers/java.py
index 5a1483f29..f4a882aad 100644
--- a/leo/plugins/importers/java.py
+++ b/leo/plugins/importers/java.py
@@ -20,7 +20,7 @@ class Java_Importer(Importer):
     block_patterns = (
         ('class', re.compile(r'.*?\bclass\s+(\w+)')),
         ('func', re.compile(r'.*?\b(\w+)\s*\(.*?\)\s*{')),
-        ('interface', re.compile(r'\w*\binterface\w*{')),
+        ('interface', re.compile(r'.*?\binterface\s+(\w*)\s*{')),
     )
 #@-others
 


Test patch:
diff --git a/leo/unittests/test_importers.py b/leo/unittests/test_importers.py
index b87795ae2..85f295250 100644
--- a/leo/unittests/test_importers.py
+++ b/leo/unittests/test_importers.py
@@ -10,6 +10,7 @@ from leo.core import leoGlobals as g
 from leo.core.leoNodes import Position
 from leo.core.leoTest2 import LeoUnitTest
 from leo.plugins.importers.base_importer import Block
+from leo.plugins.importers.java import Java_Importer
 from leo.plugins.importers.python import Python_Importer
 from leo.plugins.importers.c import C_Importer
 import leo.plugins.importers.coffeescript as coffeescript
@@ -1558,12 +1559,15 @@ class TestJava(BaseTestImporter):
         """
         expected_results = (
             (0, '',  # Ignore the first headline.
+                '@others\n'
+                '@language java\n'
+                '@tabwidth -4\n'
+            ),
+            (1, 'interface Bicycle',
                 'interface Bicycle {\n'
                 '    void changeCadence(int newValue);\n'
                 '    void changeGear(int newValue);\n'
                 '}\n'
-                '@language java\n'
-                '@tabwidth -4\n'
             ),
         )
         self.new_run_test(s, expected_results)
@@ -1578,15 +1582,64 @@ class TestJava(BaseTestImporter):
         """
         expected_results = (
             (0, '',  # Ignore the first headline.
+                '@others\n'
+                '@language java\n'
+                '@tabwidth -4\n'
+            ),
+            (1, 'interface Bicycle',
                 'interface Bicycle {\n'
                 'void changeCadence(int newValue);\n'
                 'void changeGear(int newValue);\n'
                 '}\n'
-                '@language java\n'
-                '@tabwidth -4\n'
             ),
         )
         self.new_run_test(s, expected_results)
+    #@+node:ekr.20231225065750.1: *3* TestJava.test_round_trip
+    def test_round_trip(self):
+        
+        c, root = self.c, self.c.p
+        at = c.atFileCommands
+        #@+<< define contents: test_round_trip >>
+        #@+node:ekr.20231225065840.1: *4* << define contents: test_round_trip >>
+        # #3727: The blank lines below cause the round-trip to fail.
+        #        For now, this unit test will hack the expected lines.
+        contents =  textwrap.dedent("""
+            public class Main {
+                public static void main(String[] args) {
+                    myMethod();
+                }
+
+                static void myMethod() {
+                    System.out.println("I just got executed!");
+                }
+
+            }
+
+        """).strip() + '\n'
+        #@-<< define contents: test_round_trip >>
+
+        # Import contents into root's tree.
+        importer = Java_Importer(c)
+        importer.import_from_string(parent=root, s=contents)
+
+        if 0:
+            for z in root.self_and_subtree():
+                g.printObj(g.splitLines(z.b), tag=z.h)
+            print('\n=== End dump ===\n')
+
+        # Write the tree as if it were an @auto node.
+        root.h = '@auto test.java'
+        results = at.atAutoToString(root)
+        
+        # A hack, acknowledging that the importer strips trailing blank lines in each node.
+        expected = results.replace('\n\n', '\n')
+
+        if results != expected:
+            g.printObj(contents, tag='contents')
+            g.printObj(results, tag='results')
+            g.printObj(expected, tag='expected')
+
+        self.assertEqual(results, expected)
     #@-others
 #@+node:ekr.20211108070310.1: ** class TestJavascript (BaseTestImporter)
 class TestJavascript(BaseTestImporter):