2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
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]"
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.
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.
42
package org.netbeans.lib.editor.codetemplates;
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;
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;
56
* Implementation of the code template parameter.
58
* @author Miloslav Metelka
60
public final class CodeTemplateParameterImpl {
62
private static final String NULL_PARAMETER_NAME = "<null>"; // NOI18N
64
private static final String NULL_HINT_NAME = "<null>"; // NOI18N
66
private static final String TRUE_HINT_VALUE = "true"; // NOI18N
69
* Get parameter implementation from parameter instance.
71
public static CodeTemplateParameterImpl get(CodeTemplateParameter parameter) {
72
return CodeTemplateSpiPackageAccessor.get().getImpl(parameter);
76
* Insert handler - may be null e.g. when parsing for completion item rendering.
78
private final CodeTemplateInsertHandler handler;
80
private final CodeTemplateParameter parameter;
84
private int parametrizedTextStartOffset;
86
private int parametrizedTextEndOffset;
88
private CodeTemplateParameter master;
90
private Collection<CodeTemplateParameter> slaves;
92
private Collection<CodeTemplateParameter> slavesUnmodifiable;
96
private Map<String, String> hints;
98
private Map<String, String> hintsUnmodifiable;
100
private SyncDocumentRegion region;
102
private MutablePositionRegion positionRegion;
104
private boolean editable;
106
private boolean userModified;
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);
117
public CodeTemplateParameter getParameter() {
121
public CodeTemplateInsertHandler getHandler() {
126
* Get name of this parameter as parsed from the code template description's text.
128
public String getName() {
132
public String getValue() {
133
return isSlave() ? master.getValue()
134
: ((handler != null && handler.isInserted()) ? handler.getDocParameterValue(this) : value);
137
public void setValue(String newValue, boolean fromAPI) {
139
throw new IllegalStateException("Cannot set value for slave parameter"); // NOI18N
141
if (newValue == null) {
142
throw new NullPointerException("newValue cannot be null"); // NOI18N
144
if (!newValue.equals(value)) {
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;
154
} else { // change not from api
155
this.value = newValue;
158
handler.resetCachedInsertText();
162
public boolean isEditable() {
166
public boolean isUserModified() {
170
void markUserModified() {
171
this.userModified = true;
175
* @return >=0 index of the '${' in the parametrized text.
177
public int getParametrizedTextStartOffset() {
178
return parametrizedTextStartOffset;
182
* If the parameter is unclosed the offset will point past the end
183
* of the parametrized text.
185
* @return >=0 end offset of the parameter in the parametrized text
186
* pointing right after the closing '}' of the parameter.
188
public int getParametrizedTextEndOffset() {
189
return parametrizedTextEndOffset;
192
public int getInsertTextOffset() {
193
if (handler != null) {
194
if (!handler.isInserted()) {
195
handler.checkInsertTextBuilt();
197
return (positionRegion != null)
198
? positionRegion.getStartOffset() - handler.getInsertOffset()
200
} else { // handler is null
201
return (positionRegion != null) ? positionRegion.getStartOffset() : 0;
205
void resetPositions(Position startPosition, Position endPosition) {
206
if (positionRegion == null) {
207
positionRegion = new MutablePositionRegion(startPosition, endPosition);
209
positionRegion.reset(startPosition, endPosition);
213
public MutablePositionRegion getPositionRegion() {
214
return positionRegion;
217
public Map<String, String> getHints() {
218
return (hintsUnmodifiable != null) ? hintsUnmodifiable : Collections.<String, String>emptyMap();
221
public CodeTemplateParameter getMaster() {
225
public Collection<? extends CodeTemplateParameter> getSlaves() {
226
return (slaves != null) ? slaves : Collections.<CodeTemplateParameter>emptyList();
229
public boolean isSlave() {
230
return (master != null);
233
SyncDocumentRegion getRegion() {
237
void setRegion(SyncDocumentRegion region) {
238
this.region = region;
242
* Mark that this parameter will be slave of the given master parameter.
244
void markSlave(CodeTemplateParameter master) {
245
CodeTemplateParameterImpl masterImpl = paramImpl(master);
246
if (getMaster() != null) {
247
throw new IllegalStateException(toString() + " already slave of " + master); // NOI18N
250
masterImpl.addSlave(getParameter());
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());
263
private static CodeTemplateParameterImpl paramImpl(CodeTemplateParameter param) {
264
return CodeTemplateSpiPackageAccessor.get().getImpl(param);
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.
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.
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();
286
// Search for names or "..." values separated by whitespace
287
String completedString = null;
288
if (index >= parametrizedText.length()) {
291
char ch = parametrizedText.charAt(index);
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
299
} else if (ch == '\\') {
300
index = escapedChar(parametrizedText,
301
index + 1, stringLiteralText);
302
} else { // regular char
303
stringLiteralText.append(ch);
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);
313
// No name was accounted
316
} else if (ch == '"') { // starting string literal
317
insideStringLiteral = true;
319
} else { // starting or inside name
320
if (nameStartIndex == -1) {
321
nameStartIndex = index;
326
if (completedString != null) {
327
if (name == null) { // First string will be parameter's name
328
name = completedString;
330
if (hints == null) { // Create hints
331
hints = new LinkedHashMap<String, String>(4);
332
hintsUnmodifiable = Collections.unmodifiableMap(hints);
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);
340
// hintName stays null
342
} else { // will be hint name
343
hintName = completedString;
346
} else { // hint's name is non-null
347
if (afterEquals) { // hint's value
348
hints.put(hintName, completedString);
352
} else { // next hint
353
hints.put(hintName, TRUE_HINT_VALUE);
354
hintName = completedString;
360
if (!insideStringLiteral) {
363
} else if (ch == '}') { // end of the parameter
364
if (hintName != null) { // true-value hint
365
hints.put(hintName, TRUE_HINT_VALUE);
372
index++; // move to next char
376
name = NULL_PARAMETER_NAME;
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
384
value = defaultValue;
386
if (name.equals(CodeTemplateParameter.CURSOR_PARAMETER_NAME)) {
389
} else if (name.equals(CodeTemplateParameter.SELECTION_PARAMETER_NAME)) {
391
if (handler != null) {
392
value = handler.getComponent().getSelectedText();
395
else if (getHints().get(CodeTemplateParameter.LINE_HINT_NAME) != null && !value.endsWith("\n")) //NOI18N
396
value += "\n"; //NOI18N
399
editable = !isHintValueFalse(CodeTemplateParameter.EDITABLE_HINT_NAME);
402
parametrizedTextEndOffset = index + 1;
405
private boolean isHintValueFalse(String hintName) {
406
String hintValue = (String)getHints().get(hintName);
407
return (hintValue != null) && "false".equals(hintValue.toLowerCase()); // NOI18N
411
* Called after '\' was found in the text to complete the escaped
412
* character and append it to the given output.
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
419
* @return index of the next character to read.
421
private int escapedChar(CharSequence text, int index, StringBuffer output) {
422
if (index == text.length()) {
425
switch (text.charAt(index++)) {
442
case 'u': // Unicode sequence
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
459
output.append(value);
462
default: // not known char => append '\'
469
return index; // index of the next read
472
private void addSlave(CodeTemplateParameter slave) {
473
if (slaves == null) {
474
slaves = new ArrayList<CodeTemplateParameter>(2);
475
slavesUnmodifiable = Collections.unmodifiableCollection(slaves);
480
private void setMaster(CodeTemplateParameter master) {
481
this.master = master;
484
public String toString() {
485
return "name=" + getName() + ", slave=" + isSlave() // NOI18N
486
+ ", value=" + getValue(); // NOI18N