Skip to content

Commit

Permalink
dnault#20 add ClassResolver
Browse files Browse the repository at this point in the history
  • Loading branch information
kirkch committed Sep 19, 2020
1 parent 7902c1d commit bf53127
Show file tree
Hide file tree
Showing 11 changed files with 399 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
hs_err_pid*

# IntelliJ IDEA project files
out
*.ipr
*.iml
.idea
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ subprojects { subproject ->
apply plugin: 'com.github.johnrengelman.shadow'

group = "com.github.therapi"
version = "0.11.0"
version = "0.11.1-SNAPSHOT"

repositories {
jcenter()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.github.therapi.runtimejavadoc.internal;

import static java.util.Collections.emptySet;
import com.sun.source.tree.ImportTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import java.util.HashSet;
import java.util.Set;

import com.sun.source.tree.ImportTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import static java.util.Collections.emptySet;

/**
* Implementation in separate class file so it fails gracefully in case
Expand All @@ -31,7 +31,7 @@ public Set<String> getImports(Element element) {
}
Set<String> imports = new HashSet<>();
for (ImportTree importTree : path.getCompilationUnit().getImports()) {
imports.add(importTree.getQualifiedIdentifier().toString());
imports.add(importTree.toString());
}
return imports;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.github.therapi.runtimejavadoc.internal.RuntimeJavadocHelper.constructorsFieldName;
import static com.github.therapi.runtimejavadoc.internal.RuntimeJavadocHelper.elementDocFieldName;
import static com.github.therapi.runtimejavadoc.internal.RuntimeJavadocHelper.elementNameFieldName;
import static com.github.therapi.runtimejavadoc.internal.RuntimeJavadocHelper.enumConstantsFieldName;
import static com.github.therapi.runtimejavadoc.internal.RuntimeJavadocHelper.fieldsFieldName;
import static com.github.therapi.runtimejavadoc.internal.RuntimeJavadocHelper.importsFieldName;
import static com.github.therapi.runtimejavadoc.internal.RuntimeJavadocHelper.isBlank;
import static com.github.therapi.runtimejavadoc.internal.RuntimeJavadocHelper.methodsFieldName;
import static com.github.therapi.runtimejavadoc.internal.RuntimeJavadocHelper.paramTypesFieldName;
Expand Down Expand Up @@ -55,11 +57,13 @@ JsonObject getClassJavadocAsJsonOrNull(TypeElement classElement) {
}

final List<Element> emptyList = Collections.emptyList();
Set<String> imports = ImportUtils.getImports( classElement, processingEnv );
List<Element> enclosedFields = defaultIfNull(children.get(FIELD), emptyList);
List<Element> enclosedEnumConstants = defaultIfNull(children.get(ENUM_CONSTANT), emptyList);
List<Element> enclosedMethods = defaultIfNull(children.get(METHOD), emptyList);
List<Element> encolsedConstructors = defaultIfNull(children.get(CONSTRUCTOR), emptyList);

JsonArray importsJson = asJsonArray(imports);
JsonArray fieldDocs = getJavadocsAsJson(enclosedFields, new FieldJavadocAsJson());
JsonArray enumConstantDocs = getJavadocsAsJson(enclosedEnumConstants, new FieldJavadocAsJson());
JsonArray methodDocs = getJavadocsAsJson(enclosedMethods, new MethodJavadocAsJson());
Expand All @@ -70,6 +74,7 @@ JsonObject getClassJavadocAsJsonOrNull(TypeElement classElement) {
}

JsonObject json = new JsonObject();
json.add(importsFieldName(), importsJson);
json.add(elementDocFieldName(), classDoc);
json.add(fieldsFieldName(), fieldDocs);
json.add(enumConstantsFieldName(), enumConstantDocs);
Expand All @@ -78,6 +83,16 @@ JsonObject getClassJavadocAsJsonOrNull(TypeElement classElement) {
return json;
}

private JsonArray asJsonArray( Set<String> imports ) {
JsonArray jsonArray = new JsonArray();

for ( String imp : imports ) {
jsonArray.add(imp);
}

return jsonArray;
}

private static JsonArray getJavadocsAsJson(List<Element> elements, ElementToJsonFunction createDoc) {
JsonArray jsonArray = new JsonArray();
for (Element e : elements) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package com.github.therapi.runtimejavadoc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;


/**
* Searches a list of imports for a specified class.
*/
public abstract class ClassResolver implements Comparable<ClassResolver> {

public static ClassResolver createClassResolverFor( String sourcePackage, Import...imports ) {
return createClassResolverFor( sourcePackage, Arrays.asList(imports) );
}

public static ClassResolver createClassResolverFor( String sourcePackage, List<Import> imports ) {
return new CompositeClassResolver( createClassResolversFor(sourcePackage,imports) );
}


private final int comparisonOrder;

protected ClassResolver( int comparisonOrder ) {
this.comparisonOrder = comparisonOrder;
}

/**
* Do ones best to convert classRef into an instance of java.lang.Class.
*
* @return null if no match was found.
*/
public abstract Class resolveClass( String classRef );


public int compareTo( ClassResolver o ) {
return Integer.compare( this.comparisonOrder, o.comparisonOrder );
}


private static Class fetchClass( String fqn ) {
try {
return Class.forName( fqn );
} catch ( ClassNotFoundException e ) {
return null;
}
}


private static List<ClassResolver> createClassResolversFor( String sourcePackage, List<Import> imports ) {
List<ClassResolver> classResolvers = new ArrayList<>();

classResolvers.add( new FQNResolver() );
classResolvers.add( new FullPackageResolver(sourcePackage) );
classResolvers.add( new FullPackageResolver("java.lang") );

for ( Import declaredImport : imports ) {
ClassResolver newResolver = createClassResolverFor( declaredImport );

if ( newResolver != null ) {
classResolvers.add( newResolver );
}
}

// ensure that the fully qualified comparators appear ahead of the 'all in package' imports
Collections.sort(classResolvers);

return classResolvers;
}

private static ClassResolver createClassResolverFor( Import declaredImport ) {
if ( declaredImport.getStaticMember() == null ) {
if ( "*".equals(declaredImport.getClassName()) ) {
return new FullPackageResolver(declaredImport.getPkg());
} else {
Class importedClass = fetchClass( declaredImport.getFullyQualifiedClass() );

if ( importedClass != null ) {
return new ExactClassResolver(declaredImport.getClassName(), importedClass);
}
}
}

return null;
}


/**
* Given a list of ClassResolvers, return the result of the first one that returns a non-null
* result.
*/
private static class CompositeClassResolver extends ClassResolver {
private List<ClassResolver> classResolvers;

private CompositeClassResolver( List<ClassResolver> classResolvers ) {
super(0);

this.classResolvers = classResolvers;
}

public Class resolveClass( String classRef ) {
for ( ClassResolver resolver : classResolvers ) {
Class resolvedClass = resolver.resolveClass( classRef );

if ( resolvedClass != null ) {
return resolvedClass;
}
}

return null;
}
}

/**
* Resolves absolute class refs (eg x.y.Z).
*/
private static class FQNResolver extends ClassResolver {
public FQNResolver() {
super(1);
}

public Class resolveClass( String ref ) {
return fetchClass(ref);
}
}

/**
* Given 'import x.y.Z', match relative class refs that equal 'Z' and return the class
* for x.y.Z.
*/
private static class ExactClassResolver extends ClassResolver {
private String targetRelativeRef;
private Class explicitClass;

private ExactClassResolver( String targetRelativeRef, Class matchingClass ) {
super(2);

this.targetRelativeRef = targetRelativeRef;
this.explicitClass = matchingClass;
}

public Class resolveClass( String ref ) {
if ( !Objects.equals(targetRelativeRef, ref) ) {
return null;
}

return explicitClass;
}
}
/**
* Given 'import x.y.*', attempt to load any class ref as though it is within package x.y.
*/
private static class FullPackageResolver extends ClassResolver {

private String basePackage;

private FullPackageResolver( String basePackage ) {
super(3);

this.basePackage = basePackage;
}
public Class resolveClass( String ref ) {
return fetchClass(basePackage + "." + ref);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.github.therapi.runtimejavadoc;

import java.util.Objects;


public final class Import {
/**
* The imported package, for example com.bar.foo. A null value means that the root package.
*/
private String pkg;

/**
* The name of the imported class, for example Bar. If all classes in the package are to be
* imported, then className will be set to '*'. className will never be set to null.
*/
private String className;

/**
* The static field or method from the class. Will be set to null when there no static
* members have been imported, and '*' when all static members of the class are to be imported.
*/
private String staticMember;


public Import( String pkg, String className ) {
this(pkg,className,null);
}

public Import( String pkg, String className, String staticMember ) {
this.pkg = pkg;
this.className = className;
this.staticMember = staticMember;
}

public String getPkg() {
return pkg;
}

public String getClassName() {
return className;
}

public String getStaticMember() {
return staticMember;
}

public boolean importsAllClassesWithinPackage() {
return "*".equals( className );
}

public boolean importsAllStaticMembersWithingClass() {
return "*".equals( staticMember );
}

public String getFullyQualifiedClass() {
if ( importsAllClassesWithinPackage() ) {
return null;
}

return pkg == null ? className : pkg + '.' + className;
}


public boolean equals( Object o ) {
if ( this == o )
return true;
if ( o == null || getClass() != o.getClass() )
return false;
Import anImport = (Import) o;
return Objects.equals( pkg, anImport.pkg ) &&
Objects.equals( className, anImport.className ) &&
Objects.equals( staticMember, anImport.staticMember );
}

public int hashCode() {
return Objects.hash( pkg, className, staticMember );
}
//TODO test other methods
//TODO wire up to AST parser
//TODO write to json output
public String toString() {
StringBuilder buf = new StringBuilder();

if ( pkg == null ) {
buf.append( className );
} else {
buf.append( pkg );
buf.append( '.' );
buf.append( className );
}

if ( staticMember != null ) {
buf.append( '.' );
buf.append( staticMember );
}

return buf.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,8 @@ public static String elementNameFieldName() {
public static String elementDocFieldName() {
return "doc";
}

public static String importsFieldName() {
return "imports";
}
}
Loading

0 comments on commit bf53127

Please sign in to comment.