Skip to content

Commit

Permalink
added first version of JavaDoc analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
sdaschner committed Nov 21, 2016
1 parent a1aba94 commit fa9a3de
Show file tree
Hide file tree
Showing 13 changed files with 269 additions and 36 deletions.
9 changes: 9 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
</developers>

<dependencies>

<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>

<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
*/
public class JAXRSAnalyzer {

private final Set<Path> projectPaths = new HashSet<>();
private final Set<Path> projectClassPaths = new HashSet<>();
private final Set<Path> projectSourcePaths = new HashSet<>();
private final Set<Path> classPaths = new HashSet<>();
private final String projectName;
private final String projectVersion;
Expand All @@ -31,25 +32,28 @@ public class JAXRSAnalyzer {
/**
* Constructs a JAX-RS Analyzer.
*
* @param projectPaths The paths of the projects to be analyzed (can either be directories or jar-files, at least one is mandatory)
* @param classPaths The additional class paths (can either be directories or jar-files)
* @param projectName The project name
* @param projectVersion The project version
* @param backend The backend to render the output
* @param outputLocation The location of the output file (output will be printed to standard out if {@code null})
* @param projectClassPaths The paths of the projects classes to be analyzed (can either be directories or jar-files, at least one is mandatory)
* @param projectSourcePaths The paths of the projects sources to be analyzed (can either be directories or jar-files, optional)
* @param classPaths The additional class paths (can either be directories or jar-files)
* @param projectName The project name
* @param projectVersion The project version
* @param backend The backend to render the output
* @param outputLocation The location of the output file (output will be printed to standard out if {@code null})
*/
public JAXRSAnalyzer(final Set<Path> projectPaths, final Set<Path> classPaths, final String projectName, final String projectVersion,
public JAXRSAnalyzer(final Set<Path> projectClassPaths, final Set<Path> projectSourcePaths, final Set<Path> classPaths, final String projectName, final String projectVersion,
final Backend backend, final Path outputLocation) {
Objects.requireNonNull(projectPaths);
Objects.requireNonNull(projectClassPaths);
Objects.requireNonNull(projectSourcePaths);
Objects.requireNonNull(classPaths);
Objects.requireNonNull(projectName);
Objects.requireNonNull(projectVersion);
Objects.requireNonNull(backend);

if (projectPaths.isEmpty())
if (projectClassPaths.isEmpty())
throw new IllegalArgumentException("At least one project path is mandatory");

this.projectPaths.addAll(projectPaths);
this.projectClassPaths.addAll(projectClassPaths);
this.projectSourcePaths.addAll(projectSourcePaths);
this.classPaths.addAll(classPaths);
this.projectName = projectName;
this.projectVersion = projectVersion;
Expand All @@ -61,7 +65,7 @@ public JAXRSAnalyzer(final Set<Path> projectPaths, final Set<Path> classPaths, f
* Analyzes the JAX-RS project at the class path and produces the output as configured.
*/
public void analyze() {
final Resources resources = new ProjectAnalyzer(classPaths.toArray(new Path[classPaths.size()])).analyze(projectPaths.toArray(new Path[projectPaths.size()]));
final Resources resources = new ProjectAnalyzer(classPaths).analyze(projectClassPaths, projectSourcePaths);

if (resources.isEmpty()) {
LogProvider.info("Empty JAX-RS analysis result, omitting output");
Expand Down
14 changes: 10 additions & 4 deletions src/main/java/com/sebastian_daschner/jaxrs_analyzer/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public class Main {
private static final String DEFAULT_NAME = "project";
private static final String DEFAULT_VERSION = "0.1-SNAPSHOT";

private static final Set<Path> projectPaths = new HashSet<>();
private static final Set<Path> projectClassPaths = new HashSet<>();
private static final Set<Path> projectSourcePaths = new HashSet<>();
private static final Set<Path> classPaths = new HashSet<>();
private static String name = DEFAULT_NAME;
private static String version = DEFAULT_VERSION;
Expand All @@ -61,6 +62,7 @@ public class Main {
* <ul>
* <li>{@code -b backend} The backend to choose: {@code swagger} (default), {@code plaintext}, {@code asciidoc}</li>
* <li>{@code -cp class path[:class paths...]} The additional class paths which contain classes which are used in the project</li>
* <li>{@code -sp source path[:source paths...]} The optional source paths needed for JavaDoc analysis</li>
* <li>{@code -X} Debug enabled (prints error debugging information on Standard error out)</li>
* <li>{@code -n project name} The name of the project</li>
* <li>{@code -v project version} The version of the project</li>
Expand Down Expand Up @@ -93,7 +95,7 @@ public static void main(final String... args) {

final Backend backend = constructBackend();

final JAXRSAnalyzer jaxrsAnalyzer = new JAXRSAnalyzer(projectPaths, classPaths, name, version, backend, outputFileLocation);
final JAXRSAnalyzer jaxrsAnalyzer = new JAXRSAnalyzer(projectClassPaths, projectSourcePaths, classPaths, name, version, backend, outputFileLocation);
jaxrsAnalyzer.analyze();
}

Expand All @@ -108,6 +110,9 @@ private static void extractArgs(String[] args) {
case "-cp":
extractClassPaths(args[++i]).forEach(classPaths::add);
break;
case "-sp":
extractClassPaths(args[++i]).forEach(projectSourcePaths::add);
break;
case "-X":
LogProvider.injectDebugLogger(System.err::println);
break;
Expand Down Expand Up @@ -141,7 +146,7 @@ private static void extractArgs(String[] args) {
System.err.println("Location " + path.toFile() + " doesn't exist\n");
printUsageAndExit();
}
projectPaths.add(path);
projectClassPaths.add(path);
}
}
} catch (IndexOutOfBoundsException e) {
Expand Down Expand Up @@ -201,7 +206,7 @@ private static void validateArgs() {
printUsageAndExit();
}

if (projectPaths.isEmpty()) {
if (projectClassPaths.isEmpty()) {
System.err.println("Please provide at least one project path\n");
printUsageAndExit();
}
Expand Down Expand Up @@ -244,6 +249,7 @@ private static void printUsageAndExit() {
System.err.println("Following available options:\n");
System.err.println(" -b <backend> The backend to choose: swagger (default), plaintext, asciidoc");
System.err.println(" -cp <class path>[:class paths] Additional class paths (separated with colon) which contain classes used in the project (may be directories or jar-files)");
System.err.println(" -sp <source path>[:source paths] Optional source paths (separated with colon) needed for JavaDoc analysis (may be directories or jar-files)");
System.err.println(" -X Debug enabled (enabled error debugging information)");
System.err.println(" -n <project name> The name of the project");
System.err.println(" -v <project version> The version of the project");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.sebastian_daschner.jaxrs_analyzer.LogProvider;
import com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.BytecodeAnalyzer;
import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.JAXRSClassVisitor;
import com.sebastian_daschner.jaxrs_analyzer.analysis.javadoc.JavaDocAnalyzer;
import com.sebastian_daschner.jaxrs_analyzer.analysis.results.ResultInterpreter;
import com.sebastian_daschner.jaxrs_analyzer.model.rest.Resources;
import com.sebastian_daschner.jaxrs_analyzer.model.results.ClassResult;
Expand All @@ -41,7 +42,6 @@
import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;

import static com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils.isAnnotationPresent;

Expand All @@ -60,29 +60,33 @@ public class ProjectAnalyzer {

private final Lock lock = new ReentrantLock();
private final Set<String> classes = new HashSet<>();
private final Set<String> packages = new HashSet<>();
private final ResultInterpreter resultInterpreter = new ResultInterpreter();
private final BytecodeAnalyzer bytecodeAnalyzer = new BytecodeAnalyzer();
private final JavaDocAnalyzer javaDocAnalyzer = new JavaDocAnalyzer();
private final URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();

/**
* Creates a project analyzer with given class path locations where to search for classes.
*
* @param classPaths The locations of additional class paths (can be directories or jar-files)
*/
public ProjectAnalyzer(final Path... classPaths) {
Stream.of(classPaths).forEach(this::addToClassPool);
public ProjectAnalyzer(final Set<Path> classPaths) {
classPaths.forEach(this::addToClassPool);
addToClassPool(Paths.get(System.getProperty("java.home"), "..", "lib", "tools.jar"));
}

/**
* Analyzes all classes in the given project path.
*
* @param projectPaths The project paths
* @param projectClassPaths The project class paths
* @param projectSourcePaths The project source file paths
* @return The REST resource representations
*/
public Resources analyze(final Path... projectPaths) {
public Resources analyze(final Set<Path> projectClassPaths, final Set<Path> projectSourcePaths) {
lock.lock();
try {
Stream.of(projectPaths).forEach(this::addProjectPath);
projectClassPaths.forEach(this::addProjectPath);

// analyze relevant classes
final JobRegistry jobRegistry = JobRegistry.getInstance();
Expand All @@ -100,6 +104,8 @@ public Resources analyze(final Path... projectPaths) {
bytecodeAnalyzer.analyzeBytecode(classResult);
}

javaDocAnalyzer.analyze(classResults, packages, projectSourcePaths);

return resultInterpreter.interpret(classResults);
} finally {
lock.unlock();
Expand Down Expand Up @@ -172,9 +178,12 @@ private void addJarClasses(final Path location) {
try (final JarFile jarFile = new JarFile(location.toFile())) {
final Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
final String entryName = entries.nextElement().getName();
final JarEntry entry = entries.nextElement();
final String entryName = entry.getName();
if (entryName.endsWith(".class"))
classes.add(convertToQualifiedName(entryName));
classes.add(toQualifiedClassName(entryName));
else if (entry.isDirectory())
packages.add(entryName);
}
} catch (IOException e) {
throw new IllegalArgumentException("Could not read jar-file '" + location + "', reason: " + e.getMessage());
Expand All @@ -192,8 +201,9 @@ private void addDirectoryClasses(final Path location, final Path subPath) {
if (file.isDirectory())
addDirectoryClasses(location.resolve(file.getName()), subPath.resolve(file.getName()));
else if (file.isFile() && file.getName().endsWith(".class")) {
packages.add(toQualifiedPackageName(subPath.toString()));
final String classFileName = subPath.resolve(file.getName()).toString();
classes.add(convertToQualifiedName(classFileName));
classes.add(toQualifiedClassName(classFileName));
}
}
}
Expand All @@ -204,9 +214,19 @@ else if (file.isFile() && file.getName().endsWith(".class")) {
* @param fileName The file name (e.g. a/package/AClass.class)
* @return The fully-qualified class name (e.g. a.package.AClass)
*/
private static String convertToQualifiedName(final String fileName) {
private static String toQualifiedClassName(final String fileName) {
final String replacedSeparators = fileName.replace(File.separatorChar, '.');
return replacedSeparators.substring(0, replacedSeparators.length() - ".class".length());
}

/**
* Converts the given path name of a directory to the fully-qualified package name.
*
* @param pathName The directory name (e.g. a/package/)
* @return The fully-qualified package name (e.g. a.package)
*/
private static String toQualifiedPackageName(final String pathName) {
return pathName.replace(File.separatorChar, '.');
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.sebastian_daschner.jaxrs_analyzer.analysis.javadoc;

import com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils;
import com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.RootDoc;

import java.util.stream.Stream;

import static com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils.getMethodSignature;
import static com.sebastian_daschner.jaxrs_analyzer.model.Types.*;
import static com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier.of;

/**
* @author Sebastian Daschner
*/
public class JAXRSDoclet {

public static boolean start(RootDoc rootDoc) {
Stream.of(rootDoc.classes()).forEach(JAXRSDoclet::handleClassDoc);
return true;
}

private static void handleClassDoc(final ClassDoc classDoc) {
final String className = toClassName(classDoc.qualifiedName());
JavaDocAnalyzer.put(className, classDoc);
Stream.of(classDoc.methods()).forEach(m -> handleMethodDoc(m, className));
}

private static void handleMethodDoc(final MethodDoc methodDoc, final String className) {
final String[] parameterTypes = Stream.of(methodDoc.parameters())
.map(p -> p.type().qualifiedTypeName())
.map(JAXRSDoclet::toType)
.toArray(String[]::new);

final String returnType = toType(methodDoc.returnType().qualifiedTypeName());
final String signature = getMethodSignature(returnType, parameterTypes);

final MethodIdentifier identifier = of(className, methodDoc.name(), signature, methodDoc.isStatic());
JavaDocAnalyzer.put(identifier, methodDoc);
}

private static String toClassName(final String qualifiedName) {
return qualifiedName.replace('.', '/');
}

private static String toType(final String qualifiedName) {
switch (qualifiedName) {
case CLASS_PRIMITIVE_VOID:
return PRIMITIVE_VOID;
case CLASS_PRIMITIVE_BOOLEAN:
return PRIMITIVE_BOOLEAN;
case CLASS_PRIMITIVE_CHAR:
return PRIMITIVE_CHAR;
case CLASS_PRIMITIVE_INT:
return PRIMITIVE_INT;
case CLASS_PRIMITIVE_BYTE:
return PRIMITIVE_BYTE;
case CLASS_PRIMITIVE_SHORT:
return PRIMITIVE_SHORT;
case CLASS_PRIMITIVE_DOUBLE:
return PRIMITIVE_DOUBLE;
case CLASS_PRIMITIVE_FLOAT:
return PRIMITIVE_FLOAT;
case CLASS_PRIMITIVE_LONG:
return PRIMITIVE_LONG;
default:
return JavaUtils.toType(toClassName(qualifiedName));
}
}

}
Loading

0 comments on commit fa9a3de

Please sign in to comment.