forked from eclipse-jdtls/eclipse.jdt.ls
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Image extraction works, regular paths work too. No tests. Need to figure out how to get path of source jar. Fixes eclipse-jdtls#1007 Signed-off-by: Nikolas Komonen <[email protected]>
- Loading branch information
1 parent
2da5a29
commit df3a32a
Showing
9 changed files
with
649 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
335 changes: 335 additions & 0 deletions
335
...ipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/javadoc/JavaDocHTMLPathHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,335 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2019 Red Hat Inc. and others. | ||
* All rights reserved. This program and the accompanying materials | ||
* are made available under the terms of the Eclipse Public License v1.0 | ||
* which accompanies this distribution, and is available at | ||
* http://www.eclipse.org/legal/epl-v10.html | ||
* | ||
* Contributors: | ||
* Red Hat Inc. - initial API and implementation | ||
*******************************************************************************/ | ||
package org.eclipse.jdt.ls.core.internal.javadoc; | ||
|
||
import java.io.File; | ||
import java.io.FileOutputStream; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import java.net.URL; | ||
import java.nio.file.Files; | ||
import java.nio.file.Paths; | ||
import java.nio.file.attribute.BasicFileAttributes; | ||
import java.nio.file.attribute.FileTime; | ||
import java.util.jar.JarFile; | ||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
import java.util.zip.ZipEntry; | ||
|
||
import org.eclipse.core.internal.runtime.InternalPlatform; | ||
import org.eclipse.core.resources.IResource; | ||
import org.eclipse.core.runtime.IPath; | ||
import org.eclipse.core.runtime.Platform; | ||
import org.eclipse.jdt.core.IJavaElement; | ||
import org.eclipse.jdt.core.IPackageFragment; | ||
import org.eclipse.jdt.core.JavaModelException; | ||
import org.eclipse.jdt.core.dom.TextElement; | ||
import org.eclipse.jdt.ls.core.internal.IConstants; | ||
|
||
/** | ||
* @author Nikolas Komonen - [email protected] | ||
* | ||
*/ | ||
public class JavaDocHTMLPathHandler { | ||
|
||
public static final String EXTRACTED_JAR_IMAGES = "/extracted-jar-images/"; | ||
|
||
public static final String[] tags = { "img" }; | ||
|
||
/** | ||
* Returns true if the text is an HTML tag in the defined array of tag names in | ||
* {@link JavaDocHTMLPathHandler#tags} | ||
* | ||
* @param text | ||
* @return | ||
*/ | ||
public static boolean isHTMLTag(String text) { | ||
|
||
if (!text.startsWith("<") && !text.endsWith(">")) { | ||
return false; | ||
} | ||
|
||
//find index of tag name | ||
int i; | ||
for (i = 1; i < text.length() - 1; i++) { // - 1 to exclude '>' | ||
char c = text.charAt(i); | ||
if (Character.isWhitespace(c)) { | ||
break; | ||
} | ||
} | ||
|
||
if (i == 1) { | ||
return false; | ||
} | ||
|
||
String tagName = text.substring(1, i); | ||
|
||
for (String tag : tags) { | ||
if (tag.equals(tagName)) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Given a {@link TextElement} that represents an HTML tag with a 'src' | ||
* attribute, it will extract the image from the jar if necessary and copy it to | ||
* the 'outputPath'. | ||
* | ||
* @param child | ||
* @param fElement | ||
* @return | ||
*/ | ||
public static String getValidatedHTMLSrcAttribute(TextElement child, IJavaElement fElement) { | ||
|
||
//Check if current src attribute path needs to be validated | ||
String text = child.getText(); | ||
int offsets[] = extractSourcePathFromHTMLTag(text); | ||
if (offsets == null) { | ||
return text; | ||
} | ||
String srcPath = text.substring(offsets[0], offsets[1]); | ||
String fileName = Paths.get(srcPath).getFileName().toString(); | ||
|
||
if (isPathAbsolute(srcPath)) { | ||
return text; //Current path is good as is. | ||
} | ||
|
||
//Get the initial internal jar fragment, which is going to be the relative parent folder to the file | ||
IPackageFragment internalJarFragment = (IPackageFragment) fElement.getAncestor(IJavaElement.PACKAGE_FRAGMENT); | ||
|
||
if (internalJarFragment == null) { | ||
return text; | ||
} | ||
|
||
|
||
//test if srcPath points to the correct file, relative to the fElement file. | ||
IResource fragmentResource = internalJarFragment.getResource(); | ||
if (fragmentResource != null) { | ||
IPath absoluteFragmentPath = fragmentResource.getLocation(); | ||
File absoluteFragmentFile = new File(absoluteFragmentPath.toPortableString() + "/" + srcPath); | ||
if (absoluteFragmentFile.exists()) { | ||
String uriString = "file://" + absoluteFragmentFile.toURI().getPath(); | ||
return text.substring(0, offsets[0]) + uriString + text.substring(offsets[1]); | ||
} | ||
} | ||
|
||
|
||
//folder names are separated by '.' | ||
String fragmentName = internalJarFragment.getElementName(); | ||
|
||
if (fragmentName == null || fragmentName.length() < 1) { | ||
return text; | ||
} | ||
|
||
String[] names = fragmentName.split("\\."); | ||
String fragmentPath = ""; | ||
for (String name : names) { | ||
fragmentPath += name + "/"; | ||
} | ||
|
||
srcPath = srcPath.replace('\\', '/'); | ||
|
||
String relativeToJarPath = fragmentPath + srcPath; // /relative/path/to/file.abc | ||
InputStream is = null; // file from jar (ZipEntry) | ||
JarFile jar = null; | ||
|
||
try { | ||
File currentJarPath = null; | ||
|
||
ZipEntry currentZipEntry = null; | ||
|
||
URL javadocJarBaseLocationURL = JavaDocLocations.getJavadocBaseLocation(internalJarFragment); //Absolute location of javadoc jar (not class or source jar) | ||
//Attempt to get file from javadoc jar | ||
if (javadocJarBaseLocationURL != null) { | ||
URI javadocJarBaseLocationURI = javadocJarBaseLocationURL.toURI(); | ||
currentJarPath = getJarPathFromURI(javadocJarBaseLocationURI); | ||
jar = new JarFile(currentJarPath); | ||
currentZipEntry = jar.getEntry(relativeToJarPath); | ||
} | ||
|
||
//No file was in the javadoc jar, try the source jar | ||
if (currentZipEntry == null) { | ||
currentJarPath = SourceJarLocations.getSourceJarPath(internalJarFragment); //Absolute location of source jar | ||
jar = new JarFile(currentJarPath); | ||
currentZipEntry = jar.getEntry(relativeToJarPath); | ||
} | ||
|
||
if (jar == null || currentZipEntry == null) { | ||
return text; //File from source path could not be located in either jar | ||
} | ||
|
||
//Create new path to extract images to | ||
IPath stateLocationPath = InternalPlatform.getDefault().getStateLocation(Platform.getBundle(IConstants.PLUGIN_ID)); | ||
|
||
String jarRootName = internalJarFragment.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT).getElementName(); | ||
if (jarRootName.endsWith(".jar")) { | ||
jarRootName = jarRootName.substring(0, jarRootName.length() - 4); | ||
} | ||
|
||
String stateLocationPathString = "/" + stateLocationPath.toPortableString(); | ||
|
||
//Path to the extracted file | ||
String outputPath = "file://" + stateLocationPathString + EXTRACTED_JAR_IMAGES + jarRootName + "/" + fileName; | ||
URI outputURI = new URI(outputPath); | ||
File outputFile = new File(outputURI.getPath()); | ||
|
||
//Check if the file actually needs to be extracted | ||
if (!outputFile.exists()) { | ||
is = jar.getInputStream(currentZipEntry); | ||
extractFileTo(is, outputFile); | ||
} else {//Check if the file is outdated | ||
BasicFileAttributes existingOutputFileAttributes = Files.readAttributes(outputFile.toPath(), BasicFileAttributes.class); | ||
FileTime existingFileCreationTime = existingOutputFileAttributes.creationTime(); | ||
BasicFileAttributes jarOutputFileAttributes = Files.readAttributes(currentJarPath.toPath(), BasicFileAttributes.class); | ||
|
||
FileTime jarFileCreationTime = jarOutputFileAttributes.creationTime(); | ||
|
||
if (jarFileCreationTime.compareTo(existingFileCreationTime) > 0) { | ||
is = jar.getInputStream(currentZipEntry); | ||
extractFileTo(is, outputFile); | ||
} | ||
} | ||
|
||
//Insert new path into text | ||
return text.substring(0, offsets[0]) + outputPath + text.substring(offsets[1]); | ||
|
||
} catch (JavaModelException e3) { | ||
return text; | ||
} catch (IOException e) { | ||
return text; | ||
} catch (URISyntaxException e) { | ||
return text; | ||
} finally { | ||
//cleanup | ||
if (is != null) { | ||
try { | ||
is.close(); | ||
} catch (IOException e) { | ||
} | ||
} | ||
if (jar != null) { | ||
try { | ||
jar.close(); | ||
} catch (IOException e) { | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Gets the position between the quotes of a src attribute. Will look for | ||
* something similar to (src="...") and extract the path from inside. | ||
* | ||
* Start offset is at offsets[0], after the start quotation. End offset is at | ||
* offsets[1], before the end quotation. | ||
* | ||
* Offsets are at '|': | ||
* | ||
* src="|nikolas/wrote/this|" | ||
* | ||
* If the src attribute cannot be found, null is returned. | ||
* | ||
* @param text | ||
* @return int[] with start and end offset of src attribute value, else null. | ||
*/ | ||
public static int[] extractSourcePathFromHTMLTag(String text) { | ||
Pattern p = Pattern.compile("(src\\s*=\\s*['\"])"); | ||
Matcher m = p.matcher(text); | ||
if (m.find()) { | ||
int srcStartQuote = m.end(); | ||
char quote = text.charAt(srcStartQuote - 1); | ||
int srcEndQuote = text.indexOf(quote, srcStartQuote); | ||
int[] offsets = { srcStartQuote, srcEndQuote }; | ||
return offsets; | ||
} | ||
return null; | ||
} | ||
|
||
public static File getJarPathFromURI(URI uri) { | ||
String pathWithScheme = uri.getSchemeSpecificPart(); | ||
String finalJarRootPath = pathWithScheme.substring(pathWithScheme.indexOf(':') + 1); | ||
|
||
//clean up/verify the jar path | ||
int actualJarIndex = finalJarRootPath.lastIndexOf(".jar"); | ||
|
||
if (actualJarIndex == -1) { | ||
return null; | ||
} | ||
|
||
return new File(finalJarRootPath.substring(0, actualJarIndex + 4)); | ||
} | ||
|
||
/** | ||
* Checks if a given path is absolute. This path must be in the format of a URI | ||
* (with scheme). | ||
* | ||
* eg: | ||
* | ||
* Linux: file:///home/nikolas/file.txt Windows: | ||
* file:///C:/Users/Nikolas/file.txt | ||
* | ||
* If the path is not in the URI format, it will always return false since the | ||
* scheme is missing. | ||
* | ||
* @param path | ||
* in format of URI | ||
* @return true if path/URI is absolute | ||
*/ | ||
private static boolean isPathAbsolute(String path) { | ||
try { | ||
URI uri = new URI(path); | ||
|
||
if (uri.isAbsolute()) { | ||
return true; | ||
} | ||
|
||
return false; | ||
|
||
} catch (URISyntaxException e) { | ||
return false; | ||
} | ||
} | ||
|
||
/** | ||
* Given an inputstream, outputs a file to the given path | ||
* | ||
* @param fileToExtract | ||
* @param pathToExtractTo | ||
* @return | ||
*/ | ||
private static boolean extractFileTo(InputStream fileToExtract, File pathToExtractTo) { | ||
FileOutputStream os = null; | ||
try { | ||
pathToExtractTo.getParentFile().mkdirs(); | ||
os = new FileOutputStream(pathToExtractTo); | ||
int c; | ||
while ((c = fileToExtract.read()) != -1) { | ||
os.write(c); | ||
} | ||
} catch (IOException e) { | ||
return false; | ||
} finally { | ||
if (os != null) { | ||
try { | ||
os.close(); | ||
} catch (IOException e) { | ||
return false; | ||
} | ||
} | ||
} | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/javadoc/SourceJarLocations.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2019 Red Hat Inc. and others. | ||
* All rights reserved. This program and the accompanying materials | ||
* are made available under the terms of the Eclipse Public License v1.0 | ||
* which accompanies this distribution, and is available at | ||
* http://www.eclipse.org/legal/epl-v10.html | ||
* | ||
* Contributors: | ||
* Red Hat Inc. - initial API and implementation | ||
*******************************************************************************/ | ||
package org.eclipse.jdt.ls.core.internal.javadoc; | ||
|
||
import java.io.File; | ||
|
||
import org.eclipse.core.runtime.IPath; | ||
import org.eclipse.jdt.core.IClasspathEntry; | ||
import org.eclipse.jdt.core.IJavaElement; | ||
import org.eclipse.jdt.core.IPackageFragmentRoot; | ||
import org.eclipse.jdt.core.JavaModelException; | ||
import org.eclipse.jdt.internal.corext.util.JavaModelUtil; | ||
|
||
/** | ||
* @author Nikolas Komonen - [email protected] | ||
* | ||
*/ | ||
public class SourceJarLocations { | ||
|
||
|
||
public static File getSourceJarPath(IJavaElement element) throws JavaModelException { | ||
IPackageFragmentRoot root = JavaModelUtil.getPackageFragmentRoot(element); | ||
|
||
if (root == null) { | ||
return null; | ||
} | ||
|
||
IClasspathEntry entry = root.getResolvedClasspathEntry(); | ||
IPath sourceAttachment = entry.getSourceAttachmentPath(); | ||
return sourceAttachment.toFile(); | ||
} | ||
} |
Oops, something went wrong.