blob: fc592b5e5d98e2ba2ee5f33f549f167ef4b73cda [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.gradle.api;
import org.netbeans.modules.gradle.spi.WatchedResourceProvider;
import org.netbeans.modules.gradle.NbGradleProjectImpl;
import java.awt.Image;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.prefs.Preferences;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.annotations.common.StaticResource;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.modules.gradle.spi.GradleFiles;
import org.openide.filesystems.FileAttributeEvent;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileRenameEvent;
import org.openide.filesystems.FileUtil;
import org.openide.util.ImageUtilities;
import org.openide.util.Utilities;
/**
* Facade object for NetBeans Gradle project internals, with some convenience
* methods.
*
*
* @since 1.0
* @author Laszlo Kishalmi
*/
public final class NbGradleProject {
/**
* As loading a Gradle project information into the memory could be a time
* consuming task each the Gradle Plugin uses heuristics and offline
* evaluation of a project in order to provide optimal responsiveness.
* E.g. If we just need to know if the project is a Gradle project, there
* is no need to go and fetch all the dependencies.
* <p>
* <img src="doc-files/gradle-quality.png" alt="Quality States"/>
* </p>
* <p>
* Gradle project is associated with the quality of the
* information available at the time. The quality of data can be improved,
* by reloading the project.
* </p>
* @since 1.0
*/
public static enum Quality {
/**
* The data of this project is unreliable, based on heuristics. This is
* the quickest way to retrieve some information as it the code do not
* even turns to Gradle for it. Tries to apply some common usage
* patterns.
*/
FALLBACK,
/** The data of this project is unreliable. This usually means that the
* project was once in a better quality, but some recent change made the
* the project un-loadable. E.g. syntax error in the recently edited
* {@code build.gradle} file. The IDE cannot reload it but tries to work with
* the previously retrieved information. */
EVALUATED,
/** The data of this project is reliable, dependency information can be partial though. */
SIMPLE,
/** The data of this project is reliable, full dependency information is available offline. */
FULL,
/** The data of this project is reliable. with full dependency information. */
FULL_ONLINE;
public boolean betterThan(Quality q) {
return this.ordinal() > q.ordinal();
}
public boolean atLeast(Quality q) {
return this.ordinal() >= q.ordinal();
}
public boolean worseThan(Quality q) {
return this.ordinal() < q.ordinal();
}
public boolean notBetterThan(Quality q) {
return this.ordinal() <= q.ordinal();
}
}
public static final String GRADLE_PROJECT_TYPE = "org-netbeans-modules-gradle";
public static final String GRADLE_PLUGIN_TYPE = GRADLE_PROJECT_TYPE + "/Plugins";
/** This property is fired to change on every project reload. */
public static final String PROP_PROJECT_INFO = "ProjectInfo";
/** This property is fired when a project watched resource is changed.
* E.g. a previously non existent source root appears. */
public static final String PROP_RESOURCES = "resources";
@StaticResource
private static final String GRADLE_ICON = "org/netbeans/modules/gradle/resources/gradle.png"; //NOI18
@StaticResource
private static final String WARNING_BADGE = "org/netbeans/modules/gradle/resources/warning-badge.png"; //NOI18
private static Icon warningIcon;
public static final String CODENAME_BASE = "org.netbeans.modules.gradle";
private final NbGradleProjectImpl project;
private final PropertyChangeSupport support;
private final Set<File> resources = new HashSet<>();
private Preferences privatePrefs;
private Preferences sharedPrefs;
static {
AccessorImpl impl = new AccessorImpl();
impl.assign();
}
public boolean isGradleProjectLoaded() {
return project.isGradleProjectLoaded();
}
static class AccessorImpl extends NbGradleProjectImpl.WatcherAccessor {
public void assign() {
if (NbGradleProjectImpl.ACCESSOR == null) {
NbGradleProjectImpl.ACCESSOR = this;
}
}
@Override
public NbGradleProject createWatcher(NbGradleProjectImpl proj) {
return new NbGradleProject(proj);
}
@Override
public void doFireReload(NbGradleProject watcher) {
watcher.doFireReload();
}
@Override
public void activate(NbGradleProject watcher) {
watcher.attachResourceWatchers();
}
@Override
public void passivate(NbGradleProject watcher) {
watcher.detachResourceWatchers();
}
@Override
public GradleReport createReport(String errorClass, String location, int line, String message, GradleReport causedBy) {
return new GradleReport(errorClass, location, line, message, causedBy);
}
@Override
public void setProblems(GradleBaseProject baseProject, Set<GradleReport> problems) {
baseProject.problems = (problems == null || problems.isEmpty())
? Collections.emptySet()
: Collections.unmodifiableSet(problems);
}
}
private NbGradleProject(NbGradleProjectImpl project) {
this.project = project;
support = new PropertyChangeSupport(project);
}
public <T> T projectLookup(Class<T> clazz) {
return project.getGradleProject().getLookup().lookup(clazz);
}
/**
* Return the actual Quality information on the currently loaded Project.
*
* @return the information Quality of the project data;
*/
public Quality getQuality() {
return project.getGradleProject().getQuality();
}
/**
* The requested information on this project. Mostly FALLBACK or FULL.
* @return the information Quality requested.
*/
public Quality getAimedQuality() {
return project.getAimedQuality();
}
/**
* The project is unloadable if it's actual quality is worse than {@link Quality#SIMPLE}.
* @return true if the project is unloadable.
*/
public boolean isUnloadable() {
return getQuality().worseThan(Quality.SIMPLE);
}
/**
* Attempts to refresh the project to at least the desired quality. The project information
* may be reloaded, if the project is currently loaded with lower {@link Quality} than {@code q}.
* If {@code forceLoad} is true, the project reloads even if the {@code q} is worse quality than
* the current {@link #getQuality()} level. Reason for the reload may be specified: if the reload
* takes some time (i.e. executing Gradle build), the IDE may use the {@code reason} text to annotate
* the ongoing progress.
* <p/>
* The returned {@link CompletionStage} may complete in this thread, or asynchronously in an unspecified thread.
* <p/>
* Note that the loading may fail, so the returned Quality may be <b>less than requested</b>. For example
* if the project is not trusted, its Gradle build will not be executed, so the returned quality can be {@link Quality#EVALUATED}.
*
* @param reason reason for reload, may be {@code null}.
* @param q the desired quality of project information
* @param forceLoad force load even though the current info quality is sufficient.
* @return {@link CompletionStage} with the reloaded project. Use {@link CompletionStage#toCompletableFuture()}.{@link CompletableFuture#get get()}
* to block waiting for the result.
* @since 2.11
*/
public @NonNull CompletionStage<NbGradleProject> toQuality(@NullAllowed String reason, @NonNull Quality q, boolean forceLoad) {
return project.projectWithQualityTask(reason, q, false, forceLoad).thenApply(p -> this);
}
public Preferences getPreferences(boolean shared) {
Preferences ret = shared ? sharedPrefs : privatePrefs;
if (ret == null) {
if (shared) {
ret = sharedPrefs = ProjectUtils.getPreferences(project, NbGradleProject.class, true);
} else {
ret = privatePrefs = ProjectUtils.getPreferences(project, NbGradleProject.class, false);
}
}
return ret;
}
private void fireProjectReload() {
project.fireProjectReload(false);
}
private void doFireReload() {
detachResourceWatchers();
support.firePropertyChange(PROP_PROJECT_INFO, null, null);
attachResourceWatchers();
}
private void detachResourceWatchers() {
for (File resource : resources) {
try {
FileUtil.removeFileChangeListener(FCHSL, resource);
} catch (IllegalArgumentException ex) {
assert false : "Something is wrong with the resource handling";
}
}
resources.clear();
}
private void attachResourceWatchers() {
//Never listen on resource changes when only FALLBACK quality is needed
if (project.getAimedQuality() == Quality.FALLBACK) return;
Collection<? extends WatchedResourceProvider> all
= project.getLookup().lookupAll(WatchedResourceProvider.class);
for (WatchedResourceProvider pvd : all) {
resources.addAll(pvd.getWatchedResources());
}
for (File resource : resources) {
try {
FileUtil.addFileChangeListener(FCHSL, resource);
} catch (IllegalArgumentException ex) {
assert false : "Something is wrong with the resource handling";
}
}
}
public void addPropertyChangeListener(PropertyChangeListener propertyChangeListener) {
support.addPropertyChangeListener(propertyChangeListener);
}
public void removePropertyChangeListener(PropertyChangeListener propertyChangeListener) {
support.removePropertyChangeListener(propertyChangeListener);
}
/**
* Retrieves the watcher for the given project. Usually the project watcher
* can be retrieved from the project Lookup. This implementation does not
* use the project Lookup, so it can be used inside of the constructors
* of ProjectServiceProvider implementations.
*
* @param project the project to query
* @return the watcher of the project or {@code null} if the given project
* is not a Gradle project.
*/
public static NbGradleProject get(Project project) {
return project instanceof NbGradleProjectImpl ? ((NbGradleProjectImpl) project).getProjectWatcher() : null;
}
/**
* Returns accessor for Gradle project files. Note that the returned instance is immutable, possibly lazy-initialized.
* A change (creation, removal) to project files will not be reflected by the {@link GradleFiles} instance, but this method
* may return a new instance.
* @return files accessor.
* @since 2.24
*/
public GradleFiles getGradleFiles() {
return project.getGradleFiles();
}
@Override
public String toString() {
return "Watcher for " + project.toString(); //NOI18N
}
public static ImageIcon getIcon() {
return ImageUtilities.loadImageIcon(GRADLE_ICON, false);
}
public static final Icon getWarningIcon() {
if (warningIcon == null) {
Image icon = ImageUtilities.icon2Image(NbGradleProject.getIcon());
Image badge = ImageUtilities.loadImage(WARNING_BADGE);
icon = ImageUtilities.mergeImages(icon, badge, 8, 0);
warningIcon = ImageUtilities.image2Icon(icon);
}
return warningIcon;
}
/**
* Convenient method to add a Property Listener to a Gradle project.
*
* @param project
* @param l
*/
public static void addPropertyChangeListener(Project project, PropertyChangeListener l) {
if (project != null && project instanceof NbGradleProjectImpl) {
((NbGradleProjectImpl) project).getProjectWatcher().addPropertyChangeListener(l);
} else {
assert false : "Attempted to add PropertyChangeListener to project " + project; //NOI18N
}
}
/**
* Convenient method to remove a Property Listener from a Gradle project.
*
* @param project
* @param l
*/
public static void removePropertyChangeListener(Project project, PropertyChangeListener l) {
if (project != null && project instanceof NbGradleProjectImpl) {
((NbGradleProjectImpl) project).getProjectWatcher().removePropertyChangeListener(l);
} else {
assert false : "Attempted to remove PropertyChangeListener to project " + project; //NOI18N
}
}
public static void fireGradleProjectReload(Project prj) {
if (prj != null) {
NbGradleProject watcher = NbGradleProject.get(prj);
if (watcher != null) {
watcher.fireProjectReload();
}
}
}
public static Preferences getPreferences(Project project, boolean shared) {
NbGradleProject watcher = NbGradleProject.get(project);
return watcher.getPreferences(shared);
}
private void fireChange(File f) {
support.firePropertyChange(PROP_RESOURCES, null, Utilities.toURI(f));
}
private final FileChangeListener FCHSL = new FileChangeListener() {
@Override
public void fileFolderCreated(FileEvent fe) {
fireChange(FileUtil.toFile(fe.getFile()));
}
@Override
public void fileDataCreated(FileEvent fe) {
fireChange(FileUtil.toFile(fe.getFile()));
}
@Override
public void fileChanged(FileEvent fe) {
fireChange(FileUtil.toFile(fe.getFile()));
}
@Override
public void fileDeleted(FileEvent fe) {
fireChange(FileUtil.toFile(fe.getFile()));
}
@Override
public void fileRenamed(FileRenameEvent fe) {
fireChange(FileUtil.toFile(fe.getFile()));
}
@Override
public void fileAttributeChanged(FileAttributeEvent fe) {
}
};
}