package org.netbeans.gradle.project.properties; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.RandomAccess; import org.jtrim.collections.CollectionsEx; import org.jtrim.event.ListenerRef; import org.jtrim.event.ListenerRegistries; import org.jtrim.property.MutableProperty; import org.jtrim.property.PropertyFactory; import org.jtrim.property.PropertySource; import org.jtrim.property.ValueConverter; import org.jtrim.swing.concurrent.SwingTaskExecutor; import org.jtrim.utils.ExceptionHelper; import org.netbeans.gradle.project.api.config.PropertyDef; import org.netbeans.gradle.project.api.config.SingleProfileSettings; import org.netbeans.gradle.project.api.config.ValueMerger; import org.netbeans.gradle.project.api.config.ValueReference; import org.netbeans.gradle.project.event.ChangeListenerManager; import org.netbeans.gradle.project.event.GenericChangeListenerManager; import org.netbeans.gradle.project.util.NbFunction; import org.w3c.dom.Element; public final class MultiProfileProperties implements ActiveSettingsQueryEx { private final MutableProperty<List<SingleProfileSettingsEx>> currentProfileSettingsList; private final PropertySource<SingleProfileSettings> currentProfileSettings; private final PropertySource<SingleProfileSettingsEx> currentProfileSettingsEx; private final ChangeListenerManager currentProfileChangeListeners; public MultiProfileProperties(List<SingleProfileSettingsEx> initialProfiles) { this.currentProfileSettingsList = PropertyFactory.memPropertyConcurrent( CollectionsEx.readOnlyCopy(copySettings(initialProfiles)), SwingTaskExecutor.getStrictExecutor(false)); this.currentProfileChangeListeners = GenericChangeListenerManager.getSwingNotifier(); this.currentProfileSettingsEx = new PropertySource<SingleProfileSettingsEx>() { @Override public SingleProfileSettingsEx getValue() { return getCurrentProfileSettings(); } @Override public ListenerRef addChangeListener(Runnable listener) { return currentProfileChangeListeners.registerListener(listener); } }; // Just for type safety. An unsafe cast would do because of type erasure. this.currentProfileSettings = PropertyFactory.convert(currentProfileSettingsEx, new ValueConverter<SingleProfileSettingsEx, SingleProfileSettings>() { @Override public SingleProfileSettings convert(SingleProfileSettingsEx input) { return input; } }); } @Override public Element getAuxConfigValue(DomElementKey key) { for (SingleProfileSettingsEx settings: currentProfileSettingsList.getValue()) { Element result = settings.getAuxConfigValue(key); if (result != null) { return result; } } return null; } private static List<SingleProfileSettingsEx> copySettings(List<? extends SingleProfileSettingsEx> settings) { List<SingleProfileSettingsEx> result = CollectionsEx.readOnlyCopy(settings); ExceptionHelper.checkNotNullElements(result, "settings"); ExceptionHelper.checkArgumentInRange(result.size(), 1, Integer.MAX_VALUE, "settings.size()"); return result; } private SingleProfileSettingsEx getCurrentProfileSettings() { List<SingleProfileSettingsEx> allProfileSettings = currentProfileSettingsList.getValue(); if (allProfileSettings.isEmpty()) { throw new AssertionError("No profile was set."); } return allProfileSettings.get(0); } @Override public PropertySource<SingleProfileSettingsEx> currentProfileSettingsEx() { return currentProfileSettingsEx; } @Override public PropertySource<SingleProfileSettings> currentProfileSettings() { return currentProfileSettings; } public void setProfileSettings(List<? extends SingleProfileSettingsEx> newSettings) { currentProfileSettingsList.setValue(copySettings(newSettings)); currentProfileChangeListeners.fireEventually(); } private static <ValueType> ValueType mergePropertyValues( ValueMerger<ValueType> valueMerger, List<? extends PropertySource<ValueType>> properties) { return mergePropertyValues(0, valueMerger, properties); } private static <ValueType> ValueType mergePropertyValues( final int settingsIndex, final ValueMerger<ValueType> valueMerger, final List<? extends PropertySource<ValueType>> properties) { assert properties instanceof RandomAccess; int propertiesCount = properties.size() - settingsIndex; if (propertiesCount <= 0) { return null; } ValueType childValue = properties.get(settingsIndex).getValue(); if (propertiesCount <= 1) { return childValue; } return valueMerger.mergeValues(childValue, new ValueReference<ValueType>() { @Override public ValueType getValue() { return mergePropertyValues(settingsIndex + 1, valueMerger, properties); } }); } private static <ValueType> PropertySource<ValueType> mergedProperty( PropertyDef<?, ValueType> propertyDef, List<SingleProfileSettingsEx> profileList) { // Minor optimization for the default profile if (profileList.size() == 1) { return profileList.get(0).getProperty(propertyDef); } final List<PropertySource<ValueType>> properties = new ArrayList<>(); for (SingleProfileSettingsEx profile: profileList) { properties.add(profile.getProperty(propertyDef)); } final ValueMerger<ValueType> valueMerger = propertyDef.getValueMerger(); return new PropertySource<ValueType>() { @Override public ValueType getValue() { return mergePropertyValues(valueMerger, properties); } @Override public ListenerRef addChangeListener(Runnable listener) { ExceptionHelper.checkNotNullArgument(listener, "listener"); List<ListenerRef> refs = new ArrayList<>(properties.size()); for (PropertySource<ValueType> property: properties) { refs.add(property.addChangeListener(listener)); } return ListenerRegistries.combineListenerRefs(refs); } }; } @Override public <ValueType> PropertySource<ValueType> getProperty(PropertyDef<?, ValueType> propertyDef) { return NbProperties.propertyOfProperty(currentProfileSettingsList, new CachingPropertyMerger<>(propertyDef)); } private static class CachingPropertyMerger<Value> implements NbFunction<List<SingleProfileSettingsEx>, PropertySource<Value>> { private final PropertyDef<?, Value> propertyDef; private volatile CachedMergedProperty<Value> cachedValue; public CachingPropertyMerger(PropertyDef<?, Value> propertyDef) { ExceptionHelper.checkNotNullArgument(propertyDef, "propertyDef"); this.propertyDef = propertyDef; this.cachedValue = null; } @Override public PropertySource<Value> apply(List<SingleProfileSettingsEx> arg) { CachedMergedProperty<Value> result = cachedValue; if (result == null || !result.isSame(arg)) { result = new CachedMergedProperty<>(propertyDef, arg); } return result.getProperty(); } } private static final class CachedMergedProperty<Value> { private final List<SingleProfileSettingsEx> settings; private final PropertySource<Value> property; public CachedMergedProperty( PropertyDef<?, Value> propertyDef, List<SingleProfileSettingsEx> settings) { this.settings = CollectionsEx.readOnlyCopy(settings); this.property = mergedProperty(propertyDef, this.settings); } public boolean isSame(List<SingleProfileSettingsEx> testedSettings) { return Objects.equals(this.settings, testedSettings); } public PropertySource<Value> getProperty() { return property; } } }