OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 19 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
182 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
17 ilm 5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.utils.change;
15
 
19 ilm 16
import org.openconcerto.utils.cc.IClosure;
17 ilm 17
import org.openconcerto.utils.cc.ITransformer;
18
import org.openconcerto.utils.cc.Transformer;
19
 
20
import java.util.ArrayList;
21
import java.util.Collection;
22
import java.util.IdentityHashMap;
23
import java.util.List;
24
import java.util.Map;
182 ilm 25
import java.util.concurrent.CopyOnWriteArrayList;
17 ilm 26
 
182 ilm 27
import net.jcip.annotations.GuardedBy;
28
 
17 ilm 29
/**
19 ilm 30
 * Allow to propagate ListChange to listeners and bound lists. Can also store all changes that it
31
 * was notified and replay them with {@link #apply(List, ITransformer)}.
182 ilm 32
 *
17 ilm 33
 * @author Sylvain
182 ilm 34
 *
17 ilm 35
 * @param <T> type of items.
19 ilm 36
 * @see #bind(List, ITransformer)
37
 * @see #addListener(IClosure)
17 ilm 38
 */
39
public class ListChangeRecipe<T> implements ListChange<T> {
40
 
19 ilm 41
    private final List<ListChangeIndex<T>> changes;
42
    // don't use PropertyChangeSupport since it isn't type safe and we're only interested in the
43
    // last change (and not the whole property, i.e. the whole list)
44
    private final List<IClosure<? super ListChangeIndex<T>>> listeners;
182 ilm 45
    @GuardedBy("this")
19 ilm 46
    private final Map<List<?>, Pair<?>> boundLists;
17 ilm 47
 
19 ilm 48
    /**
49
     * Create a new instance. Recording is only necessary for {@link #apply(List, ITransformer)}.
182 ilm 50
     * Thread-safe only if not recording.
51
     *
19 ilm 52
     * @param record <code>true</code> if all changes should be kept (this will leak memory until
53
     *        {@link #clear()} is called).
54
     */
55
    public ListChangeRecipe(final boolean record) {
17 ilm 56
        super();
19 ilm 57
        this.changes = record ? new ArrayList<ListChangeIndex<T>>() : null;
182 ilm 58
        this.listeners = new CopyOnWriteArrayList<>();
17 ilm 59
        // need IdentityHashMap since List.equals() depend on its items
60
        // which will change
19 ilm 61
        this.boundLists = new IdentityHashMap<List<?>, Pair<?>>();
17 ilm 62
    }
63
 
19 ilm 64
    public final boolean recordChanges() {
65
        return this.changes != null;
66
    }
67
 
182 ilm 68
    /**
69
     * The list of changes since the last {@link #clear()}. Not thread-safe.
70
     *
71
     * @return the list of changes
72
     */
19 ilm 73
    public final List<ListChangeIndex<T>> getChanges() {
74
        if (!this.recordChanges())
75
            throw new IllegalStateException("This instance wasn't created to record changes");
17 ilm 76
        return this.changes;
77
    }
78
 
182 ilm 79
    public void addListener(final IClosure<? super ListChangeIndex<T>> l) {
19 ilm 80
        this.listeners.add(l);
17 ilm 81
    }
82
 
182 ilm 83
    public void rmListener(final IClosure<? super ListChangeIndex<T>> l) {
19 ilm 84
        this.listeners.remove(l);
17 ilm 85
    }
86
 
182 ilm 87
    public void bind(final List<T> l) {
17 ilm 88
        this.bind(l, Transformer.<T> nopTransformer());
89
    }
90
 
91
    /**
92
     * From now on, every change added to this will be applied immediately to <code>l</code>.
182 ilm 93
     *
17 ilm 94
     * @param <U> type of items of <code>l</code>.
95
     * @param l the list to keep in sync.
96
     * @param transf the transformer.
97
     */
182 ilm 98
    public synchronized <U> void bind(final List<U> l, final ITransformer<T, U> transf) {
19 ilm 99
        this.boundLists.put(l, new Pair<U>(l, transf));
17 ilm 100
    }
101
 
182 ilm 102
    public synchronized <U> void unbind(final List<U> l) {
19 ilm 103
        this.boundLists.remove(l);
17 ilm 104
    }
105
 
182 ilm 106
    private final void add(final ListChangeIndex<T> change) {
19 ilm 107
        if (this.recordChanges())
108
            this.changes.add(change);
17 ilm 109
        // must change bounded lists first, otherwise listeners couldn't access them
182 ilm 110
        synchronized (this) {
111
            for (final Pair<?> p : this.boundLists.values()) {
112
                p.apply(change);
113
            }
114
            for (final IClosure<? super ListChangeIndex<T>> l : this.listeners)
115
                l.executeChecked(change);
17 ilm 116
        }
117
    }
118
 
182 ilm 119
    public void add(final int index0, final Collection<? extends T> c) {
17 ilm 120
        this.add(new ListChangeIndex.Add<T>(index0, c));
121
    }
122
 
182 ilm 123
    public void remove(final int index0, final int index1, final List<T> removed) {
17 ilm 124
        this.add(new ListChangeIndex.Rm<T>(index0, index1, removed));
125
    }
126
 
182 ilm 127
    public void set(final int index0, final T old, final T newItem) {
17 ilm 128
        this.add(new ListChangeIndex.Set<T>(index0, old, newItem));
129
    }
130
 
19 ilm 131
    /**
132
     * Clear all recorded changes. In general should be called after
133
     * {@link #apply(List, ITransformer)}.
134
     */
17 ilm 135
    public final void clear() {
19 ilm 136
        this.getChanges().clear();
17 ilm 137
    }
138
 
19 ilm 139
    /**
140
     * Apply all changes since the last {@link #clear()}.
182 ilm 141
     *
19 ilm 142
     * @param <U> type of list
143
     * @param l the list to change.
144
     * @param transf transform items between this and <code>l</code>.
145
     * @throws IllegalStateException if this instance doesn't {@link #recordChanges() record
146
     *         changes}.
147
     */
148
    @Override
182 ilm 149
    public <U> void apply(final List<U> l, final ITransformer<T, U> transf) throws IllegalStateException {
19 ilm 150
        for (final ListChange<T> change : this.getChanges()) {
17 ilm 151
            change.apply(l, transf);
152
        }
153
    }
154
 
155
    private final class Pair<U> {
156
 
157
        private final List<U> l;
158
        private final ITransformer<T, U> transf;
159
 
182 ilm 160
        public Pair(final List<U> l, final ITransformer<T, U> transf) {
17 ilm 161
            super();
162
            this.l = l;
163
            this.transf = transf;
164
        }
165
 
182 ilm 166
        void apply(final ListChange<T> change) {
17 ilm 167
            change.apply(this.l, this.transf);
168
        }
169
    }
170
}