package org.opentrafficsim;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.CallableDeclaration;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.comments.JavadocComment;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
/**
* Utility to add or update the type foe each parameter in the javadoc of all java files in /src/main/java in all or in selected
* projects in the workspace. Run this utility only from Eclipse!
*
* Copyright (c) 2003-2020 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information www.simulation.tudelft.nl. The
* source code and binary code of this software is proprietary information of Delft University of Technology.
* @author Alexander Verbraeck
*/
public class ParamComments
{
/** the lines of the file. */
private List lines;
/** file changed? */
private boolean changed;
/**
* @param args String[]; none
* @throws IOException on I/O error
* @throws URISyntaxException on I/O error
*/
public static void main(final String[] args) throws IOException, URISyntaxException
{
new ParamComments();
}
/**
* @throws IOException on I/O error
* @throws URISyntaxException on I/O error
*/
public ParamComments() throws IOException, URISyntaxException
{
File classFolder = new File(ParamComments.class.getResource("/").toURI());
File workspaceFolder = classFolder.getParentFile().getParentFile().getParentFile();
for (File projectFolder : workspaceFolder.listFiles())
{
if (projectFolder.isDirectory() && projectFolder.getName().startsWith("ots")
&& new File(projectFolder, "src/main/java").exists())
{
File sourcePathFile = new File(projectFolder, "src/main/java");
for (File srcFolder : sourcePathFile.listFiles())
{
processDirOrFile(srcFolder);
}
}
}
}
/**
* @param srcFolder File; folder to look for subfolders and/or java files
* @throws IOException on i/o error
*/
private void processDirOrFile(final File srcFolder) throws IOException
{
if (srcFolder.isDirectory())
{
for (File subFile : srcFolder.listFiles())
{
if (subFile.isDirectory())
{
processDirOrFile(subFile);
}
else if (subFile.getName().endsWith(".java") && !subFile.getName().startsWith("package-info")
&& !srcFolder.getName().contains("generated") && !subFile.getName().contains("ProtoBuf")
&& !srcFolder.getName().contains(".proto"))
{
processJavaFile(subFile);
}
}
}
}
/**
* @param javaFile File; java file to process
* @throws IOException on error
*/
private void processJavaFile(final File javaFile) throws IOException
{
System.out.println("\n" + javaFile.toURI().getPath());
this.changed = false;
this.lines = Files.readAllLines(Paths.get(javaFile.toURI()), StandardCharsets.UTF_8);
FileInputStream in = new FileInputStream(javaFile.toURI().getPath());
CompilationUnit cu = JavaParser.parse(in);
cu.accept(new CodeVisitor(this), null);
if (this.changed)
{
Files.write(Paths.get(javaFile.toURI()), this.lines);
System.out.println("CHANGED AND WRITTEN: " + javaFile.toString());
}
}
/**
* @return changed
*/
public final boolean isChanged()
{
return this.changed;
}
/**
* @param changed boolean; set changed
*/
public final void setChanged(final boolean changed)
{
this.changed = changed;
}
/**
* @return lines
*/
public final List getLines()
{
return this.lines;
}
/**
* Simple visitor implementation for visiting MethodDeclaration and ConstructorDeclaration nodes.
*/
protected static class CodeVisitor extends VoidVisitorAdapter
{
/** access to the lines of the file and to changed toggle. */
private ParamComments paramComments;
/**
* Constructor of the code visitor.
* @param paramComments ParamComments; class to be processed
*/
protected CodeVisitor(final ParamComments paramComments)
{
this.paramComments = paramComments;
}
@Override
/**
* {@inheritDoc}
* This method will be called for all constructors in this CompilationUnit, including constructors of inner classes.
*/
public void visit(final ConstructorDeclaration constructorDeclaration, final Void arg)
{
System.out.println("\n------\nCONSTRUCTOR\n" + " " + constructorDeclaration.getName() + " : "
+ constructorDeclaration.getDeclarationAsString());
processDeclaration(constructorDeclaration);
super.visit(constructorDeclaration, arg);
}
@Override
/**
* {@inheritDoc}
* This method will be called for all methods in this CompilationUnit, including inner class methods.
*/
public void visit(final MethodDeclaration methodDeclaration, final Void arg)
{
System.out
.println("\n------\n" + " " + methodDeclaration.getName() + " : " + methodDeclaration.getTypeAsString());
processDeclaration(methodDeclaration);
super.visit(methodDeclaration, arg);
}
/**
* Carry out the changes in the method comments or constructor comments.
* @param callableDeclaration CallableDeclaration<?>; the method declaration or constructor declaration
*/
private void processDeclaration(final CallableDeclaration> callableDeclaration)
{
for (Parameter parameter : callableDeclaration.getParameters())
{
System.out.println(" " + parameter.getNameAsString() + " : " + parameter.getTypeAsString()
+ (parameter.isVarArgs() ? "..." : ""));
}
if (callableDeclaration.getComment().isPresent())
{
System.out.print(callableDeclaration.getComment().get());
String parserComment = callableDeclaration.getComment().get().toString();
if (parserComment.contains("@param"))
{
// see how we would rebuild the comment
if (callableDeclaration.getComment().get().toJavadocComment().isPresent())
{
JavadocComment comment = callableDeclaration.getComment().get().toJavadocComment().get();
String[] commentLines = parserComment.split("\n");
for (String line : commentLines)
{
if (line.contains("@param") && !line.contains("@param <"))
{
// which line is it in the String[] model?
int fileLine = -1;
for (int lnr = 0; lnr < commentLines.length; lnr++)
{
if (this.paramComments.getLines().get(comment.getBegin().get().line + lnr).trim()
.equals(line.trim()))
{
fileLine = comment.getBegin().get().line + lnr;
}
}
if (fileLine == -1)
{
System.out.println("COULD NOT FIND LINE FROM COMMENT IN FILE LINES...");
break;
}
line = line.replaceAll("<", "<").replaceAll(">", ">");
// make a line without escaped HTML-sequences
String noHtmlLine = line.replaceAll("&\\w+;", "");
// variable name after @param
int paramIndex = line.indexOf("@param");
int varEndIndex = line.indexOf(' ', paramIndex + 7) == -1 ? line.length()
: line.indexOf(' ', paramIndex + 7);
String varName = line.substring(paramIndex + 6, varEndIndex).trim();
boolean found = false;
for (Parameter parameter : callableDeclaration.getParameters())
{
if (parameter.getNameAsString().equals(varName))
{
// see if type is there with a ; at the end
String varType = "";
if (noHtmlLine.indexOf(";", varEndIndex + 1) != -1)
{
varType = noHtmlLine
.substring(varEndIndex + 1, noHtmlLine.indexOf(';', varEndIndex + 1)).trim()
.replaceAll(", ", ",");
String parameterType =
parameter.getType().asString() + (parameter.isVarArgs() ? "..." : "");
// if there are spaces in the varType, we either have a type with spaces
// e.g., Type, or we have no variable but a ; later in the line,
// e.g., @param var this is the Bézier variable. In these cases,
// we have to be super careful and not replace without warning...
if (!varType.equals(parameterType))
{
if (varType.contains(" "))
{
System.out.println("NO CHANGE - SPACES IN TYPE : "
+ line.replaceAll("<", "<").replaceAll(">", ">").trim());
}
else
{
parameterType = parameterType.replaceAll(",", ", ");
line = line.substring(0, varEndIndex) + " " + parameterType
+ line.substring(line.indexOf(";", varEndIndex + 1));
line = line.replaceAll("<", "<").replaceAll(">", ">");
System.out.println("CHANGED TYPE : " + line.trim());
this.paramComments.setChanged(true);
this.paramComments.getLines().set(fileLine, line.replaceAll("\\n", ""));
}
}
}
else
{
String parameterType = parameter.getType().asString()
+ (parameter.isVarArgs() ? "..." : "").replaceAll(",", ", ");
line = line.substring(0, varEndIndex) + " " + parameterType + "; "
+ line.substring(Math.min(varEndIndex + 1, line.length() - 1));
line = line.replaceAll("<", "<").replaceAll(">", ">");
System.out.println("ADDED TYPE : " + line.trim());
this.paramComments.setChanged(true);
this.paramComments.getLines().set(fileLine, line.replaceAll("\\n", ""));
}
found = true;
break;
}
}
if (!found)
{
System.out.println("XXXXXXXX @param comment for " + varName
+ " does not match any parameters in method");
}
}
}
}
}
}
}
}
}