How to log a List interface method to existing code

I have an existing code base that sometimes uses an ArrayList or LinkedList, and I need to find a way to log whenever a call is added or removed to keep track of what has been added or removed.

What is the best way to make sure I have a login?

So for example.

ArrayList<Integer> list = new ArrayList<Integer>(); list.add(123); 

and

 LinkedList<Integer> anotherNewList = new LinkedList<Integer>(); anotherNewList.add(333); 

Not sure if I can intercept the add method for this or create an override class that implements the java.util.List interface and then use it. In any case, I am looking for a good solution that requires minimal intervention and in advance, without using third-party packages ...

+8
java
source share
7 answers

I would use the so-called Decorator Pattern to wrap your lists.

This will be a simple code example to give you an idea:

 private static class LogDecorator<T> implements Collection<T> { private final Collection<T> delegate; private LogDecorator(Collection<T> delegate) {this.delegate = delegate;} @Override public int size() { return delegate.size(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } @Override public boolean contains(Object o) { return delegate.contains(o); } @Override public Iterator<T> iterator() { return delegate.iterator(); } @Override public Object[] toArray() { return delegate.toArray(); } @Override public <T1> T1[] toArray(T1[] a) { return delegate.toArray(a); } @Override public boolean add(T t) { // ADD YOUR INTERCEPTING CODE HERE return delegate.add(t); } @Override public boolean remove(Object o) { return delegate.remove(o); } @Override public boolean containsAll(Collection<?> c) { return delegate.containsAll(c); } @Override public boolean addAll(Collection<? extends T> c) { return delegate.addAll(c); } @Override public boolean removeAll(Collection<?> c) { return delegate.removeAll(c); } @Override public boolean retainAll(Collection<?> c) { return delegate.retainAll(c); } @Override public void clear() { delegate.clear(); } } 
+3
source share

There really is no easy way to get there.

These classes are part of the "standard libraries"; therefore, you cannot change your behavior. You can create your own versions; and use class path ordering to use them; but this is really a dirty hack.

The only other option is to extend these classes; @ Override the methods you want to register; and make sure all your sources use your own versions of these classes. Or, if you prefer composition over inheritance, you go for a sample decorator; as suggested by JDC's answer.

The "third" option is really different - you turn to aspect-oriented programming (for example, using AspectJ) and use such tools to manage things at the bytecode level. But this adds a new β€œcomplexity” product to your product; therefore, I do not consider this a real option.

EDIT your answer: it seems that you do not understand the difference between an interface and an implementation ?! The interface simply describes a set of method signatures; but in order to have real code for these methods, there must be an implementing class. You see when you do

 List<X> things = new ArrayList<>(); 

the real type of things is ArrayList; but you rarely care about this real type; it’s enough to know that you can use all these List methods on things . So, when you create a new implementation of the List ... interface, which does not affect existing

 ... = new ArrayList ... 

generally. You will need to change all assignments

 List<X> things = new YourNewListImplementation<>(); 
+2
source share

JDC has provided a good way to keep track.
I would like to make important points.
The decorator template allows you to create a class that adorns another class by adding or removing dynamically new responsibility for the instance.
In your case, you want to add responsibility.

Decorator is not an annoying drawing, but the decorator's class must match the class that it decorates.
Thus, in your case, having a decorator that comes from the Collection interface does not correspond to the decorated object, since List has methods that Collection does not have.
Your need is to decorate List instances, so the decorator should be inferred from the List type.

In addition, the decorator class can, in accordance with its needs, process before and after the class that it decorates, but is also responsible for invoking the original operation of the decorated class.
In your case, you want to know if an item has been added or removed from the list. To achieve this, since the result of the method has consequences for whether you write information or not, it is preferable to delegate the processing to the decorated object first, and then your decorator can perform its processing.
Sometimes you do not need to decorate a method, do not do this, but do not forget to delegate the decorated object accordingly.

 import java.util.Iterator; import java.util.List; public class DecoratorList<T> implements List<T> { private static final Tracer tracer = ....; private List<T> decorated; private DecoratorList(List<T> decorated) { this.decorated=decorated; } // no decorated methods .... @Override public int size() { return this.decorated.size(); } @Override public boolean isEmpty() { return this.decorated.isEmpty(); } @Override public boolean contains(Object o) { return this.decorated.contains(o); } @Override public Iterator<T> iterator() { return this.decorated.iterator(); } .... // end no decorated methods // exemple of decorated methods @Override public void add(int index, T element) { tracer.info("element " + element + " added to index " + index); this.decorated.add(index,element); } @Override public boolean remove(Object o) { final boolean isRemoved = this.decorated.remove(o); if (isRemoved){ tracer.info("element " + o + " removed"); } return isRemoved; } } 

As explained, the decorator is not intrusive for decorated items.
Thus, the idea does not change your code, which works, but adds a decoration operation immediately after the list is instantiated.
If you do not program by interface when declaring list variables, then you declare ArrayList list = new ArrayList() instead of List list = new ArrayList() , of course, you must change the declared type to List, but it does not violate the code, vice versa.

Here is your sample code:

 ArrayList<Integer> list = new ArrayList<Integer>(); list.add(123); LinkedList<Integer> anotherNewList = new LinkedList<Integer>(); anotherNewList.add(333); 

Now you can do this:

 List<Integer> list = new ArrayList<Integer>(); list = new DecoratorList<Integer>(list); // line added list.add(123); List<Integer> anotherNewList = new LinkedList<Integer>(); anotherNewList = new DecoratorList<Integer>(anotherNewList); // line added anotherNewList.add(333); 

To make the task easier and safer, you can even create a utility method for applying the decoration to the list:

 private static <T> List<T> decorateList(List<T> list) { list = new DecoratorList<T>(list); return list; } 

and name it like this:

 List<Integer> list = new ArrayList<Integer>(); list = decorateList(list); // line added list.add(123); 
+2
source share

Your List is the source here. You need to track changes in the source. This is a good and natural example of an Observer pattern. You can create an Observable, which is your list. Then create Observers and register them in Observable. When the Observable is changed, notify all registered observers. Inside Observer, you can log changes using an input event. Here you should literally implement some ObservableCollection. You can use Java Rx to do this work. The following is sample code.

 package com.test; import java.util.ArrayList; import java.util.List; import rx.Observable; import rx.subjects.PublishSubject; public class ObservableListDemo { public static class ObservableList<T> { protected final List<T> list; protected final PublishSubject<T> onAdd; public ObservableList() { this.list = new ArrayList<T>(); this.onAdd = PublishSubject.create(); } public void add(T value) { list.add(value); onAdd.onNext(value); } public Observable<T> getObservable() { return onAdd; } } public static void main(String[] args) throws InterruptedException { ObservableList<Integer> observableList = new ObservableList<>(); observableList.getObservable().subscribe(System.out::println); observableList.add(1); Thread.sleep(1000); observableList.add(2); Thread.sleep(1000); observableList.add(3); } } 

Hope this helps. Happy coding!

0
source share

You can use Aspects - but it will log every call to add and remove :

 @Aspect public class ListLoggerAspect { @Around("execution(* java.util.List.add(..))") public boolean aroundAdd(ProceedingJoinPoint joinPoint) throws Throwable { boolean result = (boolean) joinPoint.proceed(joinPoint.getArgs()); // do the logging return result; } } 

You need to configure the aspect in META-INF / aop.xml:

 <aspectj> <aspects> <aspect name="com.example.ListLoggerAspect"/> </aspects> </aspectj> 
0
source share

An easy way to achieve this is to migrate the original list to an ObservableList and use it as a base list. You can simply add a listener to this list to catch each modification (and log out if you want)

Example:

 List obs = FXCollections.observableList(myOriginalList); obs.addListener(c -> { for(Item it : c.getRemoved()) System.out.println(it); for(Item it : c.getAddedSubList()) System.out.println(it); }); 

See javafx documentation on how to add a good listener

0
source share

We need a little more information to find the right solution. But I see several options.

  • You can track changes using the decorator.
  • You can copy the collection and calculate the changes.
  • You can use aspects to "decorate" each list in the JVM
  • Change existing codebase (a little)

1) It works if you know exactly how this list is used, and as soon as it is returned to your new code, you are the only user. Thus, the existing code cannot have any methods that are added to the original list (because it will cause the delegate to add / remove instead of the decorated collection).

2) This approach is used when several classes can modify the list. You should be able to get a copy of the list before any changes begin, and then calculate what happened next. If you have access to the Apache Collections library, you can use CollectionUtils to calculate intersection and disjunction.

3) This solution requires some weaving (compilation or loading time), as this will create a proxy for each list, so it can add a callback code around method calls. I would not recommend this option if you do not have a good understanding of how aspects work, since this solution has a pretty steep learning curve, and if something goes wrong and you need to debug the code, it can be a little complicated.

4) You say the existing code base makes me believe that you can really change the code if you want. If at all possible, this is the approach I would choose. If the list user should be able to track changes, the best solution is that the library returns a ChangeTrackingList (an interface that defines tracking methods), which you can create with the decoration.

One thing you need to know when making out is that List has removeAll () and addAll (), these methods may or may not call the add () and remove () functions, it depends on the implementation of the list. If you don't know how these methods are called internally, you can end up seeing the object as deleted twice (if you cannot use the set).

0
source share

All Articles