~ubuntu-branches/ubuntu/quantal/netbeans/quantal

« back to all changes in this revision

Viewing changes to editor/codetemplates/src/org/netbeans/lib/editor/codetemplates/CodeTemplateParameterImpl.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
6
 * The contents of this file are subject to the terms of either the GNU
 
7
 * General Public License Version 2 only ("GPL") or the Common
 
8
 * Development and Distribution License("CDDL") (collectively, the
 
9
 * "License"). You may not use this file except in compliance with the
 
10
 * License. You can obtain a copy of the License at
 
11
 * http://www.netbeans.org/cddl-gplv2.html
 
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 
13
 * specific language governing permissions and limitations under the
 
14
 * License.  When distributing the software, include this License Header
 
15
 * Notice in each file and include the License file at
 
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 
17
 * particular file as subject to the "Classpath" exception as provided
 
18
 * by Sun in the GPL Version 2 section of the License file that
 
19
 * accompanied this code. If applicable, add the following below the
 
20
 * License Header, with the fields enclosed by brackets [] replaced by
 
21
 * your own identifying information:
 
22
 * "Portions Copyrighted [year] [name of copyright owner]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
26
 * The Original Software is NetBeans. The Initial Developer of the Original
 
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 
28
 * Microsystems, Inc. All Rights Reserved.
 
29
 *
 
30
 * If you wish your version of this file to be governed by only the CDDL
 
31
 * or only the GPL Version 2, indicate your decision by adding
 
32
 * "[Contributor] elects to include this software in this distribution
 
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 
34
 * single choice of license, a recipient has the option to distribute
 
35
 * your version of this file under either the CDDL, the GPL Version 2 or
 
36
 * to extend the choice of license to its licensees as provided above.
 
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 
38
 * Version 2 license, then the option applies only if the new code is
 
39
 * made subject to such option by the copyright holder.
 
40
 */
 
41
 
 
42
package org.netbeans.lib.editor.codetemplates;
 
43
 
 
44
import java.util.ArrayList;
 
45
import java.util.Collection;
 
46
import java.util.Collections;
 
47
import java.util.LinkedHashMap;
 
48
import java.util.Iterator;
 
49
import java.util.Map;
 
50
import javax.swing.text.Position;
 
51
import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateInsertRequest;
 
52
import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateParameter;
 
53
import org.netbeans.lib.editor.util.swing.MutablePositionRegion;
 
54
 
 
55
/**
 
56
 * Implementation of the code template parameter.
 
57
 *
 
58
 * @author Miloslav Metelka
 
59
 */
 
