OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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

Rev Author Line No. Line
67 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
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.cc;
15
 
16
import org.openconcerto.utils.CollectionUtils;
17
import org.openconcerto.utils.CompareUtils;
18
import org.openconcerto.utils.Tuple2;
19
 
177 ilm 20
import java.util.Arrays;
67 ilm 21
import java.util.Collection;
22
import java.util.Collections;
23
import java.util.HashSet;
24
import java.util.Set;
25
 
26
import net.jcip.annotations.Immutable;
27
 
28
/**
29
 * A class to specify which objects to include or exclude.
149 ilm 30
 *
67 ilm 31
 * @author Sylvain
149 ilm 32
 * @param <T> type of items
67 ilm 33
 * @see #isIncluded(Object)
34
 */
35
@Immutable
36
public class IncludeExclude<T> {
37
 
38
    static private final IncludeExclude<Object> EMPTY = new IncludeExclude<Object>(Collections.<Object> emptySet(), Collections.<Object> emptySet(), true);
39
    static private final IncludeExclude<Object> FULL = new IncludeExclude<Object>(null, Collections.<Object> emptySet(), true);
40
 
41
    @SuppressWarnings("unchecked")
42
    public static <T> IncludeExclude<T> getEmpty() {
43
        return (IncludeExclude<T>) EMPTY;
44
    }
45
 
46
    @SuppressWarnings("unchecked")
47
    public static <T> IncludeExclude<T> getFull() {
48
        return (IncludeExclude<T>) FULL;
49
    }
50
 
177 ilm 51
    @SafeVarargs
52
    public static <T> IncludeExclude<T> getNormalized(final T... includes) {
53
        return getNormalized(Arrays.asList(includes));
54
    }
55
 
149 ilm 56
    public static <T> IncludeExclude<T> getNormalized(final Collection<? extends T> includes) {
67 ilm 57
        return getNormalized(includes, Collections.<T> emptySet());
58
    }
59
 
149 ilm 60
    public static <T> IncludeExclude<T> getNormalized(final Collection<? extends T> includes, final Collection<? extends T> excludes) {
67 ilm 61
        return new IncludeExclude<T>(includes, excludes).normal;
62
    }
63
 
64
    private final Set<T> includes;
65
    private final Set<T> excludes;
66
    private final IncludeExclude<T> normal;
67
 
149 ilm 68
    public IncludeExclude(final Collection<? extends T> includes) {
67 ilm 69
        this(includes, Collections.<T> emptySet());
70
    }
71
 
72
    /**
73
     * Create a new instance.
149 ilm 74
     *
67 ilm 75
     * @param includes which objects to include, <code>null</code> meaning all.
76
     * @param excludes which objects to exclude, <code>null</code> meaning all.
77
     */
149 ilm 78
    public IncludeExclude(final Collection<? extends T> includes, final Collection<? extends T> excludes) {
67 ilm 79
        this(includes, excludes, false);
80
    }
81
 
149 ilm 82
    private IncludeExclude(final Collection<? extends T> includes, final Collection<? extends T> excludes, final boolean isNormal) {
83
        this(includes == null ? null : Collections.unmodifiableSet(new HashSet<T>(includes)), excludes == null ? null : Collections.unmodifiableSet(new HashSet<T>(excludes)), false, isNormal);
84
    }
85
 
86
    private IncludeExclude(final Set<T> includes, final Set<T> excludes, final boolean areMutable, final boolean isNormal) {
67 ilm 87
        super();
149 ilm 88
        // parameter just to overload constructor
89
        if (areMutable)
90
            throw new IllegalStateException();
91
        this.includes = includes;
92
        this.excludes = excludes;
67 ilm 93
        this.normal = isNormal ? this : this.normalize();
94
        assert this.normal.excludes.isEmpty() || this.normal.includes == null;
95
    }
96
 
97
    private final IncludeExclude<T> normalize() {
149 ilm 98
        if (this.excludes == null || (this.includes != null && this.includes.isEmpty()))
67 ilm 99
            return getEmpty();
100
        if (this.excludes.isEmpty() && this.includes == null)
101
            return getFull();
102
        if (this.excludes.isEmpty() || this.includes == null)
103
            return this;
104
        return new IncludeExclude<T>(CollectionUtils.substract(this.includes, this.excludes), Collections.<T> emptySet(), true);
105
    }
106
 
107
    public final IncludeExclude<T> getNormal() {
108
        return this.normal;
109
    }
110
 
111
    /**
112
     * Whether the passed object is included or excluded by this.
149 ilm 113
     *
67 ilm 114
     * @param s an object to test.
115
     * @return <code>true</code> if is in include and not in exclude.
116
     */
117
    public final boolean isIncluded(final T s) {
177 ilm 118
        // "if" order is irrelevant since those methods use getNormal() and it can't be both "all"
119
        // and "none".
67 ilm 120
        if (this.isAllIncluded())
121
            return true;
122
        else if (this.isNoneIncluded())
123
            return false;
124
        else
125
            return (this.normal.includes == null || this.normal.includes.contains(s)) && !this.normal.excludes.contains(s);
126
    }
127
 
149 ilm 128
    public final <S extends T> Collection<S> getExcluded(final Collection<S> items) {
129
        if (this.areNoneIncluded(items))
130
            return items;
131
        else if (this.areAllIncluded(items))
132
            return Collections.emptySet();
133
 
134
        final Set<S> res = new HashSet<>(items);
135
        // avoid checks of getIncluded()
136
        res.removeAll(this.createIncluded(items));
137
        return res;
138
    }
139
 
140
    public final <S extends T> Collection<S> getIncluded(final Collection<S> items) {
141
        if (this.areAllIncluded(items))
142
            return items;
143
        else if (this.isNoneIncluded())
144
            return Collections.emptySet();
145
        else
146
            return createIncluded(items);
147
    }
148
 
149
    private <S extends T> Collection<S> createIncluded(final Collection<S> items) {
150
        final Set<S> res = new HashSet<>(items);
151
        if (this.normal.includes != null)
152
            res.retainAll(this.normal.includes);
153
        res.removeAll(this.normal.excludes);
154
        return res;
155
    }
156
 
157
    public final boolean areAllIncluded(final Collection<? extends T> items) {
158
        if (this.isAllIncluded() || items.isEmpty())
159
            return true;
160
        else if (this.isNoneIncluded())
161
            return false;
162
        else
163
            return (this.normal.includes == null || this.normal.includes.containsAll(items)) && Collections.disjoint(this.normal.excludes, items);
164
    }
165
 
166
    public final boolean areNoneIncluded(final Collection<? extends T> items) {
167
        if (this.isNoneIncluded() || items.isEmpty())
168
            return true;
169
        else if (this.isAllIncluded())
170
            return false;
171
        else
172
            return (this.normal.includes != null && Collections.disjoint(this.normal.includes, items)) || this.normal.excludes.containsAll(items);
173
    }
174
 
67 ilm 175
    /**
176
     * Whether this includes all objects.
149 ilm 177
     *
67 ilm 178
     * @return <code>true</code> if {@link #isIncluded(Object)} always return <code>true</code>
179
     */
180
    public final boolean isAllIncluded() {
177 ilm 181
        // use normalized value to avoid ambiguous "include all" and "exclude all"
67 ilm 182
        return this.normal == FULL;
183
    }
184
 
185
    /**
186
     * Whether this includes no objects.
149 ilm 187
     *
67 ilm 188
     * @return <code>true</code> if {@link #isIncluded(Object)} always return <code>false</code>
189
     */
190
    public final boolean isNoneIncluded() {
177 ilm 191
        // use normalized value to avoid ambiguous "include all" and "exclude all"
67 ilm 192
        return this.normal == EMPTY;
193
    }
194
 
195
    /**
196
     * The one and only object included by this.
149 ilm 197
     *
67 ilm 198
     * @return <code>true</code> if {@link #isIncluded(Object)} returns <code>true</code> for one
199
     *         and only one object.
200
     */
201
    public final Tuple2<Boolean, T> getSole() {
202
        if (this.normal.includes == null) {
203
            return Tuple2.create(false, null);
204
        } else {
205
            assert this.normal.excludes.isEmpty();
206
            final boolean res = this.normal.includes.size() == 1;
207
            return Tuple2.create(res, res ? this.normal.includes.iterator().next() : null);
208
        }
209
    }
210
 
211
    /**
212
     * Return the one and only object included by this, or the passed object.
149 ilm 213
     *
67 ilm 214
     * @param ifNoSole the object to return if this doesn't include exactly one object.
215
     * @return {@link #getSole()} or <code>ifNoSole</code>.
216
     */
217
    public final T getSole(final T ifNoSole) {
218
        final Tuple2<Boolean, T> sole = this.getSole();
219
        if (sole.get0())
220
            return sole.get1();
221
        else
222
            return ifNoSole;
223
    }
224
 
177 ilm 225
    @SafeVarargs
226
    public final IncludeExclude<T> include(final T... items) {
227
        return this.include(Arrays.asList(items));
228
    }
229
 
149 ilm 230
    public final IncludeExclude<T> include(final Collection<? extends T> items) {
231
        if (this.areAllIncluded(items))
232
            return this;
233
        else if (this.excludes == null)
234
            throw new IllegalStateException("Cannot include an item when excluding all");
235
 
236
        Set<T> newIncludes;
237
        if (this.includes == null || this.includes.containsAll(items)) {
238
            newIncludes = this.includes;
239
        } else {
240
            newIncludes = new HashSet<>(this.includes);
241
            newIncludes.addAll(items);
242
            newIncludes = Collections.unmodifiableSet(newIncludes);
243
        }
244
        Set<T> newExcludes;
245
        if (Collections.disjoint(this.excludes, items)) {
246
            newExcludes = this.excludes;
247
        } else {
248
            newExcludes = new HashSet<>(this.excludes);
249
            newExcludes.removeAll(items);
250
            newExcludes = Collections.unmodifiableSet(newExcludes);
251
        }
252
 
253
        return new IncludeExclude<T>(newIncludes, newExcludes, false, false);
254
    }
255
 
177 ilm 256
    @SafeVarargs
257
    public final IncludeExclude<T> exclude(final T... items) {
258
        return this.exclude(Arrays.asList(items));
259
    }
260
 
149 ilm 261
    public final IncludeExclude<T> exclude(final Collection<? extends T> items) {
262
        if (this.areNoneIncluded(items))
263
            return this;
264
 
265
        assert this.excludes != null : "!areNoneIncluded() but this.excludes == null";
266
        Set<T> newExcludes = new HashSet<>(this.excludes);
267
        newExcludes.addAll(items);
268
        newExcludes = Collections.unmodifiableSet(newExcludes);
269
        return new IncludeExclude<T>(this.includes, Collections.unmodifiableSet(newExcludes), false, false);
270
    }
271
 
67 ilm 272
    @Override
273
    public int hashCode() {
274
        final int prime = 31;
275
        int result = 1;
149 ilm 276
        result = prime * result + ((this.excludes == null) ? 0 : this.excludes.hashCode());
277
        result = prime * result + ((this.includes == null) ? 0 : this.includes.hashCode());
67 ilm 278
        return result;
279
    }
280
 
281
    @Override
149 ilm 282
    public boolean equals(final Object obj) {
67 ilm 283
        if (this == obj)
284
            return true;
285
        if (obj == null)
286
            return false;
287
        if (getClass() != obj.getClass())
288
            return false;
289
        final IncludeExclude<?> other = (IncludeExclude<?>) obj;
290
        return CompareUtils.equals(this.includes, other.includes) && CompareUtils.equals(this.excludes, other.excludes);
291
    }
292
 
149 ilm 293
    @Override
294
    public String toString() {
295
        final String suffix;
296
        if (this == FULL)
297
            suffix = " ALL";
298
        else if (this == EMPTY)
299
            suffix = " NONE";
300
        else
301
            suffix = " includes " + this.includes + " except " + this.excludes;
302
        return this.getClass().getSimpleName() + suffix;
303
    }
67 ilm 304
}