8

I have existing codebase that sometimes uses ArrayList or LinkedList and I need to find a way to log whenever add or remove is called to track what has been either added or removed.

What is the best way to make sure I have logging in place?

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 add method to achieve this or create overriding class that implements java.util.List interface then use it instead. Either way I'm looking for a good solution that requires minimum intervention and prefrerrably without using any third party packages...

  • 1
    you mean you can't modify the code? – Nicolas Filotto Dec 20 '16 at 10:32
  • @NicolasFilotto no ideally not – Jobook Jobook Dec 20 '16 at 10:33
  • a good solution will be to rethink your original problem – AdamSkywalker Dec 20 '16 at 10:50
  • @AdamSkywalker I need ArrayList and LinkedList when I do random inserts which seems faster in those cases than ArrayList. So ideally I would fall into using decorator pattern instead I'm thinking... – Jobook Jobook Dec 20 '16 at 10:53
  • you say that LinkedList is faster on random inserts. it is generally not true. also if you have random inserts maybe you need another datastructure, like hashtable or tree – AdamSkywalker Dec 20 '16 at 10:57
  • Is this only for testing/debugging purpose or it is meant to be used in production env ? – Nicolas Filotto Dec 20 '16 at 11:10
  • @NicolasFilotto it's for prod env but I would also like to treat testing code like prod code also.... – Jobook Jobook Dec 20 '16 at 11:13
  • @NicolasFilotto I could probably use http://bytebuddy.net – Jobook Jobook Dec 20 '16 at 11:14
  • Some sort of instrumentation/aspect-oriented programming could be reasonable, but I'm not sure whether it's always trivial to set this up. In any case, this question (and the difficulty to give an answer) shows why you should basically **never** write `ArrayList list =...` but **always** write `List list = ...`. See http://stackoverflow.com/q/383947/3182664 – Marco13 Dec 20 '16 at 12:08

7 Answers7

3

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

This would be a simple example code just 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();
    }
  }
JDC
  • 4,247
  • 5
  • 31
  • 74
  • How would I use this with existing code? Do I have to change for example ```ArrayList list = new ArrayList();``` with ```LogDecorator> list = new LogDecorator>();``` ? – Jobook Jobook Dec 20 '16 at 10:51
  • also would it be better to implement List interface instead since I'm only concerned about ArrayList and LinkedList? – Jobook Jobook Dec 20 '16 at 10:58
  • Yes, if you want you could also implement the `List` interface instead. And you are right with your code, you will have to wrap it on instantiation: `List myList = new LogDecorator<>(new ArrayList());` – JDC Dec 20 '16 at 11:47
  • I'm not sure whether this proposed implementation will achieve the goal. @JobookJobook : Note that this will **not** create log messages when `addAll` is used. Although they could easily be added, you'd have to add further log messages in all mutating methods (because e.g. `retainAll` may also remove elements). In that sense, the question could be close to a duplicate of http://stackoverflow.com/q/1942144/3182664 ... – Marco13 Dec 20 '16 at 23:22
2

There is not really a simple way to get there.

Those classes are part of the "standard libraries"; so you can't change their behavior. You could create your own versions of them; and use class path ordering to get them used; but this really dirty hack.

The only other option: extend those classes; @Override the methods you want to be logged; and make sure all your sources use your own versions of those classes. Or if you prefer composition over inheritance you go for the decorator pattern; 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 manipulate things on a bytecode level. But that adds a whole new layer of "complexity" to your product; thus I am not counting it as real option.

EDIT on your answer: it seems that you don't understand the difference between interface and implementation?! An interface simply describes a set of method signatures; but in order to have real code behind those methods, there needs to 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 that real type; it is good enough to know that you can all those List methods on things. So, when you create some new implementation of the List interface ... that doesn't affect any existing

... = new ArrayList ...

declarations at all. You would have to change all assignments to

List<X> things = new YourNewListImplementation<>();
GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • Then can I just implement List interface with a new Generic class and use that instead? That way I can just use regardless whether it's ArrayList or LinkedList I guess? – Jobook Jobook Dec 20 '16 at 10:42
  • Not sure what you are asking here; but I updated my answer. Hope that helps. – GhostCat Dec 20 '16 at 11:40
