Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 53 additions & 47 deletions src/main/java/org/jabref/gui/util/FilteredListProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,52 @@

public class FilteredListProxy {
private static final Logger LOGGER = LoggerFactory.getLogger(FilteredListProxy.class);

private FilteredListProxy() {
private static final Method BEGIN_CHANGE_METHOD;
private static final Method END_CHANGE_METHOD;
private static final Method NEXT_ADD_METHOD;
private static final Method NEXT_UPDATE_METHOD;
private static final Method NEXT_REMOVE_METHOD;
private static final Method REFILTER_METHOD;
private static final Method ENSURE_SIZE_METHOD;
private static final Method GET_PREDICATE_IMPL_METHOD;
private static final Field FILTERED_FIELD;
private static final Field SIZE_FIELD;

static {
try {
BEGIN_CHANGE_METHOD = ObservableListBase.class.getDeclaredMethod("beginChange");
END_CHANGE_METHOD = ObservableListBase.class.getDeclaredMethod("endChange");
NEXT_ADD_METHOD = ObservableListBase.class.getDeclaredMethod("nextAdd", int.class, int.class);
NEXT_UPDATE_METHOD = ObservableListBase.class.getDeclaredMethod("nextUpdate", int.class);
NEXT_REMOVE_METHOD = ObservableListBase.class.getDeclaredMethod("nextRemove", int.class, Object.class);

REFILTER_METHOD = FilteredList.class.getDeclaredMethod("refilter");
ENSURE_SIZE_METHOD = FilteredList.class.getDeclaredMethod("ensureSize", int.class);
GET_PREDICATE_IMPL_METHOD = FilteredList.class.getDeclaredMethod("getPredicateImpl");

FILTERED_FIELD = FilteredList.class.getDeclaredField("filtered");
SIZE_FIELD = FilteredList.class.getDeclaredField("size");

BEGIN_CHANGE_METHOD.setAccessible(true);
END_CHANGE_METHOD.setAccessible(true);
NEXT_ADD_METHOD.setAccessible(true);
NEXT_UPDATE_METHOD.setAccessible(true);
NEXT_REMOVE_METHOD.setAccessible(true);

REFILTER_METHOD.setAccessible(true);
ENSURE_SIZE_METHOD.setAccessible(true);
GET_PREDICATE_IMPL_METHOD.setAccessible(true);

FILTERED_FIELD.setAccessible(true);
SIZE_FIELD.setAccessible(true);
} catch (NoSuchMethodException | NoSuchFieldException e) {
throw new RuntimeException("Failed to initialize reflective methods or fields", e);
}
}

public static void refilterListReflection(FilteredList<BibEntryTableViewModel> filteredList) {
try {
Method refilter = FilteredList.class.getDeclaredMethod("refilter");
refilter.setAccessible(true);
refilter.invoke(filteredList);
REFILTER_METHOD.invoke(filteredList);
} catch (Exception e) {
LOGGER.warn("Could not refilter list", e);
}
Expand All @@ -36,19 +73,15 @@ public static void refilterListReflection(FilteredList<BibEntryTableViewModel> f
throw new IndexOutOfBoundsException();
}

invoke(filteredList, ObservableListBase.class, "beginChange");

invoke(filteredList, FilteredList.class, "ensureSize", filteredList.getSource().size());
BEGIN_CHANGE_METHOD.invoke(filteredList);
ENSURE_SIZE_METHOD.invoke(filteredList, filteredList.getSource().size());

@SuppressWarnings("unchecked")
Predicate<BibEntryTableViewModel> predicateImpl = (Predicate<BibEntryTableViewModel>) invoke(filteredList, FilteredList.class, "getPredicateImpl");
Predicate<BibEntryTableViewModel> predicateImpl = (Predicate<BibEntryTableViewModel>) GET_PREDICATE_IMPL_METHOD.invoke(filteredList);
ListIterator<? extends BibEntryTableViewModel> it = filteredList.getSource().listIterator(sourceFrom);

Field filteredField = getField("filtered");
int[] filtered = (int[]) filteredField.get(filteredList);

Field sizeField = getField("size");
int size = (int) sizeField.get(filteredList);
int[] filtered = (int[]) FILTERED_FIELD.get(filteredList);
int size = (int) SIZE_FIELD.get(filteredList);

for (int i = sourceFrom; i < sourceTo; ++i) {
BibEntryTableViewModel el = it.next();
Expand All @@ -60,54 +93,27 @@ public static void refilterListReflection(FilteredList<BibEntryTableViewModel> f
* 3. not passed before and now -> nextAdd
* 4. not passed before and not now -> do nothing */
if (passedBefore && passedNow) {
invoke(filteredList, ObservableListBase.class, "nextUpdate", pos);
NEXT_UPDATE_METHOD.invoke(filteredList, pos);
} else if (passedBefore) {
invoke(filteredList, ObservableListBase.class, "nextRemove", pos, el);
NEXT_REMOVE_METHOD.invoke(filteredList, pos, el);
System.arraycopy(filtered, pos + 1, filtered, pos, size - pos - 1);
size--;
} else if (passedNow) {
int insertionPoint = ~pos;
System.arraycopy(filtered, insertionPoint, filtered, insertionPoint + 1, size - insertionPoint);
filtered[insertionPoint] = i;
invoke(filteredList, ObservableListBase.class, "nextAdd", insertionPoint, insertionPoint + 1);
NEXT_ADD_METHOD.invoke(filteredList, insertionPoint, insertionPoint + 1);
size++;
}
}

// Write back
filteredField.set(filteredList, filtered);
sizeField.set(filteredList, size);
FILTERED_FIELD.set(filteredList, filtered);
SIZE_FIELD.set(filteredList, size);

invoke(filteredList, ObservableListBase.class, "endChange");
END_CHANGE_METHOD.invoke(filteredList);
} catch (ReflectiveOperationException e) {
LOGGER.warn("Could not refilter list", e);
}
}

/**
* We directly invoke the specified method on the given filteredList
*/
private static Object invoke(FilteredList<?> filteredList, Class<?> clazz, String methodName, Object... params) throws ReflectiveOperationException {
// Determine the parameter types for the method lookup
Class<?>[] paramTypes = new Class[params.length];
for (int i = 0; i < params.length; i++) {
paramTypes[i] = params[i].getClass();
if (paramTypes[i] == Integer.class) {
// quick hack, because int is converted to Object when calling this method.
paramTypes[i] = int.class;
}
}
Method method = clazz.getDeclaredMethod(methodName, paramTypes);
method.setAccessible(true);
return method.invoke(filteredList, params);
}

/**
* Get the class field (we need it for read and write later)
*/
private static Field getField(String fieldName) throws ReflectiveOperationException {
Field field = FilteredList.class.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
}
}