| /* |
| * 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; |
| |
| import org.netbeans.modules.gradle.spi.GradleFiles; |
| import org.netbeans.modules.gradle.api.NbGradleProject; |
| import org.netbeans.modules.gradle.api.NbGradleProject.Quality; |
| import org.netbeans.modules.gradle.spi.GradleSettings; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.io.File; |
| import java.io.IOException; |
| import java.lang.ref.WeakReference; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.spi.project.ProjectState; |
| 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.util.*; |
| import org.openide.util.lookup.Lookups; |
| |
| import static org.netbeans.modules.gradle.api.NbGradleProject.Quality.*; |
| |
| import static java.util.logging.Level.*; |
| |
| import java.util.logging.Logger; |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.netbeans.api.annotations.common.SuppressWarnings; |
| import org.netbeans.api.project.ui.ProjectProblems; |
| import org.netbeans.modules.gradle.api.GradleBaseProject; |
| import org.netbeans.spi.project.CacheDirectoryProvider; |
| import org.netbeans.spi.project.support.LookupProviderSupport; |
| import org.netbeans.spi.project.ui.ProjectOpenedHook; |
| import org.netbeans.spi.project.ui.support.UILookupMergerSupport; |
| import org.openide.util.lookup.ProxyLookup; |
| |
| /** |
| * |
| * @author Laszlo Kishalmi |
| */ |
| public final class NbGradleProjectImpl implements Project { |
| |
| private static final Logger LOG = Logger.getLogger(NbGradleProjectImpl.class.getName()); |
| |
| public static final RequestProcessor RELOAD_RP = new RequestProcessor("Gradle project reloading", 1); //NOI18 |
| private final RequestProcessor.Task reloadTask = RELOAD_RP.create(new Runnable() { |
| @Override |
| public void run() { |
| project = loadProject(); |
| ACCESSOR.doFireReload(watcher); |
| } |
| }); |
| |
| private final FileObject projectDir; |
| private final ProjectState projectState; |
| private final Lookup lookup; |
| private final Lookup basicLookup; |
| private final Lookup completeLookup; |
| private Updater openedProjectUpdater; |
| private Quality aimedQuality = FALLBACK; |
| private final @NonNull NbGradleProject watcher; |
| @SuppressWarnings("MS_SHOULD_BE_FINAL") |
| public static WatcherAccessor ACCESSOR = null; |
| |
| GradleProject project; |
| |
| static { |
| // invokes static initializer of ModelHandle.class |
| // that will assign value to the ACCESSOR field above |
| Class<?> c = NbGradleProject.class; |
| try { |
| Class.forName(c.getName(), true, c.getClassLoader()); |
| } catch (ClassNotFoundException ex) { |
| LOG.log(SEVERE, "very wrong, very wrong, yes indeed", ex); |
| } |
| } |
| |
| private final GradleFiles gradleFiles; |
| |
| public boolean isGradleProjectLoaded() { |
| return project != null; |
| } |
| |
| public static abstract class WatcherAccessor { |
| |
| public abstract NbGradleProject createWatcher(NbGradleProjectImpl proj); |
| |
| public abstract void doFireReload(NbGradleProject watcher); |
| |
| public abstract void activate(NbGradleProject watcher); |
| |
| public abstract void passivate(NbGradleProject watcher); |
| } |
| |
| @java.lang.SuppressWarnings("LeakingThisInConstructor") |
| public NbGradleProjectImpl(final FileObject projectDir, ProjectState projectState) { |
| this.projectDir = projectDir; |
| this.projectState = projectState; |
| this.gradleFiles = new GradleFiles(FileUtil.normalizeFile(FileUtil.toFile(projectDir)), true); |
| 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 " + gradleFiles.getBuildScript(), new Exception()); |
| assert basicLookup != null; |
| return basicLookup; |
| } else { |
| return completeLookup; |
| } |
| } |
| }); |
| watcher = ACCESSOR.createWatcher(this); |
| GradleAuxiliaryConfigImpl aux = new GradleAuxiliaryConfigImpl(projectDir, true); |
| basicLookup = createBasicLookup(projectState, aux); |
| completeLookup = LookupProviderSupport.createCompositeLookup(basicLookup, new PluginDependentLookup(watcher)); |
| } |
| |
| public GradleFiles getGradleFiles() { |
| return gradleFiles; |
| } |
| |
| @Override |
| public FileObject getProjectDirectory() { |
| return projectDir; |
| } |
| |
| @Override |
| public Lookup getLookup() { |
| return lookup; |
| } |
| |
| private Lookup createBasicLookup(ProjectState state, GradleAuxiliaryConfigImpl aux) { |
| return Lookups.fixed(this, |
| watcher, |
| new CacheDirProvider(), |
| aux, |
| aux.getProblemProvider(), |
| new GradleAuxiliaryPropertiesImpl(this), |
| new GradleSharabilityQueryImpl(this), |
| UILookupMergerSupport.createProjectOpenHookMerger(new ProjectOpenedHookImpl()), |
| UILookupMergerSupport.createProjectProblemsProviderMerger(), |
| UILookupMergerSupport.createRecommendedTemplatesMerger(), |
| UILookupMergerSupport.createPrivilegedTemplatesMerger(), |
| state |
| ); |
| } |
| |
| public GradleProject getGradleProject() { |
| if (project == null) { |
| project = loadProject(); |
| } |
| return project; |
| } |
| |
| public void fireProjectReload(boolean wait) { |
| reloadTask.schedule(0); |
| if (wait) { |
| reloadTask.waitFinished(); |
| } |
| } |
| |
| |
| void attachAllUpdater() { |
| synchronized (this) { |
| if (openedProjectUpdater == null) { |
| openedProjectUpdater = new Updater((new FileProvider() { |
| |
| @Override |
| public Set<File> getFiles() { |
| GradleFiles gf = getGradleFiles(); |
| Set<File> ret = new LinkedHashSet<>(); |
| for (GradleFiles.Kind kind : GradleFiles.Kind.PROJECT_FILES) { |
| ret.add(gf.getFile(kind)); |
| } |
| return ret; |
| } |
| })); |
| } |
| } |
| |
| openedProjectUpdater.attachAll(); |
| } |
| |
| void detachAllUpdater() { |
| synchronized (this) { |
| if (openedProjectUpdater != null) { |
| openedProjectUpdater.detachAll(); |
| } |
| } |
| } |
| |
| void dumpProject() { |
| project = null; |
| } |
| |
| public Quality getAimedQuality() { |
| return aimedQuality; |
| } |
| |
| public NbGradleProject getProjectWatcher() { |
| return watcher; |
| } |
| |
| public void setAimedQuality(Quality aim) { |
| //TODO: Shall we do some locking here? |
| if ((aimedQuality == FALLBACK) && aim.betterThan(FALLBACK)) { |
| ACCESSOR.activate(watcher); |
| } |
| if ((aim == FALLBACK) && aimedQuality.betterThan(FALLBACK)) { |
| ACCESSOR.passivate(watcher); |
| } |
| this.aimedQuality = aim; |
| if ((project == null) || project.getQuality().worseThan(aim)) { |
| project = loadProject(); |
| ACCESSOR.doFireReload(watcher); |
| } |
| } |
| |
| private GradleProject loadProject() { |
| return loadProject(false, aimedQuality); |
| } |
| |
| private GradleProject loadProject(boolean ignoreCache, Quality aim, String... args) { |
| GradleProject prj = GradleProjectCache.loadProject(this, aim, ignoreCache, false, args); |
| return prj; |
| } |
| |
| void reloadProject(final boolean ignoreCache, final Quality aim, final String... args) { |
| RELOAD_RP.post(() -> { |
| project = loadProject(ignoreCache, aim, args); |
| ACCESSOR.doFireReload(watcher); |
| }); |
| } |
| |
| @Override |
| public int hashCode() { |
| return gradleFiles.hashCode() * 3; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof Project) { |
| NbGradleProjectImpl impl = ((Project) obj).getLookup().lookup(NbGradleProjectImpl.class); |
| if (impl != null) { |
| return getGradleFiles().equals(impl.getGradleFiles()); |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public String toString() { |
| if (isGradleProjectLoaded()) { |
| return "Gradle: " + project.getBaseProject().getName() + "[" + project.getQuality() + "]"; |
| } else { |
| return "Unloaded Gradle Project: " + gradleFiles.toString(); |
| } |
| } |
| |
| private class ProjectOpenedHookImpl extends ProjectOpenedHook { |
| |
| @Override |
| protected void projectOpened() { |
| Runnable open = () -> { |
| setAimedQuality(FULL); |
| attachAllUpdater(); |
| if (ProjectProblems.isBroken(NbGradleProjectImpl.this)) { |
| ProjectProblems.showAlert(NbGradleProjectImpl.this); |
| } |
| }; |
| if (GradleSettings.getDefault().isOpenLazy()) { |
| RELOAD_RP.post(open, 100); |
| } else { |
| open.run(); |
| } |
| } |
| |
| @Override |
| protected void projectClosed() { |
| setAimedQuality(Quality.FALLBACK); |
| detachAllUpdater(); |
| dumpProject(); |
| } |
| } |
| |
| interface FileProvider { |
| |
| Set<File> getFiles(); |
| } |
| |
| private class CacheDirProvider implements CacheDirectoryProvider { |
| |
| @Override |
| public FileObject getCacheDirectory() throws IOException { |
| return FileUtil.createFolder(GradleProjectCache.getCacheDir(gradleFiles)); |
| } |
| } |
| |
| private static class PluginDependentLookup extends ProxyLookup implements PropertyChangeListener { |
| |
| private static final String NB_GENERAL = "<nb-general>"; //NOI18N |
| private static final String NB_ROOT_PLUGIN = "root"; //NOI18N |
| private final WeakReference<NbGradleProject> watcherRef; |
| private final Map<String, Lookup> pluginLookups = new HashMap<>(); |
| |
| @java.lang.SuppressWarnings("LeakingThisInConstructor") |
| public PluginDependentLookup(NbGradleProject watcher) { |
| watcherRef = new WeakReference<>(watcher); |
| Lookup general = Lookups.forPath("Projects/" + NbGradleProject.GRADLE_PROJECT_TYPE + "/Lookup"); //NOI18N |
| pluginLookups.put(NB_GENERAL, general); //NOI18N |
| check(); |
| watcher.addPropertyChangeListener(WeakListeners.propertyChange(this, watcher)); |
| } |
| |
| private void check() { |
| boolean lookupsChanged = false; |
| NbGradleProject watcher = watcherRef.get(); |
| if (watcher != null) { |
| lookupsChanged = !watcher.isGradleProjectLoaded(); |
| GradleBaseProject prj = watcher.projectLookup(GradleBaseProject.class); |
| Set<String> currentPlugins = new HashSet<>(prj.getPlugins()); |
| if (prj.isRoot()) { |
| currentPlugins.add(NB_ROOT_PLUGIN); |
| } |
| for (String cp : currentPlugins) { |
| //Add Lookups for new plugins |
| if (!pluginLookups.containsKey(cp)) { |
| Lookup pluginLookup = Lookups.forPath("Projects/" + NbGradleProject.GRADLE_PLUGIN_TYPE + "/" + cp + "/Lookup"); //NOI18N |
| pluginLookups.put(cp, pluginLookup); |
| lookupsChanged = true; |
| } |
| } |
| Iterator<String> it = pluginLookups.keySet().iterator(); |
| while (it.hasNext()) { |
| String oldPlugin = it.next(); |
| if (!currentPlugins.contains(oldPlugin) && !NB_GENERAL.equals(oldPlugin)) { |
| it.remove(); |
| lookupsChanged = true; |
| } |
| } |
| } |
| if (lookupsChanged) { |
| setLookups(pluginLookups.values().toArray(new Lookup[pluginLookups.size()])); |
| } |
| } |
| |
| @Override |
| public void propertyChange(PropertyChangeEvent evt) { |
| if (NbGradleProject.PROP_PROJECT_INFO.equals(evt.getPropertyName())) { |
| check(); |
| } |
| } |
| |
| } |
| |
| private class Updater implements FileChangeListener { |
| |
| final FileProvider fileProvider; |
| Set<File> filesToWatch; |
| long lastEventTime = 0; |
| |
| Updater(FileProvider fp) { |
| fileProvider = fp; |
| } |
| |
| @Override |
| public void fileFolderCreated(FileEvent fe) { |
| } |
| |
| @Override |
| public void fileDataCreated(FileEvent fe) { |
| if (lastEventTime < fe.getTime()) { |
| lastEventTime = System.currentTimeMillis(); |
| fireProjectReload(false); |
| } |
| } |
| |
| @Override |
| public void fileChanged(FileEvent fe) { |
| if (lastEventTime < fe.getTime()) { |
| lastEventTime = System.currentTimeMillis(); |
| fireProjectReload(false); |
| } |
| } |
| |
| @Override |
| public void fileDeleted(FileEvent fe) { |
| lastEventTime = System.currentTimeMillis(); |
| fireProjectReload(false); |
| } |
| |
| @Override |
| public void fileRenamed(FileRenameEvent fe) { |
| } |
| |
| @Override |
| public void fileAttributeChanged(FileAttributeEvent fe) { |
| } |
| |
| synchronized void attachAll() { |
| filesToWatch = fileProvider.getFiles(); |
| if (filesToWatch != null) { |
| for (File f : filesToWatch) { |
| try { |
| FileUtil.addFileChangeListener(this, f); |
| } catch (IllegalArgumentException ex) { |
| assert false : "Project opened twice in a row"; |
| } |
| } |
| } |
| } |
| |
| synchronized void detachAll() { |
| if (filesToWatch != null) { |
| for (File f : filesToWatch) { |
| try { |
| FileUtil.removeFileChangeListener(this, f); |
| } catch (IllegalArgumentException ex) { |
| assert false : "Project closed twice in a row"; |
| } |
| } |
| } |
| } |
| } |
| |
| } |