OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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