diff --git a/zookeeper-jute/src/main/java/org/apache/jute/compiler/JField.java b/zookeeper-jute/src/main/java/org/apache/jute/compiler/JField.java
index 395c0b6a966..96cfff017ee 100644
--- a/zookeeper-jute/src/main/java/org/apache/jute/compiler/JField.java
+++ b/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,19 @@ public class JField {
private JType mType;
private String mName;
+ /**
+ * {@link #mType} of token.
+ */
+ private Token mTypeToken;
+
+ /**
+ * 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.
+ *
+ *
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 +49,22 @@ public JField(JType type, String name) {
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 String getSignature() {
return mType.getSignature();
}
diff --git a/zookeeper-jute/src/main/java/org/apache/jute/compiler/JRecord.java b/zookeeper-jute/src/main/java/org/apache/jute/compiler/JRecord.java
index 618b4c50bb0..a25c91bf574 100644
--- a/zookeeper-jute/src/main/java/org/apache/jute/compiler/JRecord.java
+++ b/zookeeper-jute/src/main/java/org/apache/jute/compiler/JRecord.java
@@ -26,6 +26,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import org.apache.jute.compiler.generated.Token;
/**
*
@@ -36,11 +37,12 @@ public class JRecord extends JCompType {
private String mName;
private String mModule;
private List mFields;
+ private Token mRecordToken;
/**
* Creates a new instance of JRecord.
*/
- public JRecord(String name, ArrayList flist) {
+ public JRecord(String name, ArrayList flist, Token recordToken) {
super("struct " + name.substring(name.lastIndexOf('.') + 1),
name.replaceAll("\\.", "::"), getCsharpFQName(name), name, "Record", name, getCsharpFQName("IRecord"));
mFQName = name;
@@ -48,12 +50,17 @@ public JRecord(String name, ArrayList flist) {
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 +215,18 @@ public void genCCode(FileWriter h, FileWriter c) throws IOException {
}
}
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 +453,19 @@ public void genJavaCode(File outputDirectory) throws IOException {
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 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,88 @@ public static String getCsharpFQName(String name) {
}
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 space) {
+ if (jField == null || jField.getTypeToken() == null || jField.getNextToken() == null) {
+ return "";
+ }
+
+ // get the comment before the line
+ List comments = getComments(jField.getTypeToken().specialToken);
+
+ // get the end-of-line comments of fields
+ // If the current field and the next field are on the same line,
+ // the leading field comment of the next field should be discarded
+ Token tmpToken = jField.getNextToken();
+ while (tmpToken.specialToken != null) {
+
+ if (jField.getTypeToken().beginLine == tmpToken.specialToken.beginLine) {
+ Token endLineComments = tmpToken.specialToken;
+ tmpToken.specialToken = null;
+ comments.addAll(getComments(endLineComments));
+ break;
+ }
+
+ tmpToken = tmpToken.specialToken;
+ }
+
+ return formatComments(space, comments);
+ }
+
+ public String getRecordComments() {
+ if (getRecordToken() == null || getRecordToken().specialToken == null) {
+ return "";
+ }
+
+ // get the comments before the class
+ return formatComments("", getComments(getRecordToken().specialToken));
+ }
+
+ private static String formatComments(String space, List commentList) {
+ if (commentList == null || commentList.isEmpty()) {
+ return "";
+ }
+
+ String comments = String.join("", commentList);
+
+ StringBuilder formatBuilder = new StringBuilder();
+ for (String s : comments.split("\r?\n")) {
+ formatBuilder.append("\n")
+ .append(space)
+ .append(s.replaceAll("^[ \t]+", ""));
+ }
+
+ return formatBuilder.append("\n").toString();
+ }
+
+ private static List getComments(Token token) {
+ List comments = new ArrayList<>();
+
+ if (token == null) {
+ return comments;
+ }
+
+ Token tmpToken = token;
+ while (tmpToken.specialToken != null) {
+ tmpToken = tmpToken.specialToken;
+ }
+
+ while (tmpToken != null) {
+ comments.add(tmpToken.image);
+ if (tmpToken == token) {
+ break;
+ }
+ tmpToken = tmpToken.next;
+ }
+
+ return comments;
+ }
}
diff --git a/zookeeper-jute/src/main/java/org/apache/jute/compiler/generated/rcc.jj b/zookeeper-jute/src/main/java/org/apache/jute/compiler/generated/rcc.jj
index 94d4f42f6b3..b039bbb9bc2 100644
--- a/zookeeper-jute/src/main/java/org/apache/jute/compiler/generated/rcc.jj
+++ b/zookeeper-jute/src/main/java/org/apache/jute/compiler/generated/rcc.jj
@@ -37,6 +37,8 @@ public class Rcc {
private static String curDir = System.getProperty("user.dir");
private static String curFileName;
private static String curModuleName;
+ private Token tmpToken = null;
+ private JField prevField = null;
public static void main(String args[]) {
String language = "java";
@@ -97,6 +99,12 @@ public class Rcc {
}
}
}
+
+ private void prevFieldSetNextTkn(Token token) {
+ if (prevField != null) {
+ prevField.setNextToken(token);
+ }
+ }
}
PARSER_END(Rcc)
@@ -111,17 +119,7 @@ SKIP :
SPECIAL_TOKEN :
{
- "//" : WithinOneLineComment
-}
-
- SPECIAL_TOKEN :
-{
- <("\n" | "\r" | "\r\n" )> : DEFAULT
-}
-
- MORE :
-{
- <~[]>
+
}
SPECIAL_TOKEN :
@@ -246,7 +244,10 @@ String ModuleName() :
}
{
t =
- { name += t.image; }
+ {
+ tmpToken = t;
+ name += t.image;
+ }
(
t =
@@ -274,22 +275,37 @@ JRecord Record() :
ArrayList flist = new ArrayList();
Token t;
JField f;
+ // Get the comments on the class token
+ Token recordTkn;
+ Token rbraceTkn;
}
{
-
+ recordTkn =
t =
{ rname = t.image; }
(
f = Field()
- { flist.add(f); }
+ {
+ flist.add(f);
+ }
+ {
+ f.setTypeToken(tmpToken);
+ prevFieldSetNextTkn(tmpToken);
+ prevField = f;
+ tmpToken = null;
+ }
)+
-
+
+ // Get the line ending comment of the last attribute
+ rbraceTkn =
{
String fqn = curModuleName + "." + rname;
- JRecord r = new JRecord(fqn, flist);
+ JRecord r = new JRecord(fqn, flist, recordTkn);
recTab.put(fqn, r);
+ prevFieldSetNextTkn(rbraceTkn);
+ prevField = null;
return r;
}
}
@@ -308,30 +324,28 @@ JField Field() :
JType Type() :
{
JType jt;
- Token t;
+ Token t = null;
String rname;
}
{
- jt = Map()
- { return jt; }
+ (jt = Map()
| jt = Vector()
- { return jt; }
-|
- { return new JByte(); }
-|
- { return new JBoolean(); }
-|
- { return new JInt(); }
-|
- { return new JLong(); }
-|
- { return new JFloat(); }
-|
- { return new JDouble(); }
-|
- { return new JString(); }
-|
- { return new JBuffer(); }
+| t =
+ { jt = new JByte(); }
+| t =
+ { jt = new JBoolean(); }
+| t =
+ { jt = new JInt(); }
+| t =
+ { jt = new JLong(); }
+| t =
+ { jt = new JFloat(); }
+| t =
+ { jt = new JDouble(); }
+| t =
+ { jt = new JString(); }
+| t =
+ { jt = new JBuffer(); }
| rname = ModuleName()
{
if (rname.indexOf('.', 0) < 0) {
@@ -343,6 +357,13 @@ JType Type() :
System.exit(1);
}
return r;
+ })
+
+ {
+ if (t != null) {
+ tmpToken = t;
+ }
+ return jt;
}
}
@@ -350,25 +371,33 @@ JMap Map() :
{
JType jt1;
JType jt2;
+ Token t;
}
{
-
+ t =
jt1 = Type()
jt2 = Type()
- { return new JMap(jt1, jt2); }
+ {
+ tmpToken = t;
+ return new JMap(jt1, jt2);
+ }
}
JVector Vector() :
{
JType jt;
+ Token t;
}
{
-
+ t =
jt = Type()
- { return new JVector(jt); }
+ {
+ tmpToken = t;
+ return new JVector(jt);
+ }
}
diff --git a/zookeeper-jute/src/test/java/org/apache/jute/compiler/JRecordTest.java b/zookeeper-jute/src/test/java/org/apache/jute/compiler/JRecordTest.java
new file mode 100644
index 00000000000..8247b7b23f2
--- /dev/null
+++ b/zookeeper-jute/src/test/java/org/apache/jute/compiler/JRecordTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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"
+ + " }\n"
+ + "}";
+
+ try (StringReader stringReader = new StringReader(juteStr)) {
+ Rcc parser = new Rcc(stringReader);
+ JFile jFile = parser.Input();
+ List mRecords = getField(jFile, "mRecords", List.class);
+ assertEquals(1, mRecords.size());
+
+ JRecord jRecord = mRecords.get(0);
+ assertEquals("StatPersisted", jRecord.getName());
+ List fields = jRecord.getFields();
+ assertFiled(fields);
+
+ assertEquals("\n// information explicitly stored by the server persistently\n", jRecord.getRecordComments());
+ assertEquals("\n // created zxid\n", jRecord.getJavaFieldComments(fields.get(0)));
+ assertEquals("\n // last modified zxid\n", jRecord.getJavaFieldComments(fields.get(1)));
+ assertEquals("\n // created\n", jRecord.getJavaFieldComments(fields.get(2)));
+ assertEquals("\n // last modified\n", jRecord.getJavaFieldComments(fields.get(3)));
+ }
+ }
+
+ @Test
+ public void testCommentBeforeLine() throws ParseException, NoSuchFieldException, IllegalAccessException {
+ String juteStr = "module org.apache.zookeeper.data {\n"
+ + " // information explicitly stored by the server persistently\n"
+ + " class StatPersisted {\n"
+ + " // created zxid\n"
+ + " long czxid;\n"
+ + " // last modified zxid\n"
+ + " long mzxid;\n"
+ + " // created\n"
+ + " long ctime;\n"
+ + " // last modified\n"
+ + " long mtime;\n"
+ + " }\n"
+ + "}";
+ try (StringReader stringReader = new StringReader(juteStr)) {
+ Rcc parser = new Rcc(stringReader);
+ JFile jFile = parser.Input();
+ List mRecords = getField(jFile, "mRecords", List.class);
+ assertEquals(1, mRecords.size());
+
+ JRecord jRecord = mRecords.get(0);
+ assertEquals("StatPersisted", jRecord.getName());
+ List fields = jRecord.getFields();
+ assertFiled(fields);
+
+ assertEquals("\n// information explicitly stored by the server persistently\n", jRecord.getRecordComments());
+ assertEquals("\n // created zxid\n", jRecord.getJavaFieldComments(fields.get(0)));
+ assertEquals("\n // last modified zxid\n", jRecord.getJavaFieldComments(fields.get(1)));
+ assertEquals("\n // created\n", jRecord.getJavaFieldComments(fields.get(2)));
+ assertEquals("\n // last modified\n", jRecord.getJavaFieldComments(fields.get(3)));
+ }
+ }
+
+ @Test
+ public void testMultiLineComments() throws ParseException, NoSuchFieldException, IllegalAccessException {
+ String juteStr = "module org.apache.zookeeper.data {\n"
+ + " // information explicitly stored by the server persistently\n"
+ + " class StatPersisted {\n"
+ + " /**\n"
+ + " * created zxid\n"
+ + " */\n"
+ + " long czxid;\n"
+ + " /* last modified zxid */\n"
+ + " long mzxid;\n"
+ + " /*\n"
+ + " * created\n"
+ + " */\n"
+ + " long ctime;\n"
+ + " /*\n"
+ + " last modified\n"
+ + " */"
+ + " long mtime;\n"
+ + " }\n"
+ + "}";
+ try (StringReader stringReader = new StringReader(juteStr)) {
+ Rcc parser = new Rcc(stringReader);
+ JFile jFile = parser.Input();
+ List mRecords = getField(jFile, "mRecords", List.class);
+ assertEquals(1, mRecords.size());
+
+ JRecord jRecord = mRecords.get(0);
+ assertEquals("StatPersisted", jRecord.getName());
+ List fields = jRecord.getFields();
+ assertFiled(fields);
+
+ assertEquals("\n// information explicitly stored by the server persistently\n", jRecord.getRecordComments());
+ assertEquals("\n /**\n * created zxid\n */\n", jRecord.getJavaFieldComments(fields.get(0)));
+ assertEquals("\n /* last modified zxid */\n", jRecord.getJavaFieldComments(fields.get(1)));
+ assertEquals("\n /*\n * created\n */\n", jRecord.getJavaFieldComments(fields.get(2)));
+ assertEquals("\n /*\n last modified\n */\n", jRecord.getJavaFieldComments(fields.get(3)));
+ }
+ }
+
+ private void assertFiled(List fields) {
+ assertEquals(4, 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());
+ }
+
+ private T getField(final Object target,
+ final String fieldName,
+ final Class fieldClassType) throws NoSuchFieldException, IllegalAccessException {
+ Class> targetClazz = target.getClass();
+ Field field = targetClazz.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return fieldClassType.cast(field.get(target));
+ }
+}