Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use child models to implement index filters #378

Merged
merged 12 commits into from
May 14, 2024
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2024 the Andlogview authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package name.mlopatkin.andlogview.utils.events;

import org.checkerframework.checker.nullness.qual.Nullable;

/**
* A combination of two observables. The observers registered with this observable will receive notifications from both.
*
* @param <T> the type of the observer
*/
public class CompoundObservable<T> implements Observable<T> {
private final Observable<? super T> first;
private final Observable<? super T> second;

public CompoundObservable(Observable<? super T> first, Observable<? super T> second) {
this.first = first;
this.second = second;
}

@Override
public void addObserver(T observer) {
first.addObserver(observer);
second.addObserver(observer);
}

@Override
public void removeObserver(@Nullable T observer) {
first.removeObserver(observer);
second.removeObserver(observer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2024 the Andlogview authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package name.mlopatkin.andlogview.utils.events;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import org.junit.jupiter.api.Test;

class CompoundObservableTest {
private final Runnable observer = mock();

private final Subject<Runnable> first = new Subject<>();
private final Subject<Runnable> second = new Subject<>();

@Test
void receivesNotificationsFromFirstSubject() {
var compound = createCompound();
compound.addObserver(observer);

notifyObservers(first);

verify(observer).run();
}

@Test
void receivesNotificationsFromSecondSubject() {
var compound = createCompound();
compound.addObserver(observer);

notifyObservers(second);

verify(observer).run();
}

@Test
void unsubscribesFromBothSubjects() {
var compound = createCompound();
compound.addObserver(observer);
compound.removeObserver(observer);

notifyObservers(first);
notifyObservers(second);

verify(observer, never()).run();
}

private void notifyObservers(Subject<Runnable> subject) {
subject.forEach(Runnable::run);
}

private CompoundObservable<Runnable> createCompound() {
return new CompoundObservable<>(first.asObservable(), second.asObservable());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2024 the Andlogview authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package name.mlopatkin.andlogview.filters;

/**
* Abstract base class to implement a filter
*
* @param <SELF> the implementation type
*/
public abstract class AbstractFilter<SELF extends AbstractFilter<SELF>> implements Filter {
protected final FilteringMode mode;
private final boolean enabled;

protected AbstractFilter(FilteringMode mode, boolean enabled) {
this.mode = mode;
this.enabled = enabled;
}

@Override
public final FilteringMode getMode() {
return mode;
}

@SuppressWarnings("unchecked")
protected final SELF self() {
return (SELF) this;
}

protected abstract SELF copy(boolean enabled);

@Override
public final boolean isEnabled() {
return enabled;
}

@Override
public final SELF enabled() {
return isEnabled() ? self() : copy(true);
}

@Override
public final SELF disabled() {
return isEnabled() ? copy(false) : self();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2024 the Andlogview authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package name.mlopatkin.andlogview.filters;

/**
* A Filter that has a submodel.
*/
public interface ChildModelFilter extends Filter {
// TODO {@link FilterModel} should know about these filters and may provide a "parent" model for it.

/**
* Returns the child model of this filter.
*
* @return the model of this filter
*/
FilterModel getFilters();
// TODO(mlopatkin) FilterModel can subscribe to child model upon adding a filter and unsubscribe afterwards.
// This way, the notifications can still be sent from the main model, so clients do not have to care about watching
// for children and subscribing themselves.
// TODO(mlopatkin) The returned model doesn't have to be mutable, but some filters may implement it as such.
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import java.awt.Color;

public interface ColoringFilter extends Filter {
public interface ColoringFilter extends PredicateFilter {
@Override
default FilteringMode getMode() {
return FilteringMode.HIGHLIGHT;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2024 the Andlogview authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package name.mlopatkin.andlogview.filters;

import name.mlopatkin.andlogview.utils.events.CompoundObservable;
import name.mlopatkin.andlogview.utils.events.Observable;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;

import java.util.Collection;

/**
* A combination of several {@link FilterModel}.
*/
public class CompoundFilterModel implements FilterModel {
private final FilterModel first;
private final FilterModel second;
private final Observable<FilterModel.Observer> observable;

public CompoundFilterModel(FilterModel first, FilterModel second) {
this.first = first;
this.second = second;
this.observable = new CompoundObservable<>(first.asObservable(), second.asObservable());
}

@Override
public Observable<Observer> asObservable() {
return observable;
}

@Override
public Collection<? extends Filter> getFilters() {
return ImmutableList.copyOf(Iterables.concat(first.getFilters(), second.getFilters()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,10 @@

package name.mlopatkin.andlogview.filters;

import name.mlopatkin.andlogview.logmodel.LogRecord;

import java.util.function.Predicate;

/**
* A filter for the log.
*/
public interface Filter extends Predicate<LogRecord> {
public interface Filter {
/**
* The filtering mode used by this filter.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
* <p/>
* The order in which filters are added to/removed from FilterChain doesn't matter.
*/
public class FilterChain implements FilterCollection<Filter> {
public class FilterChain implements FilterCollection<PredicateFilter> {
/**
* An observer to be notified when the set of filters in this chain changes.
*/
Expand All @@ -43,7 +43,7 @@ public interface Observer {
void onFiltersChanged();
}

private final SetMultimap<FilteringMode, Filter> filters =
private final SetMultimap<FilteringMode, PredicateFilter> filters =
MultimapBuilder.enumKeys(FilteringMode.class).hashSetValues().build();
private final Subject<Observer> observers = new Subject<>();

Expand All @@ -61,16 +61,16 @@ private boolean include(FilteringMode mode, LogRecord record) {
}

@Override
public @Nullable Filter transformFilter(Filter filter) {
public @Nullable PredicateFilter transformFilter(Filter filter) {
var mode = filter.getMode();
if (FilteringMode.SHOW.equals(mode) || FilteringMode.HIDE.equals(mode)) {
return filter;
return (PredicateFilter) filter;
}
return null;
}

@Override
public void addFilter(Filter filter) {
public void addFilter(PredicateFilter filter) {
if (filter.isEnabled()) {
filters.put(filter.getMode(), filter);
notifyObservers();
Expand All @@ -82,7 +82,7 @@ public boolean shouldShow(LogRecord record) {
}

@Override
public void removeFilter(Filter filter) {
public void removeFilter(PredicateFilter filter) {
if (filters.remove(filter.getMode(), filter)) {
notifyObservers();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@
*/
public interface FilterCollection<T> {
/**
* Called when a new filter is added to {@link FilterModel}, which is not filtered out.
* Called when a new filter is added to {@link MutableFilterModel}, which is not filtered out.
*
* @param filter the transformed filter to be added
*/
void addFilter(T filter);

/**
* Called when a filter was removed from {@link FilterModel}, which was not filtered out before. The supplied
* Called when a filter was removed from {@link MutableFilterModel}, which was not filtered out before. The supplied
* instance may or may not be passed to {@link #addFilter(Object)} or {@link #replaceFilter(Object, Object)}}
* before.
*
Expand All @@ -43,7 +43,7 @@ public interface FilterCollection<T> {
void removeFilter(T filter);

/**
* Called when a filter is being replaced with another in {@link FilterModel}.
* Called when a filter is being replaced with another in {@link MutableFilterModel}.
*
* @param oldFilter the transformed filter to be removed
* @param newFilter the transformed filter to be added instead
Expand All @@ -60,17 +60,19 @@ default void replaceFilter(T oldFilter, T newFilter) {
* {@link #addFilter(Object)}, {@link #removeFilter(Object)}, or {@link #replaceFilter(Object, Object)}, unless
* {@code null} is returned.
* <p>
* If this method filters one but not other argument of {@link FilterModel#replaceFilter(Filter, Filter)}, then only
* {@link #addFilter(Object)} or {@link #removeFilter(Object)} methods of this object are going to be called.
* If this method filters one but not other argument of {@link MutableFilterModel#replaceFilter(Filter, Filter)},
* then only {@link #addFilter(Object)} or {@link #removeFilter(Object)} methods of this object are going to be
* called.
*
* @param filter the filter that is being operated by the {@link FilterModel}
* @param filter the filter that is being operated by the {@link MutableFilterModel}
* @return the transformed filter or {@code null} if the implementation is not interested in this particular filter
*/
@Nullable
T transformFilter(Filter filter);

/**
* Connects this object to the {@link FilterModel}. This method should only be called once. There is no need to
* Connects this object to the {@link FilterModel}. This method should only be called once. There is no need
* to
* override the default implementation.
*
* @param model the model to connect this collection to
Expand Down
Loading
Loading