This commit is contained in:
Vladimir Krivosheev
2014-12-19 19:00:24 +01:00
parent 6db0340f99
commit 16d9d5767b
3 changed files with 122 additions and 135 deletions
@@ -102,7 +102,7 @@ public class JavaDocExternalFilter extends AbstractExternalFilter {
}
@Override
protected RefConvertor[] getRefConvertors() {
protected RefConvertor[] getRefConverters() {
return myReferenceConvertors;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -71,12 +71,12 @@ public class ExternalJavadocPresentationTest extends LightCodeInsightTestCase {
}
@Override
public void doBuildFromStream(String surl, Reader input, StringBuffer data, boolean search4Encoding) throws IOException {
super.doBuildFromStream(surl, input, data, search4Encoding);
public void doBuildFromStream(String url, Reader input, StringBuilder data, boolean search4Encoding) throws IOException {
super.doBuildFromStream(url, input, data, search4Encoding);
}
}
JavadocExternalTestFilter filter = new JavadocExternalTestFilter(getProject());
final StringBuffer extractedData = new StringBuffer();
StringBuilder extractedData = new StringBuilder();
filter.doBuildFromStream(url, new StringReader(LoadTextUtil.loadText(pageTextFile).toString()), extractedData, false);
assertEquals(LoadTextUtil.loadText(expectedTextFile).toString(), extractedData.toString());
}
@@ -20,8 +20,6 @@ import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Trinity;
@@ -31,8 +29,8 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.psi.PsiElement;
import com.intellij.util.SystemProperties;
import com.intellij.util.io.UrlConnectionUtil;
import com.intellij.util.net.HttpConfigurable;
import com.intellij.util.ThrowableConsumer;
import com.intellij.util.io.HttpRequests;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -40,54 +38,53 @@ import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.Locale;
import java.util.concurrent.Future;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
/**
* @author db
* @since May 2, 2003
*/
public abstract class AbstractExternalFilter {
private static final Logger LOG = Logger.getInstance(AbstractExternalFilter.class);
private static final boolean EXTRACT_IMAGES_FROM_JARS = SystemProperties.getBooleanProperty("extract.doc.images", true);
@NotNull public static final String QUICK_DOC_DIR_NAME = "quickdoc";
@NotNull
@NonNls
public static final String QUICK_DOC_DIR_NAME = "quickdoc";
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.javadoc.JavaDocExternalFilter");
private static final Pattern ourClassDataStartPattern = Pattern.compile("START OF CLASS DATA", Pattern.CASE_INSENSITIVE);
private static final Pattern ourClassDataEndPattern = Pattern.compile("SUMMARY ========", Pattern.CASE_INSENSITIVE);
private static final Pattern ourClassDataStartPattern = Pattern.compile("START OF CLASS DATA", Pattern.CASE_INSENSITIVE);
private static final Pattern ourClassDataEndPattern = Pattern.compile("SUMMARY ========", Pattern.CASE_INSENSITIVE);
private static final Pattern ourNonClassDataEndPattern = Pattern.compile("<A NAME=", Pattern.CASE_INSENSITIVE);
protected static @NonNls final Pattern ourAnchorsuffix = Pattern.compile("#(.*)$");
protected static @NonNls final Pattern ourHTMLFilesuffix = Pattern.compile("/([^/]*[.][hH][tT][mM][lL]?)$");
private static @NonNls final Pattern ourAnnihilator = Pattern.compile("/[^/^.]*/[.][.]/");
private static @NonNls final Pattern ourIMGselector =
@NonNls
protected static final Pattern ourAnchorSuffix = Pattern.compile("#(.*)$");
protected static @NonNls final Pattern ourHtmlFileSuffix = Pattern.compile("/([^/]*[.][hH][tT][mM][lL]?)$");
private static @NonNls final Pattern ourAnnihilator = Pattern.compile("/[^/^.]*/[.][.]/");
private static @NonNls final Pattern ourImgSelector =
Pattern.compile("<IMG[ \\t\\n\\r\\f]+SRC=\"([^>]*?)\"", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
private static @NonNls final Pattern ourPathInsideJarPattern = Pattern.compile(
private static @NonNls final Pattern ourPathInsideJarPattern = Pattern.compile(
String.format("%s(.+\\.jar)!/(.+?)[^/]+", JarFileSystem.PROTOCOL_PREFIX),
Pattern.CASE_INSENSITIVE | Pattern.DOTALL
);
private static @NonNls final String JAR_PROTOCOL = "jar:";
@NonNls private static final String HR = "<HR>";
@NonNls private static final String P = "<P>";
@NonNls private static final String DL = "<DL>";
@NonNls protected static final String H2 = "</H2>";
@NonNls protected static final String HTML_CLOSE = "</HTML>";
@NonNls protected static final String HTML = "<HTML>";
@NonNls private static final String BR = "<BR>";
@NonNls private static final String DT = "<DT>";
private static final Pattern CHARSET_META_PATTERN =
private static @NonNls final String JAR_PROTOCOL = "jar:";
@NonNls private static final String HR = "<HR>";
@NonNls private static final String P = "<P>";
@NonNls private static final String DL = "<DL>";
@NonNls protected static final String H2 = "</H2>";
@NonNls protected static final String HTML_CLOSE = "</HTML>";
@NonNls protected static final String HTML = "<HTML>";
@NonNls private static final String BR = "<BR>";
@NonNls private static final String DT = "<DT>";
private static final Pattern CHARSET_META_PATTERN =
Pattern.compile("<meta[^>]+\\s*charset=\"?([\\w\\-]*)\\s*\">", Pattern.CASE_INSENSITIVE);
private static final String FIELD_SUMMARY = "<!-- =========== FIELD SUMMARY =========== -->";
private static final String CLASS_SUMMARY = "<div class=\"summary\">";
private final HttpConfigurable myHttpConfigurable = HttpConfigurable.getInstance();
private static final String FIELD_SUMMARY = "<!-- =========== FIELD SUMMARY =========== -->";
private static final String CLASS_SUMMARY = "<div class=\"summary\">";
protected static abstract class RefConvertor {
@NotNull private final Pattern mySelector;
@NotNull
private final Pattern mySelector;
public RefConvertor(@NotNull Pattern selector) {
mySelector = selector;
@@ -124,7 +121,7 @@ public abstract class AbstractExternalFilter {
}
}
protected final RefConvertor myIMGConvertor = new RefConvertor(ourIMGselector) {
protected final RefConvertor myIMGConvertor = new RefConvertor(ourImgSelector) {
@Override
protected String convertReference(String root, String href) {
if (StringUtil.startsWithChar(href, '#')) {
@@ -192,7 +189,7 @@ public abstract class AbstractExternalFilter {
root = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, "/" + path);
}
}
return ourHTMLFilesuffix.matcher(root).replaceAll("/") + href;
return ourHtmlFileSuffix.matcher(root).replaceAll("/") + href;
}
};
@@ -210,39 +207,40 @@ public abstract class AbstractExternalFilter {
public String correctRefs(String root, String read) {
String result = read;
for (RefConvertor myReferenceConvertor : getRefConvertors()) {
for (RefConvertor myReferenceConvertor : getRefConverters()) {
result = myReferenceConvertor.refFilter(root, result);
}
return result;
}
protected abstract RefConvertor[] getRefConvertors();
protected abstract RefConvertor[] getRefConverters();
@Nullable
private static Reader getReaderByUrl(final String surl, final HttpConfigurable httpConfigurable, final ProgressIndicator pi)
throws IOException
{
if (surl.startsWith(JAR_PROTOCOL)) {
VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(BrowserUtil.getDocURL(surl));
private static void getReaderByUrl(@NotNull String url, final @NotNull ThrowableConsumer<Reader, IOException> consumer)
throws IOException {
if (url.startsWith(JAR_PROTOCOL)) {
VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(BrowserUtil.getDocURL(url));
if (file != null) {
consumer.consume(new StringReader(VfsUtilCore.loadText(file)));
}
return;
}
if (file == null) {
final URL parsedUrl = BrowserUtil.getURL(url);
if (parsedUrl == null) {
return;
}
HttpRequests.request(parsedUrl.toString()).connect(new HttpRequests.RequestProcessor<Void>() {
@Override
public Void process(@NotNull HttpRequests.Request request) throws IOException {
String contentEncoding = guessEncoding(parsedUrl);
InputStream inputStream = request.getInputStream();
//noinspection IOResourceOpenedButNotSafelyClosed
consumer.consume(contentEncoding != null ? new MyReader(inputStream, contentEncoding) : new MyReader(inputStream));
return null;
}
return new StringReader(VfsUtilCore.loadText(file));
}
URL url = BrowserUtil.getURL(surl);
if (url == null) {
return null;
}
final URLConnection urlConnection = httpConfigurable.openConnection(url.toString());
final String contentEncoding = guessEncoding(url);
final InputStream inputStream =
pi != null ? UrlConnectionUtil.getConnectionInputStreamWithException(urlConnection, pi) : urlConnection.getInputStream();
//noinspection IOResourceOpenedButNotSafelyClosed
return contentEncoding != null ? new MyReader(inputStream, contentEncoding) : new MyReader(inputStream);
});
}
private static String guessEncoding(URL url) {
@@ -264,31 +262,34 @@ public abstract class AbstractExternalFilter {
catch (IOException ignored) {
}
finally {
if (reader != null)
if (reader != null) {
try {
reader.close();
}
catch (IOException ignored) {
}
}
}
return result;
}
@Nullable
@SuppressWarnings({"HardCodedStringLiteral"})
public String getExternalDocInfo(final String surl) throws Exception {
public String getExternalDocInfo(final String url) throws Exception {
Application app = ApplicationManager.getApplication();
if (!app.isUnitTestMode() && app.isDispatchThread() || app.isWriteAccessAllowed()) {
LOG.error("May block indefinitely: shouldn't be called from EDT or under write lock");
return null;
}
if (surl == null) return null;
if (url == null) {
return null;
}
if (MyJavadocFetcher.isFree()) {
final MyJavadocFetcher fetcher = new MyJavadocFetcher(surl, new MyDocBuilder() {
final MyJavadocFetcher fetcher = new MyJavadocFetcher(url, new MyDocBuilder() {
@Override
public void buildFromStream(String surl, Reader input, StringBuffer result) throws IOException {
doBuildFromStream(surl, input, result);
public void buildFromStream(String url, Reader input, StringBuilder result) throws IOException {
doBuildFromStream(url, input, result);
}
});
final Future<?> fetcherFuture = app.executeOnPooledThread(fetcher);
@@ -304,7 +305,7 @@ public abstract class AbstractExternalFilter {
throw exception;
}
final String docText = correctRefs(ourAnchorsuffix.matcher(surl).replaceAll(""), fetcher.getData());
final String docText = correctRefs(ourAnchorSuffix.matcher(url).replaceAll(""), fetcher.getData());
if (LOG.isDebugEnabled()) {
LOG.debug("Filtered JavaDoc: " + docText + "\n");
}
@@ -318,13 +319,12 @@ public abstract class AbstractExternalFilter {
return getExternalDocInfo(docURL);
}
protected void doBuildFromStream(String surl, Reader input, StringBuffer data) throws IOException {
doBuildFromStream(surl, input, data, true);
protected void doBuildFromStream(String url, Reader input, StringBuilder data) throws IOException {
doBuildFromStream(url, input, data, true);
}
protected void doBuildFromStream(String surl, Reader input, StringBuffer data, boolean search4Encoding) throws IOException {
BufferedReader buf = new BufferedReader(input);
Trinity<Pattern, Pattern, Boolean> settings = getParseSettings(surl);
protected void doBuildFromStream(final String url, Reader input, final StringBuilder data, boolean search4Encoding) throws IOException {
Trinity<Pattern, Pattern, Boolean> settings = getParseSettings(url);
@NonNls Pattern startSection = settings.first;
@NonNls Pattern endSection = settings.second;
boolean useDt = settings.third;
@@ -349,6 +349,8 @@ public abstract class AbstractExternalFilter {
String read;
String contentEncoding = null;
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
BufferedReader buf = new BufferedReader(input);
do {
read = buf.readLine();
if (read != null && search4Encoding && read.contains("charset")) {
@@ -360,21 +362,23 @@ public abstract class AbstractExternalFilter {
}
while (read != null && !startSection.matcher(StringUtil.toUpperCase(read)).find());
if (input instanceof MyReader && contentEncoding != null) {
if (!contentEncoding.equalsIgnoreCase(CharsetToolkit.UTF8) &&
!contentEncoding.equals(((MyReader)input).getEncoding()))
{ //restart page parsing with correct encoding
Reader stream;
try {
stream = getReaderByUrl(surl, myHttpConfigurable, new ProgressIndicatorBase());
}
catch (ProcessCanceledException e) {
return;
}
data.delete(0, data.length());
doBuildFromStream(surl, new MyReader(((MyReader)stream).getInputStream(), contentEncoding), data, false);
if (input instanceof MyReader && contentEncoding != null && !contentEncoding.equalsIgnoreCase(CharsetToolkit.UTF8) &&
!contentEncoding.equals(((MyReader)input).getEncoding())) {
//restart page parsing with correct encoding
try {
final String finalContentEncoding = contentEncoding;
getReaderByUrl(url, new ThrowableConsumer<Reader, IOException>() {
@Override
public void consume(Reader reader) throws IOException {
data.delete(0, data.length());
doBuildFromStream(url, new MyReader(((MyReader)reader).getInputStream(), finalContentEncoding), data, false);
}
});
}
catch (ProcessCanceledException e) {
return;
}
return;
}
if (read == null) {
@@ -386,7 +390,7 @@ public abstract class AbstractExternalFilter {
boolean skip = false;
do {
if (StringUtil.toUpperCase(read).contains(H2) && !read.toUpperCase().contains("H2")) { // read=class name in <H2>
if (StringUtil.toUpperCase(read).contains(H2) && !read.toUpperCase(Locale.ENGLISH).contains("H2")) { // read=class name in <H2>
data.append(H2);
skip = true;
}
@@ -403,8 +407,7 @@ public abstract class AbstractExternalFilter {
data.append(DL);
StringBuffer classDetails = new StringBuffer();
StringBuilder classDetails = new StringBuilder();
while (((read = buf.readLine()) != null) && !StringUtil.toUpperCase(read).equals(HR) && !StringUtil.toUpperCase(read).equals(P)) {
if (reachTheEnd(data, read, classDetails)) return;
appendLine(classDetails, read);
@@ -438,9 +441,9 @@ public abstract class AbstractExternalFilter {
/**
* Decides what settings should be used for parsing content represented by the given url.
*
* @param url url which points to the target content
* @return following data: (start interested data boundary pattern; end interested data boundary pattern;
* replace table data by &lt;dt&gt;)
* @param url url which points to the target content
* @return following data: (start interested data boundary pattern; end interested data boundary pattern;
* replace table data by &lt;dt&gt;)
*/
@NotNull
protected Trinity<Pattern, Pattern, Boolean> getParseSettings(@NotNull String url) {
@@ -448,7 +451,7 @@ public abstract class AbstractExternalFilter {
Pattern endSection = ourClassDataEndPattern;
boolean useDt = true;
Matcher anchorMatcher = ourAnchorsuffix.matcher(url);
Matcher anchorMatcher = ourAnchorSuffix.matcher(url);
if (anchorMatcher.find()) {
useDt = false;
startSection = Pattern.compile(Pattern.quote("<a name=\"" + anchorMatcher.group(1) + "\""), Pattern.CASE_INSENSITIVE);
@@ -457,10 +460,9 @@ public abstract class AbstractExternalFilter {
return Trinity.create(startSection, endSection, useDt);
}
private static boolean reachTheEnd(StringBuffer data, String read, StringBuffer classDetails) {
private static boolean reachTheEnd(StringBuilder data, String read, StringBuilder classDetails) {
if (StringUtil.indexOfIgnoreCase(read, FIELD_SUMMARY, 0) != -1 ||
StringUtil.indexOfIgnoreCase(read, CLASS_SUMMARY, 0) != -1)
{
StringUtil.indexOfIgnoreCase(read, CLASS_SUMMARY, 0) != -1) {
data.append(classDetails);
data.append(HTML_CLOSE);
return true;
@@ -470,8 +472,9 @@ public abstract class AbstractExternalFilter {
@Nullable
static String parseContentEncoding(@NotNull String htmlLine) {
if (!htmlLine.contains("charset"))
if (!htmlLine.contains("charset")) {
return null;
}
final Matcher matcher = CHARSET_META_PATTERN.matcher(htmlLine);
if (matcher.find()) {
return matcher.group(1);
@@ -479,28 +482,27 @@ public abstract class AbstractExternalFilter {
return null;
}
private static void appendLine(final StringBuffer buffer, final String read) {
private static void appendLine(StringBuilder buffer, final String read) {
buffer.append(read);
buffer.append("\n");
}
private interface MyDocBuilder {
void buildFromStream(String surl, Reader input, StringBuffer result) throws IOException;
void buildFromStream(String url, Reader input, StringBuilder result) throws IOException;
}
private static class MyJavadocFetcher implements Runnable {
private static boolean ourFree = true;
private final StringBuffer data = new StringBuffer();
private final String surl;
private static boolean ourFree = true;
private final StringBuilder data = new StringBuilder();
private final String url;
private final MyDocBuilder myBuilder;
private final Exception[] myExceptions = new Exception[1];
private final HttpConfigurable myHttpConfigurable;
private Exception myException;
public MyJavadocFetcher(final String surl, MyDocBuilder builder) {
this.surl = surl;
public MyJavadocFetcher(String url, MyDocBuilder builder) {
this.url = url;
myBuilder = builder;
//noinspection AssignmentToStaticFieldFromInstanceMethod
ourFree = false;
myHttpConfigurable = HttpConfigurable.getInstance();
}
public static boolean isFree() {
@@ -514,51 +516,36 @@ public abstract class AbstractExternalFilter {
@Override
public void run() {
try {
if (surl == null) {
if (url == null) {
return;
}
Reader stream = null;
try {
stream = getReaderByUrl(surl, myHttpConfigurable, new ProgressIndicatorBase());
getReaderByUrl(url, new ThrowableConsumer<Reader, IOException>() {
@Override
public void consume(Reader reader) throws IOException {
myBuilder.buildFromStream(url, reader, data);
}
});
}
catch (ProcessCanceledException e) {
return;
catch (ProcessCanceledException ignored) {
}
catch (IOException e) {
myExceptions[0] = e;
}
if (stream == null) {
return;
}
try {
myBuilder.buildFromStream(surl, stream, data);
}
catch (final IOException e) {
myExceptions[0] = e;
}
finally {
try {
stream.close();
}
catch (IOException e) {
myExceptions[0] = e;
}
myException = e;
}
}
finally {
//noinspection AssignmentToStaticFieldFromInstanceMethod
ourFree = true;
}
}
public Exception getException() {
return myExceptions[0];
return myException;
}
public void cleanup() {
myExceptions[0] = null;
myException = null;
}
}