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

« back to all changes in this revision

Viewing changes to editor/libsrc/org/netbeans/modules/editor/lib/NavigationHistory.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.modules.editor.lib;
 
43
 
 
44
import java.beans.PropertyChangeListener;
 
45
import java.beans.PropertyChangeSupport;
 
46
import java.lang.ref.Reference;
 
47
import java.lang.ref.WeakReference;
 
48
import java.net.URL;
 
49
import java.util.AbstractList;
 
50
import java.util.ArrayList;
 
51
import java.util.Collections;
 
52
import java.util.HashMap;
 
53
import java.util.List;
 
54
import java.util.Map;
 
55
import java.util.logging.Level;
 
56
import java.util.logging.Logger;
 
57
import javax.swing.text.BadLocationException;
 
58
import javax.swing.text.JTextComponent;
 
59
import javax.swing.text.Position;
 
60
import javax.swing.text.Position;
 
61
 
 
62
/**
 
63
 *                       0    1    2    3    4    waypoints.size()
 
64
 *                     | W1 | W2 | W3 | W4 | W5 |
 
65
 *                                 ^
 
66
 *                                 |
 
67
 * previous waypoints <-     pointer      -> next Waypoints
 
68
 *                          current waypoint
 
69
 * 
 
70
 * getPreviousWaypoints() == { W1, W2, W3 }
 
71
 * getNextWaypoints() == { W4, W5 }
 
72
 * navigateBack() == W3, moves pointer one position left
 
73
 * navigateForward() == W4, moves pointer one position right
 
74
 * 
 
75
 * 
 
76
 * @author Vita Stejskal
 
77
 */
 
