OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 67 | 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
 *
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.model;
15
 
16
import org.openconcerto.utils.CollectionUtils;
17
import org.openconcerto.utils.cc.ITransformer;
18
 
19
import java.util.ArrayList;
20
import java.util.Arrays;
21
import java.util.Collections;
22
import java.util.List;
23
import java.util.regex.Matcher;
24
import java.util.regex.Pattern;
25
 
83 ilm 26
import net.jcip.annotations.Immutable;
27
 
17 ilm 28
/**
29
 * A dotted SQL name, eg "table.field" or "schema.table".
30
 *
31
 * @author Sylvain
32
 */
83 ilm 33
@Immutable
17 ilm 34
public final class SQLName {
35
 
36
    private static final Pattern unquoted = Pattern.compile("\\w+");
83 ilm 37
    private static final Pattern MS_END_QUOTE = Pattern.compile("]", Pattern.LITERAL);
17 ilm 38
 
39
    /**
40
     * Parse a possibly quoted string to an SQL name.
41
     *
42
     * @param name a String, eg public."ta.ble seq".
43
     * @return the corresponding SQL name, eg "public"."ta.ble seq".
44
     */
45
    public static SQLName parse(String name) {
83 ilm 46
        return parse(name, '"', '"');
47
    }
48
 
49
    public static SQLName parseMS(String name) {
50
        // lucky for us, the rules are the same as for standard SQL
51
        return parse(name, '[', ']');
52
    }
53
 
54
    private static SQLName parse(String name, final char startQuote, final char endQuote) {
17 ilm 55
        name = name.trim();
56
        final List<String> res = new ArrayList<String>();
57
        int index = 0;
58
        while (index < name.length()) {
59
            final char c = name.charAt(index);
83 ilm 60
            final boolean inQuote = c == startQuote;
17 ilm 61
            if (inQuote) {
62
                // pass the opening quote
63
                index += 1;
83 ilm 64
                int index2 = findNextQuote(name, index, endQuote);
17 ilm 65
                // handle escaped "
66
                String part = "";
67
                // while the char after " is also "
83 ilm 68
                while ((index2 + 1) < name.length() && name.charAt(index2 + 1) == endQuote) {
17 ilm 69
                    // index2+1 to keep the first quote
70
                    part += name.substring(index, index2 + 1);
71
                    // pass ""
72
                    index = index2 + 2;
83 ilm 73
                    index2 = findNextQuote(name, index, endQuote);
17 ilm 74
                }
75
                part += name.substring(index, index2);
76
                res.add(part);
77
                // pass the closing quote
78
                index = index2 + 1;
79
            } else {
80
                final Matcher matcher = unquoted.matcher(name);
81
                if (!matcher.find(index))
82
                    throw new IllegalArgumentException("illegal unquoted name at " + index);
83
                final int index2 = matcher.end();
84
                res.add(name.substring(index, index2));
85
                index = index2;
86
            }
87
            if (index != name.length()) {
88
                if (name.charAt(index) != '.')
89
                    throw new IllegalArgumentException("no dot at " + index);
90
                if (index == name.length() - 1)
91
                    throw new IllegalArgumentException("trailing dot");
92
                // pass the dot
93
                index += 1;
94
            }
95
        }
96
 
97
        return new SQLName(res);
98
    }
99
 
83 ilm 100
    private static int findNextQuote(final String name, final int index, final char c) {
101
        final int res = name.indexOf(c, index);
17 ilm 102
        if (res < 0)
103
            throw new IllegalArgumentException("no corresponding quote " + index);
104
        return res;
105
    }
106
 
83 ilm 107
    private final List<String> items;
17 ilm 108
 
109
    public SQLName(String... items) {
110
        this(Arrays.asList(items));
111
    }
112
 
113
    /**
67 ilm 114
     * Create a new instance, ignoring null and empty items. Ignore <code>null</code> for systems
115
     * missing some JDBC level (e.g. MySQL has no schema), ignore "" for systems with private base
116
     * (e.g. in H2 the JDBC name is "", but cannot be used in SQL queries).
17 ilm 117
     *
118
     * @param items the names.
119
     */
120
    public SQLName(List<String> items) {
83 ilm 121
        this(items, false);
122
    }
123
 
124
    private SQLName(List<String> items, final boolean safe) {
17 ilm 125
        super();
83 ilm 126
        if (safe) {
127
            this.items = items;
128
        } else {
129
            final List<String> tmp = new ArrayList<String>(items.size());
130
            for (final String item : items) {
131
                if (item != null && item.length() > 0)
132
                    tmp.add(item);
133
            }
134
            this.items = Collections.unmodifiableList(tmp);
17 ilm 135
        }
136
    }
137
 
138
    /**
139
     * Return the quoted form, eg for table.field : "table"."field".
140
     *
141
     * @return the quoted form of this name.
142
     */
143
    public String quote() {
144
        return CollectionUtils.join(this.items, ".", new ITransformer<String, String>() {
145
            public String transformChecked(String input) {
146
                return SQLBase.quoteIdentifier(input);
147
            }
148
        });
149
    }
150
 
83 ilm 151
    public String quoteMS() {
152
        return CollectionUtils.join(this.items, ".", new ITransformer<String, String>() {
153
            public String transformChecked(String input) {
154
                return '[' + MS_END_QUOTE.matcher(input).replaceAll("]]") + ']';
155
            }
156
        });
157
    }
158
 
17 ilm 159
    /**
160
     * Return the item at the given index. You can use negatives to count backwards (ie -1 is the
161
     * last item).
162
     *
163
     * @param index an int between 0 and count - 1, or between -count and -1.
164
     * @return the corresponding item.
165
     */
166
    public String getItem(int index) {
167
        if (index < 0)
168
            index = this.getItemCount() + index;
169
        return this.items.get(index);
170
    }
171
 
172
    /**
173
     * Same as getItem, but will return <code>null</code> if index is out of bound.
174
     *
175
     * @param index an int.
176
     * @return the corresponding item, or <code>null</code>.
177
     */
178
    public String getItemLenient(int index) {
179
        try {
180
            return this.getItem(index);
181
        } catch (IndexOutOfBoundsException e) {
182
            return null;
183
        }
184
    }
185
 
186
    public int getItemCount() {
187
        return this.items.size();
188
    }
189
 
190
    /**
191
     * The last part, eg "field" for table.field.
192
     *
193
     * @return the name.
194
     */
195
    public String getName() {
196
        return this.getItem(this.items.size() - 1);
197
    }
198
 
199
    public String getFirst() {
200
        return this.getItem(0);
201
    }
202
 
203
    public SQLName getRest() {
83 ilm 204
        return new SQLName(this.items.subList(1, this.items.size()), true);
17 ilm 205
    }
206
 
67 ilm 207
    /**
208
     * Resolve the passed name from this.
209
     *
210
     * @param to the name to resolve, e.g. "t2".
211
     * @return the resolved name, e.g. if this is "root"."t1", "root"."t2".
212
     */
213
    public final SQLName resolve(final SQLName to) {
214
        final SQLName from = this;
215
        final int fromCount = from.getItemCount();
216
        final int toCount = to.getItemCount();
217
        if (fromCount <= toCount) {
218
            return to;
219
        } else {
220
            final List<String> l = new ArrayList<String>(fromCount);
221
            l.addAll(from.asList().subList(0, fromCount - toCount));
222
            l.addAll(to.asList());
83 ilm 223
            return new SQLName(Collections.unmodifiableList(l), true);
67 ilm 224
        }
225
    }
226
 
227
    /**
228
     * The shortest SQLName to identify <code>to</code> from this.
229
     *
230
     * @param to the name to shorten, e.g. "root"."t2".
231
     * @return the shortest name identifying <code>to</code>, e.g. if this is "root"."t1", "t2".
232
     */
233
    public final SQLName getContextualName(final SQLName to) {
234
        final SQLName from = this;
235
        final int fromCount = from.getItemCount();
236
        final int toCount = to.getItemCount();
237
        if (fromCount < toCount) {
238
            return to;
239
        } else if (fromCount > toCount) {
240
            final SQLName resolved = from.resolve(to);
241
            assert resolved.getItemCount() == fromCount;
242
            return from.getContextualName(resolved);
243
        }
244
        assert fromCount == toCount;
245
        final int common = CollectionUtils.equalsFromStart(from.asList(), to.asList());
246
        if (common == 0) {
247
            return to;
248
        } else {
83 ilm 249
            return new SQLName(to.asList().subList(common, toCount), true);
67 ilm 250
        }
251
    }
252
 
17 ilm 253
    public final List<String> asList() {
83 ilm 254
        return this.items;
17 ilm 255
    }
256
 
257
    @Override
258
    public boolean equals(Object obj) {
259
        if (obj instanceof SQLName) {
260
            final SQLName o = (SQLName) obj;
261
            return this.items.equals(o.items);
262
        } else
263
            return false;
264
    }
265
 
266
    @Override
267
    public int hashCode() {
268
        return this.items.hashCode();
269
    }
270
 
271
    public String toString() {
272
        return this.quote();
273
    }
274
}