package org.netbeans.gradle.project; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; import org.jtrim.cancel.CancellationToken; import org.jtrim.property.PropertySource; import org.netbeans.api.project.Project; import org.netbeans.gradle.project.api.config.ProjectSettingsProvider; import org.netbeans.gradle.project.api.task.BuiltInGradleCommandQuery; import org.netbeans.gradle.project.api.task.GradleCommandExecutor; import org.netbeans.gradle.project.extensions.ExtensionLoader; import org.netbeans.gradle.project.extensions.NbGradleExtensionRef; import org.netbeans.gradle.project.license.DefaultLicenseStore; import org.netbeans.gradle.project.license.LicenseHeaderInfo; import org.netbeans.gradle.project.license.LicenseManager; import org.netbeans.gradle.project.license.LicenseManagers; import org.netbeans.gradle.project.license.LicenseSource; import org.netbeans.gradle.project.lookups.LookupsEx; import org.netbeans.gradle.project.model.DefaultGradleModelLoader; import org.netbeans.gradle.project.model.ModelRetrievedListener; import org.netbeans.gradle.project.model.NbGradleModel; import org.netbeans.gradle.project.model.SettingsGradleDef; import org.netbeans.gradle.project.model.issue.ModelLoadIssue; import org.netbeans.gradle.project.model.issue.ModelLoadIssueReporter; import org.netbeans.gradle.project.properties.DefaultProjectSettingsProvider; import org.netbeans.gradle.project.properties.GradleAuxiliaryConfiguration; import org.netbeans.gradle.project.properties.GradleAuxiliaryProperties; import org.netbeans.gradle.project.properties.GradleCustomizer; import org.netbeans.gradle.project.properties.NbGradleCommonProperties; import org.netbeans.gradle.project.properties.NbGradleSingleProjectConfigProvider; import org.netbeans.gradle.project.properties.ProjectProfileLoader; import org.netbeans.gradle.project.properties.ProjectPropertiesApi; import org.netbeans.gradle.project.query.GradleSharabilityQuery; import org.netbeans.gradle.project.query.GradleSourceEncodingQuery; import org.netbeans.gradle.project.query.GradleTemplateAttrProvider; import org.netbeans.gradle.project.script.ScriptFileProvider; import org.netbeans.gradle.project.tasks.DefaultGradleCommandExecutor; import org.netbeans.gradle.project.tasks.MergedBuiltInGradleCommandQuery; import org.netbeans.gradle.project.util.CloseableAction; import org.netbeans.gradle.project.view.ContextActionProvider; import org.netbeans.gradle.project.view.GradleActionProvider; import org.netbeans.gradle.project.view.GradleProjectLogicalViewProvider; import org.netbeans.gradle.project.view.ProjectContextActionProvider; import org.netbeans.spi.project.ProjectState; import org.netbeans.spi.project.ui.CustomizerProvider; import org.netbeans.spi.project.ui.ProjectOpenedHook; import org.netbeans.spi.queries.FileEncodingQueryImplementation; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.Lookup; import org.openide.util.lookup.Lookups; public final class NbGradleProject implements Project { private final String name; private final FileObject projectDir; private final File projectDirAsFile; private final Path projectDirAsPath; private final AtomicReference<ServiceObjects> serviceObjectsRef; private NbGradleProject(FileObject projectDir) throws IOException { this.projectDir = projectDir; this.projectDirAsFile = FileUtil.toFile(projectDir); if (projectDirAsFile == null) { throw new IOException("Project directory does not exist."); } this.projectDirAsPath = projectDirAsFile.toPath(); this.name = projectDir.getNameExt(); this.serviceObjectsRef = new AtomicReference<>(null); } private ServiceObjects initServiceObjects(ProjectState state) { ServiceObjects serviceObjects = new ServiceObjects(this, state); if (!serviceObjectsRef.compareAndSet(null, serviceObjects)) { throw new IllegalStateException("Alread initialized: ServiceObjects"); } for (ProjectInitListener listener: serviceObjects.services.lookupAll(ProjectInitListener.class)) { listener.onInitProject(); } return serviceObjects; } private ServiceObjects getServiceObjects() { ServiceObjects result = serviceObjectsRef.get(); if (result == null) { throw new IllegalStateException("Services are not yet initialized."); } return result; } @Nonnull public static NbGradleProject createProject(FileObject projectDir, ProjectState state) throws IOException { NbGradleProject project = new NbGradleProject(projectDir); ServiceObjects serviceObjects = project.initServiceObjects(state); serviceObjects.updateExtensions(ExtensionLoader.loadExtensions(project)); LoadedProjectManager.getDefault().addProject(project); return project; } private SettingsFileManager getSettingsFileManager() { return getServiceObjects().settingsFileManager; } public SettingsGradleDef getPreferredSettingsGradleDef() { return getSettingsFileManager().getPreferredSettingsGradleDef(); } @Nonnull public BuiltInGradleCommandQuery getMergedCommandQuery() { return getServiceObjects().mergedCommandQuery; } private ProjectModelManager getModelManager() { return getServiceObjects().modelManager; } private ProjectModelUpdater<NbGradleModel> getModelUpdater() { return getServiceObjects().modelUpdater; } public void ensureLoadRequested() { getModelUpdater().ensureLoadRequested(); } public void reloadProject() { getModelUpdater().reloadProject(); } public void waitForLoadedProject(CancellationToken cancelToken) { getModelUpdater().waitForLoadedProject(cancelToken); } public boolean tryWaitForLoadedProject(long timeout, TimeUnit unit) { return getModelUpdater().tryWaitForLoadedProject(timeout, unit); } public boolean tryWaitForLoadedProject(CancellationToken cancelToken, long timeout, TimeUnit unit) { return getModelUpdater().tryWaitForLoadedProject(cancelToken, timeout, unit); } public NbGradleProjectExtensions getExtensions() { return getServiceObjects().extensions; } public NbGradleSingleProjectConfigProvider getConfigProvider() { return getServiceObjects().configProvider; } public ProjectProfileLoader getProfileLoader() { return getServiceObjects().profileLoader; } public void displayError(String errorText, Throwable exception) { if (!ModelLoadIssueReporter.reportIfBuildScriptError(this, exception)) { ModelLoadIssueReporter.reportAllIssues(errorText, Collections.singleton( new ModelLoadIssue(this, null, null, null, exception))); } } public ProjectIssueManager getProjectIssueManager() { return getServiceObjects().projectIssueManager; } public PropertySource<NbGradleModel> currentModel() { return getModelManager().currentModel(); } public ProjectSettingsProvider getProjectSettingsProvider() { return getServiceObjects().projectSettingsProvider; } public GradleCommandExecutor getGradleCommandExecutor() { return getServiceObjects().commandExecutor; } public NbGradleCommonProperties getCommonProperties() { return getServiceObjects().commonProperties; } public FileEncodingQueryImplementation getEncodingQuery() { return getServiceObjects().sourceEncoding; } public CustomizerProvider getCustomizer() { return getServiceObjects().customizer; } public LicenseSource getLicenseSource() { return ServiceObjects.LICENSE_STORE; } public ScriptFileProvider getScriptFileProvider() { return getServiceObjects().scriptFileProvider; } @Nonnull public String getName() { return name; } public ProjectDisplayInfo getDisplayInfo() { return getServiceObjects().projectDisplayInfo; } public String getDisplayName() { return getDisplayInfo().displayName().getValue(); } @Nonnull public Path getProjectDirectoryAsPath() { return projectDirAsPath; } @Nonnull public File getProjectDirectoryAsFile() { return projectDirAsFile; } @Override public FileObject getProjectDirectory() { return projectDir; } public void tryReplaceModel(NbGradleModel model) { if (getProjectDirectoryAsFile().equals(model.getProjectDir())) { getModelManager().updateModel(model, null); } } public boolean wasModelEverSet() { return getServiceObjects().modelUpdater.wasModelEverSet(); } @Override public Lookup getLookup() { return getServiceObjects().projectLookups.getMainLookup(); } // equals and hashCode is provided, so that NetBeans doesn't load the // same project multiple times. @Override public int hashCode() { return 201 + projectDir.hashCode(); } @Override public boolean equals(Object obj) { if (obj == null) return false; if (obj == this) return true; if (getClass() != obj.getClass()) return false; final NbGradleProject other = (NbGradleProject)obj; return this.projectDir.equals(other.projectDir); } private static final class ServiceObjects { public static final DefaultLicenseStore LICENSE_STORE = new DefaultLicenseStore(); public static final LicenseManager<NbGradleModel> LICENSE_MANAGER = LicenseManagers.createProjectLicenseManager(LICENSE_STORE); public static final RootProjectRegistry ROOT_PROJECT_REGISTRY = NbGradleProjectFactory.ROOT_PROJECT_REGISTRY; public static final GlobalSettingsFileManager SETTINGS_FILE_MANAGER = NbGradleProjectFactory.SETTINGS_FILE_MANAGER; public final GradleAuxiliaryConfiguration auxConfig; public final NbGradleSingleProjectConfigProvider configProvider; public final ProjectProfileLoader profileLoader; public final NbGradleCommonProperties commonProperties; public final ProjectState state; public final GradleProjectInformation projectInformation; public final GradleProjectLogicalViewProvider logicalViewProvider; public final GradleActionProvider actionProvider; public final GradleSharabilityQuery sharabilityQuery; public final GradleSourceEncodingQuery sourceEncoding; public final GradleCustomizer customizer; public final GradleAuxiliaryProperties auxProperties; public final GradleTemplateAttrProvider templateAttrProvider; public final DefaultGradleCommandExecutor commandExecutor; public final ProjectIssueManager projectIssueManager; public final ProjectSettingsProvider projectSettingsProvider; public final ProjectModelManager modelManager; public final ProjectModelUpdater<NbGradleModel> modelUpdater; public final ProjectDisplayInfo projectDisplayInfo; public final BuiltInGradleCommandQuery mergedCommandQuery; public final SettingsFileManager settingsFileManager; public final ScriptFileProvider scriptFileProvider; public final Lookup services; public final NbGradleProjectLookups projectLookups; public final UpdatableProjectExtensions extensions; public ServiceObjects(NbGradleProject project, ProjectState state) { List<Object> serviceObjects = new ArrayList<>(); serviceObjects.add(project); Path projectDir = project.getProjectDirectoryAsPath(); File projectDirAsFile = project.getProjectDirectoryAsFile(); ContextActionProvider provider = new ProjectContextActionProvider(project); serviceObjects.add(provider); this.scriptFileProvider = add(NbGradleProjectFactory.DEFAULT_SCRIPT_FILE_PROVIDER, serviceObjects); Path predictedSettingsDir = getSettingsDir(project.getProjectDirectoryAsPath(), this.scriptFileProvider); this.configProvider = add( NbGradleSingleProjectConfigProvider.create(predictedSettingsDir, project), serviceObjects); this.profileLoader = new ProjectProfileLoader(configProvider); this.commonProperties = configProvider.getCommonProperties(configProvider.getActiveSettingsQuery()); this.modelManager = new ProjectModelManager(project, DefaultGradleModelLoader.createEmptyModel(projectDir, scriptFileProvider)); ModelRetrievedListener<NbGradleModel> modelUpdateListener = new ModelRetrievedListener<NbGradleModel>() { @Override public void updateModel(NbGradleModel model, Throwable error) { modelManager.updateModel(model, error); if (model != null && error == null) { SETTINGS_FILE_MANAGER.updateSettingsFile(model); } } }; this.modelUpdater = new ProjectModelUpdater<>(createModelLoader(project), modelUpdateListener); this.settingsFileManager = new SettingsFileManager(projectDirAsFile, SETTINGS_FILE_MANAGER); this.projectDisplayInfo = new ProjectDisplayInfo( modelManager.currentModel(), commonProperties.displayNamePattern().getActiveSource()); this.auxConfig = add(new GradleAuxiliaryConfiguration(profileLoader), serviceObjects); this.state = add(state, serviceObjects); this.projectInformation = add(new GradleProjectInformation(project), serviceObjects); this.logicalViewProvider = add(new GradleProjectLogicalViewProvider(project, provider), serviceObjects); this.actionProvider = add(new GradleActionProvider(project), serviceObjects); this.sharabilityQuery = add(new GradleSharabilityQuery(modelManager.currentModel()), serviceObjects); this.sourceEncoding = add( new GradleSourceEncodingQuery(project.getProjectDirectory(), commonProperties.sourceEncoding().getActiveSource()), serviceObjects); this.customizer = add(new GradleCustomizer(project), serviceObjects); this.auxProperties = add(new GradleAuxiliaryProperties(auxConfig), serviceObjects); this.templateAttrProvider = add(new GradleTemplateAttrProvider(project, LICENSE_MANAGER), serviceObjects); this.commandExecutor = add(new DefaultGradleCommandExecutor(project), serviceObjects); this.projectIssueManager = add(new ProjectIssueManager(), serviceObjects); this.projectSettingsProvider = add( new DefaultProjectSettingsProvider(configProvider, profileLoader), serviceObjects); add(projectIssueManager.asProblemProvider(), serviceObjects); serviceObjects.add(createOpenHook( modelUpdater, modelManager.currentModel(), commonProperties.licenseHeaderInfo().getActiveSource())); add(ProjectPropertiesApi.buildPlatform(commonProperties.targetPlatform().getActiveSource()), serviceObjects); add(ProjectPropertiesApi.scriptPlatform(commonProperties.scriptPlatform().getActiveSource()), serviceObjects); add(ProjectPropertiesApi.sourceEncoding(commonProperties.sourceEncoding().getActiveSource()), serviceObjects); add(ProjectPropertiesApi.sourceLevel(commonProperties.sourceLevel().getActiveSource()), serviceObjects); add(ModelCacheSizeAutoUpdater.getDefault(), serviceObjects); this.services = Lookups.fixed(serviceObjects.toArray()); this.projectLookups = new NbGradleProjectLookups(project, services); this.extensions = new UpdatableProjectExtensions(projectLookups.getCombinedExtensionLookup()); this.mergedCommandQuery = new MergedBuiltInGradleCommandQuery( LookupsEx.asSupplier(extensions.getCombinedExtensionLookup(), BuiltInGradleCommandQuery.class)); } public void updateExtensions(Collection<? extends NbGradleExtensionRef> newExtensions) { extensions.setExtensions(newExtensions); projectLookups.updateExtensions(newExtensions); } private static <T> T add(T obj, Collection<? super T> serviceContainer) { serviceContainer.add(obj); return obj; } private static DefaultGradleModelLoader createModelLoader(NbGradleProject project) { DefaultGradleModelLoader.Builder result = new DefaultGradleModelLoader.Builder(project); return result.create(); } private static Path getSettingsDir(Path projectDir, ScriptFileProvider scriptProvider) { Path settingsGradle = NbGradleModel.findSettingsGradle(projectDir, scriptProvider); Path rootDir = settingsGradle != null ? settingsGradle.getParent() : null; return rootDir != null ? rootDir : projectDir; } private static ProjectOpenedHook createOpenHook( final ProjectModelUpdater<?> modelUpdater, final PropertySource<NbGradleModel> currentModel, final PropertySource<LicenseHeaderInfo> licenseInfo) { List<PropertySource<CloseableAction>> actionProperties = Arrays.asList( LICENSE_MANAGER.getRegisterListenerAction(currentModel, licenseInfo), ROOT_PROJECT_REGISTRY.forProject(currentModel) ); return GenericOpenHook.create(actionProperties, new Runnable() { @Override public void run() { modelUpdater.reloadProjectMayUseCache(); } }); } } }