OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 28 | Go to most recent revision | Details | 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
 *
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.sql.view.search;
15
 
16
import org.openconcerto.utils.FormatGroup;
17
 
18
import java.text.Format;
19
import java.text.Normalizer;
20
import java.text.Normalizer.Form;
21
import java.text.ParseException;
22
import java.util.HashMap;
23
import java.util.List;
24
import java.util.Map;
25
import java.util.regex.Pattern;
26
 
27
/**
28
 * Search a text in an object (first in toString() then using formats).
29
 *
30
 * @author Sylvain
31
 */
32
public class TextSearchSpec implements SearchSpec {
33
 
34
    public static enum Mode {
35
        CONTAINS, CONTAINS_STRICT, LESS_THAN, EQUALS, EQUALS_STRICT, GREATER_THAN
36
    }
37
 
38
    // cannot use Collator : it doesn't works for CONTAINS
39
    static private final Pattern thoroughPattern = Pattern.compile("(\\p{Punct}|\\p{InCombiningDiacriticalMarks})+");
40
    static private final Pattern multipleSpacesPattern = Pattern.compile("\\p{Space}+");
41
 
42
    private final Mode mode;
43
    private final String filterString, normalizedFilterString;
44
    private final Map<Class<?>, FormatGroup> formats;
45
    // parsing of filterString for each format
46
    private final Map<Format, Object> parsedFilter;
47
    private Double parsedFilterD;
48
    private boolean parsedFilterD_tried = false;
49
 
50
    public TextSearchSpec(String filterString) {
51
        this(filterString, Mode.CONTAINS);
52
    }
53
 
54
    public TextSearchSpec(String filterString, final Mode mode) {
55
        this.mode = mode;
56
        this.filterString = filterString;
57
        this.normalizedFilterString = normalize(filterString);
58
        this.formats = new HashMap<Class<?>, FormatGroup>();
59
        this.parsedFilter = new HashMap<Format, Object>();
60
    }
61
 
62
    private String normalize(String s) {
63
        if (this.mode == Mode.CONTAINS_STRICT || this.mode == Mode.EQUALS_STRICT) {
64
            return s.trim();
65
        } else {
66
            final String sansAccents = thoroughPattern.matcher(Normalizer.normalize(s.trim(), Form.NFD)).replaceAll("");
67
            return multipleSpacesPattern.matcher(sansAccents).replaceAll(" ").toLowerCase();
68
        }
69
    }
70
 
71
    private final Object getParsed(final Format fmt) {
72
        Object res;
73
        if (this.parsedFilter.containsKey(fmt)) {
74
            res = this.parsedFilter.get(fmt);
75
        } else {
76
            try {
77
                res = fmt.parseObject(this.filterString);
78
                assert res != null : "Cannot tell apart parsing failed from parsed to null";
79
            } catch (ParseException e) {
80
                res = null;
81
            }
82
            this.parsedFilter.put(fmt, res);
83
        }
84
        return res;
85
    }
86
 
87
    private final Double getDouble() {
88
        if (!this.parsedFilterD_tried) {
89
            try {
90
                this.parsedFilterD = Double.valueOf(this.filterString);
91
            } catch (NumberFormatException e) {
92
                this.parsedFilterD = null;
93
            }
94
            this.parsedFilterD_tried = true;
95
        }
96
        return this.parsedFilterD;
97
    }
98
 
99
    private boolean matchWithFormats(Object cell) {
100
        if (cell == null)
101
            return false;
102
 
103
        // return now since only the toString() of strings can be sorted (it makes no sense to sort
104
        // 12/25/2010)
105
        if (cell.getClass() == String.class)
106
            return test(cell.toString());
107
 
108
        final boolean containsOrEquals = isContainsOrEquals();
109
        final boolean isContains = isContains();
110
 
111
        // first an inexpensive comparison
112
        if (containsOrEquals && containsOrEquals(cell.toString()))
113
            return true;
114
 
115
        // then try to format the cell
116
        final FormatGroup fg = getFormat(cell);
117
        if (fg != null) {
118
            final List<? extends Format> fmts = fg.getFormats();
119
            final int stop = fmts.size();
120
            for (int i = 0; i < stop; i++) {
121
                final Format fmt = fmts.get(i);
122
                // e.g. test if "2006" is contained in "25 déc. 2010"
123
                if (containsOrEquals && containsOrEquals(fmt.format(cell)))
124
                    return true;
125
                // e.g. test if "01/01/2006" is before "25 déc. 2010"
126
                else if (!isContains && test(getParsed(fmt), cell))
127
                    return true;
128
            }
129
        } else if (!isContains && cell instanceof Number) {
130
            final Number n = (Number) cell;
131
            if (test(this.getDouble(), n.doubleValue()))
132
                return true;
133
        }
134
        return false;
135
    }
136
 
137
    private boolean test(final String searched) {
138
        final String normalized = normalize(searched);
139
        if (isContains())
140
            return normalized.indexOf(this.normalizedFilterString) >= 0;
141
        else if (this.mode == Mode.EQUALS || this.mode == Mode.EQUALS_STRICT)
142
            return normalized.equals(this.normalizedFilterString);
143
        else if (this.mode == Mode.LESS_THAN)
144
            return normalized.compareTo(this.normalizedFilterString) <= 0;
145
        else if (this.mode == Mode.GREATER_THAN)
146
            return normalized.compareTo(this.normalizedFilterString) >= 0;
147
        else
148
            throw new IllegalArgumentException("unknown mode " + this.mode);
149
    }
150
 
151
    private boolean isContainsOrEquals() {
152
        // only real Strings can be sorted (it makes no sense to sort 12/25/2010)
153
        return this.mode != Mode.LESS_THAN && this.mode != Mode.GREATER_THAN;
154
    }
155
 
156
    private boolean isContains() {
157
        return this.mode == Mode.CONTAINS || this.mode == Mode.CONTAINS_STRICT;
158
    }
159
 
160
    private boolean containsOrEquals(final String searched) {
161
        // don't normalize otherwise 2005-06-20 matches 2006 :
162
        // searched is first formatted to 20/06/2005 then normalized to 20062005
163
        if (isContains()) {
164
            return searched.indexOf(this.filterString) >= 0;
165
        } else {
166
            assert this.mode == Mode.EQUALS || this.mode == Mode.EQUALS_STRICT : "Only call contains() if isContainsOrEquals()";
167
            return searched.equals(this.filterString);
168
        }
169
    }
170
 
171
    @SuppressWarnings("unchecked")
172
    private boolean test(Object search, final Object cell) {
173
        assert !(this.mode == Mode.CONTAINS || this.mode == Mode.CONTAINS_STRICT) : "Only call test() if not isContains()";
174
        if (search == null)
175
            return false;
176
        if (this.mode == Mode.EQUALS || this.mode == Mode.EQUALS_STRICT)
177
            return cell.equals(search);
178
 
179
        if (cell instanceof Comparable) {
180
            final Comparable c = (Comparable<?>) cell;
181
            if (this.mode == Mode.LESS_THAN) {
182
                return c.compareTo(search) <= 0;
183
            } else {
184
                assert this.mode == Mode.GREATER_THAN;
185
                return c.compareTo(search) >= 0;
186
            }
187
        } else {
188
            return false;
189
        }
190
    }
191
 
192
    private FormatGroup getFormat(Object cell) {
193
        final Class<?> clazz = cell.getClass();
194
        if (!this.formats.containsKey(clazz)) {
195
            // cache the findings (eg sql.Date can be formatted like util.Date)
196
            this.formats.put(clazz, findFormat(clazz));
197
        }
198
        return this.formats.get(clazz);
199
    }
200
 
201
    // find if there's a format for cell
202
    // 1st tries its class, then goes up the hierarchy
203
    private FormatGroup findFormat(final Class<?> clazz) {
204
        Class<?> c = clazz;
205
        FormatGroup res = null;
206
        while (res == null && c != Object.class) {
207
            res = this.formats.get(c);
208
            c = c.getSuperclass();
209
        }
210
        return res;
211
    }
212
 
213
    @Override
214
    public boolean match(Object line) {
215
        return this.isEmpty() || matchWithFormats(line);
216
    }
217
 
218
    @Override
219
    public String toString() {
220
        return this.getClass().getSimpleName() + ":" + this.filterString;
221
    }
222
 
223
    @Override
224
    public boolean isEmpty() {
225
        return this.filterString == null || this.filterString.length() == 0;
226
    }
227
 
228
    public void setFormats(Map<Class<?>, FormatGroup> formats) {
229
        this.formats.clear();
230
        this.formats.putAll(formats);
231
    }
232
}