package org.netbeans.gradle.project;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import org.jtrim.concurrent.UpdateTaskExecutor;
import org.jtrim.property.PropertySource;
import org.jtrim.swing.concurrent.SwingUpdateTaskExecutor;
import org.jtrim.utils.ExceptionHelper;
import org.netbeans.gradle.project.event.ChangeListenerManager;
import org.netbeans.gradle.project.event.GenericChangeListenerManager;
import org.netbeans.gradle.project.extensions.NbGradleExtensionRef;
import org.netbeans.gradle.project.model.ModelRefreshListener;
import org.netbeans.gradle.project.model.ModelRetrievedListener;
import org.netbeans.gradle.project.model.NbGradleModel;
import org.netbeans.gradle.project.model.ProjectModelChangeListener;
import org.netbeans.gradle.project.properties.NbProperties;
import org.netbeans.gradle.project.query.GradleCacheBinaryForSourceQuery;
import org.netbeans.gradle.project.query.GradleCacheByBinaryLookup;
import org.netbeans.gradle.project.util.LazyValue;
import org.netbeans.gradle.project.util.NbSupplier;
final class ProjectModelManager implements ModelRetrievedListener<NbGradleModel> {
private static final Logger LOGGER = Logger.getLogger(ProjectModelManager.class.getName());
private final NbGradleProject project;
private final ChangeListenerManager modelChangeListeners;
private final AtomicReference<NbGradleModel> currentModelRef;
private final PropertySource<NbGradleModel> currentModel;
private final LazyValue<ProjectIssueRef> loadErrorRef;
private final UpdateTaskExecutor modelUpdater;
private final Runnable modelUpdateDispatcher;
public ProjectModelManager(final NbGradleProject project, final NbGradleModel initialModel) {
ExceptionHelper.checkNotNullArgument(project, "project");
ExceptionHelper.checkNotNullArgument(initialModel, "initialModel");
this.project = project;
this.modelChangeListeners = GenericChangeListenerManager.getSwingNotifier();
this.currentModelRef = new AtomicReference<>(initialModel);
this.currentModel = NbProperties.atomicValueView(currentModelRef, modelChangeListeners);
this.modelUpdater = new SwingUpdateTaskExecutor(true);
this.modelUpdateDispatcher = new Runnable() {
@Override
public void run() {
onModelChange();
}
};
this.loadErrorRef = new LazyValue<>(new NbSupplier<ProjectIssueRef>() {
@Override
public ProjectIssueRef get() {
return project.getProjectIssueManager().createIssueRef();
}
});
}
private void onModelChange() {
assert SwingUtilities.isEventDispatchThread();
try {
modelChangeListeners.fireEventually();
for (ProjectModelChangeListener listener: project.getLookup().lookupAll(ProjectModelChangeListener.class)) {
listener.onModelChanged();
}
} finally {
GradleCacheByBinaryLookup.notifyCacheChange();
GradleCacheBinaryForSourceQuery.notifyCacheChange();
}
}
public PropertySource<NbGradleModel> currentModel() {
return currentModel;
}
private void fireModelChangeEvent() {
modelUpdater.execute(modelUpdateDispatcher);
}
private boolean safelyLoadExtensions(NbGradleExtensionRef extension, Object model) {
try {
return extension.setModelForExtension(model);
} catch (Throwable ex) {
LOGGER.log(Level.SEVERE, "Extension has thrown an unexpected exception: " + extension.getName(), ex);
return false;
}
}
private Collection<NbGradleExtensionRef> getExtensionRefs() {
return project.getExtensions().getExtensionRefs();
}
private boolean notifyEmptyModelChange() {
boolean changedAny = false;
for (NbGradleExtensionRef extensionRef: getExtensionRefs()) {
boolean changed = safelyLoadExtensions(extensionRef, null);
changedAny = changedAny || changed;
}
fireModelChangeEvent();
return changedAny;
}
private boolean notifyModelChange(NbGradleModel model) {
// TODO: Consider conflicts
// GradleProjectExtensionDef.getSuppressedExtensions()
boolean changedAny = false;
for (NbGradleExtensionRef extensionRef: getExtensionRefs()) {
boolean changed = safelyLoadExtensions(extensionRef, model.getModelOfExtension(extensionRef));
changedAny = changedAny || changed;
}
fireModelChangeEvent();
return changedAny;
}
private void startRefresh(Collection<ModelRefreshListener> listeners) {
for (ModelRefreshListener listener: listeners) {
try {
listener.startRefresh();
} catch (Throwable ex) {
LOGGER.log(Level.SEVERE, "Failed to call " + listener.getClass().getName() + ".startRefresh()", ex);
}
}
}
private void endRefresh(Collection<ModelRefreshListener> listeners, boolean extensionsChanged) {
for (ModelRefreshListener listener: listeners) {
try {
listener.endRefresh(extensionsChanged);
} catch (Throwable ex) {
LOGGER.log(Level.SEVERE, "Failed to call " + listener.getClass().getName() + ".endRefresh(" + extensionsChanged + ")", ex);
}
}
}
private void updateExtensionActivation(NbGradleModel model) {
Collection<ModelRefreshListener> refreshListeners = new ArrayList<>(project.getLookup().lookupAll(ModelRefreshListener.class));
boolean extensionsChanged = false;
startRefresh(refreshListeners);
try {
if (model == null) {
extensionsChanged = notifyEmptyModelChange();
}
else {
extensionsChanged = notifyModelChange(model);
}
} finally {
endRefresh(refreshListeners, extensionsChanged);
}
}
private ProjectIssueRef getLoadErrorRef() {
return loadErrorRef.get();
}
public void updateModel(NbGradleModel model) {
updateModel(model, null);
}
@Override
public void updateModel(NbGradleModel model, Throwable error) {
boolean hasChanged = false;
if (model != null) {
NbGradleModel prevModel = currentModelRef.getAndSet(model);
hasChanged = prevModel != model;
}
if (error != null) {
ProjectIssue.Entry entry = new ProjectIssue.Entry(ProjectIssue.Kind.ERROR, NbStrings.getErrorLoadingProject(error));
getLoadErrorRef().setInfo(new ProjectIssue(Collections.singleton(entry)));
LOGGER.log(Level.INFO, "Error while loading the project model.", error);
project.displayError(NbStrings.getProjectLoadFailure(project.getName()), error);
}
else {
getLoadErrorRef().setInfo(null);
}
if (hasChanged) {
updateExtensionActivation(model);
}
}
}