OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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