package org.netbeans.gradle.project.properties;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jtrim.concurrent.GenericUpdateTaskExecutor;
import org.jtrim.concurrent.TaskExecutorService;
import org.jtrim.concurrent.UpdateTaskExecutor;
import org.jtrim.event.ListenerRef;
import org.jtrim.property.MutableProperty;
import org.jtrim.utils.ExceptionHelper;
import org.netbeans.gradle.project.api.config.ConfigTree;
import org.netbeans.gradle.project.api.config.ProfileKey;
import org.netbeans.gradle.project.api.config.PropertyDef;
import org.netbeans.gradle.project.event.OneShotChangeListenerManager;
import org.netbeans.gradle.project.util.NbTaskExecutors;
import org.w3c.dom.Element;
public final class GenericProfileSettings implements LoadableSingleProfileSettingsEx {
private static final Logger LOGGER = Logger.getLogger(ProjectProfileSettings.class.getName());
// Should be single threaded to avoid unnecessary multiple load.
private static final TaskExecutorService SAVE_LOAD_EXECUTOR
= NbTaskExecutors.newExecutor("Profile-I/O", 1);
private final ProfileLocationProvider locationProvider;
private final ProfileSettings settings;
private final Lock ioLock;
private volatile boolean loadedOnce;
private volatile boolean dirty;
private final OneShotChangeListenerManager loadedListeners;
private final UpdateTaskExecutor loadExecutor;
private final UpdateTaskExecutor saveExecutor;
public GenericProfileSettings(ProfileLocationProvider locationProvider) {
ExceptionHelper.checkNotNullArgument(locationProvider, "locationProvider");
this.locationProvider = locationProvider;
this.settings = new ProfileSettings();
this.ioLock = new ReentrantLock();
this.dirty = false;
this.loadedOnce = false;
this.loadedListeners = OneShotChangeListenerManager.getSwingNotifier();
this.saveExecutor = new GenericUpdateTaskExecutor(SAVE_LOAD_EXECUTOR);
this.loadExecutor = new GenericUpdateTaskExecutor(SAVE_LOAD_EXECUTOR);
}
public static GenericProfileSettings createTestMemorySettings() {
return new GenericProfileSettings(new ProfileLocationProvider() {
@Override
public ProfileKey getKey() {
return new ProfileKey("?", "test");
}
@Override
public Path tryGetOutputPath() throws IOException {
return null;
}
@Override
public ProfileFileDef tryGetOutputDef() throws IOException {
return null;
}
});
}
public void clearSettings() {
settings.clearSettings();
}
public ConfigTree getContentSnapshot() {
return settings.getContentSnapshot();
}
@Override
public ProfileKey getKey() {
return locationProvider.getKey();
}
@Override
public ListenerRef notifyWhenLoaded(Runnable onLoaded) {
ensureLoaded();
return loadedListeners.registerOrNotifyListener(onLoaded);
}
@Override
public void ensureLoaded() {
if (loadedOnce) {
return;
}
loadExecutor.execute(new Runnable() {
@Override
public void run() {
loadNowIfNotLoaded();
}
});
}
@Override
public void ensureLoadedAndWait() {
loadNowIfNotLoaded();
}
public void loadEventually() {
loadExecutor.execute(new Runnable() {
@Override
public void run() {
loadNowAlways();
}
});
}
public void loadAndWait() {
loadNowAlways();
}
private void loadNowIfNotLoaded() {
if (!loadedOnce) {
loadNow(true);
}
}
private void loadNowAlways() {
loadNow(false);
}
private void loadNow(boolean skipIfLoaded) {
try {
loadNowUnsafe(skipIfLoaded);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private void loadNowUnsafe(boolean skipIfLoaded) throws IOException {
try {
Path profileFile = locationProvider.tryGetOutputPath();
if (profileFile == null) {
LOGGER.log(Level.WARNING, "Cannot find location to save the profile: {0}", getKey());
return;
}
loadFromFile(profileFile, skipIfLoaded);
} finally {
loadedListeners.fireEventually();
}
}
private void loadFromFile(Path profileFile, boolean skipIfLoaded) {
ioLock.lock();
try {
if (!skipIfLoaded || !loadedOnce) {
settings.loadFromFile(profileFile);
}
} finally {
loadedOnce = true;
ioLock.unlock();
}
}
private void saveEventually() {
if (!dirty) {
return;
}
saveExecutor.execute(new Runnable() {
@Override
public void run() {
saveAndWait();
}
});
}
@Override
public void saveAndWait() {
try {
saveNowUnsafe();
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "Failed to save the properties.", ex);
}
}
private void saveNowUnsafe() throws IOException {
ProfileFileDef outputDef = locationProvider.tryGetOutputDef();
if (outputDef != null) {
savetoFile(outputDef.getProfileFile(), outputDef.getSaveOptions());
}
}
private void savetoFile(Path profileFile, ConfigSaveOptions saveOptions) throws IOException {
ioLock.lock();
try {
if (dirty) {
dirty = false;
settings.saveToFile(profileFile, saveOptions);
}
} finally {
ioLock.unlock();
}
}
@Override
public Element getAuxConfigValue(DomElementKey key) {
return settings.getAuxConfigValue(key);
}
@Override
public boolean setAuxConfigValue(DomElementKey key, Element value) {
boolean result = settings.setAuxConfigValue(key, value);
dirty = true;
saveEventually();
return result;
}
@Override
public <ValueType> MutableProperty<ValueType> getProperty(PropertyDef<?, ValueType> propertyDef) {
final MutableProperty<ValueType> result = settings.getProperty(propertyDef);
return new MutableProperty<ValueType>() {
@Override
public void setValue(ValueType value) {
result.setValue(value);
dirty = true;
saveEventually();
}
@Override
public ValueType getValue() {
return result.getValue();
}
@Override
public ListenerRef addChangeListener(Runnable listener) {
return result.addChangeListener(listener);
}
};
}
@Override
public Collection<DomElementKey> getAuxConfigKeys() {
return settings.getAuxConfigKeys();
}
}