Browse Source

ZOOKEEPER-4880: Generate comments from zookeeper.jute into code

Reviewers: kezhuw, tisonkun
Author: luozongle01
Closes #2206 from luozongle01/ZOOKEEPER-4880
luozongle01 4 months ago
parent
commit
d1d57c4af3

+ 42 - 0
zookeeper-jute/src/main/java/org/apache/jute/compiler/JField.java

@@ -18,6 +18,9 @@
 
 package org.apache.jute.compiler;
 
+import org.apache.jute.compiler.generated.RccConstants;
+import org.apache.jute.compiler.generated.Token;
+
 /**
  *
  */
@@ -25,6 +28,21 @@ public class JField {
     private JType mType;
     private String mName;
 
+    /**
+     * {@link #mType} of token.
+     */
+    private Token mTypeToken;
+
+    private Token previousToken;
+
+    /**
+     * Since we can only get the comments before the token through the {@link Token#specialToken},
+     * we need to save the next token to get the end-of-line comment.
+     *
+     * <p>It may be the type of the next field, or it may be {@link RccConstants#RBRACE_TKN} of the class.
+     */
+    private Token nextToken;
+
     /**
      * Creates a new instance of JField.
      */
@@ -33,6 +51,30 @@ public class JField {
         mName = name;
     }
 
+    public Token getTypeToken() {
+        return mTypeToken;
+    }
+
+    public void setTypeToken(Token typeToken) {
+        this.mTypeToken = typeToken;
+    }
+
+    public Token getNextToken() {
+        return nextToken;
+    }
+
+    public void setNextToken(Token nextToken) {
+        this.nextToken = nextToken;
+    }
+
+    public Token getPreviousToken() {
+        return previousToken;
+    }
+
+    public void setPreviousToken(Token previousToken) {
+        this.previousToken = previousToken;
+    }
+
     public String getSignature() {
         return mType.getSignature();
     }

+ 145 - 1
zookeeper-jute/src/main/java/org/apache/jute/compiler/JRecord.java

@@ -22,10 +22,13 @@ import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import org.apache.jute.compiler.generated.RccConstants;
+import org.apache.jute.compiler.generated.Token;
 
 /**
  *
@@ -36,11 +39,12 @@ public class JRecord extends JCompType {
     private String mName;
     private String mModule;
     private List<JField> mFields;
+    private Token mRecordToken;
 
     /**
      * Creates a new instance of JRecord.
      */
-    public JRecord(String name, ArrayList<JField> flist) {
+    public JRecord(String name, ArrayList<JField> flist, Token recordToken) {
         super("struct " + name.substring(name.lastIndexOf('.') + 1),
                 name.replaceAll("\\.", "::"), getCsharpFQName(name), name, "Record", name, getCsharpFQName("IRecord"));
         mFQName = name;
@@ -48,12 +52,17 @@ public class JRecord extends JCompType {
         mName = name.substring(idx + 1);
         mModule = name.substring(0, idx);
         mFields = flist;
+        mRecordToken = recordToken;
     }
 
     public String getName() {
         return mName;
     }
 
+    public Token getRecordToken() {
+        return mRecordToken;
+    }
+
     public String getCsharpName() {
         return "Id".equals(mName) ? "ZKId" : mName;
     }
@@ -208,8 +217,17 @@ public class JRecord extends JCompType {
             }
         }
         String recName = getName();
+
+        String recordComments = getRecordComments();
+        if (recordComments != null && !recordComments.isEmpty()) {
+            h.write(recordComments);
+        }
         h.write("struct " + recName + " {\n");
         for (JField f : mFields) {
+            String fieldComments = getCFieldComments(f);
+            if (fieldComments != null && !fieldComments.isEmpty()) {
+                h.write(fieldComments);
+            }
             h.write(f.genCDecl());
         }
         h.write("};\n");
@@ -436,10 +454,18 @@ public class JRecord extends JCompType {
             jj.write("import org.apache.jute.*;\n");
             jj.write("import org.apache.jute.Record; // JDK14 needs explicit import due to clash with java.lang.Record\n");
             jj.write("import org.apache.yetus.audience.InterfaceAudience;\n");
+            String recordComments = getRecordComments();
+            if (recordComments != null && !recordComments.isEmpty()) {
+                jj.write(recordComments);
+            }
             jj.write("@InterfaceAudience.Public\n");
             jj.write("public class " + getName() + " implements Record {\n");
             for (Iterator<JField> i = mFields.iterator(); i.hasNext(); ) {
                 JField jf = i.next();
+                String fieldComments = getJavaFieldComments(jf);
+                if (fieldComments != null && !fieldComments.isEmpty()) {
+                    jj.write(fieldComments);
+                }
                 jj.write(jf.genJavaDecl());
             }
             jj.write("  public " + getName() + "() {\n");
@@ -767,4 +793,122 @@ public class JRecord extends JCompType {
         }
         return fQName.toString();
     }
+
+    public String getJavaFieldComments(JField jField) {
+        return getFieldComments(jField, "  ");
+    }
+
+    public String getCFieldComments(JField jField) {
+        return getFieldComments(jField, "    ");
+    }
+
+    private String getFieldComments(JField jField, String indent) {
+        if (jField == null || jField.getTypeToken() == null || jField.getNextToken() == null) {
+            return "";
+        }
+
+        // get the comment before the line
+        Token beforeTheLineCommentToken = getCommentToken(jField.getTypeToken(), jField.getPreviousToken());
+        List<String> comments = extractComments(beforeTheLineCommentToken, Integer.MAX_VALUE);
+
+        Token endOfLineCommentToken = getCommentToken(jField.getNextToken(), null);
+        if (endOfLineCommentToken != null && jField.getTypeToken().beginLine == endOfLineCommentToken.beginLine) {
+
+            comments.addAll(extractComments(endOfLineCommentToken, endOfLineCommentToken.beginLine));
+        }
+
+        return formatComments(indent, comments);
+    }
+
+    private Token getCommentToken(Token token, Token previousToken) {
+        if (token == null || token.specialToken == null) {
+            return null;
+        }
+
+        Token commentToken = token.specialToken;
+        while (commentToken.specialToken != null) {
+            commentToken = commentToken.specialToken;
+        }
+        // Skip end of line comment belong to previous token.
+        while (previousToken != null && commentToken != null && commentToken.beginLine == previousToken.endLine) {
+            commentToken = commentToken.next;
+        }
+        return commentToken;
+    }
+
+    public String getRecordComments() {
+        if (getRecordToken() == null || getRecordToken().specialToken == null) {
+            return "";
+        }
+
+        // get the comments before the class
+        Token commentToken = getCommentToken(getRecordToken(), null);
+        return formatComments("", extractComments(commentToken, Integer.MAX_VALUE));
+    }
+
+    private static String formatComments(String indent, List<String> commentLines) {
+        if (commentLines == null || commentLines.isEmpty()) {
+            return "";
+        }
+
+        StringBuilder builder = new StringBuilder();
+        for (String line : commentLines) {
+            if (!line.isEmpty()) {
+                builder.append(indent).append(line);
+            }
+            builder.append(System.lineSeparator());
+        }
+
+        return builder.toString();
+    }
+
+    /**
+     * Extracts comments with indentation and line separator trimmed.
+     *
+     * <p>Empty line is represented as empty string.
+     */
+    private static List<String> extractComments(Token token, int endLine) {
+        List<String> comments = new ArrayList<>();
+
+        if (token == null) {
+            return comments;
+        }
+
+        int nextLine = token.beginLine;
+        while (token != null && token.beginLine <= endLine) {
+            while (nextLine < token.beginLine) {
+                comments.add("");
+                nextLine++;
+            }
+            nextLine = token.endLine + 1;
+            switch (token.kind) {
+                case RccConstants.ONE_LINE_COMMENT:
+                    comments.add(token.image);
+                    break;
+                case RccConstants.MULTI_LINE_COMMENT: {
+                    List<String> lines = Arrays.asList(token.image.split("\r|\n|\r\n"));
+                    // First line captures no indentation.
+                    comments.add(lines.get(0));
+                    int indentSpaces = token.beginColumn - 1;
+                    for (int i = 1; i < lines.size(); i++) {
+                        String line = lines.get(i);
+                        int j = 0;
+                        while (j < indentSpaces && j < line.length()) {
+                            if (line.charAt(j) != ' ') {
+                                break;
+                            }
+                            j++;
+                        }
+                        comments.add(line.substring(j));
+                    }
+                }
+                break;
+                default:
+                    throw new IllegalStateException("expect comment token, but get token kind " + token.kind);
+            }
+            token = token.next;
+        }
+
+        return comments;
+    }
 }

+ 16 - 29
zookeeper-jute/src/main/java/org/apache/jute/compiler/generated/rcc.jj

@@ -111,32 +111,8 @@ SKIP :
 
 SPECIAL_TOKEN :
 {
-  "//" : WithinOneLineComment
-}
-
-<WithinOneLineComment> SPECIAL_TOKEN :
-{
-  <("\n" | "\r" | "\r\n" )> : DEFAULT
-}
-
-<WithinOneLineComment> MORE :
-{
-  <~[]>
-}
-
-SPECIAL_TOKEN :
-{
-  "/*" : WithinMultiLineComment
-}
-
-<WithinMultiLineComment> SPECIAL_TOKEN :
-{
-  "*/" : DEFAULT
-}
-
-<WithinMultiLineComment> MORE :
-{
-  <~[]>
+    <ONE_LINE_COMMENT: "//" (~["\n","\r"])*>
+|   <MULTI_LINE_COMMENT: "/*" (~["*"])* "*" (~["*","/"] (~["*"])* "*" | "*")* "/">
 }
 
 TOKEN :
@@ -274,21 +250,32 @@ JRecord Record() :
     ArrayList<JField> flist = new ArrayList<JField>();
     Token t;
     JField f;
+    // Get the comments on the class token
+    Token recordTkn;
+    Token typeTkn;
+    Token previousToken = null;
 }
 {
-    <RECORD_TKN>
+    recordTkn = <RECORD_TKN>
     t = <IDENT_TKN>
     { rname = t.image; }
-    <LBRACE_TKN>
+    previousToken = <LBRACE_TKN>
     (
+        {typeTkn = getToken(1);}
         f = Field()
         { flist.add(f); }
         <SEMICOLON_TKN>
+        {
+            f.setTypeToken(typeTkn);
+            f.setPreviousToken(previousToken);
+            f.setNextToken(getToken(1));
+            previousToken = typeTkn;
+        }
     )+
     <RBRACE_TKN>
     {
         String fqn = curModuleName + "." + rname;
-        JRecord r = new JRecord(fqn, flist);
+        JRecord r = new JRecord(fqn, flist, recordTkn);
         recTab.put(fqn, r);
         return r;
     }

+ 344 - 0
zookeeper-jute/src/test/java/org/apache/jute/compiler/JRecordTest.java

@@ -0,0 +1,344 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jute.compiler;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.io.StringReader;
+import java.lang.reflect.Field;
+import java.util.List;
+import org.apache.jute.compiler.generated.ParseException;
+import org.apache.jute.compiler.generated.Rcc;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings({"unchecked", "SameParameterValue"})
+public class JRecordTest {
+
+    @Test
+    public void testEndOfLineComments() throws ParseException, NoSuchFieldException, IllegalAccessException {
+        String juteStr = "module org.apache.zookeeper.data {\n"
+                + "    // information explicitly stored by the server persistently\n"
+                + "    class StatPersisted {\n"
+                + "        long czxid;      // created zxid\n"
+                + "        long mzxid;      // last modified zxid\n"
+                + "        long ctime;      /* created */\n"
+                + "        long mtime;      /** last modified */\n"
+                + "        int version;     /* version */ /* testComment1 */\n"
+                + "        int cversion;    /* child version */ /* child versionComment2 */ /* child versionComment3 */\n"
+                + "        int aversion;    /** acl version */ /** acl versionComment2 */ /** acl version */\n"
+                + "        long ephemeralOwner;  /* A multi-line end of line comment. */ /* Another multi-line end of line comment. */ /* Yet another\n"
+                + "end of line comment. */ /* Comment belong to new field */\n"
+                + "        long pzxid;     /* A multi-line end of line comment. */ /* Another multi-line end of line comment. */ /* Yet another\n"
+                + "end of line comment. */ /* Comment belong to new field */\n"
+                + "    }\n"
+                + "}";
+
+        try (StringReader stringReader = new StringReader(juteStr)) {
+            Rcc parser = new Rcc(stringReader);
+            JFile jFile = parser.Input();
+            List<JRecord> mRecords = getField(jFile, "mRecords", List.class);
+            assertEquals(1, mRecords.size());
+
+            JRecord jRecord = mRecords.get(0);
+            assertEquals("StatPersisted", jRecord.getName());
+            List<JField> fields = jRecord.getFields();
+            assertFiled(fields);
+
+            assertEquals("// information explicitly stored by the server persistently\n", jRecord.getRecordComments());
+            assertEquals("  // created zxid\n", jRecord.getJavaFieldComments(fields.get(0)));
+            assertEquals("  // last modified zxid\n", jRecord.getJavaFieldComments(fields.get(1)));
+            assertEquals("  /* created */\n", jRecord.getJavaFieldComments(fields.get(2)));
+            assertEquals("  /** last modified */\n", jRecord.getJavaFieldComments(fields.get(3)));
+            assertEquals("  /* version */\n  /* testComment1 */\n", jRecord.getJavaFieldComments(fields.get(4)));
+            assertEquals("  /* child version */\n  /* child versionComment2 */\n  /* child versionComment3 */\n", jRecord.getJavaFieldComments(fields.get(5)));
+            assertEquals("  /** acl version */\n  /** acl versionComment2 */\n  /** acl version */\n", jRecord.getJavaFieldComments(fields.get(6)));
+            assertEquals(
+                    "  /* A multi-line end of line comment. */\n"
+                            + "  /* Another multi-line end of line comment. */\n"
+                            + "  /* Yet another\n"
+                            + "  end of line comment. */\n", jRecord.getJavaFieldComments(fields.get(7)));
+            assertEquals(
+                    "  /* Comment belong to new field */\n"
+                            + "  /* A multi-line end of line comment. */\n"
+                            + "  /* Another multi-line end of line comment. */\n"
+                            + "  /* Yet another\n"
+                            + "  end of line comment. */\n", jRecord.getJavaFieldComments(fields.get(8)));
+        }
+    }
+
+    @Test
+    public void testCommentBeforeLineAndEndOfLine() throws ParseException, NoSuchFieldException, IllegalAccessException {
+        String juteStr = "module org.apache.zookeeper.data {\n"
+                + "    /**\n"
+                + "      * information explicitly stored by the server persistently\n"
+                + "      */ \n"
+                + "    class StatPersisted {\n"
+                + "        // created zxid\n"
+                + "        long czxid; // created zxid comment2\n"
+                + "        /* last modified zxid */\n"
+                + "        long mzxid; // last modified zxid comment2\n"
+                + "        /* created */\n"
+                + "        long ctime; // created comment2\n"
+                + "        /* last modified */\n"
+                + "        /* last modified */\n"
+                + "        long mtime; // last modified comment2\n"
+                + "        // version comment\n "
+                + "        int version;     /* version comment1 */ /* version\n comment2 */\n"
+                + "        /** child version */\n"
+                + "        /** child version */\n"
+                + "        int cversion;  /** child version */  // child version\n"
+                + "        // acl version\n"
+                + "        // acl version\n"
+                + "        // acl version\n"
+                + "        int aversion;    // acl version\n"
+                + "        // ephemeralOwner comment\n"
+                + "        long ephemeralOwner;  /* A multi-line end of line comment. */ /* Another multi-line end of line comment. */ /* Yet another\n"
+                + "end of line comment. */ /* Comment belong to new field */\n"
+                + "        // pzxid comment\n"
+                + "        long pzxid;     /* A multi-line end of line comment. */ /* Another multi-line end of line comment. */ /* Yet another\n"
+                + "end of line comment. */ /* Comment belong to new field */\n"
+                + "    }\n"
+                + "}";
+        try (StringReader stringReader = new StringReader(juteStr)) {
+            Rcc parser = new Rcc(stringReader);
+            JFile jFile = parser.Input();
+            List<JRecord> mRecords = getField(jFile, "mRecords", List.class);
+            assertEquals(1, mRecords.size());
+
+            JRecord jRecord = mRecords.get(0);
+            assertEquals("StatPersisted", jRecord.getName());
+            List<JField> fields = jRecord.getFields();
+            assertFiled(fields);
+
+            assertEquals("/**\n  * information explicitly stored by the server persistently\n  */\n", jRecord.getRecordComments());
+            assertEquals("  // created zxid\n  // created zxid comment2\n", jRecord.getJavaFieldComments(fields.get(0)));
+            assertEquals("  /* last modified zxid */\n  // last modified zxid comment2\n", jRecord.getJavaFieldComments(fields.get(1)));
+            assertEquals("  /* created */\n  // created comment2\n", jRecord.getJavaFieldComments(fields.get(2)));
+            assertEquals("  /* last modified */\n  /* last modified */\n  // last modified comment2\n", jRecord.getJavaFieldComments(fields.get(3)));
+
+            assertEquals("  // version comment\n  /* version comment1 */\n  /* version\n  comment2 */\n", jRecord.getJavaFieldComments(fields.get(4)));
+            assertEquals("  /** child version */\n  /** child version */\n  /** child version */\n  // child version\n", jRecord.getJavaFieldComments(fields.get(5)));
+            assertEquals("  // acl version\n  // acl version\n  // acl version\n  // acl version\n", jRecord.getJavaFieldComments(fields.get(6)));
+            assertEquals(
+                    "  // ephemeralOwner comment\n"
+                            + "  /* A multi-line end of line comment. */\n"
+                            + "  /* Another multi-line end of line comment. */\n"
+                            + "  /* Yet another\n"
+                            + "  end of line comment. */\n", jRecord.getJavaFieldComments(fields.get(7)));
+            assertEquals(
+                    "  /* Comment belong to new field */\n"
+                            + "  // pzxid comment\n"
+                            + "  /* A multi-line end of line comment. */\n"
+                            + "  /* Another multi-line end of line comment. */\n"
+                            + "  /* Yet another\n"
+                            + "  end of line comment. */\n", jRecord.getJavaFieldComments(fields.get(8)));
+        }
+    }
+
+    @Test
+    public void testCommentBeforeLine() throws ParseException, NoSuchFieldException, IllegalAccessException {
+        String juteStr = "module org.apache.zookeeper.data {\n"
+                + "    // information explicitly stored by the server persistently\n"
+                + "    // StatPersisted Comment1\n"
+                + "    // StatPersisted Comment2\n"
+                + "    class StatPersisted {\n"
+                + "        // created zxid comment1\n"
+                + "        // created zxid comment2\n"
+                + "        // created zxid comment3\n"
+                + "        long czxid;\n"
+                + "        // last modified zxid\n"
+                + "     // last modified zxid\n"
+                + "             // last modified zxid\n"
+                + "        long mzxid;\n"
+                + "        /* created */\n"
+                + "           /* created */\n"
+                + "      /* created */\n"
+                + "        long ctime;\n"
+                + "        /** last modified */\n"
+                + "        /** last modified */\n"
+                + "        long mtime;\n"
+                + "        /** version */\n"
+                + "             /** version */\n"
+                + "        int version;\n"
+                + "        // child version\n"
+                + "        /** child version */\n"
+                + "        /** child version */\n"
+                + "        int cversion;\n"
+                + "        /* acl version */\n"
+                + "        // acl version\n"
+                + "        /* acl version */\n"
+                + "        int aversion;\n"
+                + "        // owner id if ephemeral, 0 otw\n"
+                + "        long ephemeralOwner;\n"
+                + "        // last modified children\n"
+                + "        long pzxid;\n"
+                + "    }\n"
+                + "}";
+        try (StringReader stringReader = new StringReader(juteStr)) {
+            Rcc parser = new Rcc(stringReader);
+            JFile jFile = parser.Input();
+            List<JRecord> mRecords = getField(jFile, "mRecords", List.class);
+            assertEquals(1, mRecords.size());
+
+            JRecord jRecord = mRecords.get(0);
+            assertEquals("StatPersisted", jRecord.getName());
+            List<JField> fields = jRecord.getFields();
+            assertFiled(fields);
+
+            assertEquals("// information explicitly stored by the server persistently\n// StatPersisted Comment1\n// StatPersisted Comment2\n", jRecord.getRecordComments());
+            assertEquals("  // created zxid comment1\n  // created zxid comment2\n  // created zxid comment3\n", jRecord.getJavaFieldComments(fields.get(0)));
+            assertEquals("  // last modified zxid\n  // last modified zxid\n  // last modified zxid\n", jRecord.getJavaFieldComments(fields.get(1)));
+            assertEquals("  /* created */\n  /* created */\n  /* created */\n", jRecord.getJavaFieldComments(fields.get(2)));
+            assertEquals("  /** last modified */\n  /** last modified */\n", jRecord.getJavaFieldComments(fields.get(3)));
+            assertEquals("  /** version */\n  /** version */\n", jRecord.getJavaFieldComments(fields.get(4)));
+            assertEquals("  // child version\n  /** child version */\n  /** child version */\n", jRecord.getJavaFieldComments(fields.get(5)));
+            assertEquals("  /* acl version */\n  // acl version\n  /* acl version */\n", jRecord.getJavaFieldComments(fields.get(6)));
+            assertEquals("  // owner id if ephemeral, 0 otw\n", jRecord.getJavaFieldComments(fields.get(7)));
+            assertEquals("  // last modified children\n", jRecord.getJavaFieldComments(fields.get(8)));
+        }
+    }
+
+    @Test
+    public void testMultiLineComments() throws ParseException, NoSuchFieldException, IllegalAccessException {
+        String juteStr = "module org.apache.zookeeper.data {\n"
+                + "    /**\n"
+                + "      * information explicitly stored by the server persistently\n"
+                + "      */\n"
+                + "     // StatPersisted Comment\n"
+                + "     /* StatPersisted Comment */\n"
+                + "    class StatPersisted {\n"
+                + "        /**\n"
+                + "         * created zxid\n"
+                + "         */\n"
+                + "        long czxid;\n"
+                + "        /**\n"
+                + "         * last modified zxid comment1\n"
+                + "         */\n"
+                + "        /**\n"
+                + "         * last modified zxid comment2\n"
+                + "         */\n"
+                + "        long mzxid;\n"
+                + "        /*\n"
+                + "         * created\n"
+                + "         */\n"
+                + "        long ctime; /* multi-line\n"
+                + "end of line */\n"
+                + "        /*\n"
+                + "         last modified\n"
+                + "         */"
+                + "        long mtime; /* A multi-line end of line comment. */ /* Another multi-line end of line comment. */ /* Yet another\n"
+                + "end of line comment. */ /* Comment belong to new field */\n"
+                + "        /* version comment */\n"
+                + "        int version;     // version\n"
+                + "        /**\n"
+                + "         * child version\n"
+                + "         */\n"
+                + "        /**\n"
+                + "         * child version comment2\n"
+                + "         */\n"
+                + "        int cversion;    // child version\n"
+                + "        /* acl version */\n"
+                + "        int aversion;  /* acl version */ /* acl version */  // acl version\n"
+                + "        /*\n"
+                + "        ephemeralOwner comment\n"
+                + "        */\n"
+                + "        // ephemeralOwner comment\n"
+                + "        /*\n"
+                + "        ephemeralOwner comment\n"
+                + "        */\n"
+                + "        long ephemeralOwner;  // owner id if ephemeral, 0 otw\n"
+                + "        /*\n"
+                + "        pzxid comment\n"
+                + "        */\n"
+                + "        long pzxid;      // last modified children\n"
+                + "    }\n"
+                + "}";
+        try (StringReader stringReader = new StringReader(juteStr)) {
+            Rcc parser = new Rcc(stringReader);
+            JFile jFile = parser.Input();
+            List<JRecord> mRecords = getField(jFile, "mRecords", List.class);
+            assertEquals(1, mRecords.size());
+
+            JRecord jRecord = mRecords.get(0);
+            assertEquals("StatPersisted", jRecord.getName());
+            List<JField> fields = jRecord.getFields();
+            assertFiled(fields);
+
+            assertEquals(
+                    "/**\n"
+                            + "  * information explicitly stored by the server persistently\n"
+                            + "  */\n"
+                            + "// StatPersisted Comment\n"
+                            + "/* StatPersisted Comment */\n", jRecord.getRecordComments());
+            assertEquals("  /**\n   * created zxid\n   */\n", jRecord.getJavaFieldComments(fields.get(0)));
+            assertEquals("  /**\n   * last modified zxid comment1\n   */\n  /**\n   * last modified zxid comment2\n   */\n",
+                    jRecord.getJavaFieldComments(fields.get(1)));
+            assertEquals("  /*\n   * created\n   */\n  /* multi-line\n  end of line */\n", jRecord.getJavaFieldComments(fields.get(2)));
+            assertEquals("  /*\n   last modified\n   */\n"
+                            + "  /* A multi-line end of line comment. */\n"
+                            + "  /* Another multi-line end of line comment. */\n"
+                            + "  /* Yet another\n"
+                            + "  end of line comment. */\n", jRecord.getJavaFieldComments(fields.get(3)));
+            assertEquals("  /* Comment belong to new field */\n  /* version comment */\n  // version\n",
+                    jRecord.getJavaFieldComments(fields.get(4)));
+            assertEquals("  /**\n   * child version\n   */\n  /**\n   * child version comment2\n   */\n  // child version\n",
+                    jRecord.getJavaFieldComments(fields.get(5)));
+            assertEquals("  /* acl version */\n  /* acl version */\n  /* acl version */\n  // acl version\n", jRecord.getJavaFieldComments(fields.get(6)));
+            assertEquals("  /*\n"
+                    + "  ephemeralOwner comment\n"
+                    + "  */\n"
+                    + "  // ephemeralOwner comment\n"
+                    + "  /*\n"
+                    + "  ephemeralOwner comment\n"
+                    + "  */\n"
+                    + "  // owner id if ephemeral, 0 otw\n", jRecord.getJavaFieldComments(fields.get(7)));
+            assertEquals("  /*\n  pzxid comment\n  */\n  // last modified children\n", jRecord.getJavaFieldComments(fields.get(8)));
+        }
+    }
+
+    private void assertFiled(List<JField> fields) {
+        assertEquals(9, fields.size());
+        assertEquals("long", fields.get(0).getType().getJavaType());
+        assertEquals("czxid", fields.get(0).getName());
+        assertEquals("long", fields.get(1).getType().getJavaType());
+        assertEquals("mzxid", fields.get(1).getName());
+        assertEquals("long", fields.get(2).getType().getJavaType());
+        assertEquals("ctime", fields.get(2).getName());
+        assertEquals("long", fields.get(3).getType().getJavaType());
+        assertEquals("mtime", fields.get(3).getName());
+        assertEquals("int", fields.get(4).getType().getJavaType());
+        assertEquals("version", fields.get(4).getName());
+        assertEquals("int", fields.get(5).getType().getJavaType());
+        assertEquals("cversion", fields.get(5).getName());
+        assertEquals("int", fields.get(6).getType().getJavaType());
+        assertEquals("aversion", fields.get(6).getName());
+        assertEquals("long", fields.get(7).getType().getJavaType());
+        assertEquals("ephemeralOwner", fields.get(7).getName());
+        assertEquals("long", fields.get(8).getType().getJavaType());
+        assertEquals("pzxid", fields.get(8).getName());
+    }
+
+    private <T> T getField(final Object target,
+                           final String fieldName,
+                           final Class<T> fieldClassType) throws NoSuchFieldException, IllegalAccessException {
+        Class<?> targetClazz = target.getClass();
+        Field field = targetClazz.getDeclaredField(fieldName);
+        field.setAccessible(true);
+        return fieldClassType.cast(field.get(target));
+    }
+}