package sk.stuba.fiit.perconik.activity.listeners; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; import com.google.common.base.Function; import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Stopwatch; import com.google.common.base.Supplier; import com.google.common.base.Ticker; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ListMultimap; import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; import sk.stuba.fiit.perconik.activity.data.ObjectData; import sk.stuba.fiit.perconik.activity.preferences.ActivityPreferences; import sk.stuba.fiit.perconik.activity.probes.Probe; import sk.stuba.fiit.perconik.activity.probes.Prober; import sk.stuba.fiit.perconik.activity.probes.Probers; import sk.stuba.fiit.perconik.core.Listener; import sk.stuba.fiit.perconik.core.preferences.ListenerPreferences; import sk.stuba.fiit.perconik.data.AnyStructuredData; import sk.stuba.fiit.perconik.data.content.Content; import sk.stuba.fiit.perconik.data.events.Event; import sk.stuba.fiit.perconik.data.store.Store; import sk.stuba.fiit.perconik.eclipse.core.runtime.PluginConsole; import sk.stuba.fiit.perconik.eclipse.swt.widgets.DisplayExecutor; import sk.stuba.fiit.perconik.eclipse.swt.widgets.DisplayTask; import sk.stuba.fiit.perconik.utilities.concurrent.NamedRunnable; import sk.stuba.fiit.perconik.utilities.concurrent.TimeUnits; import sk.stuba.fiit.perconik.utilities.configuration.ForwardingOptions; import sk.stuba.fiit.perconik.utilities.configuration.MapOptions; import sk.stuba.fiit.perconik.utilities.configuration.Options; import sk.stuba.fiit.perconik.utilities.configuration.Scope; import sk.stuba.fiit.perconik.utilities.configuration.ScopedConfigurable; import sk.stuba.fiit.perconik.utilities.configuration.StandardScope; import sk.stuba.fiit.perconik.utilities.time.TimeSource; import static java.lang.String.format; import static java.util.Collections.emptyMap; import static java.util.concurrent.Executors.newSingleThreadExecutor; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static com.google.common.base.Functions.constant; import static com.google.common.base.Functions.toStringFunction; import static com.google.common.base.Optional.fromNullable; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.base.Suppliers.ofInstance; import static com.google.common.base.Throwables.propagate; import static com.google.common.collect.Maps.newHashMap; import static com.google.common.collect.Maps.transformValues; import static sk.stuba.fiit.perconik.activity.data.DataCollections.toObjectData; import static sk.stuba.fiit.perconik.activity.plugin.Activator.defaultInstance; import static sk.stuba.fiit.perconik.data.content.StructuredContents.key; import static sk.stuba.fiit.perconik.eclipse.swt.widgets.DisplayExecutor.defaultSynchronous; import static sk.stuba.fiit.perconik.utilities.MorePreconditions.checkNotNullAsState; import static sk.stuba.fiit.perconik.utilities.MoreStrings.toDefaultString; import static sk.stuba.fiit.perconik.utilities.configuration.Configurables.compound; import static sk.stuba.fiit.perconik.utilities.configuration.Configurables.emptyOptions; /** * TODO * * @author Pavol Zbell * @since 1.0 */ public abstract class RegularListener<L extends RegularListener<L>> extends AbstractListener implements ScopedConfigurable { static final String internalProbeKeyPrefix = "meta"; /** * Underlying listener options holder. */ protected final OptionsLoader<? super L> optionsLoader; /** * Underlying listener options provider. */ final OptionsProvider<L> optionsProvider; /** * Underlying time context. */ protected final TimeContext timeContext; /** * Underlying plug-in console for general logging. */ protected final PluginConsole pluginConsole; /** * Underlying display executor for executing tasks requiring UI threads. */ protected final DisplayExecutor displayExecutor; /** * Underlying executor service for executing common commands. */ protected final ExecutorService sharedExecutor; /** * Underlying event data injector for injecting * additional data to event objects before validation. */ protected final DataInjector<? super L> dataInjector; /** * Underlying event data validator for validating * event objects before persistence. */ protected final EventValidator<? super L> eventValidator; /** * Underlying persistence store for persisting event data. */ protected final PersistenceStore<? super L> persistenceStore; /** * Underlying event data send failure handler. */ protected final SendFailureHandler<? super L> sendFailureHandler; /** * Underlying listener statistics. */ final RuntimeStatistics runtimeStatistics; /** * Underlying listener registration failure handler. */ final RegisterFailureHandler<? super L> registerFailureHandler; /** * Underlying listener disposal hook. */ final DisposalHook<? super L> disposalHook; /** * Constructor for use by subclasses. */ protected <C> RegularListener(final Configuration<C, L> configuration) { this(configuration, ofInstance((C) null)); } /** * Constructor for use by subclasses. */ protected <C> RegularListener(final Configuration<C, L> configuration, final C context) { this(configuration, ofInstance(context)); } // TODO note that if C is the instance being constructed - which actually can be, // and almost always is - great care must be taken when accessing fields during the // construction, since some may be observed as uninitialized in different construction states; // this behavior should be well documented (or refactored) as well as field initialization order; // for example one can not read dataInjector while configuring sharedExecutor private <C> RegularListener(final Configuration<C, L> configuration, final Supplier<? extends C> supplier) { final C context = this.resolveContext(configuration, supplier); // note that field initialization order is significant this.optionsLoader = this.resolveOptionsLoader(configuration, context); this.optionsProvider = this.setupOptionsProvider(); this.timeContext = configuration.timeContext(context).or(SystemTimeContext.instance); this.pluginConsole = configuration.pluginConsole(context).or(defaultInstance().getConsole()); this.displayExecutor = configuration.diplayExecutor(context).or(defaultSynchronous()); this.sharedExecutor = configuration.sharedExecutor(context).or(newSingleThreadExecutor()); this.dataInjector = this.resolveDataInjector(configuration, context); this.eventValidator = configuration.eventValidator(context).or(StandardEventValidator.instance); this.persistenceStore = configuration.persistenceStore(context).or(VoidPersistenceStore.instance); this.sendFailureHandler = configuration.sendFailureHandler(context).or(PropagatingSendFailureHandler.instance); this.runtimeStatistics = this.setupRuntimeStatistics(); this.registerFailureHandler = configuration.registerFailureHandler(context).or(PropagatingRegisterFailureHandler.instance); this.disposalHook = configuration.disposalHook(context).or(IgnoringDisposalHook.instance); } /** * Always returns {@code this}. */ final L asSubtype() { // cast is most likely to be safe since listener base // type has been already checked at context resolving @SuppressWarnings("unchecked") L self = (L) this; return self; } private <C> C resolveContext(final Configuration<C, L> configuration, final Supplier<? extends C> supplier) { Class<? extends C> contextType = checkNotNull(configuration.contextType()); Class<? extends L> listenerType = checkNotNull(configuration.listenerType()); C context = checkNotNull(supplier).get(); checkState(listenerType.isInstance(this), "%s: %s is not an instance of %s", this, toDefaultString(this), listenerType.getName()); try { return contextType.cast(context == null ? this : context); } catch (ClassCastException failure) { throw new IllegalStateException(format("%s: %s is not an instance of %s", this, toDefaultString(this), contextType.getName())); } } private <C> OptionsLoader<? super L> resolveOptionsLoader(final Configuration<C, L> configuration, final C context) { Optional<OptionsLoader<? super L>> loader = configuration.optionsLoader(context); Optional<ActivityPreferences> activity = configuration.activityPreferences(context); Optional<ListenerPreferences> listener = configuration.listenerPreferences(context); if (loader.isPresent()) { checkState(!activity.isPresent(), "%s: options loader configured but activity preferences also present", this); checkState(!listener.isPresent(), "%s: options loader configured but listener preferences also present", this); return loader.get(); } return UpdatingOptionsLoader.of(this.asSubtype(), activity.or(ActivityPreferences.getShared()), listener.or(ListenerPreferences.getShared())); } private <C> DataInjector<? super L> resolveDataInjector(final Configuration<C, L> configuration, final C context) { Optional<DataInjector<? super L>> injector = configuration.dataInjector(context); Optional<Map<String, Probe<?>>> mappings = configuration.probeMappings(context); if (injector.isPresent()) { checkState(!mappings.isPresent(), "%s: custom data injector configured but probe mappings also present", this); return injector.get(); } if (mappings.isPresent()) { Map<String, Probe<?>> mix = newHashMap(mappings.get()); for (Entry<String, InternalProbe<?>> entry: this.internalProbeMappings().entrySet()) { // potentially unsafe raw cast to InternalProbe is correct and safe in this case mix.put(key(internalProbeKeyPrefix, entry.getKey()), InternalProbe.class.cast(entry.getValue())); } Optional<Predicate<Entry<String, Probe<?>>>> filter = configuration.probeFilter(context); Optional<ExecutorService> executor = configuration.probeExecutor(context); if (filter.isPresent() && executor.isPresent()) { return ProbingDataInjector.of(Probers.create(mix, filter.get(), executor.get())); } else if (filter.isPresent()) { return ProbingDataInjector.of(Probers.create(mix, filter.get())); } else if (executor.isPresent()) { return ProbingDataInjector.of(Probers.create(mix, executor.get())); } return ProbingDataInjector.of(Probers.create(mix)); } return NoDataInjector.instance; } private OptionsProvider<L> setupOptionsProvider() { final OptionsProvider<L> provider = new LazyOptionsProvider<>(this.optionsLoader, this.asSubtype()); RegistrationHook.PRE_REGISTER.add(this, new NamedRunnable(OptionsProvider.class) { public void run() { reloadOptions(); } }); return provider; } private RuntimeStatistics setupRuntimeStatistics() { final RuntimeStatistics statistics = new RuntimeStatistics(); RegistrationHook.PRE_REGISTER.add(this, new NamedRunnable(RuntimeStatistics.class) { public void run() { statistics.registrationCount.incrementAndGet(); } }); RegistrationHook.POST_UNREGISTER.add(this, new NamedRunnable(RuntimeStatistics.class) { public void run() { statistics.unregistrationCount.incrementAndGet(); } }); return statistics; } public interface Configuration<C, L extends Listener> { public Class<? extends C> contextType(); public Class<? extends L> listenerType(); public Optional<ActivityPreferences> activityPreferences(C context); public Optional<ListenerPreferences> listenerPreferences(C context); public Optional<OptionsLoader<? super L>> optionsLoader(C context); public Optional<TimeContext> timeContext(C context); public Optional<PluginConsole> pluginConsole(C context); public Optional<DisplayExecutor> diplayExecutor(C context); public Optional<ExecutorService> sharedExecutor(C context); public Optional<Map<String, Probe<?>>> probeMappings(C context); public Optional<Predicate<Entry<String, Probe<?>>>> probeFilter(C context); public Optional<ExecutorService> probeExecutor(C context); public Optional<DataInjector<? super L>> dataInjector(C context); public Optional<EventValidator<? super L>> eventValidator(C context); public Optional<PersistenceStore<? super L>> persistenceStore(C context); public Optional<SendFailureHandler<? super L>> sendFailureHandler(C context); public Optional<RegisterFailureHandler<? super L>> registerFailureHandler(C context); public Optional<DisposalHook<? super L>> disposalHook(C context); public interface Builder<C, L extends Listener> { public Configuration<C, L> build(); } } public static abstract class AbstractConfiguration<C, L extends Listener> implements Configuration<C, L> { private final Class<? extends C> contextType; private final Class<? extends L> listenerType; private final Function<? super C, ? extends ActivityPreferences> activityPreferences; private final Function<? super C, ? extends ListenerPreferences> listenerPreferences; private final Function<? super C, ? extends OptionsLoader<? super L>> optionsLoader; private final Function<? super C, ? extends TimeContext> timeContext; private final Function<? super C, ? extends PluginConsole> pluginConsole; private final Function<? super C, ? extends DisplayExecutor> diplayExecutor; private final Function<? super C, ? extends ExecutorService> sharedExecutor; private final Function<? super C, ? extends Map<String, Probe<?>>> probeMappings; private final Function<? super C, ? extends Predicate<Entry<String, Probe<?>>>> probeFilter; private final Function<? super C, ? extends ExecutorService> probeExecutor; private final Function<? super C, ? extends DataInjector<? super L>> dataInjector; private final Function<? super C, ? extends EventValidator<? super L>> eventValidator; private final Function<? super C, ? extends PersistenceStore<? super L>> persistenceStore; private final Function<? super C, ? extends SendFailureHandler<? super L>> sendFailureHandler; private final Function<? super C, ? extends RegisterFailureHandler<? super L>> registerFailureHandler; private final Function<? super C, ? extends DisposalHook<? super L>> disposalHook; /** * Constructor for use by subclasses. */ protected AbstractConfiguration(final AbstractBuilder<?, C, L> builder) { this.contextType = checkNotNull(builder.contextType); this.listenerType = checkNotNull(builder.listenerType); this.activityPreferences = checkNotNull(builder.activityPreferences); this.listenerPreferences = checkNotNull(builder.listenerPreferences); this.optionsLoader = checkNotNull(builder.optionsLoader); this.timeContext = checkNotNull(builder.timeContext); this.pluginConsole = checkNotNull(builder.pluginConsole); this.diplayExecutor = checkNotNull(builder.diplayExecutor); this.sharedExecutor = checkNotNull(builder.sharedExecutor); this.probeMappings = checkNotNull(builder.probeMappings); this.probeFilter = checkNotNull(builder.probeFilter); this.probeExecutor = checkNotNull(builder.probeExecutor); this.dataInjector = checkNotNull(builder.dataInjector); this.eventValidator = checkNotNull(builder.eventValidator); this.persistenceStore = checkNotNull(builder.persistenceStore); this.sendFailureHandler = checkNotNull(builder.sendFailureHandler); this.registerFailureHandler = checkNotNull(builder.registerFailureHandler); this.disposalHook = checkNotNull(builder.disposalHook); } public static abstract class AbstractBuilder<B extends AbstractBuilder<B, C, L>, C, L extends Listener> implements Builder<C, L> { Class<? extends C> contextType; Class<? extends L> listenerType; Function<? super C, ? extends ActivityPreferences> activityPreferences = constant(null); Function<? super C, ? extends ListenerPreferences> listenerPreferences = constant(null); Function<? super C, ? extends OptionsLoader<? super L>> optionsLoader = constant(null); Function<? super C, ? extends TimeContext> timeContext = constant(null); Function<? super C, ? extends PluginConsole> pluginConsole = constant(null); Function<? super C, ? extends DisplayExecutor> diplayExecutor = constant(null); Function<? super C, ? extends ExecutorService> sharedExecutor = constant(null); Function<? super C, ? extends Map<String, Probe<?>>> probeMappings = constant(null); Function<? super C, ? extends Predicate<Entry<String, Probe<?>>>> probeFilter = constant(null); Function<? super C, ? extends ExecutorService> probeExecutor = constant(null); Function<? super C, ? extends DataInjector<? super L>> dataInjector = constant(null); Function<? super C, ? extends EventValidator<? super L>> eventValidator = constant(null); Function<? super C, ? extends PersistenceStore<? super L>> persistenceStore = constant(null); Function<? super C, ? extends SendFailureHandler<? super L>> sendFailureHandler = constant(null); Function<? super C, ? extends RegisterFailureHandler<? super L>> registerFailureHandler = constant(null); Function<? super C, ? extends DisposalHook<? super L>> disposalHook = constant(null); /** * Constructor for use by subclasses. */ protected AbstractBuilder() {} /** * Always returns {@code this}. */ protected abstract B asSubtype(); public final B contextType(final Class<? extends C> type) { this.contextType = checkNotNull(type); return this.asSubtype(); } public final B listenerType(final Class<? extends L> type) { this.listenerType = checkNotNull(type); return this.asSubtype(); } public final B activityPreferences(final ActivityPreferences preferences) { return this.activityPreferences(constant(checkNotNull(preferences))); } public final B activityPreferences(final Function<? super C, ? extends ActivityPreferences> relation) { this.activityPreferences = checkNotNull(relation); return this.asSubtype(); } public final B listenerPreferences(final ListenerPreferences preferences) { return this.listenerPreferences(constant(checkNotNull(preferences))); } public final B listenerPreferences(final Function<? super C, ? extends ListenerPreferences> relation) { this.listenerPreferences = checkNotNull(relation); return this.asSubtype(); } public final B optionsLoader(final OptionsLoader<? super L> loader) { return this.optionsLoader(constant(checkNotNull(loader))); } public final B optionsLoader(final Function<? super C, ? extends OptionsLoader<? super L>> relation) { this.optionsLoader = checkNotNull(relation); return this.asSubtype(); } public final B timeContext(final TimeContext context) { return this.timeContext(constant(checkNotNull(context))); } public final B timeContext(final Function<? super C, ? extends TimeContext> relation) { this.timeContext = checkNotNull(relation); return this.asSubtype(); } public final B pluginConsole(final PluginConsole console) { return this.pluginConsole(constant(checkNotNull(console))); } public final B pluginConsole(final Function<? super C, ? extends PluginConsole> relation) { this.pluginConsole = checkNotNull(relation); return this.asSubtype(); } public final B diplayExecutor(final DisplayExecutor executor) { return this.diplayExecutor(constant(checkNotNull(executor))); } public final B diplayExecutor(final Function<? super C, ? extends DisplayExecutor> relation) { this.diplayExecutor = checkNotNull(relation); return this.asSubtype(); } public final B sharedExecutor(final ExecutorService executor) { return this.sharedExecutor(constant(checkNotNull(executor))); } public final B sharedExecutor(final Function<? super C, ? extends ExecutorService> relation) { this.sharedExecutor = checkNotNull(relation); return this.asSubtype(); } public final B probeMappings(final Map<String, Probe<?>> probes) { return this.probeMappings(constant(checkNotNull(probes))); } public final B probeMappings(final Function<? super C, ? extends Map<String, Probe<?>>> relation) { this.probeMappings = checkNotNull(relation); return this.asSubtype(); } public final B probeFilter(final Predicate<Entry<String, Probe<?>>> filter) { return this.probeFilter(constant(checkNotNull(filter))); } public final B probeFilter(final Function<? super C, ? extends Predicate<Entry<String, Probe<?>>>> relation) { this.probeFilter = checkNotNull(relation); return this.asSubtype(); } public final B probeExecutor(final ExecutorService executor) { return this.probeExecutor(constant(checkNotNull(executor))); } public final B probeExecutor(final Function<? super C, ? extends ExecutorService> relation) { this.probeExecutor = checkNotNull(relation); return this.asSubtype(); } public final B dataInjector(final DataInjector<? super L> injector) { return this.dataInjector(constant(checkNotNull(injector))); } public final B dataInjector(final Function<? super C, ? extends DataInjector<? super L>> relation) { this.dataInjector = checkNotNull(relation); return this.asSubtype(); } public final B eventValidator(final EventValidator<? super L> validator) { return this.eventValidator(constant(checkNotNull(validator))); } public final B eventValidator(final Function<? super C, ? extends EventValidator<? super L>> relation) { this.eventValidator = checkNotNull(relation); return this.asSubtype(); } public final B persistenceStore(final PersistenceStore<? super L> store) { return this.persistenceStore(constant(checkNotNull(store))); } public final B persistenceStore(final Function<? super C, ? extends PersistenceStore<? super L>> relation) { this.persistenceStore = checkNotNull(relation); return this.asSubtype(); } public final B sendFailureHandler(final SendFailureHandler<? super L> handler) { return this.sendFailureHandler(constant(checkNotNull(handler))); } public final B sendFailureHandler(final Function<? super C, ? extends SendFailureHandler<? super L>> relation) { this.sendFailureHandler = checkNotNull(relation); return this.asSubtype(); } public final B registerFailureHandler(final RegisterFailureHandler<? super L> handler) { return this.registerFailureHandler(constant(checkNotNull(handler))); } public final B registerFailureHandler(final Function<? super C, ? extends RegisterFailureHandler<? super L>> relation) { this.registerFailureHandler = checkNotNull(relation); return this.asSubtype(); } public final B disposalHook(final DisposalHook<? super L> hook) { return this.disposalHook(constant(checkNotNull(hook))); } public final B disposalHook(final Function<? super C, ? extends DisposalHook<? super L>> relation) { this.disposalHook = checkNotNull(relation); return this.asSubtype(); } public abstract Configuration<C, L> build(); } private static <T> Optional<T> fromNullableUnsafe(final Object object) { // although the cast itself is unsafe, this method is used only to correct the compiler // then creating an optional from a function's result which has a bounded type parameter @SuppressWarnings("unchecked") Optional<T> optional = (Optional<T>) fromNullable(object); return optional; } public final Class<? extends C> contextType() { return this.contextType; } public final Class<? extends L> listenerType() { return this.listenerType; } public final Optional<ActivityPreferences> activityPreferences(final C context) { return fromNullable((ActivityPreferences) this.activityPreferences.apply(context)); } public final Optional<ListenerPreferences> listenerPreferences(final C context) { return fromNullable((ListenerPreferences) this.listenerPreferences.apply(context)); } public final Optional<OptionsLoader<? super L>> optionsLoader(final C context) { return fromNullableUnsafe(this.optionsLoader.apply(context)); } public final Optional<TimeContext> timeContext(final C context) { return fromNullable((TimeContext) this.timeContext.apply(context)); } public final Optional<PluginConsole> pluginConsole(final C context) { return fromNullable((PluginConsole) this.pluginConsole.apply(context)); } public final Optional<DisplayExecutor> diplayExecutor(final C context) { return fromNullable((DisplayExecutor) this.diplayExecutor.apply(context)); } public final Optional<ExecutorService> sharedExecutor(final C context) { return fromNullable((ExecutorService) this.sharedExecutor.apply(context)); } public final Optional<Map<String, Probe<?>>> probeMappings(final C context) { return fromNullable((Map<String, Probe<?>>) this.probeMappings.apply(context)); } public final Optional<Predicate<Entry<String, Probe<?>>>> probeFilter(final C context) { return fromNullable((Predicate<Entry<String, Probe<?>>>) this.probeFilter.apply(context)); } public final Optional<ExecutorService> probeExecutor(final C context) { return fromNullable((ExecutorService) this.probeExecutor.apply(context)); } public final Optional<DataInjector<? super L>> dataInjector(final C context) { return fromNullableUnsafe((DataInjector<? super L>) this.dataInjector.apply(context)); } public final Optional<EventValidator<? super L>> eventValidator(final C context) { return fromNullableUnsafe((EventValidator<? super L>) this.eventValidator.apply(context)); } public final Optional<PersistenceStore<? super L>> persistenceStore(final C context) { return fromNullableUnsafe((PersistenceStore<? super L>) this.persistenceStore.apply(context)); } public final Optional<SendFailureHandler<? super L>> sendFailureHandler(final C context) { return fromNullableUnsafe((SendFailureHandler<? super L>) this.sendFailureHandler.apply(context)); } public final Optional<RegisterFailureHandler<? super L>> registerFailureHandler(final C context) { return fromNullableUnsafe((RegisterFailureHandler<? super L>) this.registerFailureHandler.apply(context)); } public final Optional<DisposalHook<? super L>> disposalHook(final C context) { return fromNullableUnsafe((DisposalHook<? super L>) this.disposalHook.apply(context)); } } public static final class RegularConfiguration<C, L extends Listener> extends AbstractConfiguration<C, L> { RegularConfiguration(final Builder<C, L> builder) { super(builder); } public static final class Builder<C, L extends Listener> extends AbstractBuilder<Builder<C, L>, C, L> { public Builder() {} @Override protected Builder<C, L> asSubtype() { return this; } @Override public RegularConfiguration<C, L> build() { return new RegularConfiguration<>(this); } } public static <C, L extends Listener> Builder<C, L> builder() { return new Builder<>(); } } /** * Returns an updating view of default options. * Updates are dependent on configured {@link OptionsLoader}. * * <p><b>Note:</b> always returns the same instance. */ protected final Options defaultOptions() { return this.optionsProvider.defaultOptions(this.optionsLoader, this.asSubtype()); } /** * Returns an updating view of custom options. * Updates are dependent on configured {@link OptionsLoader}. * * <p><b>Note:</b> always returns the same instance. * * @see #reloadOptions() */ protected final Options customOptions() { return this.optionsProvider.customOptions(this.optionsLoader, this.asSubtype()); } /** * Returns an updating view of effective options. * Updates are dependent on configured {@link OptionsLoader}. * * <p><b>Note:</b> always returns the same instance. * * @see #reloadOptions() */ protected final Options effectiveOptions() { return this.optionsProvider.effectiveOptions(this.optionsLoader, this.asSubtype()); } /** * An {@code OptionsLoader} invokes this method to reload effective options. * Always invokes {@link #onOptionsReload()} after reloading options. * * @see #onOptionsReload() */ protected final void reloadOptions() { this.reloadOptions(this.optionsProvider); } private final void reloadOptions(final OptionsProvider<L> provider) { checkNotNullAsState(this.optionsLoader, "%s: options loader not initialized", this); if (provider.reload(this.optionsLoader, this.asSubtype())) { this.onOptionsReload(); } } public interface OptionsLoader<L extends Listener> { public Options loadDefaultOptions(L listener); public Options loadCustomOptions(L listener); } public static abstract class AbstractOptionsLoader<L extends RegularListener<L>> implements OptionsLoader<L> { private final OptionsReloader reloader; protected AbstractOptionsLoader(final L listener) { this.reloader = new OptionsReloader(listener); this.hook(listener); } private void hook(final L listener) { final OptionsReloader reloader = this.reloader; RegistrationHook.PRE_REGISTER.add(listener, new NamedRunnable(OptionsReloader.class) { public void run() { defaultPreferences().node().addPreferenceChangeListener(reloader); customPreferences().node().addPreferenceChangeListener(reloader); } }); RegistrationHook.POST_UNREGISTER.add(listener, new NamedRunnable(OptionsReloader.class) { public void run() { defaultPreferences().node().removePreferenceChangeListener(reloader); customPreferences().node().removePreferenceChangeListener(reloader); } }); } private static final class OptionsReloader implements IPreferenceChangeListener { private final RegularListener<?> listener; OptionsReloader(final RegularListener<?> listener) { this.listener = checkNotNull(listener); } public void preferenceChange(final PreferenceChangeEvent event) { String key = event.getKey(); if (ActivityPreferences.Keys.listenerDefaultOptions.equals(key) || ListenerPreferences.Keys.configuration.equals(key)) { this.listener.reloadOptions(); } } } protected abstract ActivityPreferences defaultPreferences(); protected abstract ListenerPreferences customPreferences(); public final Options loadDefaultOptions(final L listener) { Options defaults = checkNotNull(this.defaultPreferences().getListenerDefaultOptions()); return compound(defaults, this.adjustDefaultOptions(listener)); } public final Options loadCustomOptions(final L listener) { Options custom = this.customPreferences().getListenerConfigurationData().get(listener.getClass()); return compound(custom != null ? custom : emptyOptions(), this.adjustCustomOptions(listener)); } protected abstract Options adjustDefaultOptions(final L listener); protected abstract Options adjustCustomOptions(final L listener); @Override public String toString() { return this.toStringHelper().toString(); } protected ToStringHelper toStringHelper() { ToStringHelper helper = MoreObjects.toStringHelper(this); helper.add("default", this.defaultPreferences()); helper.add("custom", this.customPreferences()); return helper; } } public static final class UpdatingOptionsLoader<L extends RegularListener<L>> extends AbstractOptionsLoader<L> { private final ActivityPreferences defaults; private final ListenerPreferences custom; private UpdatingOptionsLoader(final L listener, final ActivityPreferences defaults, final ListenerPreferences custom) { super(listener); this.defaults = checkNotNull(defaults); this.custom = checkNotNull(custom); } public static <L extends RegularListener<L>> UpdatingOptionsLoader<L> of(final L listener, final ActivityPreferences defaults, final ListenerPreferences custom) { return new UpdatingOptionsLoader<>(listener, defaults, custom); } @Override protected ActivityPreferences defaultPreferences() { return this.defaults; } @Override protected ListenerPreferences customPreferences() { return this.custom; } @Override protected Options adjustDefaultOptions(final L listener) { return emptyOptions(); } @Override protected Options adjustCustomOptions(final L listener) { return emptyOptions(); } } private interface OptionsProvider<L extends Listener> { public Options defaultOptions(OptionsLoader<? super L> loader, L listener); public Options customOptions(OptionsLoader<? super L> loader, L listener); public Options effectiveOptions(OptionsLoader<? super L> loader, L listener); public boolean reload(OptionsLoader<? super L> loader, L listener); } // TODO OptionsProvider must be as lazy as possible: options can not be loaded and effectively // used (but can be referenced) during listener construction since an infinite loop may occur // (defaults require listener instance and listener instance requires defaults, hence the loop) private static final class LazyOptionsProvider<L extends RegularListener<L>> implements OptionsProvider<L> { private final DefaultOptions<L> defaults; private final CustomOptions<L> custom; private final EffectiveOptions<L> effective; LazyOptionsProvider(final OptionsLoader<? super L> loader, final L listener) { this.defaults = new DefaultOptions<>(loader, listener); this.custom = new CustomOptions<>(loader, listener); this.effective = new EffectiveOptions<>(loader, listener); } public boolean reload(final OptionsLoader<? super L> loader, final L listener) { checkNotNull(loader); checkNotNull(listener); Options effective = this.effective.delegate; this.defaults.load(loader, listener); this.custom.load(loader, listener); this.effective.load(loader, listener); return effective == null || !this.effective.delegate.toMap().equals(effective.toMap()); } private static abstract class LoadingOptions<L extends RegularListener<L>> extends ForwardingOptions { private final OptionsLoader<? super L> loader; private final L listener; @Nullable Options delegate; LoadingOptions(final OptionsLoader<? super L> loader, final L listener) { this.loader = checkNotNull(loader); this.listener = checkNotNull(listener); } static final Options secure(@Nullable final Options untrusted) { return untrusted != null ? MapOptions.from(ImmutableMap.copyOf(untrusted.toMap())) : emptyOptions(); } abstract void load(OptionsLoader<? super L> loader, L listener); @Override protected final Options delegate() { if (this.delegate == null) { this.load(this.loader, this.listener); } return checkNotNullAsState(this.delegate); } @Override public final String toString() { return this.getClass().getSimpleName() + this.toMap().toString(); } } private static final class DefaultOptions<L extends RegularListener<L>> extends LoadingOptions<L> { DefaultOptions(final OptionsLoader<? super L> loader, final L listener) { super(loader, listener); } @Override void load(final OptionsLoader<? super L> loader, final L listener) { // TODO to secure defaults one needs to properly listen to UacaPreferences.getShared() changes Options defaults = ActivityPreferences.getDefault().getListenerDefaultOptions(); this.delegate = compound(defaults, secure(loader.loadDefaultOptions(listener))); } } private static final class CustomOptions<L extends RegularListener<L>> extends LoadingOptions<L> { CustomOptions(final OptionsLoader<? super L> loader, final L listener) { super(loader, listener); } @Override void load(final OptionsLoader<? super L> loader, final L listener) { this.delegate = secure(loader.loadCustomOptions(listener)); } } private static final class EffectiveOptions<L extends RegularListener<L>> extends LoadingOptions<L> { EffectiveOptions(final OptionsLoader<? super L> loader, final L listener) { super(loader, listener); } @Override void load(final OptionsLoader<? super L> loader, final L listener) { this.delegate = compound(listener.defaultOptions(), listener.customOptions()); } } public Options defaultOptions(final OptionsLoader<? super L> loader, final L listener) { return this.defaults; } public Options customOptions(final OptionsLoader<? super L> loader, final L listener) { return this.custom; } public Options effectiveOptions(final OptionsLoader<? super L> loader, final L listener) { return this.effective; } } /** * Invoked always after {@link OptionsLoader} loads any options. * * @see #reloadOptions() */ protected void onOptionsReload() {} public interface TimeContext { public long currentTime(); public TimeSource wallTimeSource(); public Stopwatch createStopwatch(); public Ticker elapsedTimeTicker(); } private enum SystemTimeContext implements TimeContext { instance; public long currentTime() { return this.wallTimeSource().read(); } public TimeSource wallTimeSource() { return TimeSource.systemTimeSource(); } public Stopwatch createStopwatch() { return Stopwatch.createUnstarted(this.elapsedTimeTicker()); } public Ticker elapsedTimeTicker() { return Ticker.systemTicker(); } @Override public String toString() { return this.getClass().getSimpleName(); } } protected final long currentTime() { return this.timeContext.currentTime(); } protected final TimeSource wallTimeSource() { return this.timeContext.wallTimeSource(); } protected final Stopwatch createStopwatch() { return this.timeContext.createStopwatch(); } protected final Ticker elapsedTimeTicker() { return this.timeContext.elapsedTimeTicker(); } @Override protected final <V> V execute(final DisplayTask<V> task) { this.runtimeStatistics.displayTaskCount.incrementAndGet(); return task.get(this.displayExecutor); } @Override protected final void execute(final Runnable command) { this.runtimeStatistics.runnableCommandCount.incrementAndGet(); this.sharedExecutor.execute(command); } public interface RegisterFailureHandler<L extends Listener> { public void preRegisterFailure(L listener, Runnable task, Exception failure); public void postRegisterFailure(L listener, Runnable task, Exception failure); public void preUnregisterFailure(L listener, Runnable task, Exception failure); public void postUnregisterFailure(L listener, Runnable task, Exception failure); } private enum PropagatingRegisterFailureHandler implements RegisterFailureHandler<Listener> { instance; public void preRegisterFailure(final Listener listener, final Runnable task, final Exception failure) { propagate(failure); } public void postRegisterFailure(final Listener listener, final Runnable task, final Exception failure) { propagate(failure); } public void preUnregisterFailure(final Listener listener, final Runnable task, final Exception failure) { propagate(failure); } public void postUnregisterFailure(final Listener listener, final Runnable task, final Exception failure) { propagate(failure); } @Override public String toString() { return this.getClass().getSimpleName(); } } @Override protected final void preRegisterFailure(final Runnable task, final Exception failure) { this.registerFailureHandler.preRegisterFailure(this.asSubtype(), task, failure); } @Override protected final void postRegisterFailure(final Runnable task, final Exception failure) { this.registerFailureHandler.postRegisterFailure(this.asSubtype(), task, failure); } @Override protected final void preUnregisterFailure(final Runnable task, final Exception failure) { this.registerFailureHandler.preUnregisterFailure(this.asSubtype(), task, failure); } @Override protected final void postUnregisterFailure(final Runnable task, final Exception failure) { this.registerFailureHandler.postUnregisterFailure(this.asSubtype(), task, failure); } public interface DataInjector<L extends Listener> { public void inject(L listener, String path, Event data); } private enum NoDataInjector implements DataInjector<Listener> { instance; public void inject(final Listener listener, final String path, final Event data) {} @Override public String toString() { return this.getClass().getSimpleName(); } } public static final class ProbingDataInjector implements DataInjector<Listener> { private final Prober<? super Event, Probe<?>> prober; private ProbingDataInjector(final Prober<? super Event, Probe<?>> prober) { this.prober = checkNotNull(prober); } public static ProbingDataInjector of(final Prober<? super Event, Probe<?>> prober) { return new ProbingDataInjector(prober); } public void inject(final Listener listener, final String path, final Event data) { this.prober.inject(data); } @Override public String toString() { return toStringDelegate(this.getClass(), this.prober); } } @Override protected final void inject(final String path, final Event data) { this.runtimeStatistics.injectCount.incrementAndGet(); final Stopwatch watch = this.createStopwatch().start(); this.dataInjector.inject(this.asSubtype(), path, data); final long delta = watch.elapsed(RuntimeStatistics.timeUnit); this.runtimeStatistics.injectTime.addAndGet(delta); } protected static abstract class ContinuousEvent<L extends ActivityListener, E> extends AbstractListener.ContinuousEvent<E> { protected final L listener; protected ContinuousEvent(final L listener, final long pause, final long window, final TimeUnit unit) { super(listener.createStopwatch(), pause, window, unit); this.listener = checkNotNull(listener); } @Override public final void push(final E event) { this.listener.execute(new Runnable() { public void run() { synchronizedPush(event); } }); } @Override public final void flush() { this.listener.execute(new Runnable() { public void run() { synchronizedFlush(); } }); } } public interface EventValidator<L extends Listener> { public void validate(L listener, String path, Event data); } private enum StandardEventValidator implements EventValidator<Listener> { instance; public void validate(final Listener listener, final String path, final Event data) { checkArgument(!isNullOrEmpty(path), "%s: path is null or empty", listener); checkArgument(data.getTimestamp() > 0L, "%s: timestamp is less than zero", listener); checkArgument(!isNullOrEmpty(data.getAction()), "%s: action is null or empty", listener); } @Override public String toString() { return this.getClass().getSimpleName(); } } @Override protected final void validate(final String path, final Event data) { this.runtimeStatistics.validateCount.incrementAndGet(); final Stopwatch watch = this.createStopwatch().start(); this.eventValidator.validate(this.asSubtype(), path, data); final long delta = watch.elapsed(RuntimeStatistics.timeUnit); this.runtimeStatistics.validateTime.addAndGet(delta); } public interface PersistenceStore<L extends Listener> extends AutoCloseable { public void persist(L listener, String path, Event data) throws Exception; } private enum VoidPersistenceStore implements PersistenceStore<Listener> { instance; public void persist(final Listener listener, final String path, final Event data) {} public void close() {} @Override public String toString() { return this.getClass().getSimpleName(); } } public static final class StoreWrapper<L extends Listener> implements PersistenceStore<L> { private final Store<? super L, ? super Event> store; private StoreWrapper(final Store<? super L, ? super Event> store) { this.store = checkNotNull(store); } public static <L extends Listener> StoreWrapper<L> of(final Store<? super L, ? super Event> store) { return new StoreWrapper<>(store); } public void persist(final L listener, final String path, final Event data) throws Exception { this.store.save(listener, path, data); } public void close() throws Exception { this.store.close(); } @Override public String toString() { return toStringDelegate(this.getClass(), this.store); } } @Override protected final void persist(final String path, final Event data) throws Exception { this.runtimeStatistics.persistCount.incrementAndGet(); final Stopwatch watch = this.createStopwatch().start(); this.persistenceStore.persist(this.asSubtype(), path, data); final long delta = watch.elapsed(RuntimeStatistics.timeUnit); this.runtimeStatistics.persistTime.addAndGet(delta); } public interface SendFailureHandler<L extends Listener> { public void handleSendFailure(L listener, String path, Event data, Exception failure); } private enum PropagatingSendFailureHandler implements SendFailureHandler<Listener> { instance; public void handleSendFailure(final Listener listener, final String path, final Event data, final Exception failure) { propagate(failure); } @Override public String toString() { return this.getClass().getSimpleName(); } } @Override protected final void sendFailure(final String path, final Event data, final Exception failure) { this.runtimeStatistics.sendFailures.incrementAndGet(); this.sendFailureHandler.handleSendFailure(this.asSubtype(), path, data, failure); } static String toStringDelegate(final Class<?> wrapper, final Object delegate) { return MoreObjects.toStringHelper(wrapper).add("delegate", delegate).toString(); } private static final class RuntimeStatistics { static final TimeUnit timeUnit = NANOSECONDS; final AtomicLong registrationCount = zero(); final AtomicLong unregistrationCount = zero(); final AtomicLong displayTaskCount = zero(); final AtomicLong runnableCommandCount = zero(); final AtomicLong sendCalls = zero(); final AtomicLong sendFailures = zero(); final AtomicLong injectCount = zero(); final AtomicLong injectTime = zero(); final AtomicLong validateCount = zero(); final AtomicLong validateTime = zero(); final AtomicLong persistCount = zero(); final AtomicLong persistTime = zero(); RuntimeStatistics() {} private static AtomicLong zero() { return new AtomicLong(); } } @Override final void preSend(final String path, final Event data) { this.runtimeStatistics.sendCalls.incrementAndGet(); } @Override final void postSend(final String path, final Event data) {} protected abstract class AbstractInstanceProbe extends InternalProbe<Content> { /** * Constructor for use by subclasses. */ protected AbstractInstanceProbe() {} public Content get() { return ObjectData.of(RegularListener.this); } } protected final class RegularInstanceProbe extends AbstractInstanceProbe { protected RegularInstanceProbe() {} } protected abstract class AbstractRegistrationProbe extends InternalProbe<Content> { /** * Constructor for use by subclasses. */ protected AbstractRegistrationProbe() {} private void put(final AnyStructuredData data, final RegistrationHook hook, final ListMultimap<RegistrationHook, Runnable> tasks) { data.put(key("hooks", hook.toString()), toObjectData(tasks.get(hook))); } public Content get() { ListMultimap<RegistrationHook, Runnable> tasks = ArrayListMultimap.create(RegularListener.this.registerHooks); AnyStructuredData data = new AnyStructuredData(); this.put(data, RegistrationHook.PRE_REGISTER, tasks); this.put(data, RegistrationHook.POST_REGISTER, tasks); this.put(data, RegistrationHook.PRE_UNREGISTER, tasks); this.put(data, RegistrationHook.POST_UNREGISTER, tasks); return data; } } protected final class RegularRegistrationProbe extends AbstractRegistrationProbe { protected RegularRegistrationProbe() {} } protected abstract class AbstractConfigurationProbe extends InternalProbe<Content> { /** * Constructor for use by subclasses. */ protected AbstractConfigurationProbe() {} public Content get() { RegularListener<?> listener = RegularListener.this; AnyStructuredData data = new AnyStructuredData(); data.put(key("optionsLoader"), ObjectData.of(listener.optionsLoader)); data.put(key("timeContext"), ObjectData.of(listener.timeContext)); data.put(key("pluginConsole"), ObjectData.of(listener.pluginConsole)); data.put(key("displayExecutor"), ObjectData.of(listener.displayExecutor)); data.put(key("sharedExecutor"), ObjectData.of(listener.sharedExecutor)); data.put(key("dataInjector"), ObjectData.of(listener.dataInjector)); data.put(key("eventValidator"), ObjectData.of(listener.eventValidator)); data.put(key("persistenceStore"), ObjectData.of(listener.persistenceStore)); data.put(key("sendFailureHandler"), ObjectData.of(listener.sendFailureHandler)); data.put(key("registerFailureHandler"), ObjectData.of(listener.registerFailureHandler)); data.put(key("disposalHook"), ObjectData.of(listener.disposalHook)); return data; } } protected final class RegularConfigurationProbe extends AbstractConfigurationProbe { protected RegularConfigurationProbe() {} } protected abstract class AbstractOptionsProbe extends InternalProbe<Content> { /** * Constructor for use by subclasses. */ protected AbstractOptionsProbe() {} public Content get() { RegularListener<?> listener = RegularListener.this; AnyStructuredData data = new AnyStructuredData(); data.put(key("effective"), transformValues(listener.effectiveOptions().toMap(), toStringFunction())); return data; } } protected final class RegularOptionsProbe extends AbstractOptionsProbe { protected RegularOptionsProbe() {} } protected abstract class AbstractStatisticsProbe extends InternalProbe<Content> { /** * Constructor for use by subclasses. */ protected AbstractStatisticsProbe() {} public Content get() { RuntimeStatistics statistics = RegularListener.this.runtimeStatistics; AnyStructuredData data = new AnyStructuredData(); data.put(key("registrationCount"), statistics.registrationCount); data.put(key("unregistrationCount"), statistics.unregistrationCount); data.put(key("displayTaskCount"), statistics.displayTaskCount); data.put(key("runnableCommandCount"), statistics.runnableCommandCount); data.put(key("sendInvocations"), statistics.sendCalls); data.put(key("sendFailures"), statistics.sendFailures); data.put(key("injectCount"), statistics.injectCount); data.put(key("injectTime"), statistics.injectTime); data.put(key("validateCount"), statistics.validateCount); data.put(key("validateTime"), statistics.validateTime); data.put(key("persistCount"), statistics.persistCount); data.put(key("persistTime"), statistics.persistTime); data.put(key("timeUnit"), TimeUnits.toString(RuntimeStatistics.timeUnit)); return data; } } protected final class RegularStatisticsProbe extends AbstractStatisticsProbe { protected RegularStatisticsProbe() {} } @Override protected Map<String, InternalProbe<?>> internalProbeMappings() { return emptyMap(); } public interface DisposalHook<L extends Listener> { public void onDispose(L listener) throws Exception; } public static abstract class AbstractDisposalHook<L extends RegularListener<L>> implements DisposalHook<L> { protected final boolean closePersistenceStore; protected final boolean shutdownSharedExecutor; protected final boolean disposeDisplayExecutor; protected final boolean closeLoggerConsole; /** * Constructor for use by subclasses. */ protected AbstractDisposalHook(final AbstractBuilder<?> builder) { this.closePersistenceStore = builder.closePersistenceStore; this.shutdownSharedExecutor = builder.shutdownSharedExecutor; this.disposeDisplayExecutor = builder.disposeDisplayExecutor; this.closeLoggerConsole = builder.closeLoggerConsole; } public static abstract class AbstractBuilder<B extends AbstractBuilder<B>> { boolean closePersistenceStore; boolean shutdownProbeExecutor; boolean shutdownSharedExecutor; boolean disposeDisplayExecutor; boolean closeLoggerConsole; /** * Constructor for use by subclasses. */ protected AbstractBuilder() {} /** * Always returns {@code this}. */ protected abstract B asSubtype(); public final B closePersistenceStore() { return this.asSubtype(); } public final B shutdownProbeExecutor() { return this.asSubtype(); } public final B shutdownSharedExecutor() { return this.asSubtype(); } public final B disposeDisplayExecutor() { return this.asSubtype(); } public final B closeLoggerConsole() { return this.asSubtype(); } public abstract <L extends RegularListener<L>> DisposalHook<L> build(); } public void onDispose(final L listener) throws Exception { this.tryToClosePersistenceStore(listener); this.tryToShutdownSharedExecutor(listener); this.tryToDisposeDisplayExecutor(listener); this.tryToCloseLoggerConsole(listener); } protected abstract void report(L listener, final Object subject, final Exception failure); protected final void tryToClosePersistenceStore(final L listener) { if (this.closePersistenceStore) { try { listener.persistenceStore.close(); } catch (Exception failure) { this.report(listener, listener.persistenceStore, failure); } } } protected final void tryToShutdownSharedExecutor(final L listener) { if (this.shutdownSharedExecutor) { try { listener.sharedExecutor.shutdown(); } catch (Exception failure) { this.report(listener, listener.sharedExecutor, failure); } } } protected final void tryToDisposeDisplayExecutor(final L listener) { if (this.disposeDisplayExecutor) { try { listener.displayExecutor.getDisplay().dispose(); } catch (Exception failure) { this.report(listener, listener.displayExecutor.getDisplay(), failure); } } } protected final void tryToCloseLoggerConsole(final L listener) { if (this.closeLoggerConsole) { try { listener.pluginConsole.close(); } catch (Exception failure) { this.report(listener, listener.pluginConsole, failure); } } } @Override public String toString() { return this.toStringHelper().toString(); } protected ToStringHelper toStringHelper() { ToStringHelper helper = MoreObjects.toStringHelper(this); helper.add("closePersistenceStore", this.closePersistenceStore); helper.add("shutdownSharedExecutor", this.shutdownSharedExecutor); helper.add("disposeDisplayExecutor", this.disposeDisplayExecutor); helper.add("closeLoggerConsole", this.closeLoggerConsole); return helper; } } private enum IgnoringDisposalHook implements DisposalHook<Listener> { instance; public void onDispose(final Listener listener) {} @Override public String toString() { return this.getClass().getSimpleName(); } } public static final class BasicDisposalHook<L extends RegularListener<L>> extends AbstractDisposalHook<L> { private final Logger logger; BasicDisposalHook(final Builder builder) { super(builder); this.logger = checkNotNull(builder.logger); } public static final class Builder extends AbstractBuilder<Builder> { Logger logger; protected Builder() {} @Override protected Builder asSubtype() { return this; } public Builder logger(final Logger logger) { this.logger = checkNotNull(logger); return this; } @Override public <L extends RegularListener<L>> BasicDisposalHook<L> build() { return new BasicDisposalHook<>(this); } } public static Builder builder() { return new Builder(); } @Override protected void report(final L listener, final Object subject, final Exception failure) { this.logger.log(Level.INFO, listener + ": Unable to dispose " + subject, failure); } } /** * {@inheritDoc} * * <p>Current implementation does nothing. */ @Override protected final void onWorkbenchShutdown() throws Exception { // unused } /** * {@inheritDoc} * * <p>Current implementation invokes configured {@link DisposalHook}. */ @Override protected final void onFinalUnregistration() throws Exception { this.disposalHook.onDispose(this.asSubtype()); } /** * Gets effective options of this configurable instance. * * <p><b>Note:</b> always returns the same instance. */ public final Options getOptions() { return this.effectiveOptions(); } /** * Gets scoped options of this configurable instance. * * <p><b>Note:</b> always returns the same instance. * * @param scope the scope to be applied, not {@code null} * * @throws IllegalArgumentException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public final Options getOptions(final Scope scope) { checkNotNull(scope); if (scope == StandardScope.DEFAULT) { return this.defaultOptions(); } else if (scope == StandardScope.CUSTOM) { return this.customOptions(); } else if (scope == StandardScope.EFFECTIVE) { return this.effectiveOptions(); } return this.getOptionsForNonStandardScope(scope); } protected Options getOptionsForNonStandardScope(final Scope scope) { throw new IllegalArgumentException(format("%s: unable to get options for %s scope", this, scope)); } public final TimeContext getTimeContext() { return this.timeContext; } }