package org.netbeans.gradle.project.properties; import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.concurrent.atomic.AtomicReference; import javax.swing.JList; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.jtrim.event.EventDispatcher; import org.jtrim.event.ListenerRef; import org.jtrim.event.ListenerRegistries; import org.jtrim.event.SimpleListenerRegistry; import org.jtrim.event.UnregisteredListenerRef; import org.jtrim.property.PropertyFactory; import org.jtrim.property.PropertySource; import org.jtrim.property.ValueConverter; import org.jtrim.property.swing.SwingForwarderFactory; import org.jtrim.property.swing.SwingProperties; import org.jtrim.property.swing.SwingPropertySource; import org.jtrim.utils.ExceptionHelper; import org.netbeans.gradle.project.api.event.NbListenerRefs; import org.netbeans.gradle.project.util.NbBiFunction; import org.netbeans.gradle.project.util.NbFunction; import org.openide.util.Lookup; import org.openide.util.LookupEvent; import org.openide.util.LookupListener; public final class NbProperties { public static <Value> PropertySource<Value> weakListenerProperty(PropertySource<? extends Value> src) { return new WeakListenerProperty<>(src); } public static SimpleListenerRegistry<Runnable> asChangeListenerRegistry( final PropertySource<?> property) { ExceptionHelper.checkNotNullArgument(property, "property"); return new SimpleListenerRegistry<Runnable>() { @Override public ListenerRef registerListener(Runnable listener) { return property.addChangeListener(listener); } }; } public static <Value> PropertySource<Value> atomicValueView( final AtomicReference<? extends Value> valueRef, final SimpleListenerRegistry<Runnable> changeListeners) { ExceptionHelper.checkNotNullArgument(valueRef, "valueRef"); ExceptionHelper.checkNotNullArgument(changeListeners, "changeListeners"); return new PropertySource<Value>() { @Override public Value getValue() { return valueRef.get(); } @Override public ListenerRef addChangeListener(Runnable listener) { return changeListeners.registerListener(listener); } }; } public static PropertySource<Boolean> between( final PropertySource<Integer> wrapped, final int minValue, final int maxValue) { return PropertyFactory.convert(wrapped, new ValueConverter<Integer, Boolean>() { @Override public Boolean convert(Integer input) { if (input == null) return null; return input <= maxValue && input >= minValue; } }); } public static PropertySource<Boolean> greaterThanOrEqual( final PropertySource<Integer> wrapped, final int value) { return between(wrapped, value, Integer.MAX_VALUE); } public static PropertySource<Boolean> lessThanOrEqual( final PropertySource<Integer> wrapped, final int value) { return between(wrapped, Integer.MIN_VALUE, value); } public static <RootValue, SubValue> PropertySource<SubValue> propertyOfProperty( PropertySource<? extends RootValue> rootSrc, NbFunction<? super RootValue, ? extends PropertySource<SubValue>> subPropertyGetter) { return new PropertyOfProperty<>(rootSrc, subPropertyGetter); } public static PropertySource<Boolean> isNotNull(PropertySource<?> src) { return PropertyFactory.convert(src, new ValueConverter<Object, Boolean>() { @Override public Boolean convert(Object input) { return input != null; } }); } public static <T, U, R> PropertySource<R> combine( PropertySource<? extends T> src1, PropertySource<? extends U> src2, NbBiFunction<? super T, ? super U, ? extends R> valueCombiner) { return new CombinedProperties<>(src1, src2, valueCombiner); } public static <Value> PropertySource<Value> listSelection(final JList<? extends Value> list) { ExceptionHelper.checkNotNullArgument(list, "list"); return new PropertySource<Value>() { @Override public Value getValue() { return list.getSelectedValue(); } @Override public ListenerRef addChangeListener(final Runnable listener) { ExceptionHelper.checkNotNullArgument(listener, "listener"); final ListSelectionListener swingListener = new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { listener.run(); } }; list.addListSelectionListener(swingListener); return NbListenerRefs.fromRunnable(new Runnable() { @Override public void run() { list.removeListSelectionListener(swingListener); } }); } }; } public static <Value> SwingPropertySource<Value, ChangeListener> toOldProperty( PropertySource<? extends Value> property) { return toOldProperty(property, property); } public static <Value> SwingPropertySource<Value, ChangeListener> toOldProperty( PropertySource<? extends Value> property, Object src) { ExceptionHelper.checkNotNullArgument(property, "property"); final ChangeEvent event = new ChangeEvent(src); return SwingProperties.toSwingSource(property, new EventDispatcher<ChangeListener, Void>() { @Override public void onEvent(ChangeListener eventListener, Void arg) { eventListener.stateChanged(event); } }); } public static <T> PropertySource<T> cacheFirstNonNull(final PropertySource<? extends T> src) { return new CacheFirstNonNullProperty<>(src); } public static <T> PropertySource<T> lookupProperty(Lookup lookup, Class<? extends T> type) { ExceptionHelper.checkNotNullArgument(lookup, "lookup"); ExceptionHelper.checkNotNullArgument(type, "type"); Lookup.Result<? extends T> lookupResult = lookup.lookupResult(type); LookupResultProperty<T> oldProperty = new LookupResultProperty<>(lookupResult); return SwingProperties.fromSwingSource(oldProperty, new SwingForwarderFactory<LookupListener>() { @Override public LookupListener createForwarder(final Runnable listener) { return new LookupListener() { @Override public void resultChanged(LookupEvent ev) { listener.run(); } }; } }); } private static final class CacheFirstNonNullProperty<T> implements PropertySource<T> { private final PropertySource<? extends T> src; private final AtomicReference<T> cache; public CacheFirstNonNullProperty(PropertySource<? extends T> src) { ExceptionHelper.checkNotNullArgument(src, "src"); this.src = src; this.cache = new AtomicReference<>(null); } @Override public T getValue() { T result = cache.get(); if (result == null) { result = src.getValue(); if (!cache.compareAndSet(null, result)) { result = cache.get(); } } return result; } @Override public ListenerRef addChangeListener(final Runnable listener) { ExceptionHelper.checkNotNullArgument(listener, "listener"); if (cache.get() != null) { return UnregisteredListenerRef.INSTANCE; } return src.addChangeListener(new Runnable() { private volatile boolean stopNotifying = false; @Override public void run() { if (!stopNotifying) { stopNotifying = cache.get() != null; listener.run(); } } }); } } private static final class LookupResultProperty<T> implements SwingPropertySource<T, LookupListener> { private final Lookup.Result<? extends T> lookupResult; public LookupResultProperty(Lookup.Result<? extends T> lookupResult) { ExceptionHelper.checkNotNullArgument(lookupResult, "lookupResult"); this.lookupResult = lookupResult; } @Override public T getValue() { Iterator<? extends T> resultItr = lookupResult.allInstances().iterator(); return resultItr.hasNext() ? resultItr.next() : null; } @Override public void addChangeListener(LookupListener listener) { lookupResult.addLookupListener(listener); } @Override public void removeChangeListener(LookupListener listener) { lookupResult.removeLookupListener(listener); } } private static class ReferenceHolderListenerRef implements ListenerRef { private final Runnable listener; private final ListenerRef wrappedRef; public ReferenceHolderListenerRef(Runnable listener, ListenerRef wrappedRef) { // We won't do anything useful with listener, just storing a reference // to it to prevent it from being garbage collected. this.listener = listener; this.wrappedRef = wrappedRef; } public Runnable getListener() { return listener; } @Override public boolean isRegistered() { return wrappedRef.isRegistered(); } @Override public void unregister() { wrappedRef.unregister(); } @Override @SuppressWarnings("FinalizeDeclaration") protected void finalize() throws Throwable { wrappedRef.unregister(); super.finalize(); } } private static class WeakListenerProperty<Value> implements PropertySource<Value> { private final PropertySource<? extends Value> src; public WeakListenerProperty(PropertySource<? extends Value> src) { ExceptionHelper.checkNotNullArgument(src, "src"); this.src = src; } @Override public Value getValue() { return src.getValue(); } @Override public ListenerRef addChangeListener(final Runnable listener) { ExceptionHelper.checkNotNullArgument(listener, "listener"); final WeakReference<Runnable> listenerRef = new WeakReference<>(listener); ListenerRef result = src.addChangeListener(new Runnable() { @Override public void run() { Runnable listener = listenerRef.get(); if (listener != null) { listener.run(); } } }); return new ReferenceHolderListenerRef(listener, result); } } private static final class CombinedProperties<R> implements PropertySource<R> { private final PropertySource<?> src1; private final PropertySource<?> src2; private final CombinedValues<?, ?, ? extends R> valueRef; public <T, U> CombinedProperties( PropertySource<? extends T> src1, PropertySource<? extends U> src2, NbBiFunction<? super T, ? super U, ? extends R> valueCombiner) { ExceptionHelper.checkNotNullArgument(src1, "src1"); ExceptionHelper.checkNotNullArgument(src2, "src2"); ExceptionHelper.checkNotNullArgument(valueCombiner, "valueCombiner"); this.src1 = src1; this.src2 = src2; this.valueRef = new CombinedValues<>(src1, src2, valueCombiner); } @Override public R getValue() { return valueRef.getValue(); } @Override public ListenerRef addChangeListener(Runnable listener) { return ListenerRegistries.combineListenerRefs( src1.addChangeListener(listener), src2.addChangeListener(listener)); } } private static final class CombinedValues<T, U, R> { private final PropertySource<? extends T> src1; private final PropertySource<? extends U> src2; private final NbBiFunction<? super T, ? super U, ? extends R> valueCombiner; public CombinedValues( PropertySource<? extends T> src1, PropertySource<? extends U> src2, NbBiFunction<? super T, ? super U, ? extends R> valueCombiner) { this.src1 = src1; this.src2 = src2; this.valueCombiner = valueCombiner; } public R getValue() { T value1 = src1.getValue(); U value2 = src2.getValue(); return valueCombiner.apply(value1, value2); } } }