org.netbeans.modules.maven.NbMavenProjectImpl Maven / Gradle / Ivy
/*
* 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;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.swing.SwingUtilities;
import org.apache.maven.DefaultMaven;
import org.apache.maven.Maven;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.cli.MavenCli;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.Resource;
import org.apache.maven.model.building.ModelBuildingException;
import org.apache.maven.model.building.ModelBuildingResult;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingRequest;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.annotations.common.NullUnknown;
import org.netbeans.api.java.project.classpath.ProjectClassPathModifier;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectActionContext;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.api.queries.VisibilityQuery;
import org.netbeans.modules.maven.api.Constants;
import org.netbeans.modules.maven.api.FileUtilities;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.modules.maven.api.PluginPropertyUtils;
import org.netbeans.modules.maven.api.execute.ActiveJ2SEPlatformProvider;
import org.netbeans.modules.maven.api.execute.RunConfig;
import org.netbeans.modules.maven.configurations.M2ConfigProvider;
import org.netbeans.modules.maven.configurations.M2Configuration;
import org.netbeans.modules.maven.configurations.ProjectProfileHandlerImpl;
import org.netbeans.modules.maven.cos.CopyResourcesOnSave;
import org.netbeans.modules.maven.debug.MavenJPDAStart;
import org.netbeans.modules.maven.embedder.EmbedderFactory;
import org.netbeans.modules.maven.embedder.MavenEmbedder;
import org.netbeans.modules.maven.execute.ActionToGoalUtils;
import org.netbeans.modules.maven.modelcache.MavenProjectCache;
import org.netbeans.modules.maven.options.MavenSettings;
import org.netbeans.modules.maven.problems.ProblemReporterImpl;
import org.netbeans.modules.maven.queries.PomCompilerOptionsQueryImpl;
import org.netbeans.modules.maven.queries.UnitTestsCompilerOptionsQueryImpl;
import org.netbeans.modules.maven.spi.nodes.OtherSourcesExclude;
import org.netbeans.modules.maven.spi.queries.JavaLikeRootProvider;
import org.netbeans.spi.java.project.support.LookupMergerSupport;
import org.netbeans.spi.project.ProjectState;
import org.netbeans.spi.project.support.LookupProviderSupport;
import org.netbeans.spi.project.ui.support.UILookupMergerSupport;
import org.openide.filesystems.FileAttributeEvent;
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.filesystems.MIMEResolver;
import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages;
import org.openide.util.NbCollections;
import org.openide.util.Pair;
import org.openide.util.RequestProcessor;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;
/**
* A Maven-based project.
*/
@MIMEResolver.Registration(
displayName="#POMResolver",
position=309,
resource="POMResolver.xml"
)
public final class NbMavenProjectImpl implements Project {
private static final Logger LOG = Logger.getLogger(NbMavenProjectImpl.class.getName());
//sequential execution might be necesary for #166919
public static final RequestProcessor RELOAD_RP = new RequestProcessor("Maven project reloading", 1); //NOI18
//minor optimization. In case the queue already holds the task and is not run, delay, if running reschedule.
private final RequestProcessor.Task reloadTask = RELOAD_RP.create(new Runnable() {
@Override
public void run() {
if (LOG.isLoggable(Level.FINE)) {
MavenProject x;
synchronized (NbMavenProjectImpl.this) {
x = project == null ? null : project.get();
}
LOG.log(Level.FINE, "Project {0} starting reload. Currentproject is: {1}",
new Object[] { Integer.toHexString(System.identityHashCode(x)), x });
}
problemReporter.clearReports(); //#167741 -this will trigger node refresh?
MavenProject prj = loadOriginalMavenProject(true);
MavenProject old;
synchronized (NbMavenProjectImpl.this) {
old = project == null ? null : project.get();
LOG.log(Level.FINE, "Project {0} reloaded. Old project is: {1}, new project {2}",
new Object[] { prj, Integer.toHexString(System.identityHashCode(old)), Integer.toHexString(System.identityHashCode(prj)) });
if (old != null && MavenProjectCache.isFallbackproject(prj)) {
prj.setPackaging(old.getPackaging()); //#229366 preserve packaging for broken projects to avoid changing lookup.
}
project = new SoftReference(prj);
if (hardReferencingMavenProject) {
hardRefProject = prj;
}
projectVariants.clear();
}
ACCESSOR.doFireReload(watcher);
reloadPossibleBrokenModules(old, prj);
}
}, true);
private final FileObject fileObject;
private final FileObject folderFileObject;
private final File projectFile;
private final Lookup basicLookup;
private final Lookup completeLookup;
private final Lookup lookup;
private final Updater openedProjectUpdater;
private Reference project;
private boolean hardReferencingMavenProject = false; //only should be true when project is open.
private MavenProject hardRefProject;
private ProblemReporterImpl problemReporter;
private final @NonNull NbMavenProject watcher;
private final M2ConfigProvider configProvider;
private final @NonNull MavenProjectPropsImpl auxprops;
private ProjectProfileHandlerImpl profileHandler;
private CopyResourcesOnSave copyResourcesOnSave;
private final Object COPYRESOURCES_LOCK = new Object();
@org.netbeans.api.annotations.common.SuppressWarnings("MS_SHOULD_BE_FINAL")
public static WatcherAccessor ACCESSOR = null;
static {
// invokes static initializer of ModelHandle.class
// that will assign value to the ACCESSOR field above
Class> c = NbMavenProject.class;
try {
Class.forName(c.getName(), true, c.getClassLoader());
} catch (Exception ex) {
LOG.log(Level.SEVERE, "very wrong, very wrong, yes indeed", ex);
}
}
//#224012
private ProjectOpenedHookImpl hookImpl;
private Exception ex;
private final Object LOCK_224012 = new Object();
boolean setIssue224012(ProjectOpenedHookImpl hook, Exception exception) {
synchronized (LOCK_224012) {
if (hookImpl == null) {
hookImpl = hook;
ex = exception;
return true;
} else {
LOG.log(Level.INFO, " first creation stacktrace", ex);
LOG.log(Level.INFO, " second creation stacktrace", exception);
LOG.log(Level.WARNING, "Spotted issue 224012 (https://netbeans.org/bugzilla/show_bug.cgi?id=224012). Please report the incident wth IDE log attached.");
return false;
}
}
}
private final Object MODEL_LOCK = new Object();
private Model model;
public Model getRawModel() throws ModelBuildingException {
synchronized(MODEL_LOCK) {
if(model == null) {
MavenEmbedder projectEmbedder = EmbedderFactory.getProjectEmbedder();
ModelBuildingResult br = projectEmbedder.executeModelBuilder(getPOMFile());
model = br.getRawModel();
}
return model;
}
}
public abstract static class WatcherAccessor {
public abstract NbMavenProject createWatcher(NbMavenProjectImpl proj);
public abstract void doFireReload(NbMavenProject watcher);
}
/**
* Creates a new instance of MavenProject, should never be called by user code.
* but only by MavenProjectFactory!!!
*/
NbMavenProjectImpl(FileObject folder, FileObject projectFO, ProjectState projectState) {
this.projectFile = FileUtil.normalizeFile(FileUtil.toFile(projectFO));
fileObject = projectFO;
folderFileObject = folder;
lookup = Lookups.proxy(new Lookup.Provider() {
@Override
public Lookup getLookup() {
if (completeLookup == null) {
//not fully initialized constructor
LOG.log(Level.FINE, "accessing project's lookup before the instance is fully initialized at " + projectFile, new Exception());
assert basicLookup != null;
return basicLookup;
} else {
return completeLookup;
}
}
});
watcher = ACCESSOR.createWatcher(this);
openedProjectUpdater = new Updater(new FileProvider() {
@Override
public File[] getFiles() {
File homeFile = FileUtil.normalizeFile(MavenCli.USER_MAVEN_CONFIGURATION_HOME);
return new File[] {
new File(projectFile.getParentFile(), "nb-configuration.xml"), //NOI18N
projectFile,
new File(new File(projectFile.getParentFile(), ".mvn"), "maven.config"), //NOI18N
new File(homeFile, "settings.xml"), //NOI18N
};
}
});
problemReporter = new ProblemReporterImpl(this);
M2AuxilaryConfigImpl auxiliary = new M2AuxilaryConfigImpl(folder, true);
auxprops = new MavenProjectPropsImpl(auxiliary, this);
profileHandler = new ProjectProfileHandlerImpl(this, auxiliary);
configProvider = new M2ConfigProvider(this, auxiliary, profileHandler);
// @PSP's and the like, and PackagingProvider impls, may check project lookup for e.g. NbMavenProject, so init lookup in two stages:
basicLookup = createBasicLookup(projectState, auxiliary);
//here we always load the MavenProject instance because we need to touch the packaging from pom.
completeLookup = LookupProviderSupport.createCompositeLookup(basicLookup, new PackagingTypeDependentLookup(watcher));
}
public File getPOMFile() {
return projectFile;
}
public @NonNull NbMavenProject getProjectWatcher() {
return watcher;
}
public ProblemReporterImpl getProblemReporter() {
return problemReporter;
}
public long getLoadTimestamp() {
return MavenProjectCache.getLoadTimestamp(this.project.get());
}
public String getHintJavaPlatform() {
String hint = getAuxProps().get(Constants.HINT_JDK_PLATFORM, true);
if (hint == null) {
hint = MavenSettings.getDefault().getDefaultJdk();
}
return hint == null || hint.isEmpty() ? null : hint;
}
/**
* load a project with properties and profiles other than the current ones.
* @param embedder embedder to use
* @param activeProfiles
* @param properties
* @return
*/
//TODO revisit usage, eventually should be only reuse MavenProjectCache
public @NonNull MavenProject loadMavenProject(MavenEmbedder embedder, List activeProfiles, Properties properties) {
ProjectActionContext.Builder b = ProjectActionContext.newBuilder(this).
withProfiles(activeProfiles);
if (properties != null) {
for (String pn : properties.stringPropertyNames()) {
b.withProperty(pn, properties.getProperty(pn));
}
}
return MavenProjectCache.loadMavenProject(projectFile,
b.context(), null);
/*
try {
MavenExecutionRequest req = embedder.createMavenExecutionRequest();
req.addActiveProfiles(activeProfiles);
req.setPom(projectFile);
req.setNoSnapshotUpdates(true);
req.setUpdateSnapshots(false);
//#238800 important to merge, not replace
if (properties != null) {
Properties uprops = req.getUserProperties();
uprops.putAll(properties);
req.setUserProperties(uprops);
}
//MEVENIDE-634 i'm wondering if this fixes the issue
req.setInteractiveMode(false);
req.setOffline(true);
// recursive == false is important to avoid checking all submodules for extensions
// that will not be used in current pom anyway..
// #135070
req.setRecursive(false);
MavenExecutionResult res = embedder.readProjectWithDependencies(req, true);
//#215159 clear the project building request, it references multiple Maven Models via the RepositorySession cache
//is not used in maven itself, most likely used by m2e only..
if (!res.hasExceptions()) {
res.getProject().setProjectBuildingRequest(null);
return res.getProject();
} else {
List exc = res.getExceptions();
for (Throwable ex : exc) {
LOG.log(Level.FINE, "Exception thrown while loading maven project at " + getProjectDirectory(), ex); //NOI18N
}
}
} catch (RuntimeException exc) {
//guard against exceptions that are not processed by the embedder
//#136184 NumberFormatException
LOG.log(Level.INFO, "Runtime exception thrown while loading maven project at " + getProjectDirectory(), exc); //NOI18N
}
return MavenProjectCache.getFallbackProject(this.getPOMFile());
*/
}
/**
* replacement for MavenProject.getParent() which has bad long term memory behaviour. We offset it by recalculating/reparsing everything
* therefore should not be used lightly!
* pass a MavenProject instance and current configuration and other settings will be applied when loading the parent.
* @param project
* @return null or the parent mavenproject
*/
public MavenProject loadParentOf(MavenEmbedder embedder, MavenProject project) throws ProjectBuildingException {
MavenProject parent = null;
ProjectBuilder builder = embedder.lookupComponent(ProjectBuilder.class);
MavenExecutionRequest req = embedder.createMavenExecutionRequest();
M2Configuration active = configProvider.getActiveConfiguration();
req.addActiveProfiles(active.getActivatedProfiles());
req.setNoSnapshotUpdates(true);
req.setUpdateSnapshots(false);
req.setInteractiveMode(false);
req.setRecursive(false);
req.setOffline(true);
//#238800 important to merge, not replace
Properties uprops = req.getUserProperties();
uprops.putAll(MavenProjectCache.createUserPropsForProjectLoading(active.getProperties()));
req.setUserProperties(uprops);
ProjectBuildingRequest request = req.getProjectBuildingRequest();
request.setRemoteRepositories(project.getRemoteArtifactRepositories());
DefaultMaven maven = (DefaultMaven) embedder.lookupComponent(Maven.class);
request.setRepositorySession(maven.newRepositorySession(req));
if (project.getParentFile() != null) {
req.setPom(project.getParentFile());
parent = MavenProjectCache.loadOriginalMavenProjectInternal(embedder, req);
} else if (project.getModel().getParent() != null) {
parent = builder.build(project.getParentArtifact(), request).getProject();
}
//clear the project building request, it references multiple Maven Models via the RepositorySession cache
//is not used in maven itself, most likely used by m2e only..
if (parent != null) {
parent.setProjectBuildingRequest(null);
}
MavenEmbedder.normalizePaths(parent);
return parent;
}
public List getCurrentActiveProfiles() {
List toRet = new ArrayList();
toRet.addAll(configProvider.getActiveConfiguration().getActivatedProfiles());
return toRet;
}
//#172952 for property expression resolution we need this to include
// the properties of the platform to properly resolve stuff like com.sun.boot.class.path
public Map extends String,? extends String> createSystemPropsForPropertyExpressions() {
Map props = NbCollections.checkedMapByCopy(EmbedderFactory.getProjectEmbedder().getSystemProperties(), String.class, String.class, true);
ActiveJ2SEPlatformProvider platformProvider = getLookup().lookup(ActiveJ2SEPlatformProvider.class);
if (platformProvider != null) { // may be null inside PackagingProvider
props.putAll(platformProvider.getJavaPlatform().getSystemProperties());
}
return props;
}
public Map extends String,? extends String> createUserPropsForPropertyExpressions() {
return NbCollections.checkedMapByCopy(configProvider.getActiveConfiguration().getProperties(), String.class, String.class, true);
}
/**
* Returns the current parsed project state. May return {@code null}, if the project was never loaded or expired from the cache, but
* never blocks on Maven infrastructure and is very fast.
* @return current project or {@code null}
*/
@CheckForNull
public MavenProject getOriginalMavenProjectOrNull() {
synchronized (this) {
if (project == null) {
return null;
}
return project.get();
}
}
/**
* getter for the maven's own project representation.. this instance is cached but gets reloaded
* when one the pom files have changed.
*/
public @NonNull MavenProject getOriginalMavenProject() {
MavenProject mp;
synchronized (this) {
mp = project == null ? null : project.get();
if (mp != null) {
return mp;
}
if (mp == null) {
// PENDING: should be the whole project load synchronized ?
mp = loadOriginalMavenProject(false);
project = new SoftReference<>(mp);
if (hardReferencingMavenProject) {
hardRefProject = mp;
}
}
}
// in case someone got already information from the NbMavenProject:
ACCESSOR.doFireReload(watcher);
return mp;
}
/**
* Returns the original project, or waits for reload task if already pending. Use with care, as
* the method blocks until the project reload eventually finishes in the reload thread / RP.
* @return possibly reloaded Maven project.
*/
public CompletableFuture getFreshOriginalMavenProject() {
if (reloadTask.isFinished()) {
return CompletableFuture.completedFuture(getOriginalMavenProject());
} else {
LOG.log(Level.FINE, "Asked for project {0} being updated, waiting for the refresh to complete.", projectFile);
CompletableFuture f = new CompletableFuture<>();
reloadTask.addTaskListener((e) -> {
// TODO: must remove the listener, it is one shot !
LOG.log(Level.FINE, "Project {0} update done.", projectFile);
f.complete(getOriginalMavenProject());
});
return f;
}
}
/**
* Variants of the projects, possibly other than the ones with the
* active configuration
*/
private Map> projectVariants = new WeakHashMap<>();
public @NonNull MavenProject getEvaluatedProject(ProjectActionContext ctx) {
if (ctx == null) {
return getOriginalMavenProject();
}
ProjectActionContext stripped =
ProjectActionContext.newBuilder(ctx.getProject())
.withProfiles(ctx.getProfiles())
.withProperties(ctx.getProperties())
.forProjectAction(ctx.getProjectAction())
.context();
MavenProject result;
synchronized (this) {
Reference ref = projectVariants.get(stripped);
if (ref != null) {
result = ref.get();
if (result != null) {
return result;
} else {
projectVariants.remove(stripped);
}
}
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Loading evaluated project. Action = {0}, properties = {1}, profiles = {2}",
new Object[] { stripped.getProjectAction(), stripped.getProperties(), stripped.getProfiles() });
}
}
RunConfig runConf = null;
if (ctx != null && ctx.getProjectAction() != null) {
runConf = ActionToGoalUtils.createRunConfig(ctx.getProjectAction(), this, ctx.getConfiguration(), Lookup.EMPTY);
}
MavenProject newproject = MavenProjectCache.loadMavenProject(this.getPOMFile(), ctx, runConf);
synchronized (this) {
Reference ref = projectVariants.get(stripped);
if (ref == null || ref.get() == null) {
projectVariants.put(stripped, new SoftReference<>(newproject));
}
}
return newproject;
}
/**
* a marginally unreliable, non blocking method for figuring if the model is loaded or not.
* @return
*/
public boolean isMavenProjectLoaded() {
Reference prj = project;
if (prj != null) {
return prj.get() != null;
}
return false;
}
/**
* open projects should always hard reference the Mavenproject instance to prevent it from
* being GCed, the instance will get reloaded almost instantly anyway
*/
void startHardReferencingMavenPoject() {
synchronized (this) {
hardReferencingMavenProject = true;
MavenProject mp = project == null ? null : project.get();
hardRefProject = mp;
}
}
/**
* open projects should always hard reference the Mavenproject instance to prevent it from
* being GCed, the instance will get reloaded almost instantly anyway
*/
void stopHardReferencingMavenPoject() {
synchronized (this) {
hardReferencingMavenProject = false;
hardRefProject = null;
}
}
@Messages({
"TXT_RuntimeException=RuntimeException occurred in Apache Maven embedder while loading",
"TXT_RuntimeExceptionLong=RuntimeException occurred in Apache Maven embedder while loading the project. \n"
+ "This is preventing the project model from loading properly. \n"
+ "Please file a bug report with details about your project and the IDE's log file.\n\n"
})
private @NonNull MavenProject loadOriginalMavenProject(boolean reload) {
LOG.log(Level.FINE, "Loading original project: {0}", getPOMFile());
MavenProject newproject;
try {
synchronized(MODEL_LOCK) {
model = null;
}
newproject = MavenProjectCache.getMavenProject(this.getPOMFile(), reload);
} finally {
if (LOG.isLoggable(Level.FINE) && SwingUtilities.isEventDispatchThread()) {
LOG.log(Level.FINE, "Project " + getProjectDirectory().getPath() + " loaded in AWT event dispatching thread!", new RuntimeException());
}
}
assert newproject != null;
return newproject;
}
/**
* Task that potential project reloads should wait on. If set, a {@link fireProjectReload}(true) will be scheduled only after this blocker finishes.
*/
// @GuardedBy(this)
private List blockingList = new ArrayList<>();
/**
* Task, that will be returned if the reload is blocked. Needed as an existing available reload task instance is finished before it is scheduled again.
*/
// @GuardedBy(this)
private RequestProcessor.Task reloadCompletionTask;
// @GuardedBy(this)
private RequestProcessor.Task operationCompletionTask;
// tests only !
synchronized Pair, RequestProcessor.Task> reloadBlockingState() {
return Pair.of(new ArrayList<>(this.blockingList), this.reloadCompletionTask);
}
// tests only !
RequestProcessor.Task getReloadTask() {
return reloadTask;
}
public RequestProcessor.Task scheduleProjectOperation(RequestProcessor rp, Runnable r, int delay) {
return scheduleProjectOperation(rp, r, delay, false);
}
/**
* Schedules project operation that delays potential reloads. If a reload is posted, it will be performed only after
* this operation compeltes (successfully, or erroneously). Multiple project operations can be scheduled, an eventual project reload
* should happen after all those operations complete. It is possible to postpone project reload indefinitely, avoid unnecessary
* operation schedules.
*
* To avoid race condition on task startup, this method actually creates and schedules the task so it blocks reloads from its inception.
* Since the project might be reloaded and full effects of the reload will be visible only after broadcasting and completing PROP_PROJECT
* event, the caller might ask to return a Task that will complete after all that is complete. If no project reload happens the task
* completes as soon as the Runnable finishes.
*
* @param rp request processor that should schedule the task
* @param delay optional delay, use 0 for immediate run
* @param r operation to run
* @param afterChangesFired if true, the returned Task will complete only after the post-operation project reload fires its events.
* @return the scheduled task
*/
public RequestProcessor.Task scheduleProjectOperation(RequestProcessor rp, Runnable r, int delay, boolean afterChangesFired) {
RequestProcessor.Task t = rp.create(r);
if (Boolean.getBoolean("test.reload.sync")) {
LOG.log(Level.FINE, "Running the blocking task synchronously (test.reload.sync set)");
t.run();
return t;
} else {
RequestProcessor.Task t2;
synchronized (this) {
if (operationCompletionTask == null) {
operationCompletionTask = RELOAD_RP.create(() -> {});
}
t2 = operationCompletionTask;
blockingList.add(t);
if (LOG.isLoggable(Level.FINER)) {
LOG.log(Level.FINER, "Blocking project reload on task {0}, blocking queue: {1}", new Object[] { t, blockingList });
}
t.addTaskListener((e) -> {
boolean runImmediately;
synchronized (this) {
blockingList.remove(t);
if (!blockingList.isEmpty()) {
LOG.log(Level.FINER, "Project {0} task {1} finished, still blocked", new Object[] { this, t });
return;
}
// the value is still cached in t2
operationCompletionTask = null;
runImmediately = reloadCompletionTask == null;
}
if (runImmediately) {
LOG.log(Level.FINER, "Project {0} task {1} finished, no reload requested", new Object[] { this, t });
t2.run();
return;
}
LOG.log(Level.FINER, "Project {0} task {1} finished, project reload released", new Object[] { this, t });
fireProjectReload(true).addTaskListener((e2) -> {
t2.run();
});
});
}
t.schedule(delay);
return afterChangesFired ? t2 : t;
}
}
public RequestProcessor.Task fireProjectReload() {
return fireProjectReload(false);
}
/**
* Schedules project reload. If `waitForBlockers` is true and {@link #scheduleProjectOperation} registered some task(s), project reload
* will be postponed until after those task(s) finish. The returned task completes after the project reload itself completes (after the potential
* delays).
*
* As a result of project's reload, child projects may be reloaded, but the returned task does not wait for children reload to complete.
*
* @param waitForBlockers
* @return the task that completes after project reloads.
*/
public RequestProcessor.Task fireProjectReload(boolean waitForBlockers) {
//#227101 not only AWT and project read/write mutex has to be checked, there are some additional more
//complex scenarios that can lead to deadlock. Just give up and always fire changes in separate RP.
if (Boolean.getBoolean("test.reload.sync")) {
reloadTask.run();
//for tests just do sync reload, even though silly, even sillier is to attempt to sync the threads..
} else {
synchronized (this) {
if (blockingList.isEmpty()) {
RequestProcessor.Task fin;
fin = this.reloadCompletionTask;
reloadCompletionTask = null;
LOG.log(Level.FINER, "Project {0} reload scheduled, no blockers", this );
reloadTask.schedule(0);
if (fin != null) {
reloadTask.addTaskListener((e) -> {
fin.run();
});
}
} else if (waitForBlockers) {
LOG.log(Level.FINER, "Project {0} reload blocked, blockers: {1}", new Object[] { this, blockingList });
if (reloadCompletionTask == null) {
reloadCompletionTask = RELOAD_RP.create(() -> {});
}
return reloadCompletionTask;
} else {
// potentially reload will happen again, after all blocking tasks will complete.
LOG.log(Level.FINER, "Project {0} reload forced, blockers: {1}, completion task: {2}", new Object[] { this, blockingList, reloadCompletionTask });
reloadTask.schedule(0);
}
}
}
return reloadTask;
}
private void reloadPossibleBrokenModules(MavenProject preceding, MavenProject p) {
LOG.log(Level.FINE, "Recovery for project {2}, preceding: {0}, current: {1}, ",
new Object[] { preceding == null ? -1 : System.identityHashCode(preceding), System.identityHashCode(p), p });
// restrict to just poms that were marked as broken/incomplete.
if (!(MavenProjectCache.isIncompleteProject(preceding) ||
// the project is tagged by Boolean.TRUE, if a SanityBuildAction was created for it.
preceding.getContextValue("org.netbeans.modules.maven.problems.primingNotDone") == Boolean.TRUE)) {
LOG.log(Level.FINER, "Project is not fallbach: {0}, {1}", new Object[] {
MavenProjectCache.isIncompleteProject(preceding),
preceding.getContextValue("org.netbeans.modules.maven.problems.primingNotDone")
});
return;
}
// but do not cascade from projects, which are themselves broken.
if (MavenProjectCache.isFallbackproject(p)) {
LOG.log(Level.FINE, "New project is still fallback, skipping");
return;
}
File basePOMFile = p.getFile().getParentFile();
for (String modName : p.getModules()) {
File modPom = new File(new File(basePOMFile, modName), "pom.xml");
if (!modPom.exists() || !modPom.isFile()) {
LOG.log(Level.FINE, "POM file {0} for module {1} does not exist", new Object[] { modPom, modName });
continue;
}
MavenProject child = MavenProjectCache.getMavenProject(modPom, true, false);
if (child == null) {
LOG.log(Level.FINE, "Child project {0} is not cached yet", modPom);
continue;
}
LOG.log(Level.FINE, "Child project fallback status: {0}, {1}", new Object[] {
MavenProjectCache.isIncompleteProject(child),
child.getContextValue("org.netbeans.modules.maven.problems.primingNotDone")
});
// the project may have more problems, more subtle, but now repair just total breakage
if (!MavenProjectCache.isIncompleteProject(child) && child.getContextValue("org.netbeans.modules.maven.problems.primingNotDone") != Boolean.TRUE) {
LOG.log(Level.FINE, "Project for module {0} is not a fallback, skipping", modName);
continue;
}
FileObject dir = FileUtil.toFileObject(modPom.getParentFile());
if (dir == null) {
LOG.log(Level.FINE, "Project directory for {0} is not a FileObject", modName);
continue;
}
try {
Project c = ProjectManager.getDefault().findProject(dir);
if (c == null) {
LOG.log(Level.FINE, "Module {0} is not a project", modName);
} else {
LOG.log(Level.INFO, "Recovering module {0}, pomfile {1}", new Object[] { modName, modPom });
NbMavenProjectImpl childImpl = c.getLookup().lookup(NbMavenProjectImpl.class);
childImpl.fireProjectReload(true).waitFinished();
}
} catch (IOException ex) {
LOG.log(Level.FINE, "Error getting module project {0} is not a project", modName);
LOG.log(Level.FINE, "Exception was: ", ex);
}
}
}
public static void refreshLocalRepository(NbMavenProjectImpl project) {
File file = project.getEmbedder().getLocalRepositoryFile();
FileUtil.refreshFor(file);
}
/** Begin listening to pom.xml changes. */
void attachUpdater() {
openedProjectUpdater.attachAll();
}
void detachUpdater() {
openedProjectUpdater.detachAll();
}
/**
* The root directory of the project where the POM resides.
*/
@Override
public FileObject getProjectDirectory() {
return folderFileObject;
}
public @CheckForNull String getArtifactRelativeRepositoryPath() {
Artifact artifact = getOriginalMavenProject().getArtifact();
if (artifact == null) {
return null;
}
return getArtifactRelativeRepositoryPath(artifact);
}
/**
* path of test artifact in local repository
* @return
*/
public @CheckForNull String getTestArtifactRelativeRepositoryPath() {
Artifact main = getOriginalMavenProject().getArtifact();
if (main == null) {
return null;
}
ArtifactHandlerManager artifactHandlerManager = getEmbedder().lookupComponent(ArtifactHandlerManager.class);
assert artifactHandlerManager != null : "ArtifactHandlerManager component not found in maven";
Artifact test = new DefaultArtifact(main.getGroupId(), main.getArtifactId(), main.getVersionRange(),
Artifact.SCOPE_TEST, "test-jar", "tests", artifactHandlerManager.getArtifactHandler("test-jar"));
return getArtifactRelativeRepositoryPath(test);
}
public String getArtifactRelativeRepositoryPath(@NonNull Artifact artifact) {
return getEmbedder().getLocalRepository().pathOf(artifact);
}
public MavenEmbedder getEmbedder() {
return EmbedderFactory.getProjectEmbedder();
}
public @NonNull MavenProjectPropsImpl getAuxProps() {
return auxprops;
}
/**
* The method will migrate to regular FileUtilities after NB13 release. The issue is that the result of
* {@link FileUtilities#convertStringToUri(java.lang.String)} result depends on whether the directory
* identified by the string exists or not. If it exists, the URI ends with a "/". For non-existent directories
* the URI lacks the trailing "/". This can break URI keys in a Map (if the directory gets created) and prevents
* from creating a ClassPath from such URLs (/ is checked). But FileUtilities is API and this behaviour is there for
* ages, so the correction should be added with a parameter.
*/
public static @NullUnknown URI convertStringToUri(@NullAllowed String str, boolean slashIfNotExist) {
if (str != null) {
File fil = new File(str);
fil = FileUtil.normalizeFile(fil);
// this conversion returns URIs that end with "/" if fil is an existing directory, but returns
// without the slash if the directory just does not exist yet.
URI uri = Utilities.toURI(fil);
String s = uri.toString();
if (slashIfNotExist && !s.endsWith("/") && (fil.isDirectory() || !fil.exists())) { // NOI18N
try {
return new URI(s + "/"); // NOI18N
} catch (URISyntaxException ex) {
throw new IllegalArgumentException(str);
}
} else {
return uri;
}
}
return null;
}
public URI[] getSourceRoots(boolean test) {
List uris = new ArrayList();
for (String root : test ? getOriginalMavenProject().getTestCompileSourceRoots() : getOriginalMavenProject().getCompileSourceRoots()) {
uris.add(convertStringToUri(root, true));
}
for (JavaLikeRootProvider rp : getLookup().lookupAll(JavaLikeRootProvider.class)) {
// XXX for a few purposes (listening) it is desirable to list these even before they exist, but usually it is just noise (cf. #196414 comment #2)
FileObject root = getProjectDirectory().getFileObject("src/" + (test ? "test" : "main") + "/" + rp.kind());
if (root != null && root.isFolder()) {
uris.add(root.toURI());
}
}
return uris.toArray(new URI[0]);
}
public URI[] getGeneratedSourceRoots(boolean test) {
//#241874 calculate the test source roots up front just in case they are in target/generated-sources. if so, remove the from non-test generated source roots
Set BHTestUris = new HashSet();
String[] buildHelpers = PluginPropertyUtils.getPluginPropertyList(this,
"org.codehaus.mojo", //NOI18N
"build-helper-maven-plugin", "sources", "source", "add-test-source"); //NOI18N
if (buildHelpers != null && buildHelpers.length > 0) {
File root = FileUtil.toFile(getProjectDirectory());
for (String helper : buildHelpers) {
BHTestUris.add(FileUtilities.getDirURI(root, helper));
}
}
URI uri = FileUtilities.getDirURI(getProjectDirectory(), test ? "target/generated-test-sources" : "target/generated-sources"); //NOI18N
Set uris = new HashSet();
File[] roots = Utilities.toFile(uri).listFiles();
if (roots != null) {
for (File root : roots) {
if (!VisibilityQuery.getDefault().isVisible(root)) { //#214002
continue;
}
if (!test && root.getName().startsWith("test-")) {
continue;
}
File[] kids = root.listFiles();
URI u = Utilities.toURI(root);
if (!test && BHTestUris.contains(u)) {
continue; //a test source root was put in target/generated-sources - #241874
}
if (kids != null && /* #190626 */kids.length > 0) {
uris.add(u);
} else {
watcher.addWatchedPath(u); //TODO who reacts to this?
}
}
}
if (test) { // MCOMPILER-167
roots = Utilities.toFile(FileUtilities.getDirURI(getProjectDirectory(), "target/generated-sources")).listFiles();
if (roots != null) {
for (File root : roots) {
if (!VisibilityQuery.getDefault().isVisible(root)) { //#214002
continue;
}
if (root.getName().startsWith("test-")) {
File[] kids = root.listFiles();
if (kids != null && kids.length > 0) {
uris.add(Utilities.toURI(root));
} else {
watcher.addWatchedPath(Utilities.toURI(root)); //TODO who reacts to this?
}
}
}
}
}
if (!test) {
buildHelpers = PluginPropertyUtils.getPluginPropertyList(this,
"org.codehaus.mojo", //NOI18N
"build-helper-maven-plugin", "sources", "source", "add-source"); //NOI18N
if (buildHelpers != null && buildHelpers.length > 0) {
File root = FileUtil.toFile(getProjectDirectory());
for (String helper : buildHelpers) {
uris.add(FileUtilities.getDirURI(root, helper));
}
}
} else {
uris.addAll(BHTestUris);
}
return uris.toArray(new URI[0]);
}
public URI getWebAppDirectory() {
//TODO hack, should be supported somehow to read this..
String prop = PluginPropertyUtils.getPluginProperty(this, Constants.GROUP_APACHE_PLUGINS,
Constants.PLUGIN_WAR, //NOI18N
"warSourceDirectory", //NOI18N
"war", null); //NOI18N
prop = prop == null ? "src/main/webapp" : prop; //NOI18N
return FileUtilities.getDirURI(getProjectDirectory(), prop);
}
public URI getSiteDirectory() {
//TODO hack, should be supported somehow to read this..
String prop = PluginPropertyUtils.getPluginProperty(this, Constants.GROUP_APACHE_PLUGINS,
Constants.PLUGIN_SITE, //NOI18N
"siteDirectory", //NOI18N
"site", null); //NOI18N
prop = prop == null ? "src/site" : prop; //NOI18N
return FileUtilities.getDirURI(getProjectDirectory(), prop);
}
public URI getEarAppDirectory() {
//TODO hack, should be supported somehow to read this..
String prop = PluginPropertyUtils.getPluginProperty(this, Constants.GROUP_APACHE_PLUGINS,
Constants.PLUGIN_EAR, //NOI18N
"earSourceDirectory", //NOI18N
"ear", null); //NOI18N
prop = prop == null ? "src/main/application" : prop; //NOI18N
return FileUtilities.getDirURI(getProjectDirectory(), prop);
}
public URI[] getResources(boolean test) {
List toRet = new ArrayList();
URI projectroot = getProjectDirectory().toURI();
Set sourceRoots = null;
List res = test ? getOriginalMavenProject().getTestResources() : getOriginalMavenProject().getResources();
LBL : for (Resource elem : res) {
String dir = elem.getDirectory();
if (dir == null) {
continue; // #191742
}
URI uri = FileUtilities.getDirURI(getProjectDirectory(), dir);
if (elem.getTargetPath() != null || !elem.getExcludes().isEmpty() || !elem.getIncludes().isEmpty()) {
URI rel = projectroot.relativize(uri);
if (rel.isAbsolute()) { //outside of project directory
continue;// #195928, #231517
}
if (sourceRoots == null) {
sourceRoots = new HashSet();
sourceRoots.addAll(Arrays.asList(getSourceRoots(true)));
sourceRoots.addAll(Arrays.asList(getSourceRoots(false)));
//should we also consider generated sources? most like not necessary
}
for (URI sr : sourceRoots) {
if (!uri.relativize(sr).isAbsolute()) {
continue LBL;// #195928, #231517
}
}
//hope for the best now
}
// if (new File(uri).exists()) {
toRet.add(uri);
// }
}
return toRet.toArray(new URI[0]);
}
public File[] getOtherRoots(boolean test) {
URI uri = FileUtilities.getDirURI(getProjectDirectory(), test ? "src/test" : "src/main"); //NOI18N
Set toRet = new HashSet();
File fil = Utilities.toFile(uri);
if (fil.exists()) {
try {
Path sourceRoot = fil.toPath();
OtherRootsVisitor visitor = new OtherRootsVisitor(getLookup(), sourceRoot);
Files.walkFileTree(sourceRoot, visitor);
toRet.addAll(visitor.getOtherRoots());
} catch (IOException ex) {
// log as info to keep trace about possible problems,
// but lets not be too agressive with level and notification
// see also issue #251071
LOG.log(Level.INFO, null, ex);
}
}
URI[] res = getResources(test);
for (URI rs : res) {
File fl = Utilities.toFile(rs);
//in node view we need only the existing ones, if anything else needs all,
// a new method is probably necessary..
if (fl.exists()) {
toRet.add(fl);
}
}
return toRet.toArray(new File[0]);
}
private static class OtherRootsVisitor extends SimpleFileVisitor {
private final Lookup lookup;
private final List otherRoots;
private final Path sourceRoot;
public OtherRootsVisitor(Lookup lookup, Path sourceRoot) {
this.lookup = lookup;
this.sourceRoot = sourceRoot;
this.otherRoots = new ArrayList<>();
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
// To avoid including src/main and src/test directories
if (sourceRoot.equals(dir)) {
return FileVisitResult.CONTINUE;
}
if(!VisibilityQuery.getDefault().isVisible(dir.toFile())) {
return FileVisitResult.SKIP_SUBTREE;
}
for (OtherSourcesExclude rp : lookup.lookupAll(OtherSourcesExclude.class)) {
for (Path folder : rp.excludedFolders()) {
// In case of excluded folders (e.g. src/main/java) we can simply skip whole subtree
if (folder.equals(dir)) {
return FileVisitResult.SKIP_SUBTREE;
}
// We are vising directory which is a parent of one of excluded directory.
// In such case we don't want to skip the subtree (b/c it might contain directories
// that falling under other roots), but we also don't want to add it to the results
// (to avoid adding everything)
if (folder.startsWith(dir)) {
return FileVisitResult.CONTINUE;
}
}
}
for (JavaLikeRootProvider rp : lookup.lookupAll(JavaLikeRootProvider.class)) {
if (rp.kind().equalsIgnoreCase(dir.getFileName().toString())) {
return FileVisitResult.SKIP_SUBTREE;
}
}
// If the directory wasn't excluded until now, it should be shown in Other Sources node
if (isOtherRoot(dir)) {
otherRoots.add(dir);
}
return FileVisitResult.CONTINUE;
}
private boolean isOtherRoot(Path dir) throws IOException {
if (!dir.toFile().isDirectory() || Files.isHidden(dir)) {
return false;
}
// Walk through the other roots and check if a parent of this dir is
// already available in other roots to avoid folder duplication
for (Path path : otherRoots) {
if (dir.startsWith(path)) {
return false;
}
}
return true;
}
public List getOtherRoots() {
List result = new ArrayList<>();
for (Path path : otherRoots) {
result.add(path.toFile());
}
return Collections.unmodifiableList(result);
}
}
@Override
public Lookup getLookup() {
return lookup;
}
CopyResourcesOnSave getCopyOnSaveResources() {
synchronized (COPYRESOURCES_LOCK) {
if (copyResourcesOnSave == null) {
copyResourcesOnSave = new CopyResourcesOnSave(watcher, this);
}
return copyResourcesOnSave;
}
}
private static class PackagingTypeDependentLookup extends ProxyLookup implements PropertyChangeListener {
//#243866 both NbMavenProject and PackagingTypeDependentLookup are hard referenced from NbMavenProjectImpl
//it should be safe to weak reference here, all should be GCed together.
private final WeakReference watcherRef;
private String packaging;
private final Lookup general;
private volatile List currentIds = new ArrayList<>();
@SuppressWarnings("LeakingThisInConstructor")
PackagingTypeDependentLookup(NbMavenProject watcher) {
this.watcherRef = new WeakReference(watcher);
//needs to be kept around to prevent recreating instances
general = Lookups.forPath("Projects/org-netbeans-modules-maven/Lookup"); //NOI18N
check();
watcher.addPropertyChangeListener(WeakListeners.propertyChange(this, watcher));
}
private String pluginDirectory(Artifact pluginArtifact) {
String groupId = pluginArtifact.getGroupId();
String artId = pluginArtifact.getArtifactId();
return groupId + ":" + artId;
}
/**
* Defines at least some order: let the layer positions to
* @param componentSet
* @return
*/
private List partialComponentsOrder(Collection componentSet) {
return partialComponentsOrder(componentSet, null); // NOI18N
}
private List partialComponentsOrder(Collection componentSet, String subdir) {
List fos = new ArrayList<>();
String r = "Projects/org-netbeans-modules-maven";
if (subdir != null) {
r = r + "/" + subdir;
}
FileObject root = FileUtil.getConfigFile(r);
if (root == null) {
return Collections.emptyList();
}
for (String s : componentSet) {
FileObject f = root.getFileObject(s);
if (f != null) {
fos.add(f);
}
}
List orderedNames = FileUtil.getOrder(fos, false).stream().map(FileObject::getNameExt).collect(Collectors.toList());
List origList = new ArrayList<>(componentSet);
origList.removeAll(orderedNames);
orderedNames.addAll(origList);
if (subdir != null) {
for (int i = 0; i < orderedNames.size(); i++) {
orderedNames.set(i, subdir + "/" + orderedNames.get(i));
}
}
return orderedNames;
}
private void check() {
//this call effectively calls project.getLookup(), when called in constructor will get back to the project's baselookup only.
// but when called from propertyChange() then will call on entire composite lookup, is it a problem? #230469
List newComponents = new ArrayList<>();
NbMavenProject watcher = watcherRef.get();
String newPackaging = packaging != null ? packaging : NbMavenProject.TYPE_JAR;
List lookups = new ArrayList<>();
List old = currentIds;
LOG.log(Level.FINE, "Watcher is: {0}, packaging is: {1}", new Object[] { watcher, newPackaging });
if (watcher != null) {
newPackaging = watcher.getPackagingType();
LOG.log(Level.FINE, "Watcher {0} returned packacing: {1}", new Object[] { watcher, newPackaging });
if (newPackaging == null) {
newPackaging = NbMavenProject.TYPE_JAR;
}
MavenProject mprj = watcher.getMavenProject();
Set arts = mprj.getPluginArtifacts();
List compNames = new ArrayList<>();
if (arts != null) {
for (Artifact a : arts) {
compNames.add(pluginDirectory(a));
}
}
List configuredCompNames = new ArrayList<>();
if (mprj.getPluginManagement() != null) {
for (Plugin p : mprj.getPluginManagement().getPlugins()) {
String groupId = p.getGroupId();
String artId = p.getArtifactId();
String pn = groupId + ":" + artId;
configuredCompNames.add(pn);
}
}
compNames.add(newPackaging);
newComponents = partialComponentsOrder(compNames);
newComponents.addAll(partialComponentsOrder(configuredCompNames, "configuredPlugins")); // NOI18N
} else {
newComponents.add(newPackaging);
}
if (!newComponents.equals(old)) {
for (String s : newComponents) {
lookups.add(Lookups.forPath("Projects/org-netbeans-modules-maven/" + s + "/Lookup")); // NOI18N
}
// put the general lookup last, so plugin - specific ones can override it
lookups.add(general);
lookups.add(Lookups.forPath("Projects/org-netbeans-modules-maven/_any/Lookup")); // NOI18N
synchronized (this) {
if (currentIds != old) {
// the next computation started after us, do not interfere.
return;
}
currentIds = newComponents;
}
LOG.log(Level.FINE, "Composing lookups for {0}, packaging: {1}, lookups: {2}: ",
new Object[] { watcher.getMavenProject().getFile(), newPackaging, newComponents });
setLookups(lookups.toArray(new Lookup[0]));
}
}
public @Override void propertyChange(PropertyChangeEvent evt) {
if (NbMavenProject.PROP_PROJECT.equals(evt.getPropertyName())) {
check();
}
}
}
private Lookup createBasicLookup(ProjectState state, M2AuxilaryConfigImpl auxiliary) {
return Lookups.fixed(
this,
fileObject,
auxiliary,
auxiliary.getProblemProvider(),
auxprops,
new MavenProjectPropsImpl.Merger(auxprops),
profileHandler,
configProvider,
problemReporter,
watcher,
state,
UILookupMergerSupport.createPrivilegedTemplatesMerger(),
UILookupMergerSupport.createRecommendedTemplatesMerger(),
UILookupMergerSupport.createProjectProblemsProviderMerger(),
LookupProviderSupport.createSourcesMerger(),
LookupProviderSupport.createSharabilityQueryMerger(),
ProjectClassPathModifier.extenderForModifier(this),
LookupMergerSupport.createClassPathModifierMerger(),
new UnitTestsCompilerOptionsQueryImpl(this),
new PomCompilerOptionsQueryImpl(this),
LookupMergerSupport.createCompilerOptionsQueryMerger(),
MavenJPDAStart.create(this)
);
}
//MEVENIDE-448 seems to help against creation of duplicate project instances
// no idea why, it's supposed to be ProjectManager job.. maybe related to
// maven impl of SubProjectProvider or FileOwnerQueryImplementation
//TODO need to investigate why it's like that..
//a renamed FileObject for project folder stays the same, changing the identity of the project, we have to use File.
@Override
public int hashCode() {
return getPOMFile().hashCode() * 13;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Project) {
NbMavenProjectImpl impl;
if (obj instanceof NbMavenProjectImpl) {
impl = ((NbMavenProjectImpl) obj);
} else {
impl = ((Project) obj).getLookup().lookup(NbMavenProjectImpl.class);
}
if (impl != null) {
return getPOMFile().equals(impl.getPOMFile());
}
}
return false;
}
@Override
public String toString() {
return "Maven[" + getPOMFile().getAbsolutePath() + "]"; //NOI18N
}
interface FileProvider {
File[] getFiles();
}
private class Updater implements FileChangeListener {
private final FileProvider fileProvider;
private List filesToWatch;
private long lastTime = 0;
private Map lastMods;
/** Relative file paths to watch. */
Updater(FileProvider toWatch) {
fileProvider = toWatch;
}
@Override
public void fileAttributeChanged(FileAttributeEvent fileAttributeEvent) {
}
@Override
public void fileChanged(FileEvent fileEvent) {
if (lastTime < fileEvent.getTime()) {
lastTime = System.currentTimeMillis();
// System.out.println("fired based on " + fileEvent.getFile() + fileEvent.getTime());
NbMavenProject.fireMavenProjectReload(NbMavenProjectImpl.this);
}
}
@Override
public void fileDataCreated(FileEvent fileEvent) {
if (lastTime < fileEvent.getTime()) {
lastTime = System.currentTimeMillis();
// System.out.println("fired based on " + fileEvent.getFile() + fileEvent.getTime());
NbMavenProject.fireMavenProjectReload(NbMavenProjectImpl.this);
}
}
@Override
public void fileDeleted(FileEvent fileEvent) {
lastTime = System.currentTimeMillis();
NbMavenProject.fireMavenProjectReload(NbMavenProjectImpl.this);
}
@Override
public void fileFolderCreated(FileEvent fileEvent) {
//TODO possibly remove this fire.. watch for actual path..
// NbMavenProject.fireMavenProjectReload(NbMavenProjectImpl.this);
}
@Override
public void fileRenamed(FileRenameEvent fileRenameEvent) {
}
synchronized void attachAll() {
this.filesToWatch = new ArrayList<>(Arrays.asList(fileProvider.getFiles()));
filesToWatch.addAll(getParents());
Collections.sort(filesToWatch);
for (File file : filesToWatch) {
try {
FileUtil.addFileChangeListener(this, file);
} catch (IllegalArgumentException ex) {
//giving up on ever figuring why OPH is sometimes calls opened() twice in a row on single project.
//There's way too many moving parts.
// * project lookup could be creating multiple instances of OPH
// * a close or open method for a random project/OPH could throw exception skipping our close?
// while OPL catches RuntimeExceptions, OPH merger bypasses that behaviour and handles all OPH as unit.
// * something in OPL or Group is wrong in terms of threading, timing or open/close projects calculation (could be equals/hascode on project related)
LOG.log(Level.INFO, "project opened twice in a row, issue #236211 for " + projectFile.getAbsolutePath(), ex);
Thread.dumpStack();
assert false : "project opened twice in a row, issue #236211 for " + projectFile.getAbsolutePath();
}
}
if(lastMods == null) {
// attached for the first time,
// preserve lastModified of interestig files
lastMods = new HashMap<>(filesToWatch.size());
for (File file : filesToWatch) {
lastMods.put(file, file.lastModified());
}
} else {
for (Map.Entry e : lastMods.entrySet()) {
File file = e.getKey();
long ts = file.lastModified();
if( e.getValue() < ts ) {
// attached after being previously dettached and
// lastModified of an interesting file changed in the meantime
// -> force pom refresh
lastMods.put(file, ts);
NbMavenProject.fireMavenProjectReload(NbMavenProjectImpl.this);
}
}
}
}
protected List getParents() {
LinkedList ret = new LinkedList<>();
MavenProject project = getOriginalMavenProject();
while(true) {
try {
MavenProject parent = loadParentOf(getEmbedder(), project);
if (parent == null || NbMavenProject.isErrorPlaceholder(parent)) {
break;
}
File parentFile = parent.getFile();
if(parentFile != null) {
ret.add(parentFile);
project = parent;
} else {
break;
}
} catch (ProjectBuildingException ex) {
break;
}
}
return ret;
}
synchronized void detachAll() {
if (filesToWatch != null) {
List toWatch = filesToWatch;
filesToWatch = null;
for (File file : toWatch) {
try {
FileUtil.removeFileChangeListener(this, file);
} catch (IllegalArgumentException ex) {
LOG.log(Level.INFO, "project closed twice in a row, issue #236211 for " + projectFile.getAbsolutePath(), ex);
Thread.dumpStack();
assert false : "project closed twice in a row, issue #236211 for " + projectFile.getAbsolutePath();
}
}
}
}
}
}