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)); + } +}