78
public final class NavigationHistory {
 
79
 
 
80
    public static final String PROP_WAYPOINTS = "NavigationHHistory.PROP_WAYPOINTS"; //NOI18N
 
81
 
 
82
    public static NavigationHistory getNavigations() {
 
83
        return get("navigation-history"); //NOI18N
 
84
    }
 
85
    
 
86
    public static NavigationHistory getEdits() {
 
87
        return get("last-edit-history"); //NOI18N
 
88
    }
 
89
    
 
90
    public void addPropertyChangeListener(PropertyChangeListener l) {
 
91
        PCS.addPropertyChangeListener(l);
 
92
    }
 
93
    
 
94
    public void removePropertyChangeListener(PropertyChangeListener l) {
 
95
        PCS.removePropertyChangeListener(l);
 
96
    }
 
97
    
 
98
    /**
 
99
     * @param offset A valid ofset inside the component's document or -1 if the
 
100
     *   offset is unspecified.
 
101
     */
 
102
    public Waypoint markWaypoint(JTextComponent comp, int offset, boolean currentPosition, boolean append) throws BadLocationException {
 
103
        assert comp != null : "The comp parameter must not be null"; //NOI18N
 
104
        
 
105
        Waypoint newWpt = null;
 
106
                
 
107
        synchronized (LOCK) {
 
108
            // Get the current position
 
109
            Position pos = offset == -1 ? null : WeakPositions.get(comp.getDocument(), offset);
 
110
            
 
111
            // Remove all next waypoints and the current waypoint
 
112
            if (!append) {
 
113
                while (waypoints.size() > pointer) {
 
114
                    Waypoint wpt = waypoints.remove(waypoints.size() - 1);
 
115
                    wpt.dispose();
 
116
                }
 
117
            }
 
118
            
 
119
            // compare the new position with the current waypoint
 
120
            if (waypoints.size() > 0) {
 
121
                Waypoint wpt = waypoints.get(waypoints.size() - 1);
 
122
                JTextComponent wptComp = wpt.getComponent();
 
123
                int wptOffset = wpt.getOffset();
 
124
                if (wptComp != null && wptComp.equals(comp) && wptOffset == offset) {
 
125
                    // Current waypoint has the same position, do not add anything
 
126
                    newWpt = wpt;
 
127
                }
 
128
            }
 
129
 
 
130
            if (newWpt == null) {
 
131
                // Add the new Waypoint
 
132
                newWpt = new Waypoint(this, comp, pos);
 
133
                int rawIndex = waypoints.addEx(newWpt);
 
134
                newWpt.initRawIndex(rawIndex);
 
135
            }
 
136
            
 
137
            // Update the pointer
 
138
            if (currentPosition) {
 
139
                pointer = waypoints.size() - 1;
 
140
            } else {
 
141
                pointer = waypoints.size();
 
142
            }
 
143
            
 
144
            // Reset the cache
 
145
            sublistsCache = null;
 
146
        }
 
147
        
 
148
        PCS.firePropertyChange(PROP_WAYPOINTS, null, null);
 
149
        
 
150
        return newWpt;
 
151
    }
 
152
    
 
153
    public Waypoint getCurrentWaypoint() {
 
154
        synchronized (LOCK) {
 
155
            if (pointer < waypoints.size()) {
 
156
                return waypoints.get(pointer);
 
157
            } else {
 
158
                return null;
 
159
            }
 
160
        }
 
161
    }
 
162
    
 
163
    public boolean hasPreviousWaypoints() {
 
164
        synchronized (LOCK) {
 
165
            return pointer > 0;
 
166
        }
 
167
    }
 
168
    
 
169
    public boolean hasNextWaypoints() {
 
170
        synchronized (LOCK) {
 
171
            return pointer + 1 < waypoints.size();
 
172
        }
 
173
    }
 
174
    
 
175
    public List<Waypoint> getPreviousWaypoints() {
 
176
        synchronized (LOCK) {
 
177
            if (hasPreviousWaypoints()) {
 
178
                return getSublistsCache().subList(0, pointer);
 
179
            } else {
 
180
                return Collections.<Waypoint>emptyList();
 
181
            }
 
182
        }
 
183
    }
 
184
    
 
185
    public List<Waypoint> getNextWaypoints() {
 
186
        synchronized (LOCK) {
 
187
            if (hasNextWaypoints()) {
 
188
                return getSublistsCache().subList(pointer + 1, waypoints.size());
 
189
            } else {
 
190
                return Collections.<Waypoint>emptyList();
 
191
            }
 
192
        }
 
193
    }
 
194
    
 
195
    public Waypoint navigateBack() {
 
196
        Waypoint waypoint = null;
 
197
        
 
198
        synchronized (LOCK) {
 
199
            if (hasPreviousWaypoints()) {
 
200
                pointer--;
 
201
                waypoint = waypoints.get(pointer);
 
202
            }
 
203
        }
 
204
 
 
205
        if (waypoint != null) {
 
206
            PCS.firePropertyChange(PROP_WAYPOINTS, null, null);
 
207
        }
 
208
        
 
209
        return waypoint;
 
210
    }
 
211
    
 
212
    public Waypoint navigateForward() {
 
213
        Waypoint waypoint = null;
 
214
        
 
215
        synchronized (LOCK) {
 
216
            if (hasNextWaypoints()) {
 
217
                pointer++;
 
218
                waypoint = waypoints.get(pointer);
 
219
            }
 
220
        }
 
221
        
 
222
        if (waypoint != null) {
 
223
            PCS.firePropertyChange(PROP_WAYPOINTS, null, null);
 
224
        }
 
225
        
 
226
        return waypoint;
 
227
    }
 
228
 
 
229
    public Waypoint navigateTo(Waypoint waypoint) {
 
230
        assert waypoint != null : "The waypoint parameter must not be null"; //NOI18N
 
231
        
 
232
        synchronized (LOCK) {
 
233
            int rawIndex = waypoint.getRawIndex();
 
234
            if (rawIndex == -1) {
 
235
                // invalid waypoint
 
236
                waypoint = null;
 
237
            } else {
 
238
                int wptPointer = waypoints.getIndex(rawIndex);
 
239
                if (pointer != wptPointer) {
 
240
                    // Move to the waypoint
 
241
                    pointer = wptPointer;
 
242
                } else {
 
243
                    // We are already there no need for navigation
 
244
                    waypoint = null;
 
245
                }
 
246
            }
 
247
        }
 
248
        
 
249
        if (waypoint != null) {
 
250
            PCS.firePropertyChange(PROP_WAYPOINTS, null, null);
 
251
        }
 
252
        
 
253
        return waypoint;
 
254
    }
 
255
 
 
256
    public Waypoint navigateFirst() {
 
257
        Waypoint waypoint = null;
 
258
        
 
259
        synchronized (LOCK) {
 
260
            if (waypoints.size() > 0) {
 
261
                pointer = 0;
 
262
                waypoint = waypoints.get(pointer);
 
263
            }
 
264
        }
 
265
 
 
266
        if (waypoint != null) {
 
267
            PCS.firePropertyChange(PROP_WAYPOINTS, null, null);
 
268
        }
 
269
        
 
270
        return waypoint;
 
271
    }
 
272
    
 
273
    public Waypoint navigateLast() {
 
274
        Waypoint waypoint = null;
 
275
        
 
276
        synchronized (LOCK) {
 
277
            if (waypoints.size() > 0) {
 
278
                pointer = waypoints.size() - 1;
 
279
                waypoint = waypoints.get(pointer);
 
280
            }
 
281
        }
 
282
        
 
283
        if (waypoint != null) {
 
284
            PCS.firePropertyChange(PROP_WAYPOINTS, null, null);
 
285
        }
 
286
        
 
287
        return waypoint;
 
288
    }
 
289
    
 
290
    public static final class Waypoint {
 
291
 
 
292
        private NavigationHistory navigationHistory;
 
293
        private Reference<JTextComponent> compRef;
 
294
        private Position pos;
 
295
        private URL url;
 
296
        
 
297
        private int rawIndex = -2;
 
298
        
 
299
        private Waypoint(NavigationHistory nh, JTextComponent comp, Position pos) throws BadLocationException {
 
300
            this.navigationHistory = nh;
 
301
            this.compRef = new WeakReference<JTextComponent>(comp);
 
302
            this.pos = pos;
 
303
            this.url = URLMapper.findUrl(comp);
 
304
            
 
305
            if (LOG.isLoggable(Level.FINE)) {
 
306
                LOG.fine(navigationHistory.id + ": waypoint added: " + getUrl()); //NOI18N
 
307
            }
 
308
        }
 
309
        
 
310
        public URL getUrl() {
 
311
            synchronized (navigationHistory.LOCK) {
 
312
                return url;
 
313
            }
 
314
        }
 
315
        
 
316
        public JTextComponent getComponent() {
 
317
            synchronized (navigationHistory.LOCK) {
 
318
                return compRef == null ? null : compRef.get();
 
319
            }
 
320
        }
 
321
        
 
322
        public int getOffset() {
 
323
            synchronized (navigationHistory.LOCK) {
 
324
                return pos == null ? -1 : pos.getOffset();
 
325
            }
 
326
        }
 
327
 
 
328
        // the following methods are called under the getDefault().LOCK
 
329
        
 
330
        private int getRawIndex() {
 
331
            return rawIndex;
 
332
        }
 
333
        
 
334
        private void initRawIndex(int rawIndex) {
 
335
            assert this.rawIndex == -2 : "Can't call initRawIndex more than once."; //NOI18N
 
336
            this.rawIndex = rawIndex;
 
337
        }
 
338
        
 
339
        private void dispose() {
 
340
            if (LOG.isLoggable(Level.FINE)) {
 
341
                LOG.fine(navigationHistory.id + ": waypoint disposed: " + getUrl()); //NOI18N
 
342
            }
 
343
            
 
344
            this.rawIndex = -1;
 
345
            this.url = null;
 
346
            this.compRef = null;
 
347
            this.pos = null;
 
348
        }
 
349
    } // End of Waypoint class
 
350
 
 
351
    // ----------------------------------------------
 
352
    // Private implementation
 
353
    // ----------------------------------------------
 
354
    
 
355
    private static final Logger LOG = Logger.getLogger(NavigationHistory.class.getName());
 
356
    
 
357
    private static Map<String, NavigationHistory> instances = new HashMap<String, NavigationHistory>();
 
358
 
 
359
    private final String id;
 
360
    private final String LOCK = new String("NavigationHistory.LOCK"); //NOI18N
 
361
    private final RingBuffer<Waypoint> waypoints = new RingBuffer<Waypoint>(new Waypoint [50]);
 
362
    private int pointer = 0;
 
363
    private List<Waypoint> sublistsCache = null;
 
364
    
 
365
    private final PropertyChangeSupport PCS = new PropertyChangeSupport(this);
 
366
 
 
367
    private static NavigationHistory get(String id) {
 
368
        synchronized (instances) {
 
369
            NavigationHistory nh = instances.get(id);
 
370
 
 
371
            if (nh == null) {
 
372
                nh = new NavigationHistory(id);
 
373
                instances.put(id, nh);
 
374
            }
 
375
 
 
376
            return nh;
 
377
        }
 
378
    }
 
379
    
 
380
    private NavigationHistory(String id) {
 
381
        this.id = id;
 
382
    }
 
383
    
 
384
    private List<Waypoint> getSublistsCache() {
 
385
        if (sublistsCache == null) {
 
386
            sublistsCache = Collections.unmodifiableList(new ArrayList<Waypoint>(waypoints));
 
387
        }
 
388
        return sublistsCache;
 
389
    }
 
390
    
 
391
    private static final class RingBuffer<E> extends AbstractList<E> {
 
392
 
 
393
        private final E [] buffer;
 
394
        private int head = 0;
 
395
        private int tail = 0;
 
396
        
 
397
        public RingBuffer(E [] buffer) {
 
398
            assert buffer != null : "The buffer parameter must not be null"; //NOI18N
 
399
            assert buffer.length >= 2 : "The buffer size must be at least 2."; //NOI18N
 
400
            
 
401
            this.buffer = buffer;
 
402
        }
 
403
 
 
404
        @Override
 
405
        public E set(int index, E element) {
 
406
            int rawIndex = getRawIndex(index);
 
407
            E old = buffer[rawIndex];
 
408
            buffer[rawIndex] = element;
 
409
            return old;
 
410
        }
 
411
 
 
412
        @Override
 
413
        public void add(int index, E element) {
 
414
            int rawIndex = (head + index) % buffer.length;
 
415
            if (rawIndex == tail) {
 
416
                addEx(element);
 
417
            } else {
 
418
                throw new UnsupportedOperationException("This ring buffer only allows adding to the end of the buffer."); //NOI18N
 
419
            }
 
420
        }
 
421
        
 
422
        public int addEx(E element) {
 
423
            int rawIndex = tail;
 
424
            buffer[rawIndex] = element;
 
425
            
 
426
            tail = (tail + 1) % buffer.length;
 
427
            if (tail == head) {
 
428
                // XXX: hack, not very nice
 
429
                if (buffer[head] instanceof Waypoint) {
 
430
                    ((Waypoint) buffer[head]).dispose();
 
431
                }
 
432
                buffer[head] = null;
 
433
                head = (head + 1) % buffer.length;
 
434
            }
 
435
            
 
436
            return rawIndex;
 
437
        }
 
438
 
 
439
        @Override
 
440
        public E remove(int index) {
 
441
            int rawIndex = getRawIndex(index);
 
442
            
 
443
            if (rawIndex == head) {
 
444
                head = (head + 1) % buffer.length;
 
445
            } else {
 
446
                int tailMinusOne = (tail - 1 + buffer.length) % buffer.length;
 
447
                if (rawIndex == tailMinusOne) {
 
448
                    tail = tailMinusOne;
 
449
                } else {
 
450
                    throw new UnsupportedOperationException("This ring buffer only allows removing at the beginning or end of the buffer."); //NOI18N
 
451
                }
 
452
            }
 
453
            
 
454
            E old = buffer[rawIndex];
 
455
            buffer[rawIndex] = null;
 
456
            
 
457
            return old;
 
458
        }
 
459
 
 
460
        @Override
 
461
        public E get(int index) {
 
462
            return buffer[getRawIndex(index)];
 
463
        }
 
464
 
 
465
        public int size() {
 
466
            return (tail - head + buffer.length) % buffer.length;
 
467
        }
 
468
 
 
469
        private int getRawIndex(int index) {
 
470
            if (index >= 0 && index < size()) {
 
471
                return (head + index) % buffer.length;
 
472
            } else {
 
473
                throw new IndexOutOfBoundsException("Index = " + index + ", size = " + size());
 
474
            }
 
475
        }
 
476
        
 
477
        public int getIndex(int rawIndex) {
 
478
            boolean valid;
 
479
            
 
480
            if (tail < head) {
 
481
                valid = (rawIndex >= 0 && rawIndex < tail) || (rawIndex >= head && rawIndex < buffer.length);
 
482
            } else {
 
483
                valid = rawIndex >= head && rawIndex < tail;
 
484
            }
 
485
            
 
486
            if (valid) {
 
487
                return (rawIndex - head + buffer.length) % buffer.length;
 
488
            } else {
 
489
                throw new IndexOutOfBoundsException("Invalid raw index. RawIndex = " + rawIndex + ", head = " + head + ", tail = " + tail);
 
490
            }
 
491
        }
 
492
    } // End of RingBuffer class
 
493
    
 
494
}