All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.gradle.api.NbGradleProject 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.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.logging.Logger;
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.Lookup;
import org.openide.util.Utilities;
import org.openide.util.lookup.Lookups;

/**
 * Facade object for NetBeans Gradle project internals, with some convenience
 * methods.
 *
 *
 * @since 1.0
 * @author Laszlo Kishalmi
 */
public final class NbGradleProject {

    static final Logger LOG = Logger.getLogger(NbGradleProject.class.getName());

    /**
     * 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.
     * 

* Quality States *

*

* 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. *

* @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 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(true); } @Override public void passivate(NbGradleProject watcher) { watcher.detachResourceWatchers(); } @Override public GradleReport createReport(GradleReport.Severity severity, String errorClass, String location, int line, String message, GradleReport causedBy, String[] traceLines) { return new GradleReport(severity, errorClass, location, line, message, causedBy, traceLines); } @Override public void setProblems(GradleBaseProject baseProject, Set problems) { baseProject.problems = (problems == null || problems.isEmpty()) ? Collections.emptySet() : Collections.unmodifiableSet(problems); } } private NbGradleProject(NbGradleProjectImpl project) { this.project = project; support = new PropertyChangeSupport(project); } /** * Provides full lookup of the currently loaded project state. This Lookup does NOT refreshes * as project is reload, the client must eventually watch {@link #PROP_PROJECT_INFO} property change and obtain a fresh lookup. * @return Lookup that contains the current metadata for the project. */ public Lookup curretLookup() { return project.getGradleProject().getLookup(); } public T projectLookup(Class clazz) { return project.getGradleProject().getLookup().lookup(clazz); } private transient volatile Lookup lookupProxy; /** * Returns a Lookup that tracks potential project reloads. Always delegates to the latest * loaded model and project Lookup adjusted for applied plugins etc. *

* Use this Lookup in preference to {@link #projectLookup}, if you need to adapt for changes * e.g. after script reload. * * @return Lookup instance. * @since 2.28 */ public Lookup refreshableProjectLookup() { Lookup l = lookupProxy; if (l != null) { return l; } synchronized (this) { if (lookupProxy != null) { return lookupProxy; } return lookupProxy = Lookups.proxy(() -> project.getGradleProject().getLookup()); } } /** * 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); } /** * Returns the time the project was evaluated. If the project has not been loaded at least in its * 'fallback' state, it returns a negative value. * @return evaluation time of the project. */ public long getEvaluateTime() { return project.getEvaluationTime(); } /** * 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. *

* The returned {@link CompletionStage} may complete in this thread, or asynchronously in an unspecified thread. *

* Note that the loading may fail, so the returned Quality may be less than requested. 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 toQuality(@NullAllowed String reason, @NonNull Quality q, boolean forceLoad) { return project.projectWithQualityTask(loadOptions(q).setDescription(reason).setForce(forceLoad)).thenApply(p -> this); } /** * Creates a {@link LoadOptions} object to be used with {@link #toQuality(org.netbeans.modules.gradle.api.NbGradleProject.LoadOptions)}. * @param aim the target quality * @return options object. * @since 2.43 */ public static LoadOptions loadOptions(Quality aim) { return new LoadOptions(aim); } /** * Describes options for loading a Gradle project. * @since 2.43 */ public static final class LoadOptions { private final Quality aim; private boolean force; private String description; private boolean ignoreCache; private boolean interactive; private boolean offline; private boolean checkFiles; LoadOptions(Quality aim) { this.aim = aim; } /** * Instructs to check file timestamps against project loading time when deciding whether to use current data. The default is {@code false}. * @param b true to check file timestamps. * @return this options object. */ public LoadOptions setCheckFiles(boolean b) { this.checkFiles = b; return this; } /** * Forces offline operation. The default is {@code false}. * @param b true, if the operation must be offline * @return this options object */ public LoadOptions setOffline(boolean b) { this.offline = b; return this; } /** * Sets an interactive flag. If interactive, the implementation is allowed to ask for confirmation * or other questions. False means that questions will fail as if cancelled, other prompts will resolve to * their default options. The default is {@code false}. * @param b true, if interactive process * @return this options object */ public LoadOptions setInteractive(boolean b) { this.interactive = b; return this; } /** * Sets description of the operation. The description serves as part of a message to the user about project being * loaded or a progress status indicator. The text should describe the operation that requires a load, e.g. "Creating classpath". * There's no default description. * @param desc description * @return this options object */ public LoadOptions setDescription(String desc) { this.description = desc; return this; } /** * Forces the load to bypass the on-disk cache. If set, cached data will be ignored. If false, the implementation * is allowed to satisfy the load from the cache, if the cached quality is sufficient. The default is {@code false}. * @param b true to bypass caches * @return this options object */ public LoadOptions setIgnoreCache(boolean b) { this.ignoreCache = b; return this; } /** * Forces the load, even though the quality of current project is OK and no files have been modified. The default is {@code false}. * @param b true to force load the project. * @return this options object */ public LoadOptions setForce(boolean b) { this.force = b; return this; } /** * @return true to force the load regardless of consistency and quality */ public boolean isForce() { return force; } /** * @return the desired quality level */ public NbGradleProject.Quality getAim() { return aim; } /** * @return description of the operation that initiated the load */ public String getDescription() { return description; } /** * @return true to ignore netbeans caches */ public boolean isIgnoreCache() { return ignoreCache; } /** * @return true, if the process is interactive */ public boolean isInteractive() { return interactive; } /** * @return true, if the load must not use online resources. */ public boolean isOffline() { return offline; } /** * @return true to check timestamps of gradle files */ public boolean isCheckFiles() { return checkFiles; } } /** * Unlike {@link #toQuality}, this method loads the project, if the project files have changed since the last load. If project definition * files did not change, * @param options load options and requiremens. * @return Future with the result project. * @since 2.43 */ public @NonNull CompletionStage toQuality(LoadOptions options) { return project.projectWithQualityTask(options).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(false); } private void detachResourceWatchers() { synchronized (resources) { 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(boolean elevateQuality) { //Never listen on resource changes when only FALLBACK quality is needed if ((project.getAimedQuality() == Quality.FALLBACK) && !elevateQuality) return; synchronized (resources) { if (!resources.isEmpty()) { LOG.warning("Gradle ResourceWatcher Leak: " + resources); //NOI18N resources.clear(); } Collection 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 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 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) { } }; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy