package org.netbeans.gradle.project.model;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.gradle.tooling.ProjectConnection;
import org.jtrim.utils.ExceptionHelper;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.gradle.model.BuilderIssue;
import org.netbeans.gradle.model.BuilderResult;
import org.netbeans.gradle.model.FetchedModels;
import org.netbeans.gradle.model.FetchedModelsOrError;
import org.netbeans.gradle.model.FetchedProjectModels;
import org.netbeans.gradle.model.GenericModelFetcher;
import org.netbeans.gradle.model.GenericProjectProperties;
import org.netbeans.gradle.model.GradleBuildInfoQuery;
import org.netbeans.gradle.model.OperationInitializer;
import org.netbeans.gradle.model.api.GradleProjectInfoQuery2;
import org.netbeans.gradle.model.util.CollectionUtils;
import org.netbeans.gradle.model.util.MultiMapUtils;
import org.netbeans.gradle.project.NbGradleProject;
import org.netbeans.gradle.project.NbStrings;
import org.netbeans.gradle.project.api.entry.ModelLoadResult;
import org.netbeans.gradle.project.api.entry.ParsedModel;
import org.netbeans.gradle.project.api.modelquery.GradleModelDef;
import org.netbeans.gradle.project.api.modelquery.GradleModelDefQuery2;
import org.netbeans.gradle.project.api.modelquery.GradleTarget;
import org.netbeans.gradle.project.extensions.NbGradleExtensionRef;
import org.netbeans.gradle.project.model.issue.ModelLoadIssue;
import org.netbeans.gradle.project.model.issue.ModelLoadIssues;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
public final class NbGradle18ModelLoader implements NbModelLoader {
private final SettingsGradleDef settingsGradleDef;
private final GradleTarget gradleTarget;
private final OperationInitializer setup;
public NbGradle18ModelLoader(SettingsGradleDef settingsGradleDef, OperationInitializer setup, GradleTarget gradleTarget) {
ExceptionHelper.checkNotNullArgument(settingsGradleDef, "settingsGradleDef");
ExceptionHelper.checkNotNullArgument(setup, "setup");
ExceptionHelper.checkNotNullArgument(gradleTarget, "gradleTarget");
this.settingsGradleDef = settingsGradleDef;
this.gradleTarget = gradleTarget;
this.setup = setup;
}
private static <E> void addAllNullSafe(Collection<? super E> collection, Collection<? extends E> toAdd) {
if (toAdd != null) {
collection.addAll(toAdd);
}
}
@Override
public Result loadModels(
NbGradleProject project,
ProjectConnection connection,
ProgressHandle progress) throws IOException, GradleModelLoadError {
ProjectModelFetcher modelFetcher = new ProjectModelFetcher(project, gradleTarget);
FetchedModelsOrError fetchedModelsOrError = modelFetcher.getModels(connection, setup);
FetchedModels fetchedModels = fetchedModelsOrError.getModels();
if (fetchedModels == null) {
throw new GradleModelLoadError(
project,
fetchedModelsOrError.getBuildScriptEvaluationError(),
fetchedModelsOrError.getUnexpectedError());
}
progress.progress(NbStrings.getParsingModel());
ProjectModelParser parser = new ProjectModelParser(gradleTarget, project, settingsGradleDef, modelFetcher);
return parser.parseModel(fetchedModels);
}
private static File getProjectDirFromModels(FetchedProjectModels projectModels) {
return projectModels
.getProjectDef()
.getMainProject()
.getGenericProperties()
.getProjectDir();
}
private static final class ProjectModelParser {
private final GradleTarget gradleTarget;
private final NbGradleProject mainProject;
private final List<NbGradleExtensionRef> extensions;
private final ProjectModelFetcher modelFetcher;
private final ExtensionModelCache cache;
private final List<ModelLoadIssue> issues;
private final Map<String, ModelLoadResult> modelLoadResultCache;
private final SettingsGradleDef settingsGradleDef;
public ProjectModelParser(
GradleTarget gradleTarget,
NbGradleProject mainProject,
SettingsGradleDef settingsGradleDef,
ProjectModelFetcher modelFetcher) {
this.gradleTarget = gradleTarget;
this.mainProject = mainProject;
this.settingsGradleDef = settingsGradleDef;
this.extensions = mainProject.getExtensions().getExtensionRefs();
this.modelFetcher = modelFetcher;
this.cache = new ExtensionModelCache();
this.issues = new ArrayList<>();
this.modelLoadResultCache = CollectionUtils.newHashMap(extensions.size());
}
private void addProjectInfoResults(
FetchedProjectModels projectModels,
NbGradleExtensionRef extension,
List<Object> results) {
List<BuilderResult> builderResults
= projectModels.getProjectInfoResults().get(extension.getName());
if (builderResults == null) {
return;
}
for (BuilderResult builderResult: builderResults) {
BuilderIssue issue = builderResult.getIssue();
if (issue != null) {
issues.add(ModelLoadIssues.builderError(
mainProject,
projectModels,
extension,
issue));
}
Object resultObject = builderResult.getResultObject();
if (resultObject != null) {
results.add(resultObject);
}
}
}
private Map<String, Lookup> createLookups(FetchedProjectModels projectModels) {
GenericProjectProperties genericProperties
= projectModels.getProjectDef().getMainProject().getGenericProperties();
Map<String, Lookup> result = CollectionUtils.newHashMap(extensions.size());
for (NbGradleExtensionRef extension: extensions) {
String extensionName = extension.getName();
List<Object> models = new ArrayList<>();
addProjectInfoResults(projectModels, extension, models);
addAllNullSafe(models, modelFetcher.getToolingModelsForExtension(extension, projectModels));
models.add(genericProperties);
result.put(extensionName, Lookups.fixed(models.toArray()));
}
return result;
}
private ModelLoadResult getModelLoadResult(
NbGradleExtensionRef extension,
File defaultProjectDir,
Map<File, ProjectModelsOfExtensions> extensionModels) {
Map<File, Lookup> lookups = CollectionUtils.newHashMap(extensionModels.size());
String extensionName = extension.getName();
for (Map.Entry<File, ProjectModelsOfExtensions> entry: extensionModels.entrySet()) {
Lookup lookup = entry.getValue().getExtensionLookups().get(extensionName);
if (lookup == null) {
lookup = Lookup.EMPTY;
}
lookups.put(entry.getKey(), lookup);
}
return new ModelLoadResult(gradleTarget, defaultProjectDir, lookups);
}
public Result parseModel(FetchedModels fetchedModels) {
ProjectModelsOfExtensions extensionsForDefault = new ProjectModelsOfExtensions(
this,
fetchedModels.getDefaultProjectModels());
File defaultProjectDir = extensionsForDefault.getProjectDir();
Collection<FetchedProjectModels> otherProjectModels = fetchedModels.getOtherProjectModels();
Map<File, ProjectModelsOfExtensions> extensionModels
= CollectionUtils.newHashMap(otherProjectModels.size());
for (FetchedProjectModels models: otherProjectModels) {
File projectDir = getProjectDirFromModels(models);
if (defaultProjectDir.equals(projectDir)) {
continue;
}
extensionModels.put(projectDir, new ProjectModelsOfExtensions(this, models));
}
extensionModels.put(defaultProjectDir, extensionsForDefault);
NbGradleModel mainModel = parseModel(
fetchedModels.getDefaultProjectModels(),
extensionModels);
List<NbGradleModel> otherModels = new ArrayList<>();
for (FetchedProjectModels models: otherProjectModels) {
File projectDir = getProjectDirFromModels(models);
if (defaultProjectDir.equals(projectDir)) {
continue;
}
otherModels.add(parseModel(models, extensionModels));
}
return new Result(mainModel, otherModels, issues);
}
private NbGradleModel parseModel(
FetchedProjectModels projectModels,
Map<File, ProjectModelsOfExtensions> extensionModels) {
Throwable issue = projectModels.getIssue();
if (issue != null) {
issues.add(ModelLoadIssues.projectModelLoadError(
mainProject,
projectModels,
null,
issue));
}
NbGradleMultiProjectDef projectDef = new NbGradleMultiProjectDef(projectModels.getProjectDef());
NbGenericModelInfo genericInfo = new NbGenericModelInfo(projectDef, modelFetcher.getSettingsFile());
NbGradleModel.Builder result = new NbGradleModel.Builder(genericInfo);
result.setRootWithoutSettingsGradle(!settingsGradleDef.isMaySearchUpwards());
File projectDir = genericInfo.getProjectDir();
ProjectExtensionModelCache projectCache = cache.tryGetProjectCache(projectDir);
for (NbGradleExtensionRef extension: extensions) {
String extensionName = extension.getName();
CachedModel cachedModel = projectCache != null
? projectCache.tryGetModel(extensionName)
: null;
Object extensionModel;
if (cachedModel != null) {
extensionModel = cachedModel.model;
}
else {
ModelLoadResult modelLoadResult = modelLoadResultCache.get(extensionName);
if (modelLoadResult == null) {
modelLoadResult = getModelLoadResult(extension, projectDir, extensionModels);
modelLoadResultCache.put(extensionName, modelLoadResult);
}
else {
modelLoadResult = modelLoadResult.withMainProject(projectDir);
}
ParsedModel<?> parsedModels = extension.parseModel(modelLoadResult);
extensionModel = parsedModels.getMainModel();
for (Map.Entry<File, ?> entry: parsedModels.getOtherProjectsModel().entrySet()) {
cache.getProjectCache(entry.getKey()).addModel(extensionName, entry.getValue());
}
}
result.setModelForExtension(extension, extensionModel);
}
return result.create();
}
}
private static final class ProjectModelsOfExtensions {
private final File projectDir;
private final Map<String, Lookup> extensionLookups;
public ProjectModelsOfExtensions(ProjectModelParser parser, FetchedProjectModels projectModels) {
extensionLookups = parser.createLookups(projectModels);
projectDir = getProjectDirFromModels(projectModels);
}
public File getProjectDir() {
return projectDir;
}
public Map<String, Lookup> getExtensionLookups() {
return extensionLookups;
}
}
private static final class CachedModel {
public final Object model;
public CachedModel(Object model) {
this.model = model;
}
}
private static final class ProjectExtensionModelCache {
private final Map<String, CachedModel> models;
public ProjectExtensionModelCache() {
this.models = new HashMap<>();
}
public CachedModel tryGetModel(String extensionName) {
return models.get(extensionName);
}
public void addModel(String extensionName, Object model) {
models.put(extensionName, new CachedModel(model));
}
}
private static final class ExtensionModelCache {
private final Map<File, ProjectExtensionModelCache> projectCaches;
public ExtensionModelCache() {
this.projectCaches = new HashMap<>();
}
public ProjectExtensionModelCache tryGetProjectCache(File projectDir) {
return projectCaches.get(projectDir);
}
public ProjectExtensionModelCache getProjectCache(File projectDir) {
ProjectExtensionModelCache cache = projectCaches.get(projectDir);
if (cache == null) {
cache = new ProjectExtensionModelCache();
projectCaches.put(projectDir, cache);
}
return cache;
}
}
private static final class ProjectModelFetcher {
private final Path settingsFile;
private final Map<String, List<Class<?>>> toolingModelNeeds;
private final GenericModelFetcher modelFetcher;
public ProjectModelFetcher(NbGradleProject project, GradleTarget gradleTarget) {
this.settingsFile = NbGenericModelInfo.findSettingsGradle(
project.getProjectDirectoryAsPath(),
project.getScriptFileProvider());
List<NbGradleExtensionRef> extensions = project.getExtensions().getExtensionRefs();
this.toolingModelNeeds = CollectionUtils.newHashMap(extensions.size());
Map<Object, List<GradleBuildInfoQuery<?>>> buildInfoRequests = Collections.emptyMap();
Map<Object, List<GradleProjectInfoQuery2<?>>> projectInfoRequests
= new HashMap<>();
List<Class<?>> models = new ArrayList<>();
for (NbGradleExtensionRef extensionRef: extensions) {
String extensionName = extensionRef.getName();
GradleModelDefQuery2 modelQuery = extensionRef.getModelNeeds().getQuery2();
GradleModelDef modelDef = modelQuery.getModelDef(gradleTarget);
models.addAll(modelDef.getToolingModels());
MultiMapUtils.addAllToMultiMap(extensionName, modelDef.getProjectInfoQueries2(), projectInfoRequests);
MultiMapUtils.addAllToMultiMap(extensionName, modelDef.getToolingModels(), toolingModelNeeds);
}
modelFetcher = new GenericModelFetcher(buildInfoRequests, projectInfoRequests, models);
}
public FetchedModelsOrError getModels(ProjectConnection connection, OperationInitializer init) throws IOException {
return modelFetcher.getModels(connection, init);
}
public Path getSettingsFile() {
return settingsFile;
}
public List<Object> getToolingModelsForExtension(
NbGradleExtensionRef extension,
FetchedProjectModels projectModels) {
List<Class<?>> needs = toolingModelNeeds.get(extension.getName());
if (needs == null || needs.isEmpty()) {
return Collections.emptyList();
}
Map<Class<?>, Object> allModels = projectModels.getToolingModels();
List<Object> result = new ArrayList<>(needs.size());
for (Class<?> need: needs) {
Object model = allModels.get(need);
if (model != null) {
result.add(model);
}
}
return result;
}
}
}