mirror of
https://github.com/google/nomulus.git
synced 2025-04-29 19:47:51 +02:00
Migrate the documentation package to Java 11 (#729)
* Migrate the documentation package to Java 11 The old Doclet API is deprected and removed in Java 12. This commit changes the documentation package to use the new recommended API. However it is not a drop-in replacement and there are non-idiomatic usages all over the place. I think it is eaiser to keep the current code logic and kind of shoehorn in the new API than starting afresh as the return on investment of a do-over is not great. Also note that the docs package is disabled as of this commit because we are still using Java 8 to compile which lacks the new API. Once we switch our toolchains to Java 11 (but still compiling Java 8 bytecode) we can re-enable this package. TESTED=ran `./gradlew :docs:test` locally with the documentation package enabled.
This commit is contained in:
parent
a1f4e8b985
commit
fba8af0485
11 changed files with 304 additions and 259 deletions
|
@ -31,6 +31,7 @@ dependencies {
|
|||
|
||||
task flowDocsTool(type: JavaExec) {
|
||||
systemProperty 'test.projectRoot', rootProject.projectRootDir
|
||||
jvmArgs = ['--add-exports', 'jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED']
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
main = 'google.registry.documentation.FlowDocumentationTool'
|
||||
|
||||
|
@ -42,3 +43,14 @@ task flowDocsTool(type: JavaExec) {
|
|||
}
|
||||
args arguments
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
options.compilerArgs = ["--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
|
||||
"--add-exports", "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
|
||||
"--add-exports", "jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED"]
|
||||
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
jvmArgs = ['--add-exports', 'jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED']
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ An EPP flow that checks whether a contact can be provisioned.
|
|||
|
||||
This flows can check the existence of multiple contacts simultaneously.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2306
|
||||
|
@ -20,7 +19,6 @@ This flows can check the existence of multiple contacts simultaneously.
|
|||
|
||||
An EPP flow that creates a new contact.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2005
|
||||
|
@ -43,7 +41,6 @@ as all existing domains must be checked for references to the host before the
|
|||
deletion is allowed to proceed. A poll message will be written with the success
|
||||
or failure message when the process is complete.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2201
|
||||
|
@ -67,7 +64,6 @@ contact's most recent transfer if it has ever been transferred. Any registrar
|
|||
can see any contact's information, but the authInfo is only visible to the
|
||||
registrar that owns the contact or to a registrar that already supplied it.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2201
|
||||
|
@ -87,7 +83,6 @@ default five days) after which the transfer is automatically approved. Within
|
|||
that window, this flow allows the losing client to explicitly approve the
|
||||
transfer request, which then becomes effective immediately.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2201
|
||||
|
@ -111,7 +106,6 @@ default five days) after which the transfer is automatically approved. Within
|
|||
that window, this flow allows the gaining client to withdraw the transfer
|
||||
request.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2201
|
||||
|
@ -137,7 +131,6 @@ authId) to see the status of a transfer, which may still be pending or may have
|
|||
been approved, rejected, cancelled or implicitly approved by virtue of the
|
||||
transfer period expiring.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2002
|
||||
|
@ -160,7 +153,6 @@ registrar. The losing registrar has a "transfer" time period to respond (by
|
|||
default five days) after which the transfer is automatically approved. Within
|
||||
that window, this flow allows the losing client to reject the transfer request.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2201
|
||||
|
@ -184,7 +176,6 @@ default five days) after which the transfer is automatically approved. Within
|
|||
that window, the transfer might be approved explicitly by the losing registrar
|
||||
or rejected, and the gaining registrar can also cancel the transfer request.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2002
|
||||
|
@ -206,7 +197,6 @@ or rejected, and the gaining registrar can also cancel the transfer request.
|
|||
|
||||
An EPP flow that updates a contact.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2004
|
||||
|
@ -234,7 +224,6 @@ An EPP flow that checks whether a domain can be provisioned.
|
|||
This flow also supports the EPP fee extension and can return pricing
|
||||
information.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2002
|
||||
|
@ -273,7 +262,6 @@ information.
|
|||
|
||||
An EPP flow that checks whether domain labels are trademarked.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2002
|
||||
|
@ -294,7 +282,6 @@ An EPP flow that checks whether domain labels are trademarked.
|
|||
|
||||
An EPP flow that creates a new domain resource.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2002
|
||||
|
@ -391,7 +378,6 @@ An EPP flow that creates a new domain resource.
|
|||
|
||||
An EPP flow that deletes a domain.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2002
|
||||
|
@ -420,7 +406,6 @@ authInfo for the domain, will get a rich result with all of the domain's fields.
|
|||
All other requests will be answered with a minimal result containing only basic
|
||||
information about the domain.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2004
|
||||
|
@ -452,7 +437,6 @@ In practice this means it's impossible to request a ten year renewal, since that
|
|||
will always cause the new registration to be longer than 10 years unless it
|
||||
comes in at the exact millisecond that the domain would have expired.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2003
|
||||
|
@ -508,7 +492,6 @@ Restores cost a fixed restore fee plus a one year renewal fee for the domain.
|
|||
The domain is restored to a single year expiration starting at the restore time,
|
||||
regardless of what the original expiration time was.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2002
|
||||
|
@ -559,7 +542,6 @@ Datastore with timestamps such that they only would become active when the
|
|||
transfer period passed. In this flow, those speculative objects are deleted and
|
||||
replaced with new ones with the correct approval time.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2201
|
||||
|
@ -588,7 +570,6 @@ When the transfer was requested, poll messages and billing events were saved to
|
|||
Datastore with timestamps such that they only would become active when the
|
||||
transfer period passed. In this flow, those speculative objects are deleted.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2201
|
||||
|
@ -615,7 +596,6 @@ authId) to see the status of a transfer, which may still be pending or may have
|
|||
been approved, rejected, cancelled or implicitly approved by virtue of the
|
||||
transfer period expiring.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2002
|
||||
|
@ -642,7 +622,6 @@ When the transfer was requested, poll messages and billing events were saved to
|
|||
Datastore with timestamps such that they only would become active when the
|
||||
transfer period passed. In this flow, those speculative objects are deleted.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2201
|
||||
|
@ -674,7 +653,6 @@ the domain's transfer data, and on explicit approval, rejection or cancellation
|
|||
of the request, they will be deleted (and in the approval case, replaced with
|
||||
new ones with the correct approval time).
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2002
|
||||
|
@ -729,7 +707,6 @@ applied by the superuser. As such, adding or removing these statuses incurs a
|
|||
billing event. There will be only one charge per update, even if several such
|
||||
statuses are updated at once.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2003
|
||||
|
@ -777,7 +754,6 @@ statuses are updated at once.
|
|||
|
||||
A flow for an Epp "hello".
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2001
|
||||
|
@ -791,7 +767,6 @@ An EPP flow that checks whether a host can be provisioned.
|
|||
|
||||
This flows can check the existence of multiple hosts simultaneously.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2306
|
||||
|
@ -809,7 +784,6 @@ external hosts are all other hosts. Internal hosts must have at least one ip
|
|||
address associated with them, whereas external hosts cannot have any. This flow
|
||||
allows creating a host name, and if necessary enqueues tasks to update DNS.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2003
|
||||
|
@ -845,7 +819,6 @@ as all existing domains must be checked for references to the host before the
|
|||
deletion is allowed to proceed. A poll message will be written with the success
|
||||
or failure message when the process is complete.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2005
|
||||
|
@ -871,7 +844,6 @@ The returned information included IP addresses, if any, and details of the
|
|||
host's most recent transfer if it has ever been transferred. Any registrar can
|
||||
see the information for any host.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2005
|
||||
|
@ -898,7 +870,6 @@ simultaneously removed, and when it is renamed from external to internal at
|
|||
least one must be added. If the host is renamed or IP addresses are added, tasks
|
||||
are enqueued to update DNS accordingly.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2004
|
||||
|
@ -937,7 +908,6 @@ are enqueued to update DNS accordingly.
|
|||
|
||||
An EPP flow for login.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2002
|
||||
|
@ -969,7 +939,6 @@ An EPP flow for login.
|
|||
|
||||
An EPP flow for logout.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2002
|
||||
|
@ -986,7 +955,6 @@ Registrars refer to poll messages using an externally visible id generated by
|
|||
Datastore once they are ACKed, whereas autorenew poll messages are simply marked
|
||||
as read, and won't be delivered again until the next year of their recurrence.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2003
|
||||
|
@ -1011,7 +979,6 @@ speculative and could still be changed or rescinded) are ignored. The externally
|
|||
visible id for the poll message that the registrar sees is generated by {@link
|
||||
PollMessageExternalKeyConverter}.
|
||||
|
||||
|
||||
### Errors
|
||||
|
||||
* 2005
|
||||
|
|
|
@ -18,28 +18,28 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
|
|||
import static java.util.Comparator.comparing;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.sun.javadoc.ClassDoc;
|
||||
import com.sun.javadoc.RootDoc;
|
||||
import google.registry.documentation.FlowDocumentation.ErrorCase;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.util.ElementFilter;
|
||||
import jdk.javadoc.doclet.DocletEnvironment;
|
||||
|
||||
/**
|
||||
* Main entry point class for documentation generation. An instance of this class reads data
|
||||
* via the javadoc system upon creation and stores it for answering future queries for
|
||||
* documentation information.
|
||||
* Main entry point class for documentation generation. An instance of this class reads data via the
|
||||
* javadoc system upon creation and stores it for answering future queries for documentation
|
||||
* information.
|
||||
*/
|
||||
public final class DocumentationGenerator {
|
||||
|
||||
private final RootDoc sourceRoot;
|
||||
private final DocletEnvironment sourceRoot;
|
||||
|
||||
/** Returns a new DocumentationGenerator object with parsed information from javadoc. */
|
||||
public DocumentationGenerator() throws IOException {
|
||||
sourceRoot = JavadocWrapper.getRootDoc();
|
||||
public DocumentationGenerator() throws Exception {
|
||||
sourceRoot = JavadocWrapper.getDocletEnv();
|
||||
}
|
||||
|
||||
/** Returns generated Markdown output for the flows. Convenience method for clients. */
|
||||
/** Returns generated Markdown output for the flows. Convenience method for clients. */
|
||||
public String generateMarkdown() {
|
||||
return MarkdownDocumentationFormatter.generateMarkdownOutput(getFlowDocs());
|
||||
}
|
||||
|
@ -48,8 +48,8 @@ public final class DocumentationGenerator {
|
|||
public ImmutableList<FlowDocumentation> getFlowDocs() {
|
||||
// Relevant flows are leaf flows: precisely the concrete subclasses of Flow.
|
||||
return getConcreteSubclassesStream(FlowDocumentation.BASE_FLOW_CLASS_NAME)
|
||||
.sorted(comparing(ClassDoc::typeName))
|
||||
.map(FlowDocumentation::new)
|
||||
.sorted(comparing(element -> element.getSimpleName().toString()))
|
||||
.map(typeElement -> new FlowDocumentation(typeElement, sourceRoot))
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
|
@ -57,14 +57,25 @@ public final class DocumentationGenerator {
|
|||
public ImmutableList<ErrorCase> getAllErrors() {
|
||||
// Relevant error cases are precisely the concrete subclasses of EppException.
|
||||
return getConcreteSubclassesStream(FlowDocumentation.EXCEPTION_CLASS_NAME)
|
||||
.map(ErrorCase::new)
|
||||
.map(
|
||||
typeElement ->
|
||||
new ErrorCase(
|
||||
typeElement,
|
||||
sourceRoot.getDocTrees().getDocCommentTree(typeElement),
|
||||
sourceRoot.getTypeUtils()))
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
/** Helper to return all concrete subclasses of a given named class. */
|
||||
private Stream<ClassDoc> getConcreteSubclassesStream(String baseClassName) {
|
||||
final ClassDoc baseFlowClassDoc = sourceRoot.classNamed(baseClassName);
|
||||
return Arrays.stream(sourceRoot.classes())
|
||||
.filter(classDoc -> classDoc.subclassOf(baseFlowClassDoc) && !classDoc.isAbstract());
|
||||
private Stream<TypeElement> getConcreteSubclassesStream(String baseClassName) {
|
||||
final TypeElement baseFlowTypeElement =
|
||||
sourceRoot.getElementUtils().getTypeElement(baseClassName);
|
||||
return ElementFilter.typesIn(sourceRoot.getIncludedElements()).stream()
|
||||
.filter(
|
||||
typeElement ->
|
||||
sourceRoot
|
||||
.getTypeUtils()
|
||||
.isSubtype(typeElement.asType(), baseFlowTypeElement.asType())
|
||||
&& !typeElement.getModifiers().contains(Modifier.ABSTRACT));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,53 +16,65 @@ package google.registry.documentation;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.MoreCollectors.onlyElement;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import com.sun.javadoc.AnnotationDesc;
|
||||
import com.sun.javadoc.ClassDoc;
|
||||
import com.sun.javadoc.FieldDoc;
|
||||
import com.sun.javadoc.SeeTag;
|
||||
import com.sun.javadoc.Tag;
|
||||
import com.google.re2j.Matcher;
|
||||
import com.google.re2j.Pattern;
|
||||
import com.sun.source.doctree.DocCommentTree;
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import com.sun.source.doctree.DocTree.Kind;
|
||||
import com.sun.source.doctree.LinkTree;
|
||||
import com.sun.source.doctree.ReferenceTree;
|
||||
import com.sun.source.doctree.UnknownBlockTagTree;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.model.eppoutput.Result.Code;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
import jdk.javadoc.doclet.DocletEnvironment;
|
||||
|
||||
/**
|
||||
* Class to represent documentation information for a single EPP flow.
|
||||
*
|
||||
* <p>The static method getFlowDocs() on this class returns a list of FlowDocumentation
|
||||
* instances corresponding to the leaf flows in the flows package, constructing the instances
|
||||
* from class information returned from the javadoc system. Each instance has methods for
|
||||
* retrieving relevant information about the flow, such as a description, error conditions, etc.
|
||||
* <p>The static method {@link DocumentationGenerator#getFlowDocs} returns a list of {@link
|
||||
* FlowDocumentation} instances corresponding to the leaf flows in the flows package, constructing
|
||||
* the instances from class information returned from the javadoc system. Each instance has methods
|
||||
* for retrieving relevant information about the flow, such as a description, error conditions, etc.
|
||||
*/
|
||||
public class FlowDocumentation {
|
||||
|
||||
/** Constants for names of various relevant packages and classes. */
|
||||
static final String FLOW_PACKAGE_NAME = "google.registry.flows";
|
||||
|
||||
static final String BASE_FLOW_CLASS_NAME = FLOW_PACKAGE_NAME + ".Flow";
|
||||
static final String EXCEPTION_CLASS_NAME = FLOW_PACKAGE_NAME + ".EppException";
|
||||
static final String CODE_ANNOTATION_NAME = EXCEPTION_CLASS_NAME + ".EppResultCode";
|
||||
|
||||
/** Name of the class for this flow. */
|
||||
private final String name;
|
||||
/** Root of the source doclet environment. */
|
||||
private final DocletEnvironment sourceRoot;
|
||||
|
||||
/** Fully qualified name of the class for this flow. */
|
||||
private final String qualifiedName;
|
||||
/** Type Element of the class. */
|
||||
private final TypeElement typeElement;
|
||||
|
||||
/** Name of the package in which this flow resides. */
|
||||
private final String packageName;
|
||||
|
||||
/** Class docs for the flow. */
|
||||
private final String classDocs;
|
||||
/** Doc tree for the flow. */
|
||||
private final DocCommentTree docTree;
|
||||
|
||||
/** Javadoc-tagged error conditions for this flow in list form. */
|
||||
private final List<ErrorCase> errors;
|
||||
|
@ -71,35 +83,40 @@ public class FlowDocumentation {
|
|||
private final ListMultimap<Long, ErrorCase> errorsByCode;
|
||||
|
||||
/**
|
||||
* Creates a FlowDocumentation for this flow class using data from javadoc tags. Not public
|
||||
* because clients should get FlowDocumentation objects via the DocumentationGenerator class.
|
||||
* Creates a {@link FlowDocumentation} for this flow class using data from javadoc tags. Not
|
||||
* public because clients should get FlowDocumentation objects via the DocumentationGenerator
|
||||
* class.
|
||||
*/
|
||||
protected FlowDocumentation(ClassDoc flowDoc) {
|
||||
name = flowDoc.name();
|
||||
qualifiedName = flowDoc.qualifiedName();
|
||||
packageName = flowDoc.containingPackage().name();
|
||||
classDocs = flowDoc.commentText();
|
||||
protected FlowDocumentation(TypeElement typeElement, DocletEnvironment sourceRoot) {
|
||||
this.sourceRoot = sourceRoot;
|
||||
this.typeElement = typeElement;
|
||||
this.docTree = sourceRoot.getDocTrees().getDocCommentTree(typeElement);
|
||||
errors = new ArrayList<>();
|
||||
// Store error codes in sorted order, and leave reasons in insert order.
|
||||
errorsByCode =
|
||||
Multimaps.newListMultimap(new TreeMap<Long, Collection<ErrorCase>>(), ArrayList::new);
|
||||
parseTags(flowDoc);
|
||||
errorsByCode = Multimaps.newListMultimap(new TreeMap<>(), ArrayList::new);
|
||||
parseTags();
|
||||
}
|
||||
|
||||
/** Name of the class for this flow. */
|
||||
public String getName() {
|
||||
return name;
|
||||
return typeElement.getSimpleName().toString();
|
||||
}
|
||||
|
||||
/** Fully qualified name of the class for this flow. */
|
||||
public String getQualifiedName() {
|
||||
return qualifiedName;
|
||||
return typeElement.getQualifiedName().toString();
|
||||
}
|
||||
|
||||
/** Name of the package in which this flow resides. */
|
||||
public String getPackageName() {
|
||||
return packageName;
|
||||
return sourceRoot.getElementUtils().getPackageOf(typeElement).getQualifiedName().toString();
|
||||
}
|
||||
|
||||
public String getClassDocs() {
|
||||
return classDocs;
|
||||
/** Javadoc of the class. */
|
||||
public String getDocTree() {
|
||||
StringJoiner joiner = new StringJoiner("");
|
||||
docTree.getFullBody().forEach(dt -> joiner.add(dt.toString()));
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
public ImmutableList<ErrorCase> getErrors() {
|
||||
|
@ -111,11 +128,14 @@ public class FlowDocumentation {
|
|||
}
|
||||
|
||||
/** Iterates through javadoc tags on the underlying class and calls specific parsing methods. */
|
||||
private void parseTags(ClassDoc flowDoc) {
|
||||
for (Tag tag : flowDoc.tags()) {
|
||||
// Everything else is not a relevant tag.
|
||||
if ("@error".equals(tag.name())) {
|
||||
parseErrorTag(tag);
|
||||
private void parseTags() {
|
||||
for (DocTree tag : docTree.getBlockTags()) {
|
||||
if (tag.getKind() == DocTree.Kind.UNKNOWN_BLOCK_TAG) {
|
||||
UnknownBlockTagTree unknownBlockTagTree = (UnknownBlockTagTree) tag;
|
||||
// Everything else is not a relevant tag.
|
||||
if (unknownBlockTagTree.getTagName().equals("error")) {
|
||||
parseErrorTag(unknownBlockTagTree);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -123,63 +143,131 @@ public class FlowDocumentation {
|
|||
/** Exception to throw when an @error tag cannot be parsed correctly. */
|
||||
private static class BadErrorTagFormatException extends IllegalStateException {
|
||||
/** Makes a message to use as a prefix for the reason passed up to the superclass. */
|
||||
private static String makeMessage(String reason, Tag tag) {
|
||||
return String.format("Bad @error tag format at %s - %s", tag.position(), reason);
|
||||
private static String makeMessage(
|
||||
String reason, TypeElement typeElement, UnknownBlockTagTree tagTree) {
|
||||
return String.format(
|
||||
"Bad @error tag format (%s) in class %s - %s",
|
||||
tagTree.toString(), typeElement.getQualifiedName(), reason);
|
||||
}
|
||||
|
||||
private BadErrorTagFormatException(String reason, Tag tag) {
|
||||
super(makeMessage(reason, tag));
|
||||
private BadErrorTagFormatException(
|
||||
String reason, TypeElement typeElement, UnknownBlockTagTree tagTree) {
|
||||
super(makeMessage(reason, typeElement, tagTree));
|
||||
}
|
||||
|
||||
private BadErrorTagFormatException(String reason, Tag tag, Exception cause) {
|
||||
super(makeMessage(reason, tag), cause);
|
||||
private BadErrorTagFormatException(
|
||||
String reason, TypeElement typeElement, UnknownBlockTagTree tagTree, Exception cause) {
|
||||
super(makeMessage(reason, typeElement, tagTree), cause);
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses a javadoc tag corresponding to an error case and updates the error mapping. */
|
||||
private void parseErrorTag(Tag tag) {
|
||||
private void parseErrorTag(UnknownBlockTagTree tagTree) {
|
||||
// Parse the @error tag text to find the @link inline tag.
|
||||
SeeTag linkedTag;
|
||||
LinkTree linkedTag;
|
||||
try {
|
||||
linkedTag =
|
||||
Stream.of(tag.inlineTags())
|
||||
.filter(SeeTag.class::isInstance)
|
||||
.map(SeeTag.class::cast)
|
||||
tagTree.getContent().stream()
|
||||
.filter(docTree -> docTree.getKind() == Kind.LINK)
|
||||
.map(LinkTree.class::cast)
|
||||
.collect(onlyElement());
|
||||
} catch (NoSuchElementException | IllegalArgumentException e) {
|
||||
throw new BadErrorTagFormatException(
|
||||
String.format("expected one @link tag in tag text but found %s: %s",
|
||||
(e instanceof NoSuchElementException ? "none" : "multiple"),
|
||||
tag.text()),
|
||||
tag, e);
|
||||
String.format(
|
||||
"expected one @link tag in tag text but found %s: %s",
|
||||
(e instanceof NoSuchElementException ? "none" : "multiple"), tagTree.toString()),
|
||||
typeElement,
|
||||
tagTree,
|
||||
e);
|
||||
}
|
||||
// Check to see if the @link tag references a valid class.
|
||||
ClassDoc exceptionRef = linkedTag.referencedClass();
|
||||
if (exceptionRef == null) {
|
||||
ReferenceTree referenceTree = linkedTag.getReference();
|
||||
TypeElement referencedTypeElement = getReferencedElement(referenceTree);
|
||||
if (referencedTypeElement == null) {
|
||||
throw new BadErrorTagFormatException(
|
||||
"could not resolve class from @link tag text: " + linkedTag.text(),
|
||||
tag);
|
||||
"could not resolve class from @link tag text: " + linkedTag.toString(),
|
||||
typeElement,
|
||||
tagTree);
|
||||
}
|
||||
// Try to convert the referenced class into an ErrorCase; fail if it's not an EppException.
|
||||
ErrorCase error;
|
||||
try {
|
||||
error = new ErrorCase(exceptionRef);
|
||||
DocCommentTree docCommentTree =
|
||||
sourceRoot.getDocTrees().getDocCommentTree(referencedTypeElement);
|
||||
error = new ErrorCase(referencedTypeElement, docCommentTree, sourceRoot.getTypeUtils());
|
||||
} catch (IllegalStateException | IllegalArgumentException e) {
|
||||
throw new BadErrorTagFormatException(
|
||||
"class referenced in @link is not a valid EppException: " + exceptionRef.qualifiedName(),
|
||||
tag, e);
|
||||
"class referenced in @link is not a valid EppException: "
|
||||
+ referencedTypeElement.getQualifiedName(),
|
||||
typeElement,
|
||||
tagTree,
|
||||
e);
|
||||
}
|
||||
// Success; store this as a parsed error case.
|
||||
errors.add(error);
|
||||
errorsByCode.put(error.getCode(), error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find the {@link TypeElement} of the class in the {@link ReferenceTree}.
|
||||
*
|
||||
* <p>Unfortunately the new Javadoc API doesn't expose the referenced class object directly, so we
|
||||
* have to find it by trying to find out its fully qualified class name and then loading it from
|
||||
* the {@link Elements}.
|
||||
*/
|
||||
private TypeElement getReferencedElement(ReferenceTree referenceTree) {
|
||||
String signature = referenceTree.getSignature();
|
||||
Elements elements = sourceRoot.getElementUtils();
|
||||
TypeElement referencedTypeElement = elements.getTypeElement(signature);
|
||||
// If the signature is already a qualified class name, we should find it directly. Otherwise
|
||||
// only the simple class name is used in the @error tag and we try to find its package name.
|
||||
if (referencedTypeElement == null) {
|
||||
// First try if the error class is in the same package as the flow class that we are
|
||||
// processing.
|
||||
referencedTypeElement =
|
||||
elements.getTypeElement(String.format("%s.%s", getPackageName(), signature));
|
||||
}
|
||||
if (referencedTypeElement == null) {
|
||||
// Then try if the error class is a nested class of the flow class that we are processing.
|
||||
referencedTypeElement =
|
||||
elements.getTypeElement(String.format("%s.%s", getQualifiedName(), signature));
|
||||
}
|
||||
if (referencedTypeElement == null) {
|
||||
// Lastly, the error class must have been imported. We read the flow class file, and try to
|
||||
// find the import statement that ends with the simple class name.
|
||||
String currentClassFilename =
|
||||
String.format(
|
||||
"%s/%s.java",
|
||||
JavadocWrapper.SOURCE_PATH, getQualifiedName().replaceAll("\\.", "\\/"));
|
||||
String unusedClassFileContent;
|
||||
try {
|
||||
unusedClassFileContent = Files.readString(Path.of(currentClassFilename), UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
// To understand this regex: the import statement must start with a new line or a semicolon,
|
||||
// followed by any number of whitespaces, the word "import" (we don't consider static import),
|
||||
// any number of whitespaces, repeats of "\w*." (this is not exactly precise, but for all
|
||||
// well-named classes it should suffice), the signature, any number of whitespaces, and
|
||||
// finally an ending semicolon. "?:" is used to designate non-capturing groups as we are only
|
||||
// interested in capturing the fully qualified class name.
|
||||
Pattern pattern =
|
||||
Pattern.compile(String.format("(?:\\n|;)\\s*import\\s+((?:\\w*\\.)*%s)\\s*;", signature));
|
||||
Matcher matcher = pattern.matcher(unusedClassFileContent);
|
||||
if (matcher.find()) {
|
||||
referencedTypeElement = elements.getTypeElement(matcher.group(1));
|
||||
}
|
||||
}
|
||||
|
||||
return referencedTypeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an error case for a flow, with a reason for the error and the EPP error code.
|
||||
*
|
||||
* <p>This class is an immutable wrapper for the name of an EppException subclass that gets
|
||||
* thrown to indicate an error condition. It overrides equals() and hashCode() so that
|
||||
* instances of this class can be used in collections in the normal fashion.
|
||||
* <p>This class is an immutable wrapper for the name of an {@link EppException} subclass that
|
||||
* gets thrown to indicate an error condition. It overrides {@code equals()} and {@code
|
||||
* hashCode()} so that instances of this class can be used in collections in the normal fashion.
|
||||
*/
|
||||
public static class ErrorCase {
|
||||
|
||||
|
@ -192,18 +280,23 @@ public class FlowDocumentation {
|
|||
/** The reason this error was thrown, normally documented on the low-level exception class. */
|
||||
private final String reason;
|
||||
|
||||
/** Utility class to convert {@link TypeMirror} to {@link TypeElement}. */
|
||||
private final Types types;
|
||||
|
||||
/** The EPP error code value corresponding to this error condition. */
|
||||
private final long errorCode;
|
||||
|
||||
/** Constructs an ErrorCase from the corresponding class for a low-level flow exception. */
|
||||
protected ErrorCase(ClassDoc exceptionDoc) {
|
||||
name = exceptionDoc.name();
|
||||
className = exceptionDoc.qualifiedName();
|
||||
protected ErrorCase(TypeElement typeElement, DocCommentTree commentTree, Types types) {
|
||||
name = typeElement.getSimpleName().toString();
|
||||
className = typeElement.getQualifiedName().toString();
|
||||
// The javadoc comment on the class explains the reason for the error condition.
|
||||
reason = exceptionDoc.commentText();
|
||||
ClassDoc highLevelExceptionDoc = getHighLevelExceptionFrom(exceptionDoc);
|
||||
errorCode = extractErrorCode(highLevelExceptionDoc);
|
||||
checkArgument(!exceptionDoc.isAbstract(),
|
||||
reason = commentTree.getFullBody().toString();
|
||||
this.types = types;
|
||||
TypeElement highLevelExceptionTypeElement = getHighLevelExceptionFrom(typeElement);
|
||||
errorCode = extractErrorCode(highLevelExceptionTypeElement);
|
||||
checkArgument(
|
||||
!typeElement.getModifiers().contains(Modifier.ABSTRACT),
|
||||
"Cannot use an abstract subclass of EppException as an error case");
|
||||
}
|
||||
|
||||
|
@ -224,13 +317,15 @@ public class FlowDocumentation {
|
|||
}
|
||||
|
||||
/** Returns the direct subclass of EppException that this class is a subclass of (or is). */
|
||||
private ClassDoc getHighLevelExceptionFrom(ClassDoc exceptionDoc) {
|
||||
private TypeElement getHighLevelExceptionFrom(TypeElement typeElement) {
|
||||
// While we're not yet at the root, move up the class hierarchy looking for EppException.
|
||||
while (exceptionDoc.superclass() != null) {
|
||||
if (exceptionDoc.superclass().qualifiedTypeName().equals(EXCEPTION_CLASS_NAME)) {
|
||||
return exceptionDoc;
|
||||
while (typeElement.getSuperclass() != null) {
|
||||
TypeElement superClassTypeElement =
|
||||
(TypeElement) types.asElement(typeElement.getSuperclass());
|
||||
if (superClassTypeElement.getQualifiedName().toString().equals(EXCEPTION_CLASS_NAME)) {
|
||||
return typeElement;
|
||||
}
|
||||
exceptionDoc = exceptionDoc.superclass();
|
||||
typeElement = superClassTypeElement;
|
||||
}
|
||||
// Failure; we reached the root without finding a subclass of EppException.
|
||||
throw new IllegalArgumentException(
|
||||
|
@ -238,24 +333,24 @@ public class FlowDocumentation {
|
|||
}
|
||||
|
||||
/** Returns the corresponding EPP error code for an annotated subclass of EppException. */
|
||||
private long extractErrorCode(ClassDoc exceptionDoc) {
|
||||
private long extractErrorCode(TypeElement typeElement) {
|
||||
try {
|
||||
// We're looking for a specific annotation by name that should appear only once.
|
||||
AnnotationDesc errorCodeAnnotation =
|
||||
Arrays.stream(exceptionDoc.annotations())
|
||||
.filter(
|
||||
anno -> anno.annotationType().qualifiedTypeName().equals(CODE_ANNOTATION_NAME))
|
||||
AnnotationMirror errorCodeAnnotation =
|
||||
typeElement.getAnnotationMirrors().stream()
|
||||
.filter(anno -> anno.getAnnotationType().toString().equals(CODE_ANNOTATION_NAME))
|
||||
.findFirst()
|
||||
.get();
|
||||
// The annotation should have one element whose value converts to an EppResult.Code.
|
||||
AnnotationDesc.ElementValuePair pair = errorCodeAnnotation.elementValues()[0];
|
||||
String enumConstant = ((FieldDoc) pair.value().value()).name();
|
||||
return Code.valueOf(enumConstant).code;
|
||||
AnnotationValue value =
|
||||
errorCodeAnnotation.getElementValues().entrySet().iterator().next().getValue();
|
||||
return Code.valueOf(value.getValue().toString()).code;
|
||||
} catch (IllegalStateException e) {
|
||||
throw new IllegalStateException(
|
||||
"No error code annotation found on exception " + exceptionDoc.name(), e);
|
||||
"No error code annotation found on exception " + typeElement.getQualifiedName(), e);
|
||||
} catch (ArrayIndexOutOfBoundsException | ClassCastException | IllegalArgumentException e) {
|
||||
throw new IllegalStateException("Bad annotation on exception " + exceptionDoc.name(), e);
|
||||
throw new IllegalStateException(
|
||||
"Bad annotation on exception " + typeElement.getQualifiedName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,19 +27,22 @@ import java.io.IOException;
|
|||
/**
|
||||
* Tool to generate documentation for the EPP flows and corresponding external API.
|
||||
*
|
||||
* <p>Mostly responsible for producing standalone documentation files (HTML
|
||||
* and Markdown) from flow information objects; those call into javadoc to
|
||||
* extract documentation information from the flows package source files.
|
||||
* See the {@link FlowDocumentation} class for more details.
|
||||
* <p>Mostly responsible for producing standalone documentation files (HTML and Markdown) from flow
|
||||
* information objects; those call into javadoc to extract documentation information from the flows
|
||||
* package source files. See the {@link FlowDocumentation} class for more details.
|
||||
*/
|
||||
@Parameters(separators = " =", commandDescription = "Tool to generate EPP API documentation")
|
||||
public class FlowDocumentationTool {
|
||||
|
||||
@Parameter(names = {"-o", "--output_file"},
|
||||
@Parameter(
|
||||
names = {"-o", "--output_file"},
|
||||
description = "file where generated documentation will be written (use '-' for stdout)")
|
||||
private String outputFileName;
|
||||
|
||||
@Parameter(names = {"--help", "--helpshort"}, description = "print this help", help = true)
|
||||
@Parameter(
|
||||
names = {"--help", "--helpshort"},
|
||||
description = "print this help",
|
||||
help = true)
|
||||
private boolean displayHelp = false;
|
||||
|
||||
/** Parses command line flags and then runs the documentation tool. */
|
||||
|
@ -68,7 +71,7 @@ public class FlowDocumentationTool {
|
|||
DocumentationGenerator docGenerator;
|
||||
try {
|
||||
docGenerator = new DocumentationGenerator();
|
||||
} catch (IOException e) {
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("IO error while running Javadoc tool", e);
|
||||
}
|
||||
String output = docGenerator.generateMarkdown();
|
||||
|
@ -76,7 +79,7 @@ public class FlowDocumentationTool {
|
|||
System.out.println(output);
|
||||
} else {
|
||||
if (outputFileName == null) {
|
||||
outputFileName = "doclet.html";
|
||||
outputFileName = "doclet.md";
|
||||
}
|
||||
try {
|
||||
Files.asCharSink(new File(outputFileName), UTF_8).write(output);
|
||||
|
@ -86,5 +89,3 @@ public class FlowDocumentationTool {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -17,78 +17,61 @@ package google.registry.documentation;
|
|||
import static google.registry.util.BuildPathUtils.getProjectRoot;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.sun.javadoc.RootDoc;
|
||||
import com.sun.tools.javac.file.JavacFileManager;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import com.sun.tools.javac.util.ListBuffer;
|
||||
import com.sun.tools.javadoc.JavadocTool;
|
||||
import com.sun.tools.javadoc.Messager;
|
||||
import com.sun.tools.javadoc.ModifierFilter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import javax.tools.StandardLocation;
|
||||
import jdk.javadoc.doclet.DocletEnvironment;
|
||||
import jdk.javadoc.internal.tool.AccessKind;
|
||||
import jdk.javadoc.internal.tool.JavadocTool;
|
||||
import jdk.javadoc.internal.tool.Messager;
|
||||
import jdk.javadoc.internal.tool.ToolOption;
|
||||
|
||||
/**
|
||||
* Wrapper class to simplify calls to the javadoc system and hide internal details. An instance
|
||||
* represents a set of parameters for calling out to javadoc; these parameters can be set via
|
||||
* the appropriate methods, and determine what files and packages javadoc will process. The
|
||||
* actual running of javadoc occurs when calling getRootDoc() to retrieve a javadoc RootDoc.
|
||||
* Wrapper class to simplify calls to the javadoc system and hide internal details. An instance
|
||||
* represents a set of parameters for calling out to javadoc; these parameters can be set via the
|
||||
* appropriate methods, and determine what files and packages javadoc will process. The actual
|
||||
* running of javadoc occurs when calling getRootDoc() to retrieve a javadoc RootDoc.
|
||||
*/
|
||||
public final class JavadocWrapper {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/** Shows any member visible at at least the default (package) level. */
|
||||
private static final long VISIBILITY_MASK =
|
||||
Modifier.PUBLIC | Modifier.PROTECTED | ModifierFilter.PACKAGE;
|
||||
private static final AccessKind ACCESS_KIND = AccessKind.PACKAGE;
|
||||
|
||||
/** Root directory for source files. If null, will use the current directory. */
|
||||
private static final String SOURCE_PATH = getProjectRoot().resolve("core/src/main/java")
|
||||
.toString();
|
||||
/** Specific source files to generate documentation for. */
|
||||
private static final ImmutableSet<String> SOURCE_FILE_NAMES = ImmutableSet.of();
|
||||
/** Root directory for source files. */
|
||||
public static final String SOURCE_PATH =
|
||||
getProjectRoot().resolve("core/src/main/java").toString();
|
||||
|
||||
/** Specific packages to generate documentation for. */
|
||||
private static final ImmutableSet<String> SOURCE_PACKAGE_NAMES =
|
||||
ImmutableSet.of(FlowDocumentation.FLOW_PACKAGE_NAME);
|
||||
|
||||
/** Whether or not the Javadoc tool should eschew excessive log output. */
|
||||
private static final boolean QUIET = true;
|
||||
private static final ImmutableList<String> SOURCE_PACKAGE_NAMES =
|
||||
ImmutableList.of(FlowDocumentation.FLOW_PACKAGE_NAME);
|
||||
|
||||
/**
|
||||
* Obtains a Javadoc {@link RootDoc} object containing raw Javadoc documentation.
|
||||
* Wraps a call to the static method createRootDoc() and passes in instance-specific settings.
|
||||
* Obtains a Javadoc {@link DocletEnvironment} object containing raw Javadoc documentation. Wraps
|
||||
* a call to the static method {@link #createDocletEnv} and passes in instance-specific settings.
|
||||
*/
|
||||
public static RootDoc getRootDoc() throws IOException {
|
||||
public static DocletEnvironment getDocletEnv() throws Exception {
|
||||
logger.atInfo().log("Starting Javadoc tool");
|
||||
File sourceFilePath = new File(SOURCE_PATH);
|
||||
logger.atInfo().log("Using source directory: %s", sourceFilePath.getAbsolutePath());
|
||||
try {
|
||||
return createRootDoc(
|
||||
SOURCE_PATH,
|
||||
SOURCE_PACKAGE_NAMES,
|
||||
SOURCE_FILE_NAMES,
|
||||
VISIBILITY_MASK,
|
||||
QUIET);
|
||||
return createDocletEnv(SOURCE_PATH, SOURCE_PACKAGE_NAMES);
|
||||
} finally {
|
||||
logger.atInfo().log("Javadoc tool finished");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a Javadoc root document object for the specified source path and package/Java names.
|
||||
* If the source path is null, then the working directory is assumed as the source path.
|
||||
* Obtains a Javadoc {@link DocletEnvironment} object for the specified source path and
|
||||
* package/Java names. If the source path is null, then the working directory is assumed as the
|
||||
* source path.
|
||||
*
|
||||
* <p>If a list of package names is provided, then Javadoc will run on these packages and all
|
||||
* their subpackages, based out of the specified source path.
|
||||
|
@ -96,62 +79,42 @@ public final class JavadocWrapper {
|
|||
* <p>If a list of file names is provided, then Javadoc will also run on these Java source files.
|
||||
* The specified source path is not considered in this case.
|
||||
*
|
||||
* @see <a href="http://relation.to/12969.lace">Testing Java doclets</a>
|
||||
* @see <a href="http://www.docjar.com/docs/api/com/sun/tools/javadoc/JavadocTool.html">JavadocTool</a>
|
||||
* @param sourcePath the directory where to look for packages.
|
||||
* @param packageNames name of the package to run javadoc on, including subpackages.
|
||||
* @see <a
|
||||
* href="https://docs.oracle.com/javase/9/docs/api/jdk/javadoc/doclet/package-summary.html">
|
||||
* Package jdk.javadoc.doclet</a>
|
||||
*/
|
||||
private static RootDoc createRootDoc(
|
||||
@Nullable String sourcePath,
|
||||
Collection<String> packageNames,
|
||||
Collection<String> fileNames,
|
||||
long visibilityMask,
|
||||
boolean quiet) throws IOException {
|
||||
private static DocletEnvironment createDocletEnv(
|
||||
String sourcePath, Collection<String> packageNames) throws Exception {
|
||||
|
||||
// Create a context to hold settings for Javadoc.
|
||||
Context context = new Context();
|
||||
|
||||
// Redirect Javadoc stdout/stderr to null writers, since otherwise the Java compiler
|
||||
// issues lots of errors for classes that are imported and visible to blaze but not
|
||||
// visible locally to the compiler.
|
||||
// TODO(b/19124943): Find a way to ignore those errors so we can show real ones?
|
||||
Messager.preRegister(
|
||||
context,
|
||||
JavadocWrapper.class.getName(),
|
||||
new PrintWriter(CharStreams.nullWriter()), // For errors.
|
||||
new PrintWriter(CharStreams.nullWriter()), // For warnings.
|
||||
new PrintWriter(CharStreams.nullWriter())); // For notices.
|
||||
// Pre-register a messager for the context.
|
||||
Messager.preRegister(context, JavadocWrapper.class.getName());
|
||||
|
||||
// Set source path option for Javadoc.
|
||||
try (JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8)) {
|
||||
List<File> sourcePathFiles = new ArrayList<>();
|
||||
if (sourcePath != null) {
|
||||
for (String sourcePathEntry : Splitter.on(':').split(sourcePath)) {
|
||||
sourcePathFiles.add(new File(sourcePathEntry));
|
||||
}
|
||||
}
|
||||
fileManager.setLocation(StandardLocation.SOURCE_PATH, sourcePathFiles);
|
||||
|
||||
fileManager.setLocation(StandardLocation.SOURCE_PATH, ImmutableList.of(new File(sourcePath)));
|
||||
|
||||
// Create an instance of Javadoc.
|
||||
JavadocTool javadocTool = JavadocTool.make0(context);
|
||||
|
||||
// Convert the package and file lists to a format Javadoc can understand.
|
||||
ListBuffer<String> subPackages = new ListBuffer<>();
|
||||
subPackages.addAll(packageNames);
|
||||
ListBuffer<String> javaNames = new ListBuffer<>();
|
||||
javaNames.addAll(fileNames);
|
||||
// Set up javadoc tool options.
|
||||
Map<ToolOption, Object> options = new EnumMap<>(ToolOption.class);
|
||||
options.put(ToolOption.SHOW_PACKAGES, ACCESS_KIND);
|
||||
options.put(ToolOption.SHOW_TYPES, ACCESS_KIND);
|
||||
options.put(ToolOption.SHOW_MEMBERS, ACCESS_KIND);
|
||||
options.put(ToolOption.SHOW_MODULE_CONTENTS, ACCESS_KIND);
|
||||
options.put(ToolOption.SUBPACKAGES, packageNames);
|
||||
|
||||
// Invoke Javadoc and ask it for a RootDoc containing the specified packages.
|
||||
return javadocTool.getRootDocImpl(
|
||||
Locale.US.toString(), // Javadoc comment locale
|
||||
UTF_8.name(), // Source character encoding
|
||||
new ModifierFilter(visibilityMask), // Element visibility filter
|
||||
javaNames.toList(), // Included Java file names
|
||||
com.sun.tools.javac.util.List.nil(), // Doclet options
|
||||
com.sun.tools.javac.util.List.nil(), // Source files
|
||||
false, // Don't use BreakIterator
|
||||
subPackages.toList(), // Included sub-package names
|
||||
com.sun.tools.javac.util.List.nil(), // Excluded package names
|
||||
false, // Read source files, not classes
|
||||
false, // Don't run legacy doclet
|
||||
quiet); // If asked, run Javadoc quietly
|
||||
// Invoke Javadoc and ask it for a DocletEnvironment containing the specified packages.
|
||||
return javadocTool.getEnvironment(
|
||||
options, // options
|
||||
ImmutableList.of(), // java names
|
||||
ImmutableList.of()); // java files
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,9 +24,7 @@ import google.registry.documentation.FlowDocumentation.ErrorCase;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Formatter that converts flow documentation into Markdown.
|
||||
*/
|
||||
/** Formatter that converts flow documentation into Markdown. */
|
||||
public final class MarkdownDocumentationFormatter {
|
||||
|
||||
/** Header for flow documentation HTML output. */
|
||||
|
@ -42,9 +40,9 @@ public final class MarkdownDocumentationFormatter {
|
|||
private static final int LINE_WIDTH = 80;
|
||||
|
||||
/**
|
||||
* Returns the string with all HTML tags stripped. Also, removes a single space after any
|
||||
* newlines that have one (we get a single space indent for all lines but the first because of
|
||||
* the way that javadocs are written in comments).
|
||||
* Returns the string with all HTML tags stripped. Also, removes a single space after any newlines
|
||||
* that have one (we get a single space indent for all lines but the first because of the way that
|
||||
* javadocs are written in comments).
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String fixHtml(String value) {
|
||||
|
@ -84,9 +82,7 @@ public final class MarkdownDocumentationFormatter {
|
|||
return result.toString().replace("\n ", "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a list of words into a paragraph with less than maxWidth characters per line.
|
||||
*/
|
||||
/** Formats a list of words into a paragraph with less than maxWidth characters per line. */
|
||||
@VisibleForTesting
|
||||
static String formatParagraph(List<String> words, int maxWidth) {
|
||||
int lineLength = 0;
|
||||
|
@ -117,9 +113,7 @@ public final class MarkdownDocumentationFormatter {
|
|||
return output.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 'value' with words reflowed to maxWidth characters.
|
||||
*/
|
||||
/** Returns 'value' with words reflowed to maxWidth characters. */
|
||||
@VisibleForTesting
|
||||
static String reflow(String text, int maxWidth) {
|
||||
|
||||
|
@ -139,8 +133,8 @@ public final class MarkdownDocumentationFormatter {
|
|||
}
|
||||
|
||||
// Split the line into words and add them to the current paragraph.
|
||||
words.addAll(Splitter.on(
|
||||
CharMatcher.breakingWhitespace()).omitEmptyStrings().splitToList(line));
|
||||
words.addAll(
|
||||
Splitter.on(CharMatcher.breakingWhitespace()).omitEmptyStrings().splitToList(line));
|
||||
}
|
||||
|
||||
// Format the last paragraph, if any.
|
||||
|
@ -158,7 +152,7 @@ public final class MarkdownDocumentationFormatter {
|
|||
for (FlowDocumentation flowDoc : flowDocs) {
|
||||
output.append(String.format("## %s\n\n", flowDoc.getName()));
|
||||
output.append("### Description\n\n");
|
||||
output.append(String.format("%s\n\n", reflow(fixHtml(flowDoc.getClassDocs()), LINE_WIDTH)));
|
||||
output.append(String.format("%s\n", reflow(fixHtml(flowDoc.getDocTree()), LINE_WIDTH)));
|
||||
output.append("### Errors\n\n");
|
||||
for (Long code : flowDoc.getErrorsByCode().keySet()) {
|
||||
output.append(String.format("* %d\n", code));
|
||||
|
|
|
@ -19,7 +19,6 @@ import static google.registry.util.BuildPathUtils.getProjectRoot;
|
|||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -40,10 +39,9 @@ class FlowDocumentationTest {
|
|||
"");
|
||||
|
||||
@Test
|
||||
void testGeneratedMatchesGolden() throws IOException {
|
||||
void testGeneratedMatchesGolden() throws Exception {
|
||||
// Read the markdown file.
|
||||
Path goldenMarkdownPath =
|
||||
GOLDEN_MARKDOWN_FILEPATH;
|
||||
Path goldenMarkdownPath = GOLDEN_MARKDOWN_FILEPATH;
|
||||
|
||||
String goldenMarkdown = new String(Files.readAllBytes(goldenMarkdownPath), UTF_8);
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertWithMessage;
|
|||
import com.google.common.collect.Sets;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.documentation.FlowDocumentation.ErrorCase;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -50,7 +49,7 @@ class FlowExceptionsTest {
|
|||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@Test
|
||||
void testExceptionCorrespondence() throws IOException {
|
||||
void testExceptionCorrespondence() throws Exception {
|
||||
DocumentationGenerator docGenerator = new DocumentationGenerator();
|
||||
Set<ErrorCase> possibleErrors = Sets.newHashSet(docGenerator.getAllErrors());
|
||||
Set<String> mismatchingFlows = Sets.newHashSet();
|
||||
|
|
|
@ -116,8 +116,12 @@ tasks.withType(JavaCompile).configureEach {
|
|||
}
|
||||
}
|
||||
|
||||
sourceCompatibility = '1.8'
|
||||
targetCompatibility = '1.8'
|
||||
// TODO: Change source version to 11 after target version is changed to 11 and
|
||||
// once we figure out what's wrong with Dagger compilation with source version
|
||||
// >8.
|
||||
sourceCompatibility = '8'
|
||||
// TODO: Change target version to 11. Source version can stay at 8.
|
||||
targetCompatibility = '8'
|
||||
|
||||
compileJava { options.encoding = "UTF-8" }
|
||||
compileTestJava { options.encoding = "UTF-8" }
|
||||
|
|
|
@ -30,7 +30,8 @@ rootProject.name = 'nomulus'
|
|||
include 'common'
|
||||
include 'core'
|
||||
include 'db'
|
||||
include 'docs'
|
||||
// TODO: Enable the docs package once we use Java 11 to compile.
|
||||
//include 'docs'
|
||||
include 'integration'
|
||||
include 'networking'
|
||||
include 'processor'
|
||||
|
|
Loading…
Add table
Reference in a new issue