2

JDC has given a good way to follow.
I would like bring important precisions.
The decorator pattern allows to create a class which decorates another class by adding or removing dynamically a new responsibility to an instance.
In your case, you want to add responsibility.

Decorator is not an intrusive pattern but the decorator class have to conform to the class that it decorates.
So in your case, having a decorator which derives from the Collection interface is not conform to the decorated object since List has methods that Collection has not.
Your need is decorating List instances, so decorator should derive from the List type.

Besides, the decorator class can do, according its needs, a processing before and or after the operation of the class that it decorates but it is also responsible to call the original operation of the decorated class.
In your case, you want to know if an element was added or in or removed from the List. To achieve it, as the method result has consequences on whether you log or not the information, it is preferable to delegate first the processing to the decorated object and then your decorator can perform its processings.
Sometimes, you don't need to decorate a method, don't do it but don't forget to delegate suitably to the decorated object.

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, a decorator is not intrusive for the decorated objects.
So the idea is not changing your code that works but add the decorating operation just after the list be instantiated.
If don't program by interface when you declare your list variables, that is you declare ArrayList list = new ArrayList() instead of List list = new ArrayList() , of course you should change the declared type to List but it doesn't break the code, on the contrary.

Here is your example code :

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

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

Now, you could do it :

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 ease the task and make it safer, you could even create a util method to apply the decoration on the list :

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

and call it like that :

List<Integer> list = new ArrayList<Integer>();
list = decorateList(list); // line added
list.add(123);
davidxxx
  • 125,838
  • 23
  • 214
  • 215
0

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

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 !

Ravindra Ranwala
  • 20,744
  • 6
  • 45
  • 63
0

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

@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'll need to configure the aspect in META-INF/aop.xml :

<aspectj>
    <aspects>
        <aspect name="com.example.ListLoggerAspect"/>
    </aspects>
</aspectj>
Tamas Rev
  • 7,008
  • 5
  • 32
  • 49
  • 1
    This solution require load-time weaving, I would only do this as a last option, as it affects every List in the JVM, and require additional configuration unless your code runs inside an application server. – Klaus Groenbaek Dec 20 '16 at 10:41
  • Alternatively, I'd apply a `Proxy` and an `InvocationHandler` manually on the `List` _instances_ I'm interested in. Basically, decorating a List is really boring, so I would avoid *that*, if possible :) – Tamas Rev Dec 20 '16 at 11:03
0

An easy way to accomplish this is wrapping your source list in a ObservableList and use that as base list. You can simply add an listener to this list to catch every modification (and log out if you wish)

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 the javafx documentation on how to add a good listener

n247s
  • 1,898
  • 1
  • 12
  • 30
0

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

  1. You can track changes, using a decorator.
  2. You can copy the collection and calculate the changes
  3. You can use aspects to 'decorate' every List in the JVM
  4. Change the existing codebase (a little bit)

1) works if you know exactly how the list is used, and once it is returned to your new code, you are the only user. So the existing code can't have any methods that add to the original list (because would invoke add/remove on the delegate instead of the decorated collection).

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

3) This solution requires some for of weaving (compile or load time) as this will create a proxy for every List, so it can add callback code around the method calls. I would not recommend this option unless you have a good understanding of how aspects work, as this solution has a rather steep learning curve, and if something goes wrong and you need to debug you code, it can be a bit tricky.

4) You say existing codebase, which leads me to believe, that you could actually change the code if you really wanted. If this is at all possible, that is the approach I would choose. If the user of the List needs to be able to track changes, then the best possible solution is that the library returns a ChangeTrackingList (interface defining methods from tracking), which you could build using decoration.

One thing you have to be aware of when decorating, is that List has a removeAll() and a addAll(), these methods may or may not call the add() and remove(), this depends on the list implementation. If you are not aware of how these methods are invoked internally you could end up seeing an object as removed twice (unless you can use a set).

Klaus Groenbaek
  • 4,820
  • 2
  • 15
  • 30