blob: aa7f2c4b86ad51bc9c7ff0d920e055cac1fd95c1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014 Christian Pontesegger and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* Christian Pontesegger - initial API and implementation
*******************************************************************************/
package org.eclipse.ease.helpgenerator;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.sun.javadoc.ClassDoc;
/**
* Collects registered packages and converts classes & API links to http anchors.
*/
public class LinkProvider {
/** Pattern to detect a link token. */
private static final Pattern PATTERN_LINK = Pattern.compile("\\{@(link|module)\\s+(.*?)\\}", Pattern.DOTALL);
/** Pattern to parse a link. */
private static final Pattern PATTERN_INNER_LINK = Pattern.compile("(\\w+(?:\\.\\w+)*)?(?:#(\\w+)(?:\\((.*?)\\))?)?");
/** Maps (URL to use) -> Collection of package names. */
private final Map<String, Collection<String>> fExternalDocs = new HashMap<>();
public void registerAddress(final String location, final Collection<String> packages) {
fExternalDocs.put(location, packages);
}
public static String resolveClassName(final String candidate, final ClassDoc clazz) {
final String foundCandidate = findClass(candidate, clazz);
return (foundCandidate != null) ? foundCandidate : candidate;
}
public String createClassText(String qualifiedName) {
if (qualifiedName.contains(".")) {
final String urlLocation = findClassURL(qualifiedName);
if (urlLocation != null) {
final String packageName = qualifiedName.substring(0, qualifiedName.lastIndexOf('.'));
// first run, look for exact package match
for (final Entry<String, Collection<String>> entry : fExternalDocs.entrySet()) {
if (entry.getValue().contains(packageName))
return "<a href=\"" + urlLocation + "\" title=\"" + HTMLWriter.escapeText(qualifiedName) + "\">"
+ HTMLWriter.escapeText(qualifiedName.substring(packageName.length() + 1)) + "</a>";
}
// not found; try to locate matching parent package and hope for
// the best
for (final Entry<String, Collection<String>> entry : fExternalDocs.entrySet()) {
for (final String entryPackage : entry.getValue()) {
if (packageName.startsWith(entryPackage))
return "<a href=\"" + urlLocation + "\" title=\"" + HTMLWriter.escapeText(qualifiedName) + "\">"
+ HTMLWriter.escapeText(qualifiedName.substring(packageName.length() + 1)) + "</a>";
}
}
} else
qualifiedName = HTMLWriter.escapeText(qualifiedName);
} else
qualifiedName = HTMLWriter.escapeText(qualifiedName);
return qualifiedName;
}
private static String findClass(final String name, final ClassDoc baseClass) {
try {
for (final ClassDoc doc : baseClass.importedClasses()) {
if (doc.toString().endsWith(name))
return doc.toString();
}
} catch (final NullPointerException e) {
// sometimes thrown by ClassDoc.importedClasses(). Nothing we can do here but ignore
}
final ClassDoc target = baseClass.findClass(name);
return (target != null) ? target.toString() : null;
}
private String findClassURL(String qualifiedName) {
if (qualifiedName.contains("<"))
qualifiedName = qualifiedName.substring(0, qualifiedName.indexOf("<"));
if (qualifiedName.contains(".")) {
final String packageName = qualifiedName.substring(0, qualifiedName.lastIndexOf('.'));
// first run, look for exact package match
for (final Entry<String, Collection<String>> entry : fExternalDocs.entrySet()) {
if (entry.getValue().contains(packageName))
return entry.getKey() + "/" + qualifiedName.replace('.', '/') + ".html";
}
// not found; try to locate matching parent package and hope for the
// best
for (final Entry<String, Collection<String>> entry : fExternalDocs.entrySet()) {
for (final String entryPackage : entry.getValue()) {
if (packageName.startsWith(entryPackage))
return entry.getKey() + "/" + qualifiedName.replace('.', '/') + ".html";
}
}
}
return null;
}
public String insertLinks(final ClassDoc clazz, final String text) {
final StringBuilder output = new StringBuilder();
int startPos = 0;
final Matcher matcher = PATTERN_LINK.matcher(text);
while (matcher.find()) {
output.append(text.substring(startPos, matcher.start()));
startPos = matcher.end();
final Matcher linkMatcher = PATTERN_INNER_LINK.matcher(matcher.group(2).replace('\r', ' ').replace('\n', ' '));
if (linkMatcher.matches()) {
// group 1 = class
// group 2 = method (optional)
// group 3 = params (without parenthesis)
if ("link".equals(matcher.group(1))) {
// link to java API
final StringBuilder link = new StringBuilder();
if (linkMatcher.group(2) != null) {
link.append("#");
link.append(linkMatcher.group(2));
if (linkMatcher.group(3) != null) {
link.append("-");
for (String parameter : linkMatcher.group(3).split(",")) {
parameter = parameter.trim().replace(" ", "");
if (parameter.endsWith("]"))
link.append(removeGenericsTags(resolveClassName(parameter.substring(0, parameter.indexOf('[')), clazz)));
else
link.append(removeGenericsTags(resolveClassName(parameter, clazz)));
while (parameter.endsWith("]")) {
link.append(":A");
parameter = parameter.substring(0, parameter.lastIndexOf('[')).trim();
}
link.append("-");
}
if (link.charAt(link.length() - 1) != '-')
link.append("-");
}
}
if (linkMatcher.group(1) == null) {
// link to same document
output.append("<a href=\"" + link + "\">" + linkMatcher.group(2)
+ ((linkMatcher.group(3) != null) ? "(" + linkMatcher.group(3) + ")" : "") + "</a>");
} else {
// external document
final String classURL = findClassURL(resolveClassName(linkMatcher.group(1), clazz));
if (classURL != null)
output.append("<a href=\"" + classURL + link + "\">");
output.append(linkMatcher.group(1));
if (linkMatcher.group(2) != null) {
output.append(linkMatcher.group(2));
if (linkMatcher.group(3) != null) {
output.append('(');
output.append(linkMatcher.group(3));
output.append(')');
}
}
if (classURL != null)
output.append("</a>");
}
} else if ("module".equals(matcher.group(1))) {
// link to a scripting module
if (linkMatcher.group(1) == null) {
// link to same document
output.append(
"<a href=\"#" + linkMatcher.group(2) + "\">" + linkMatcher.group(2) + ((linkMatcher.group(3) != null) ? "()" : "") + "</a>");
} else {
// external document
final String plugin = linkMatcher.group(1).substring(0, linkMatcher.group(1).lastIndexOf('.'));
if (linkMatcher.group(2) != null)
output.append("<a href=\"../../" + plugin + "/help/" + ModuleDoclet.createHTMLFileName(linkMatcher.group(1)) + "#"
+ linkMatcher.group(2) + "\">" + linkMatcher.group(2) + ((linkMatcher.group(3) != null) ? "()" : "") + "</a>");
else
output.append("<a href=\"../../" + plugin + "/help/" + ModuleDoclet.createHTMLFileName(linkMatcher.group(1)) + "\">"
+ capitalizeFirst(linkMatcher.group(1).substring(linkMatcher.group(1).lastIndexOf('.') + 1)) + " module</a>");
}
}
}
}
if (startPos == 0)
return text;
output.append(text.substring(startPos));
return output.toString();
}
/**
* Remove the generic tags from class name
*
* @param className
* The complete name of the class, including generic tags
* @return the class name without the generic tags
*/
public static String removeGenericsTags(String className) {
if (className == null) {
return null;
}
final int indexOf = className.indexOf('<');
return indexOf < 0 ? className : className.substring(0, className.indexOf('<'));
}
private static String capitalizeFirst(final String content) {
if (!content.isEmpty())
return content.substring(0, 1).toUpperCase() + content.substring(1);
return content;
}
}