| /* |
| * 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.tomee.website; |
| |
| import org.apache.openejb.loader.IO; |
| import org.tomitribe.tio.Dir; |
| import org.tomitribe.tio.Match; |
| import org.tomitribe.tio.lang.JvmLang; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.UncheckedIOException; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.function.Function; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| /** |
| * This class is responsible for creating cross-links between Javadoc and Examples |
| * as detailed in https://issues.apache.org/jira/browse/TOMEE-2346 |
| * |
| * The name "LearningLinks" is perhaps a bit weak. The spirit of the feature is to |
| * make it easier for people to click back and forth between the javadoc and examples |
| * which are two primary sources of learning a new API. |
| * |
| * Ideally someone who searches for an API on the internet find the javadoc and from |
| * there can jump to any number of examples that use it. Once on an example they can |
| * jump into other points of Javadoc and continue their learning. |
| */ |
| public class LearningLinks { |
| |
| private final Examples examples; |
| |
| public LearningLinks(final Examples examples) { |
| this.examples = examples; |
| } |
| |
| /** |
| * The primary method driving all code in this class. Prepare is called once per Source, |
| * after example and javadoc preparation has been done. |
| * |
| * At this moment all of our examples have been copied to their final locations. As well, |
| * we have created several "buckets" of source code on which we will later run the Javadoc tool. |
| * |
| * Once we have those two lists we will link the javadocs into the right examples. We will also |
| * link the applicable examples into each Javadoc as a @see tag. |
| * |
| * TODO: Note that we have several versions of the same examples, one per TomEE version. We may |
| * want to only link the latest one so we don't appear to have duplicates. There is a Source |
| * called 'latest' which can be helpful in this regard, however, there will be situations where |
| * the 'latest' Source no longer refers to a particular API anymore (e.g. javax once we switch |
| * to jakarta) so we will want to consult all versions of the examples when we pick the most |
| * current matching example. |
| * |
| * Actual running of the javadoc command does not happen here. |
| * |
| * @see Source for a deeper description that is very applicable here |
| * @see Configuration for the full list of Sources that will be seen here |
| */ |
| public void prepare(final Source source) { |
| if (!source.getName().contains("jakarta")) return; |
| final Map<String, JavadocSource> sources = getJavadocSources(source.stream()); |
| |
| for (final Example example : examples.getExamples()) { |
| final List<String> apisUsed = getImports(example).stream() |
| .filter(sources::containsKey) |
| .collect(Collectors.toList()); |
| |
| // If the example does not use any of the APIs from |
| // this Source instance (e.g Jakarta EE, MicroProfile, etc) |
| // then there is nothing to do. |
| if (apisUsed.size() == 0) continue; |
| |
| // Add @see link to Javadoc |
| for (final String api : apisUsed) { |
| addSeeLink(sources.get(api), example); |
| } |
| |
| // Add APIs Used links to Example |
| addApisUsed(example, apisUsed, sources, source); |
| |
| } |
| } |
| |
| private void addApisUsed(final Example example, final List<String> apisUsed, final Map<String, JavadocSource> sources, final Source source) { |
| Collections.sort(apisUsed); |
| |
| String content = null; |
| try { |
| content = IO.slurp(example.getDestReadme()); |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| |
| final String basePath = pathToContentRoot(example.getDestReadme()); |
| |
| final List<JavadocSource> list = apisUsed.stream() |
| .map(sources::get) |
| .filter(Objects::nonNull) |
| .collect(Collectors.toList()); |
| |
| for (JavadocSource javadocSource : list) { |
| final String link = String.format("%s%s/javadoc/%s.html", |
| basePath, |
| source.getName(), |
| javadocSource.getClassName().replace(".", "/")); |
| |
| content = ApisUsed.insertHref(content, link, javadocSource.getClassName()); |
| } |
| |
| try { |
| IO.copy(IO.read(content), example.getDestReadme()); |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| } |
| |
| static String pathToContentRoot(final File file) { |
| final StringBuilder sb = new StringBuilder(); |
| |
| File parent = file; |
| while ((parent = parent.getParentFile()) != null && !parent.getName().equals("content")) { |
| sb.append("../"); |
| } |
| |
| return sb.toString(); |
| } |
| |
| static String pathFromContentRoot(final File file) { |
| final String absolutePath = file.getAbsolutePath(); |
| |
| final String content = "/content/"; |
| final int indexOfContent = absolutePath.indexOf(content); |
| |
| if (indexOfContent == -1) { |
| throw new IllegalStateException("Expected '/content/' section of path not found: " + absolutePath); |
| } |
| |
| return absolutePath.substring(indexOfContent + content.length()); |
| } |
| |
| |
| private void addSeeLink(final JavadocSource javadocSource, final Example example) { |
| try { |
| final String content = IO.slurp(javadocSource.getSourceFile()); |
| |
| final String toContentRoot = pathToContentRoot(javadocSource.getSourceFile()); |
| final String fromContentRoot = pathFromContentRoot(example.getDestReadme()) |
| .replace(".adoc", ".html") |
| .replace(".md", ".html"); |
| |
| final String link = toContentRoot + fromContentRoot; |
| |
| final String name = example.getName(); |
| |
| |
| // Update the source contents to include an href link |
| final String modified = ExampleLinks.insertHref(content, link, name); |
| |
| // Overwrite the source with the newly linked version |
| IO.copy(IO.read(modified), javadocSource.getSourceFile()); |
| } catch (Exception e) { |
| throw new IllegalStateException("Unable to add link to java source: " + javadocSource.getSourceFile().getAbsolutePath(), e); |
| } |
| } |
| |
| /** |
| * Walk over every file in the example directory and look for import statements. |
| * |
| * Collect each statement and return a unique list of the class names |
| * referenced by each import statement. |
| */ |
| private List<String> getImports(final Example example) { |
| final Dir dir = Dir.from(example.getSrcReadme().getParentFile()); |
| |
| // Unfiltered list of imported classes used in this example |
| // This list will contain the class names themselves |
| return dir.searchFiles() |
| .flatMap(JvmLang.imports(dir)) |
| .map(Match::getMatch) |
| .distinct() |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Return a map of JavadocSource instances that are applicable to this Source |
| * and any related Source instances. If the Javadocs::prepare runs after this |
| * method is called an empty map will be returned. |
| */ |
| private Map<String, JavadocSource> getJavadocSources(final Stream<Source> sources) { |
| // The stream for the jakartaee-platform.git repo will contain ejb-api.git and all |
| // related git repos. Each repo will be a `Source` instance. |
| |
| // The Javadocs class will have set a JavadocSources in each Source instance that |
| // we can use to get a list of java files that will be fed to the javadoc processor |
| // at a later time. |
| return sources.map(source -> source.getComponent(JavadocSources.class)) |
| .filter(Optional::isPresent) |
| .map(Optional::get) |
| .map(JavadocSources::getSources) |
| .flatMap(Collection::stream) |
| .distinct() |
| .collect(Collectors.toMap(JavadocSource::getClassName, Function.identity())); |
| |
| } |
| |
| |
| } |