blob: 89604edb07ad9685e83f8698b3bfb30d0f233fce [file] [log] [blame]
/*
* 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.netbeans.modules.maven.queries;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.java.queries.JavadocForBinaryQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.maven.NbMavenProjectFactory;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.modules.maven.embedder.EmbedderFactory;
import org.netbeans.modules.maven.spi.queries.ForeignClassBundler;
import org.netbeans.spi.java.project.support.JavadocAndSourceRootDetection;
import org.netbeans.spi.java.queries.JavadocForBinaryQueryImplementation;
import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation;
import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
import org.openide.ErrorManager;
import org.openide.filesystems.FileAttributeEvent;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileRenameEvent;
import org.openide.filesystems.FileUtil;
import org.openide.modules.InstalledFileLocator;
import org.openide.util.ChangeSupport;
import org.openide.util.Exceptions;
import org.openide.util.RequestProcessor;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;
/**
*
* SourceForBinaryQueryImplementation implementation
* for items in the maven2 repository. It checks the artifact and
* looks for the same artifact but of type "sources.jar".
*
* @author Milos Kleint
*/
@ServiceProviders({
@ServiceProvider(service = SourceForBinaryQueryImplementation.class, position = 68),
@ServiceProvider(service = SourceForBinaryQueryImplementation2.class, position = 68),
@ServiceProvider(service = JavadocForBinaryQueryImplementation.class, position = 68)
})
public class RepositoryForBinaryQueryImpl extends AbstractMavenForBinaryQueryImpl {
private final Map<URL, WeakReference<SrcResult>> srcCache = Collections.synchronizedMap(new HashMap<URL, WeakReference<SrcResult>>());
private final Map<URL, WeakReference<JavadocResult>> javadocCache = Collections.synchronizedMap(new HashMap<URL, WeakReference<JavadocResult>>());
private final Map<File, List<Coordinates>> coorCache = Collections.synchronizedMap(new HashMap<>());
//http://maven.apache.org/guides/mini/guide-attached-tests.html
//issue 219453
private static final String CLASSIFIER_TESTS = "tests";
private static final RequestProcessor RP = new RequestProcessor("Maven Repository SFBQ result change");
private static final Logger LOG = Logger.getLogger(SrcResult.class.getName());
private final FileChangeAdapter binaryChangeListener = new FileChangeAdapter() {
@Override
public void fileDataCreated(FileEvent fe) {
removeCoordinates(fe);
}
@Override
public void fileChanged(FileEvent fe) {
removeCoordinates(fe);
}
@Override
public void fileDeleted(FileEvent fe) {
removeCoordinates(fe);
}
@Override
public void fileRenamed(FileRenameEvent fe) {
removeCoordinates(fe);
}
@Override
public void fileAttributeChanged(FileAttributeEvent fe) {
removeCoordinates(fe);
}
private void removeCoordinates(FileEvent fe) {
File file = FileUtil.toFile(fe.getFile());
if(file != null) {
synchronized(coorCache) {
if(coorCache.remove(file) != null) {
FileUtil.removeFileChangeListener(binaryChangeListener, file);
}
}
}
}
};
@Override
public synchronized Result findSourceRoots2(URL url) {
if (!"jar".equals(url.getProtocol())) { //NOI18N
// null for directories.
return null;
}
WeakReference<SrcResult> cached = srcCache.get(url);
if (cached != null) {
SrcResult result = cached.get();
if (result != null) {
return result;
}
}
//#223841 at least one project opened is a stronger condition, embedder gets sometimes reset.
//once we have the project loaded, not loaded embedder doesn't matter anymore, we have to process.
// sometimes the embedder is loaded even though a maven project is not yet loaded, it doesn't hurt to proceed then.
if (!NbMavenProjectFactory.isAtLeastOneMavenProjectAround() && !EmbedderFactory.isProjectEmbedderLoaded()) {
return null;
}
File jarFile = FileUtil.archiveOrDirForURL(url);
if (jarFile != null) {
// String name = jarFile.getName();
File parent = jarFile.getParentFile();
if (parent != null) {
File parentParent = parent.getParentFile();
if (parentParent != null) {
// each repository artifact should have this structure
String artifact = parentParent.getName();
String version = parent.getName();
// File pom = new File(parent, artifact + "-" + version + ".pom");
// // maybe this condition is already overkill..
// if (pom.exists()) {
String start = artifact + "-" + version;
if (jarFile.getName().startsWith(start)) { //one last perf check before calling the embedder
URI localRepo = Utilities.toURI(EmbedderFactory.getProjectEmbedder().getLocalRepositoryFile());
URI rel = localRepo.relativize(Utilities.toURI(parentParent.getParentFile()));
if (!rel.isAbsolute()) {
String groupId = rel.getPath();
if (groupId != null && !groupId.equals("")) {
groupId = groupId.replace("/", ".");
if (groupId.endsWith(".")) {
groupId = groupId.substring(0, groupId.length() - 1);
}
String classifier = null;
if (jarFile.getName().startsWith(start + "-")) {
//we have classifier here..
String end = jarFile.getName().substring((start + "-").length());
if (end.indexOf('.') > -1) {
classifier = end.substring(0, end.indexOf('.'));
}
}
File srcs = new File(parent, start + (classifier != null ? ("-" + ("tests".equals(classifier) ? "test" : classifier)) : "") + "-sources.jar"); //NOI18N
SrcResult result = new SrcResult(groupId, artifact, version, classifier, FileUtil.getArchiveFile(url), srcs, (f) -> getJarMetadataCoordinatesIntern(f));
srcCache.put(url, new WeakReference<SrcResult>(result));
return result;
}
}
}
// }
}
}
File[] f = SourceJavadocByHash.find(url, false);
if (f != null && f.length > 0) {
SrcResult result = new SrcResult(null, null, null, null, FileUtil.getArchiveFile(url), null, (file) -> getJarMetadataCoordinatesIntern(file));
srcCache.put(url, new WeakReference<SrcResult>(result));
return result;
}
}
return null;
}
@Override
public JavadocForBinaryQuery.Result findJavadoc(URL url) {
URL binRoot;
if ("jar".equals(url.getProtocol())) { //NOI18N
binRoot = FileUtil.getArchiveFile(url);
} else {
// null for directories.
return null;
}
// Hack for Java EE jar docs which we ship with netbeans and which are not in any maven repository
if (binRoot.getPath().endsWith("/javax/javaee-api/7.0/javaee-api-7.0.jar")
|| binRoot.getPath().endsWith("/javax/javaee-api/6.0/javaee-api-6.0.jar")
|| binRoot.getPath().endsWith("/javax/javaee-web-api/7.0/javaee-web-api-7.0.jar")
|| binRoot.getPath().endsWith("/javax/javaee-web-api/6.0/javaee-web-api-6.0.jar")
|| binRoot.getPath().endsWith("javax.persistence-2.1.0.jar")
|| binRoot.getPath().endsWith("javax.persistence-2.0.0.jar")) { //NOI18N
return new JavaEEJavadocResult();
}
WeakReference<JavadocResult> cached = javadocCache.get(url);
if (cached != null) {
JavadocResult result = cached.get();
if (result != null) {
return result;
}
}
File jarFile = FileUtil.archiveOrDirForURL(url);
if (jarFile != null) {
File parent = jarFile.getParentFile();
if (parent != null) {
File parentParent = parent.getParentFile();
if (parentParent != null) {
// each repository artifact should have this structure
String artifact = parentParent.getName();
String version = parent.getName();
String start = artifact + "-" + version;
if (jarFile.getName().startsWith(start)) { //one last perf check before calling the embedder
URI localRepo = Utilities.toURI(EmbedderFactory.getProjectEmbedder().getLocalRepositoryFile());
URI rel = localRepo.relativize(Utilities.toURI(parentParent.getParentFile()));
if (!rel.isAbsolute()) {
String groupId = rel.getPath();
if (groupId != null && !groupId.equals("")) {
groupId = groupId.replace("/", ".");
if (groupId.endsWith(".")) {
groupId = groupId.substring(0, groupId.length() - 1);
}
String classifier = null;
if (jarFile.getName().startsWith(start + "-")) {
//we have classifier here..
String end = jarFile.getName().substring((start + "-").length());
if (end.indexOf('.') > -1) {
classifier = end.substring(0, end.indexOf('.'));
}
}
File javadoc = new File(parent, start + (classifier != null ? ("-" + ("tests".equals(classifier) ? "test" : classifier)) : "") + "-javadoc.jar"); //NOI18N
JavadocResult result = new JavadocResult(groupId, artifact, version, classifier, binRoot, javadoc, (f) -> getJarMetadataCoordinatesIntern(f));
javadocCache.put(url, new WeakReference<JavadocResult>(result));
return result;
}
}
}
}
}
File[] f = SourceJavadocByHash.find(url, true);
if (f != null && f.length > 0) {
JavadocResult result = new JavadocResult(null, null, null, null, binRoot, null, (file) -> getJarMetadataCoordinatesIntern(file));
javadocCache.put(url, new WeakReference<JavadocResult>(result));
return result;
}
}
return null;
}
private static class SrcResult implements SourceForBinaryQueryImplementation2.Result {
private static final String ATTR_PATH = "lastRootCheckPath"; //NOI18N
private static final String ATTR_STAMP = "lastRootCheckStamp"; //NOI18N
private final File sourceJarFile;
private final File fallbackSourceJarFile;
private final ChangeSupport support;
private final ChangeListener mfoListener;
private final PropertyChangeListener projectListener;
private final FileChangeListener sourceJarChangeListener;
private final RequestProcessor.Task checkChangesTask;
private static final int CHECK_CHANGES_DELAY = 50;
private final String groupId;
private final String artifactId;
private final String version;
private final String classifier;
private final URL binary;
//TODO should this be weak referenced? how to add/remove project listeners then.
private Project currentProject;
private FileObject[] cached;
private Boolean cachedPreferedSources;
private boolean avoidScheduling = false; //to be accessed and modified only under synchronized lock
private final AtomicBoolean needsFiring = new AtomicBoolean(false);
private final Function<File, List<Coordinates>> coorProvider;
SrcResult(@NullAllowed String groupId, @NullAllowed String artifactId, @NullAllowed String version, @NullAllowed String classifier, @NonNull URL binary, @NullAllowed File sourceJar, Function<File, List<Coordinates>> coorProvider) {
sourceJarFile = sourceJar;
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
this.binary = binary;
this.classifier = classifier;
this.coorProvider = coorProvider;
support = new ChangeSupport(this);
checkChangesTask = RP.create(
new Runnable() {
@Override
public void run() {
checkChanges(true);
}
});
mfoListener = new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if (e instanceof MavenFileOwnerQueryImpl.GAVCHangeEvent) {
MavenFileOwnerQueryImpl.GAVCHangeEvent gav = (MavenFileOwnerQueryImpl.GAVCHangeEvent)e;
//reload only when the project mapping for the current result's gav changed.
if (gav.getGroupId().equals(SrcResult.this.groupId) && gav.getArtifactId().equals(SrcResult.this.artifactId) && gav.getVersion().equals(SrcResult.this.version)) {
checkChangesTask.schedule(CHECK_CHANGES_DELAY);
}
} else {
//external roots in local repository changed..
checkChangesTask.schedule(CHECK_CHANGES_DELAY);
}
}
};
projectListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
if (NbMavenProject.PROP_PROJECT.equals(event.getPropertyName())) {
//project could have changed source roots..
checkChangesTask.schedule(CHECK_CHANGES_DELAY);
}
}
};
sourceJarChangeListener = new FileChangeAdapter() {
@Override
public void fileDataCreated(FileEvent fe) {
//source jar was created..
checkChangesTask.schedule(CHECK_CHANGES_DELAY);
}
};
checkChanges(false);
MavenFileOwnerQueryImpl.getInstance().addChangeListener(
WeakListeners.create(ChangeListener.class, mfoListener, MavenFileOwnerQueryImpl.getInstance()));
if (sourceJarFile != null) {
FileUtil.addFileChangeListener(FileUtil.weakFileChangeListener(sourceJarChangeListener, null));
if (classifier != null) {
// fall back to regular sources if attached sources for classifier are missing
String regularSources = artifactId + "-" + version + "-sources.jar"; //NOI18N
if (!sourceJarFile.getName().equals(regularSources)) {
fallbackSourceJarFile = new File(sourceJarFile.getParentFile(), regularSources);
// already listening for changes through the file change listener above
return;
}
}
}
fallbackSourceJarFile = null;
}
private void checkChanges(boolean fireChanges) {
// use MFOQI to determine what is the current project owning our coordinates in local repository.
Project owner = null;
if (groupId != null && artifactId != null && version != null) {
owner = MavenFileOwnerQueryImpl.getInstance().getOwner(groupId, artifactId, version);
if (owner != null && owner.getLookup().lookup(NbMavenProject.class) == null) {
owner = null;
}
}
synchronized (this) {
if (currentProject != null && !currentProject.equals(owner)) {
currentProject.getLookup().lookup(NbMavenProject.class).removePropertyChangeListener(projectListener);
}
if (owner != null && !owner.equals(currentProject)) {
owner.getLookup().lookup(NbMavenProject.class).addPropertyChangeListener(projectListener);
}
currentProject = owner;
if (fireChanges && !needsFiring.get()) { //only do check if interested in changes and when no known change occured
avoidScheduling = true;
try {
getRoots();
preferSources();
} finally {
avoidScheduling = false;
}
}
}
if (fireChanges && needsFiring.get()) {
support.fireChange();
}
}
@Override
public void addChangeListener(ChangeListener changeListener) {
support.addChangeListener(changeListener);
}
@Override
public void removeChangeListener(ChangeListener changeListener) {
support.removeChangeListener(changeListener);
}
@Override
public FileObject[] getRoots() {
FileObject[] toRet;
Project prj;
synchronized (this) {
prj = currentProject;
}
if (prj != null && classifier == null) {
toRet = getProjectSrcRoots(prj);
} else if (prj != null && CLASSIFIER_TESTS.equals(classifier)) {
toRet = getProjectTestSrcRoots(prj);
} else {
//either no project or a classifier present
URL root = FileUtil.isArchiveFile(binary) ? FileUtil.getArchiveRoot(binary) : binary;
File[] f = SourceJavadocByHash.find(root, false);
if (f != null) {
//hashes are processed separately, should not mesh with other means of discovery.
List<FileObject> accum = new ArrayList<FileObject>();
for (File ff : f) {
accum.addAll(Arrays.asList(getSourceJarRoot(ff)));
}
toRet = accum.toArray(new FileObject[0]);
}
else {
//now comes the magic.
//all this should always come with preferSources == false
List<FileObject> fos = new ArrayList<FileObject>();
if (prj != null) { // have project here means we also have classifier
add(fos, getProjectSrcRoots(prj));
}
//ordering of source jar file and project roots is hard to guess.
// both can fail dependending on how the jar was created.
// the only way to let user decide would be some sort of stamp file inside maven local repository.
if (sourceJarFile != null && sourceJarFile.exists()) {
add(fos, getSourceJarRoot(sourceJarFile));
} else if (fallbackSourceJarFile != null && fallbackSourceJarFile.exists()) {
add(fos, getSourceJarRoot(fallbackSourceJarFile));
}
add(fos, getShadedJarSources());
toRet = fos.toArray(new FileObject[0]);
}
}
synchronized (this) {
if (cached != null && !Arrays.equals(cached, toRet)) {
if (needsFiring.compareAndSet(false, true) && !avoidScheduling) {
checkChangesTask.schedule(CHECK_CHANGES_DELAY);
}
}
cached = toRet;
}
return toRet;
}
private void add(List<FileObject> to, FileObject[] add) {
for (FileObject a : add) {
if (!to.contains(a)) {
to.add(a);
}
}
}
private static String checkPath(FileObject jarRoot, FileObject fo) {
String toRet = null;
FileObject root = JavadocAndSourceRootDetection.findSourceRoot(jarRoot);
try {
if (root != null && !root.equals(jarRoot)) {
toRet = FileUtil.getRelativePath(jarRoot, root);
fo.setAttribute(ATTR_PATH, toRet);
}
fo.setAttribute(ATTR_STAMP, new Date());
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
return toRet;
}
private @NonNull static FileObject[] getSourceJarRoot(File sourceJar) {
FileObject fo = FileUtil.toFileObject(sourceJar);
if (fo != null) {
FileObject jarRoot = FileUtil.getArchiveRoot(fo);
if (jarRoot != null) { //#139894 it seems that sometimes it can return null.
// I suppose it's in the case when the jar/zip file in repository exists
// but is corrupted (not zip, eg. when downloaded from a wrongly
//setup repository that returns html documents on missing jar files.
//try detecting the source path root, in case the source jar has the sources not in root.
Date date = (Date) fo.getAttribute(ATTR_STAMP);
String path = (String) fo.getAttribute(ATTR_PATH);
if (date == null || fo.lastModified().after(date)) {
path = checkPath(jarRoot, fo);
}
FileObject[] fos = new FileObject[1];
if (path != null) {
fos[0] = jarRoot.getFileObject(path);
}
if (fos[0] == null) {
fos[0] = jarRoot;
}
return fos;
}
}
return new FileObject[0];
}
@Override
public boolean preferSources() {
boolean toRet = false;
Project prj;
synchronized (this) {
prj = currentProject;
}
if (prj != null && classifier == null) {
if (!NbMavenProject.isErrorPlaceholder(prj.getLookup().lookup(NbMavenProject.class).getMavenProject())) {
toRet = prj.getLookup().lookup(ForeignClassBundler.class).preferSources();
}
} else if (prj != null && CLASSIFIER_TESTS.equals(classifier)) {
toRet = true;
}
synchronized (this) {
if (cachedPreferedSources != null && !cachedPreferedSources.equals(toRet)) {
if (needsFiring.compareAndSet(false, true) && !avoidScheduling) {
checkChangesTask.schedule(CHECK_CHANGES_DELAY);
}
}
cachedPreferedSources = toRet;
}
return toRet;
}
private @NonNull synchronized FileObject[] getShadedJarSources() {
try {
List<Coordinates> coordinates = coorProvider.apply(Utilities.toFile(binary.toURI()));
File lrf = EmbedderFactory.getProjectEmbedder().getLocalRepositoryFile();
List<FileObject> fos = new ArrayList<FileObject>();
if (coordinates != null) {
for (Coordinates coord : coordinates) {
if (coord.artifactId.equals(artifactId) && coord.groupId.equals(groupId) && coord.version.equals(version)) {
continue; //skip the current jar, we've catered to in other ways.
}
File sourceJar = new File(lrf, coord.groupId.replace(".", File.separator) + File.separator + coord.artifactId + File.separator + coord.version + File.separator + coord.artifactId + "-" + coord.version + "-sources.jar");
fos.addAll(Arrays.asList(getSourceJarRoot(sourceJar)));
}
}
return fos.toArray(new FileObject[0]);
} catch (Exception ex) {
LOG.log(Level.INFO, "error while examining binary " + binary, ex);
}
return new FileObject[0];
}
}
public static class Coordinates {
public final String groupId;
public final String artifactId;
public final String version;
private Coordinates(String groupId, String artifactId, String version) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
}
}
private List<Coordinates> getJarMetadataCoordinatesIntern(File binaryFile) {
if (binaryFile == null || !binaryFile.exists() || !binaryFile.isFile()) {
return null;
}
synchronized (coorCache) {
List<Coordinates> toRet = coorCache.get(binaryFile);
if(toRet == null) {
toRet = getJarMetadataCoordinates(binaryFile);
if(toRet != null) {
FileUtil.addFileChangeListener(binaryChangeListener, binaryFile);
coorCache.put(binaryFile, toRet);
}
}
return toRet;
}
}
public static List<Coordinates> getJarMetadataCoordinates(File binaryFile) {
if (binaryFile == null || !binaryFile.exists() || !binaryFile.isFile()) {
return null;
}
ZipFile zip = null;
try {
List<Coordinates> toRet = new ArrayList<Coordinates>();
zip = new ZipFile(binaryFile);
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry ent = entries.nextElement();
String name = ent.getName();
if (name.startsWith("META-INF") && name.endsWith("pom.properties")) {
Properties p = new Properties();
p.load(zip.getInputStream(ent));
String groupId = p.getProperty("groupId");
String artifactId = p.getProperty("artifactId");
String version = p.getProperty("version");
if (groupId != null && artifactId != null && version != null) {
toRet.add(new Coordinates(groupId, artifactId, version));
}
}
}
if (toRet.size() > 0) {
return toRet;
}
} catch (IOException ex) {
LOG.log(Level.INFO, "error while examining binary " + binaryFile, ex);
} finally {
if (zip != null) {
try {
zip.close();
} catch (IOException ex) {
// Exceptions.printStackTrace(ex);
}
}
}
return null;
}
private static class JavadocResult implements JavadocForBinaryQuery.Result {
private final File javadocJarFile;
private final File fallbackJavadocJarFile;
private final String groupId;
private final String artifactId;
private final String version;
private final String classifier;
private final URL binary;
private final String gav;
private final ChangeSupport support;
private final PropertyChangeListener projectListener;
private final FileChangeAdapter javadocJarChangeListener;
private Project currentProject;
private URL[] cached;
private static final String ATTR_PATH = "lastRootCheckPath"; //NOI18N
private static final String ATTR_STAMP = "lastRootCheckStamp"; //NOI18N
private final ChangeListener mfoListener;
private final Function<File, List<Coordinates>> coorProvider;
JavadocResult(@NullAllowed String groupId, @NullAllowed String artifactId, @NullAllowed String version, @NullAllowed String classifier, @NonNull URL binary, @NullAllowed File javadocJar, @NonNull Function<File, List<Coordinates>> coorProvider) {
javadocJarFile = javadocJar;
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
this.binary = binary;
this.classifier = classifier;
this.gav = MavenFileOwnerQueryImpl.cacheKey(groupId, artifactId, version);
this.coorProvider = coorProvider;
support = new ChangeSupport(this);
mfoListener = new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
//external root in local repository changed..
checkChanges();
}
};
projectListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
if (NbMavenProject.PROP_PROJECT.equals(event.getPropertyName())) {
checkChanges();
}
}
};
javadocJarChangeListener = new FileChangeAdapter() {
@Override
public void fileDataCreated(FileEvent fe) {
//source jar was created..
checkChanges();
}
};
MavenFileOwnerQueryImpl.getInstance().addChangeListener(
WeakListeners.create(ChangeListener.class, mfoListener, MavenFileOwnerQueryImpl.getInstance()));
if (javadocJarFile != null) {
FileUtil.addFileChangeListener(javadocJarChangeListener, javadocJarFile);
if (classifier != null) {
// listen for regular javadoc because attached javadoc for classifier might be missing
String regularJavadoc = artifactId + "-" + version + "-javadoc.jar"; //NOI18N
if (!javadocJarFile.getName().equals(regularJavadoc)) {
fallbackJavadocJarFile = new File(javadocJarFile.getParentFile(), regularJavadoc);
FileUtil.addFileChangeListener(javadocJarChangeListener, fallbackJavadocJarFile);
return;
}
}
}
fallbackJavadocJarFile = null;
}
@Override
public synchronized URL[] getRoots() {
URL[] toRet;
checkCurrentProject();
Project prj = currentProject;
if (prj != null) {
toRet = new URL[0];
} else {
URL root = FileUtil.isArchiveFile(binary) ? FileUtil.getArchiveRoot(binary) : binary;
File[] f = SourceJavadocByHash.find(root, true);
if (f != null) {
List<URL> accum = new ArrayList<URL>();
for (File ff : f) {
URL[] url = getJavadocJarRoot(ff);
if (url != null) {
accum.addAll(Arrays.asList(url));
}
}
toRet = accum.toArray(new URL[0]);
} else if (javadocJarFile != null && javadocJarFile.exists()) {
toRet = getJavadocJarRoot(javadocJarFile);
} else if (fallbackJavadocJarFile != null && fallbackJavadocJarFile.exists()) {
toRet = getJavadocJarRoot(fallbackJavadocJarFile);
} else {
toRet = checkShadedMultiJars();
}
}
if (!Arrays.equals(cached, toRet)) {
//how to figure otherwise that something changed, possibly multiple people hold the result instance
// and one asks the roots, later we get event from outside, but then the cached value already updated..
RP.post(new Runnable() {
@Override
public void run() {
support.fireChange();
}
});
}
cached = toRet;
return toRet;
}
@Override
public void addChangeListener(ChangeListener l) {
support.addChangeListener(l);
}
@Override
public void removeChangeListener(ChangeListener l) {
support.removeChangeListener(l);
}
private void checkChanges() {
//getRoots will fire change in the result if the old cached value is different from the newly generated one
getRoots();
}
/**
* use MFOQI to determine what is the current project owning our coordinates in local repository.
*/
private void checkCurrentProject() {
Project owner = null;
if (groupId != null && artifactId != null && version != null) {
owner = MavenFileOwnerQueryImpl.getInstance().getOwner(groupId, artifactId, version);
}
if (owner != null && owner.getLookup().lookup(NbMavenProject.class) == null) {
owner = null;
}
if (currentProject != null && !currentProject.equals(owner)) {
currentProject.getLookup().lookup(NbMavenProject.class).removePropertyChangeListener(projectListener);
}
if (owner != null && !owner.equals(currentProject)) {
owner.getLookup().lookup(NbMavenProject.class).addPropertyChangeListener(projectListener);
}
currentProject = owner;
}
private URL[] getJavadocJarRoot(File file) {
try {
if (file.exists()) {
FileObject fo = FileUtil.toFileObject(file);
if (!FileUtil.isArchiveFile(fo)) {
//#124175 ignore any jar files that are not jar files (like when downloaded file is actually an error html page).
Logger.getLogger(RepositoryForBinaryQueryImpl.class.getName()).log(Level.INFO, "javadoc in repository is not really a JAR: {0}", file);
return new URL[0];
}
//try detecting the source path root, in case the source jar has the sources not in root.
Date date = (Date) fo.getAttribute(ATTR_STAMP);
String path = (String) fo.getAttribute(ATTR_PATH);
if (date == null || fo.lastModified().after(date)) {
path = checkPath(FileUtil.getArchiveRoot(fo), fo);
}
URL[] url;
if (path != null) {
url = new URL[1];
URL root = FileUtil.getArchiveRoot(Utilities.toURI(file).toURL());
if (!path.endsWith("/")) { //NOI18N
path = path + "/"; //NOI18N
}
url[0] = new URL(root, path);
} else {
url = new URL[1];
url[0] = FileUtil.getArchiveRoot(Utilities.toURI(file).toURL());
}
return url;
}
} catch (MalformedURLException exc) {
ErrorManager.getDefault().notify(exc);
}
return new URL[0];
}
private String checkPath(FileObject jarRoot, FileObject fo) {
String toRet = null;
FileObject root = JavadocAndSourceRootDetection.findJavadocRoot(jarRoot);
try {
if (root != null && !root.equals(jarRoot)) {
toRet = FileUtil.getRelativePath(jarRoot, root);
fo.setAttribute(ATTR_PATH, toRet);
}
fo.setAttribute(ATTR_STAMP, new Date());
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
return toRet;
}
private synchronized URL[] checkShadedMultiJars() {
try {
List<Coordinates> coordinates = coorProvider.apply(Utilities.toFile(binary.toURI()));
File lrf = EmbedderFactory.getProjectEmbedder().getLocalRepositoryFile();
List<URL> urls = new ArrayList<URL>();
if (coordinates != null) {
for (Coordinates coord : coordinates) {
File javadocJar = new File(lrf, coord.groupId.replace(".", File.separator) + File.separator + coord.artifactId + File.separator + coord.version + File.separator + coord.artifactId + "-" + coord.version + "-javadoc.jar");
URL[] fo = getJavadocJarRoot(javadocJar);
if (fo.length == 1) {
urls.add(fo[0]);
}
}
}
if (urls.size() > 1) {
URL[] shaded = urls.toArray(new URL[0]);
return shaded;
}
} catch (Exception ex) {
LOG.log(Level.INFO, "error while examining binary " + binary, ex);
}
return new URL[0];
}
}
private static class JavaEEJavadocResult implements JavadocForBinaryQuery.Result {
@Override
public void addChangeListener(ChangeListener changeListener) {
}
@Override
public void removeChangeListener(ChangeListener changeListener) {
}
@Override
public URL[] getRoots() {
try {
File j2eeDoc = InstalledFileLocator.getDefault().locate("docs/javaee-doc-api.jar", "org.netbeans.modules.j2ee.platform", false); // NOI18N
if (j2eeDoc != null) {
URL url = FileUtil.getArchiveRoot(Utilities.toURI(j2eeDoc).toURL());
url = new URL(url + "docs/api/"); //NOI18N
return new URL[]{url};
}
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
}
return new URL[0];
}
}
}