Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.library / org.gvsig.utils / src / main / java / org / gvsig / utils / console / jedit / TokenMarker.java @ 40561

History | View | Annotate | Download (9.89 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24
package org.gvsig.utils.console.jedit;
25
/*
26
 * TokenMarker.java - Generic token marker
27
 * Copyright (C) 1998, 1999 Slava Pestov
28
 *
29
 * You may use and modify this package for any purpose. Redistribution is
30
 * permitted, in both source and binary form, provided that this notice
31
 * remains intact in all source distributions of this package.
32
 */
33

    
34
import javax.swing.text.Segment;
35

    
36
/**
37
 * A token marker that splits lines of text into tokens. Each token carries
38
 * a length field and an indentification tag that can be mapped to a color
39
 * for painting that token.<p>
40
 *
41
 * For performance reasons, the linked list of tokens is reused after each
42
 * line is tokenized. Therefore, the return value of <code>markTokens</code>
43
 * should only be used for immediate painting. Notably, it cannot be
44
 * cached.
45
 *
46
 * @author Slava Pestov
47
 * @version $Id$
48
 *
49
 * @see org.gjt.sp.jedit.syntax.Token
50
 */
51
public abstract class TokenMarker
52
{
53
        /**
54
         * A wrapper for the lower-level <code>markTokensImpl</code> method
55
         * that is called to split a line up into tokens.
56
         * @param line The line
57
         * @param lineIndex The line number
58
         */
59
        public Token markTokens(Segment line, int lineIndex)
60
        {
61
                if(lineIndex >= length)
62
                {
63
                        throw new IllegalArgumentException("Tokenizing invalid line: "
64
                                + lineIndex);
65
                }
66

    
67
                lastToken = null;
68

    
69
                LineInfo info = lineInfo[lineIndex];
70
                LineInfo prev;
71
                if(lineIndex == 0)
72
                        prev = null;
73
                else
74
                        prev = lineInfo[lineIndex - 1];
75

    
76
                byte oldToken = info.token;
77
                byte token = markTokensImpl(prev == null ?
78
                        Token.NULL : prev.token,line,lineIndex);
79

    
80
                info.token = token;
81

    
82
                /*
83
                 * This is a foul hack. It stops nextLineRequested
84
                 * from being cleared if the same line is marked twice.
85
                 *
86
                 * Why is this necessary? It's all JEditTextArea's fault.
87
                 * When something is inserted into the text, firing a
88
                 * document event, the insertUpdate() method shifts the
89
                 * caret (if necessary) by the amount inserted.
90
                 *
91
                 * All caret movement is handled by the select() method,
92
                 * which eventually pipes the new position to scrollTo()
93
                 * and calls repaint().
94
                 *
95
                 * Note that at this point in time, the new line hasn't
96
                 * yet been painted; the caret is moved first.
97
                 *
98
                 * scrollTo() calls offsetToX(), which tokenizes the line
99
                 * unless it is being called on the last line painted
100
                 * (in which case it uses the text area's painter cached
101
                 * token list). What scrollTo() does next is irrelevant.
102
                 *
103
                 * After scrollTo() has done it's job, repaint() is
104
                 * called, and eventually we end up in paintLine(), whose
105
                 * job is to paint the changed line. It, too, calls
106
                 * markTokens().
107
                 *
108
                 * The problem was that if the line started a multiline
109
                 * token, the first markTokens() (done in offsetToX())
110
                 * would set nextLineRequested (because the line end
111
                 * token had changed) but the second would clear it
112
                 * (because the line was the same that time) and therefore
113
                 * paintLine() would never know that it needed to repaint
114
                 * subsequent lines.
115
                 *
116
                 * This bug took me ages to track down, that's why I wrote
117
                 * all the relevant info down so that others wouldn't
118
                 * duplicate it.
119
                 */
120
                 if(!(lastLine == lineIndex && nextLineRequested))
121
                        nextLineRequested = (oldToken != token);
122

    
123
                lastLine = lineIndex;
124

    
125
                addToken(0,Token.END);
126

    
127
                return firstToken;
128
        }
129

    
130
        /**
131
         * An abstract method that splits a line up into tokens. It
132
         * should parse the line, and call <code>addToken()</code> to
133
         * add syntax tokens to the token list. Then, it should return
134
         * the initial token type for the next line.<p>
135
         *
136
         * For example if the current line contains the start of a 
137
         * multiline comment that doesn't end on that line, this method
138
         * should return the comment token type so that it continues on
139
         * the next line.
140
         *
141
         * @param token The initial token type for this line
142
         * @param line The line to be tokenized
143
         * @param lineIndex The index of the line in the document,
144
         * starting at 0
145
         * @return The initial token type for the next line
146
         */
147
        protected abstract byte markTokensImpl(byte token, Segment line,
148
                int lineIndex);
149

    
150
        /**
151
         * Returns if the token marker supports tokens that span multiple
152
         * lines. If this is true, the object using this token marker is
153
         * required to pass all lines in the document to the
154
         * <code>markTokens()</code> method (in turn).<p>
155
         *
156
         * The default implementation returns true; it should be overridden
157
         * to return false on simpler token markers for increased speed.
158
         */
159
        public boolean supportsMultilineTokens()
160
        {
161
                return true;
162
        }
163

    
164
        /**
165
         * Informs the token marker that lines have been inserted into
166
         * the document. This inserts a gap in the <code>lineInfo</code>
167
         * array.
168
         * @param index The first line number
169
         * @param lines The number of lines 
170
         */
171
        public void insertLines(int index, int lines)
172
        {
173
                if(lines <= 0)
174
                        return;
175
                length += lines;
176
                ensureCapacity(length);
177
                int len = index + lines;
178
                System.arraycopy(lineInfo,index,lineInfo,len,
179
                        lineInfo.length - len);
180

    
181
                for(int i = index + lines - 1; i >= index; i--)
182
                {
183
                        lineInfo[i] = new LineInfo();
184
                }
185
        }
186
        
187
        /**
188
         * Informs the token marker that line have been deleted from
189
         * the document. This removes the lines in question from the
190
         * <code>lineInfo</code> array.
191
         * @param index The first line number
192
         * @param lines The number of lines
193
         */
194
        public void deleteLines(int index, int lines)
195
        {
196
                if (lines <= 0)
197
                        return;
198
                int len = index + lines;
199
                length -= lines;
200
                System.arraycopy(lineInfo,len,lineInfo,
201
                        index,lineInfo.length - len);
202
        }
203

    
204
        /**
205
         * Returns the number of lines in this token marker.
206
         */
207
        public int getLineCount()
208
        {
209
                return length;
210
        }
211

    
212
        /**
213
         * Returns true if the next line should be repainted. This
214
         * will return true after a line has been tokenized that starts
215
         * a multiline token that continues onto the next line.
216
         */
217
        public boolean isNextLineRequested()
218
        {
219
                return nextLineRequested;
220
        }
221

    
222
        // protected members
223

    
224
        /**
225
         * The first token in the list. This should be used as the return
226
         * value from <code>markTokens()</code>.
227
         */
228
        protected Token firstToken;
229

    
230
        /**
231
         * The last token in the list. New tokens are added here.
232
         * This should be set to null before a new line is to be tokenized.
233
         */
234
        protected Token lastToken;
235

    
236
        /**
237
         * An array for storing information about lines. It is enlarged and
238
         * shrunk automatically by the <code>insertLines()</code> and
239
         * <code>deleteLines()</code> methods.
240
         */
241
        protected LineInfo[] lineInfo;
242

    
243
        /**
244
         * The number of lines in the model being tokenized. This can be
245
         * less than the length of the <code>lineInfo</code> array.
246
         */
247
        protected int length;
248

    
249
        /**
250
         * The last tokenized line.
251
         */
252
        protected int lastLine;
253

    
254
        /**
255
         * True if the next line should be painted.
256
         */
257
        protected boolean nextLineRequested;
258

    
259
        /**
260
         * Creates a new <code>TokenMarker</code>. This DOES NOT create
261
         * a lineInfo array; an initial call to <code>insertLines()</code>
262
         * does that.
263
         */
264
        protected TokenMarker()
265
        {
266
                lastLine = -1;
267
        }
268

    
269
        /**
270
         * Ensures that the <code>lineInfo</code> array can contain the
271
         * specified index. This enlarges it if necessary. No action is
272
         * taken if the array is large enough already.<p>
273
         *
274
         * It should be unnecessary to call this under normal
275
         * circumstances; <code>insertLine()</code> should take care of
276
         * enlarging the line info array automatically.
277
         *
278
         * @param index The array index
279
         */
280
        protected void ensureCapacity(int index)
281
        {
282
                if(lineInfo == null)
283
                        lineInfo = new LineInfo[index + 1];
284
                else if(lineInfo.length <= index)
285
                {
286
                        LineInfo[] lineInfoN = new LineInfo[(index + 1) * 2];
287
                        System.arraycopy(lineInfo,0,lineInfoN,0,
288
                                         lineInfo.length);
289
                        lineInfo = lineInfoN;
290
                }
291
        }
292

    
293
        /**
294
         * Adds a token to the token list.
295
         * @param length The length of the token
296
         * @param id The id of the token
297
         */
298
        protected void addToken(int length, byte id)
299
        {
300
                if(id >= Token.INTERNAL_FIRST && id <= Token.INTERNAL_LAST)
301
                        throw new InternalError("Invalid id: " + id);
302

    
303
                if(length == 0 && id != Token.END)
304
                        return;
305

    
306
                if(firstToken == null)
307
                {
308
                        firstToken = new Token(length,id);
309
                        lastToken = firstToken;
310
                }
311
                else if(lastToken == null)
312
                {
313
                        lastToken = firstToken;
314
                        firstToken.length = length;
315
                        firstToken.id = id;
316
                }
317
                else if(lastToken.next == null)
318
                {
319
                        lastToken.next = new Token(length,id);
320
                        lastToken = lastToken.next;
321
                }
322
                else
323
                {
324
                        lastToken = lastToken.next;
325
                        lastToken.length = length;
326
                        lastToken.id = id;
327
                }
328
        }
329

    
330
        /**
331
         * Inner class for storing information about tokenized lines.
332
         */
333
        public class LineInfo
334
        {
335
                /**
336
                 * Creates a new LineInfo object with token = Token.NULL
337
                 * and obj = null.
338
                 */
339
                public LineInfo()
340
                {
341
                }
342

    
343
                /**
344
                 * Creates a new LineInfo object with the specified
345
                 * parameters.
346
                 */
347
                public LineInfo(byte token, Object obj)
348
                {
349
                        this.token = token;
350
                        this.obj = obj;
351
                }
352

    
353
                /**
354
                 * The id of the last token of the line.
355
                 */
356
                public byte token;
357

    
358
                /**
359
                 * This is for use by the token marker implementations
360
                 * themselves. It can be used to store anything that
361
                 * is an object and that needs to exist on a per-line
362
                 * basis.
363
                 */
364
                public Object obj;
365
        }
366
}