60
public final class CodeTemplateParameterImpl {
 
61
 
 
62
    private static final String NULL_PARAMETER_NAME = "<null>"; // NOI18N
 
63
 
 
64
    private static final String NULL_HINT_NAME = "<null>"; // NOI18N
 
65
    
 
66
    private static final String TRUE_HINT_VALUE = "true"; // NOI18N
 
67
    
 
68
    /**
 
69
     * Get parameter implementation from parameter instance.
 
70
     */
 
71
    public static CodeTemplateParameterImpl get(CodeTemplateParameter parameter) {
 
72
        return CodeTemplateSpiPackageAccessor.get().getImpl(parameter);
 
73
    }
 
74
    
 
75
    /**
 
76
     * Insert handler - may be null e.g. when parsing for completion item rendering.
 
77
     */
 
78
    private final CodeTemplateInsertHandler handler;
 
79
    
 
80
    private final CodeTemplateParameter parameter;
 
81
    
 
82
    private String value;
 
83
    
 
84
    private int parametrizedTextStartOffset;
 
85
    
 
86
    private int parametrizedTextEndOffset;
 
87
    
 
88
    private CodeTemplateParameter master;
 
89
    
 
90
    private Collection<CodeTemplateParameter> slaves;
 
91
    
 
92
    private Collection<CodeTemplateParameter> slavesUnmodifiable;
 
93
    
 
94
    private String name;
 
95
    
 
96
    private Map<String, String> hints;
 
97
    
 
98
    private Map<String, String> hintsUnmodifiable;
 
99
    
 
100
    private SyncDocumentRegion region;
 
101
    
 
102
    private MutablePositionRegion positionRegion;
 
103
    
 
104
    private boolean editable;
 
105
    
 
106
    private boolean userModified;
 
107
 
 
108
 
 
109
    CodeTemplateParameterImpl(CodeTemplateInsertHandler handler,
 
110
    String parametrizedText, int parametrizedTextOffset) {
 
111
        this.handler = handler; // handler may be null for completion item parsing
 
112
        this.parametrizedTextStartOffset = parametrizedTextOffset;
 
113
        this.parameter = CodeTemplateSpiPackageAccessor.get().createParameter(this);
 
114
        parseParameterContent(parametrizedText);
 
115
    }
 
116
    
 
117
    public CodeTemplateParameter getParameter() {
 
118
        return parameter;
 
119
    }
 
120
 
 
121
    public CodeTemplateInsertHandler getHandler() {
 
122
        return handler;
 
123
    }
 
124
    
 
125
    /**
 
126
     * Get name of this parameter as parsed from the code template description's text.
 
127
     */
 
128
    public String getName() {
 
129
        return name;
 
130
    }
 
131
    
 
132
    public String getValue() {
 
133
        return isSlave() ? master.getValue()
 
134
                : ((handler != null && handler.isInserted()) ? handler.getDocParameterValue(this) : value);
 
135
    }
 
136
    
 
137
    public void setValue(String newValue, boolean fromAPI) {
 
138
        if (isSlave()) {
 
139
            throw new IllegalStateException("Cannot set value for slave parameter"); // NOI18N
 
140
        }
 
141
        if (newValue == null) {
 
142
            throw new NullPointerException("newValue cannot be null"); // NOI18N
 
143
        }
 
144
        if (!newValue.equals(value)) {
 
145
            if (fromAPI) {
 
146
                if (!handler.isReleased()) {
 
147
                    if (handler.isInserted()) { // already inserted in the document
 
148
                        handler.setDocParameterValue(this, newValue);
 
149
                    } else { // not yet inserted => set the default value
 
150
                        this.value = newValue;
 
151
                    }
 
152
                }
 
153
                
 
154
            } else { // change not from api
 
155
                this.value = newValue;
 
156
            }
 
157
            
 
158
            handler.resetCachedInsertText();
 
159
        }
 
160
    }
 
161
    
 
162
    public boolean isEditable() {
 
163
        return editable;
 
164
    }
 
165
    
 
166
    public boolean isUserModified() {
 
167
        return userModified;
 
168
    }
 
169
    
 
170
    void markUserModified() {
 
171
        this.userModified = true;
 
172
    }
 
173
    
 
174
    /**
 
175
     * @return &gt;=0 index of the '${' in the parametrized text.
 
176
     */
 
177
    public int getParametrizedTextStartOffset() {
 
178
        return parametrizedTextStartOffset;
 
179
    }
 
180
    
 
181
    /**
 
182
     * If the parameter is unclosed the offset will point past the end
 
183
     * of the parametrized text.
 
184
     *
 
185
     * @return &gt;=0 end offset of the parameter in the parametrized text
 
186
     *  pointing right after the closing '}' of the parameter.
 
187
     */
 
188
    public int getParametrizedTextEndOffset() {
 
189
        return parametrizedTextEndOffset;
 
190
    }
 
191
 
 
192
    public int getInsertTextOffset() {
 
193
        if (handler != null) {
 
194
            if (!handler.isInserted()) {
 
195
                handler.checkInsertTextBuilt();
 
196
            }
 
197
            return (positionRegion != null)
 
198
                    ? positionRegion.getStartOffset() - handler.getInsertOffset()
 
199
                    : 0;
 
200
        } else { // handler is null
 
201
            return (positionRegion != null) ? positionRegion.getStartOffset() : 0;
 
202
        }
 
203
    }
 
204
 
 
205
    void resetPositions(Position startPosition, Position endPosition) {
 
206
        if (positionRegion == null) {
 
207
            positionRegion = new MutablePositionRegion(startPosition, endPosition);
 
208
        } else {
 
209
            positionRegion.reset(startPosition, endPosition);
 
210
        }
 
211
    }
 
212
 
 
213
    public MutablePositionRegion getPositionRegion() {
 
214
        return positionRegion;
 
215
    }
 
216
 
 
217
    public Map<String, String> getHints() {
 
218
        return (hintsUnmodifiable != null) ? hintsUnmodifiable : Collections.<String, String>emptyMap();
 
219
    }
 
220
    
 
221
    public CodeTemplateParameter getMaster() {
 
222
        return master;
 
223
    }
 
224
    
 
225
    public Collection<? extends CodeTemplateParameter> getSlaves() {
 
226
        return (slaves != null) ? slaves : Collections.<CodeTemplateParameter>emptyList();
 
227
    }
 
228
    
 
229
    public boolean isSlave() {
 
230
        return (master != null);
 
231
    }
 
232
    
 
233
    SyncDocumentRegion getRegion() {
 
234
        return region;
 
235
    }
 
236
    
 
237
    void setRegion(SyncDocumentRegion region) {
 
238
        this.region = region;
 
239
    }
 
240
    
 
241
    /**
 
242
     * Mark that this parameter will be slave of the given master parameter.
 
243
     */
 
244
    void markSlave(CodeTemplateParameter master) {
 
245
        CodeTemplateParameterImpl masterImpl = paramImpl(master);
 
246
        if (getMaster() != null) {
 
247
            throw new IllegalStateException(toString() + " already slave of " + master); // NOI18N
 
248
        }
 
249
        setMaster(master);
 
250
        masterImpl.addSlave(getParameter());
 
251
        
 
252
        // reparent slaves as well
 
253
        if (slaves != null) {
 
254
            for (Iterator<CodeTemplateParameter> it = slaves.iterator(); it.hasNext();) {
 
255
                CodeTemplateParameterImpl paramImpl = paramImpl(it.next());
 
256
                paramImpl.setMaster(master);
 
257
                masterImpl.addSlave(paramImpl.getParameter());
 
258
            }
 
259
            slaves.clear();
 
260
        }
 
261
    }
 
262
    
 
263
    private static CodeTemplateParameterImpl paramImpl(CodeTemplateParameter param) {
 
264
        return CodeTemplateSpiPackageAccessor.get().getImpl(param);
 
265
    }
 
266
    
 
267
    /**
 
268
     * Initialize the hints of this parameter by parsing
 
269
     * parameter's text from the given parametrized text
 
270
     * at the offset given in the constructor.
 
271
     * 
 
272
     * @param parametrizedText text to parse at the offset given in the constructor.
 
273
     * @return index of the '}' where the parameter ends
 
274
     *  or <code>parametrizedText.length()</code> if the parameter is unclosed.
 
275
     */
 
276
    private void parseParameterContent(String parametrizedText) {
 
277
        int index = parametrizedTextStartOffset + 2;
 
278
        String hintName = null;
 
279
        String hintValue = null;
 
280
        boolean afterEquals = false;
 
281
        int nameStartIndex = -1;
 
282
        boolean insideStringLiteral = false;
 
283
        StringBuffer stringLiteralText = new StringBuffer();
 
284
 
 
285
        while (true) {
 
286
            // Search for names or "..." values separated by whitespace
 
287
            String completedString = null;
 
288
            if (index >= parametrizedText.length()) {
 
289
                break;
 
290
            }
 
291
            char ch = parametrizedText.charAt(index);
 
292
 
 
293
            if (insideStringLiteral) { // inside string constant "..."
 
294
                if (ch == '"') { // string ends
 
295
                    insideStringLiteral = false;
 
296
                    completedString = stringLiteralText.toString();
 
297
                    stringLiteralText.setLength(0); // clear the string buffer
 
298
 
 
299
                } else if (ch == '\\') {
 
300
                    index = escapedChar(parametrizedText,
 
301
                            index + 1, stringLiteralText);
 
302
                } else { // regular char
 
303
                    stringLiteralText.append(ch);
 
304
                }
 
305
 
 
306
            } else { // not string hint
 
307
                if (Character.isWhitespace(ch) || ch == '=' || ch == '}') {
 
308
                    if (nameStartIndex != -1) { // name found
 
309
                        completedString = parametrizedText.substring(
 
310
                                nameStartIndex, index);
 
311
                        nameStartIndex = -1;
 
312
                    } else {
 
313
                        // No name was accounted
 
314
                    }
 
315
 
 
316
                } else if (ch == '"') { // starting string literal
 
317
                    insideStringLiteral = true;
 
318
 
 
319
                } else { // starting or inside name
 
320
                    if (nameStartIndex == -1) {
 
321
                        nameStartIndex = index;
 
322
                    }
 
323
                }
 
324
            }
 
325
 
 
326
            if (completedString != null) {
 
327
                if (name == null) { // First string will be parameter's name
 
328
                    name = completedString;
 
329
                } else { // hints
 
330
                    if (hints == null) { // Create hints
 
331
                        hints = new LinkedHashMap<String, String>(4);
 
332
                        hintsUnmodifiable = Collections.unmodifiableMap(hints);
 
333
                    }
 
334
                    
 
335
                    if (hintName == null) { // no current hint's name
 
336
                        if (afterEquals) { // hint's value
 
337
                            // Hint name was not filled in
 
338
                            hints.put(NULL_HINT_NAME, completedString);
 
339
                            afterEquals = false;
 
340
                            // hintName stays null
 
341
                            
 
342
                        } else { // will be hint name
 
343
                            hintName = completedString;
 
344
                        }
 
345
                        
 
346
                    } else { // hint's name is non-null
 
347
                        if (afterEquals) { // hint's value
 
348
                            hints.put(hintName, completedString);
 
349
                            afterEquals = false;
 
350
                            hintName = null;
 
351
                            
 
352
                        } else { // next hint
 
353
                            hints.put(hintName, TRUE_HINT_VALUE);
 
354
                            hintName = completedString;
 
355
                        }
 
356
                    }
 
357
                }
 
358
            }
 
359
            
 
360
            if (!insideStringLiteral) {
 
361
                if (ch == '=') {
 
362
                    afterEquals = true;
 
363
                } else if (ch == '}') { // end of the parameter
 
364
                    if (hintName != null) { // true-value hint
 
365
                        hints.put(hintName, TRUE_HINT_VALUE);
 
366
                        hintName = null;
 
367
                    }
 
368
                    break;
 
369
                }
 
370
            }
 
371
            
 
372
            index++; // move to next char
 
373
        }
 
374
        
 
375
        if (name == null) {
 
376
            name = NULL_PARAMETER_NAME;
 
377
        }
 
378
        
 
379
        // Determine default parameter's value
 
380
        String defaultValue = (String)getHints().get(CodeTemplateParameter.DEFAULT_VALUE_HINT_NAME);
 
381
        if (defaultValue == null) { // implicit value will be name of the parameter
 
382
            defaultValue = name;
 
383
        }
 
384
        value = defaultValue;
 
385
        
 
386
        if (name.equals(CodeTemplateParameter.CURSOR_PARAMETER_NAME)) {
 
387
            editable = false;
 
388
            value = "";
 
389
        } else if (name.equals(CodeTemplateParameter.SELECTION_PARAMETER_NAME)) {
 
390
            editable = false;            
 
391
            if (handler != null) {
 
392
                value = handler.getComponent().getSelectedText();
 
393
                if (value == null)
 
394
                    value = ""; //NOI18N
 
395
                else if (getHints().get(CodeTemplateParameter.LINE_HINT_NAME) != null && !value.endsWith("\n")) //NOI18N
 
396
                    value += "\n"; //NOI18N
 
397
            }
 
398
        } else {
 
399
            editable = !isHintValueFalse(CodeTemplateParameter.EDITABLE_HINT_NAME);
 
400
        }
 
401
        
 
402
        parametrizedTextEndOffset = index + 1;
 
403
    }
 
404
    
 
405
    private boolean isHintValueFalse(String hintName) {
 
406
        String hintValue = (String)getHints().get(hintName);
 
407
        return (hintValue != null) && "false".equals(hintValue.toLowerCase()); // NOI18N
 
408
    }
 
409
    
 
410
    /**
 
411
     * Called after '\' was found in the text to complete the escaped
 
412
     * character and append it to the given output.
 
413
     *
 
414
     * @param text non-null text to be scanned.
 
415
     * @param index index after '\' in the text to be used for finding
 
416
     *  the target character.
 
417
     * @param output non-null output to which the resulting character should
 
418
     *  be appended.
 
419
     * @return index of the next character to read.
 
420
     */
 
421
    private int escapedChar(CharSequence text, int index, StringBuffer output) {
 
422
        if (index == text.length()) {
 
423
            output.append('\\');
 
424
        } else {
 
425
            switch (text.charAt(index++)) {
 
426
                case '\\':
 
427
                    output.append('\\');
 
428
                    break;
 
429
                case 'n':
 
430
                    output.append('\n');
 
431
                    break;
 
432
                case 'r':
 
433
                    output.append('\r');
 
434
                    break;
 
435
                case '"':
 
436
                    output.append('"');
 
437
                    break;
 
438
                case '\'':
 
439
                    output.append('\'');
 
440
                    break;
 
441
                    
 
442
                case 'u': // Unicode sequence
 
443
                    int value = 0;
 
444
                    for (int i = 0; i < 4; i++) {
 
445
                        if (index < text.length()) {
 
446
                            char ch = text.charAt(index);
 
447
                            if (ch >= '0' && ch <= '9') {
 
448
                                value = (value << 4) + (ch - '0');
 
449
                            } else if (ch >= 'a' && ch <= 'f') {
 
450
                                value = (value << 4) + 10 + (ch - 'a');
 
451
                            } else if (ch >= 'A' && ch <= 'F') {
 
452
                                value = (value << 4) + 10 + (ch - 'F');
 
453
                            } else { // invalid char
 
454
                                break;
 
455
                            }
 
456
                        }
 
457
                        index++;
 
458
                    }
 
459
                    output.append(value);
 
460
                    break;
 
461
                    
 
462
                default: // not known char => append '\'
 
463
                    index--;
 
464
                    output.append('\\');
 
465
                    break;
 
466
            }
 
467
        }
 
468
 
 
469
        return index; // index of the next read
 
470
    }
 
471
    
 
472
    private void addSlave(CodeTemplateParameter slave) {
 
473
        if (slaves == null) {
 
474
            slaves = new ArrayList<CodeTemplateParameter>(2);
 
475
            slavesUnmodifiable = Collections.unmodifiableCollection(slaves);
 
476
        }
 
477
        slaves.add(slave);
 
478
    }
 
479
    
 
480
    private void setMaster(CodeTemplateParameter master) {
 
481
        this.master = master;
 
482
    }
 
483
    
 
484
    public String toString() {
 
485
        return "name=" + getName() + ", slave=" + isSlave() // NOI18N
 
486
            + ", value=" + getValue(); // NOI18N
 
487
    }
 
488
 
 
489
}