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.modules.editor.lib;
44
import java.beans.PropertyChangeListener;
45
import java.beans.PropertyChangeSupport;
46
import java.lang.ref.Reference;
47
import java.lang.ref.WeakReference;
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;
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;
63
* 0 1 2 3 4 waypoints.size()
64
* | W1 | W2 | W3 | W4 | W5 |
67
* previous waypoints <- pointer -> next Waypoints
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
76
* @author Vita Stejskal
78
public final class NavigationHistory {
80
public static final String PROP_WAYPOINTS = "NavigationHHistory.PROP_WAYPOINTS"; //NOI18N
82
public static NavigationHistory getNavigations() {
83
return get("navigation-history"); //NOI18N
86
public static NavigationHistory getEdits() {
87
return get("last-edit-history"); //NOI18N
90
public void addPropertyChangeListener(PropertyChangeListener l) {
91
PCS.addPropertyChangeListener(l);
94
public void removePropertyChangeListener(PropertyChangeListener l) {
95
PCS.removePropertyChangeListener(l);
99
* @param offset A valid ofset inside the component's document or -1 if the
100
* offset is unspecified.
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
105
Waypoint newWpt = null;
107
synchronized (LOCK) {
108
// Get the current position
109
Position pos = offset == -1 ? null : WeakPositions.get(comp.getDocument(), offset);
111
// Remove all next waypoints and the current waypoint
113
while (waypoints.size() > pointer) {
114
Waypoint wpt = waypoints.remove(waypoints.size() - 1);
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
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);
137
// Update the pointer
138
if (currentPosition) {
139
pointer = waypoints.size() - 1;
141
pointer = waypoints.size();
145
sublistsCache = null;
148
PCS.firePropertyChange(PROP_WAYPOINTS, null, null);
153
public Waypoint getCurrentWaypoint() {
154
synchronized (LOCK) {
155
if (pointer < waypoints.size()) {
156
return waypoints.get(pointer);
163
public boolean hasPreviousWaypoints() {
164
synchronized (LOCK) {
169
public boolean hasNextWaypoints() {
170
synchronized (LOCK) {
171
return pointer + 1 < waypoints.size();
175
public List<Waypoint> getPreviousWaypoints() {
176
synchronized (LOCK) {
177
if (hasPreviousWaypoints()) {
178
return getSublistsCache().subList(0, pointer);
180
return Collections.<Waypoint>emptyList();
185
public List<Waypoint> getNextWaypoints() {
186
synchronized (LOCK) {
187
if (hasNextWaypoints()) {
188
return getSublistsCache().subList(pointer + 1, waypoints.size());
190
return Collections.<Waypoint>emptyList();
195
public Waypoint navigateBack() {
196
Waypoint waypoint = null;
198
synchronized (LOCK) {
199
if (hasPreviousWaypoints()) {
201
waypoint = waypoints.get(pointer);
205
if (waypoint != null) {
206
PCS.firePropertyChange(PROP_WAYPOINTS, null, null);
212
public Waypoint navigateForward() {
213
Waypoint waypoint = null;
215
synchronized (LOCK) {
216
if (hasNextWaypoints()) {
218
waypoint = waypoints.get(pointer);
222
if (waypoint != null) {
223
PCS.firePropertyChange(PROP_WAYPOINTS, null, null);
229
public Waypoint navigateTo(Waypoint waypoint) {
230
assert waypoint != null : "The waypoint parameter must not be null"; //NOI18N
232
synchronized (LOCK) {
233
int rawIndex = waypoint.getRawIndex();
234
if (rawIndex == -1) {
238
int wptPointer = waypoints.getIndex(rawIndex);
239
if (pointer != wptPointer) {
240
// Move to the waypoint
241
pointer = wptPointer;
243
// We are already there no need for navigation
249
if (waypoint != null) {
250
PCS.firePropertyChange(PROP_WAYPOINTS, null, null);
256
public Waypoint navigateFirst() {
257
Waypoint waypoint = null;
259
synchronized (LOCK) {
260
if (waypoints.size() > 0) {
262
waypoint = waypoints.get(pointer);
266
if (waypoint != null) {
267
PCS.firePropertyChange(PROP_WAYPOINTS, null, null);
273
public Waypoint navigateLast() {
274
Waypoint waypoint = null;
276
synchronized (LOCK) {
277
if (waypoints.size() > 0) {
278
pointer = waypoints.size() - 1;
279
waypoint = waypoints.get(pointer);
283
if (waypoint != null) {
284
PCS.firePropertyChange(PROP_WAYPOINTS, null, null);
290
public static final class Waypoint {
292
private NavigationHistory navigationHistory;
293
private Reference<JTextComponent> compRef;
294
private Position pos;
297
private int rawIndex = -2;
299
private Waypoint(NavigationHistory nh, JTextComponent comp, Position pos) throws BadLocationException {
300
this.navigationHistory = nh;
301
this.compRef = new WeakReference<JTextComponent>(comp);
303
this.url = URLMapper.findUrl(comp);
305
if (LOG.isLoggable(Level.FINE)) {
306
LOG.fine(navigationHistory.id + ": waypoint added: " + getUrl()); //NOI18N
310
public URL getUrl() {
311
synchronized (navigationHistory.LOCK) {
316
public JTextComponent getComponent() {
317
synchronized (navigationHistory.LOCK) {
318
return compRef == null ? null : compRef.get();
322
public int getOffset() {
323
synchronized (navigationHistory.LOCK) {
324
return pos == null ? -1 : pos.getOffset();
328
// the following methods are called under the getDefault().LOCK
330
private int getRawIndex() {
334
private void initRawIndex(int rawIndex) {
335
assert this.rawIndex == -2 : "Can't call initRawIndex more than once."; //NOI18N
336
this.rawIndex = rawIndex;
339
private void dispose() {
340
if (LOG.isLoggable(Level.FINE)) {
341
LOG.fine(navigationHistory.id + ": waypoint disposed: " + getUrl()); //NOI18N
349
} // End of Waypoint class
351
// ----------------------------------------------
352
// Private implementation
353
// ----------------------------------------------
355
private static final Logger LOG = Logger.getLogger(NavigationHistory.class.getName());
357
private static Map<String, NavigationHistory> instances = new HashMap<String, NavigationHistory>();
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;
365
private final PropertyChangeSupport PCS = new PropertyChangeSupport(this);
367
private static NavigationHistory get(String id) {
368
synchronized (instances) {
369
NavigationHistory nh = instances.get(id);
372
nh = new NavigationHistory(id);
373
instances.put(id, nh);
380
private NavigationHistory(String id) {
384
private List<Waypoint> getSublistsCache() {
385
if (sublistsCache == null) {
386
sublistsCache = Collections.unmodifiableList(new ArrayList<Waypoint>(waypoints));
388
return sublistsCache;
391
private static final class RingBuffer<E> extends AbstractList<E> {
393
private final E [] buffer;
394
private int head = 0;
395
private int tail = 0;
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
401
this.buffer = buffer;
405
public E set(int index, E element) {
406
int rawIndex = getRawIndex(index);
407
E old = buffer[rawIndex];
408
buffer[rawIndex] = element;
413
public void add(int index, E element) {
414
int rawIndex = (head + index) % buffer.length;
415
if (rawIndex == tail) {
418
throw new UnsupportedOperationException("This ring buffer only allows adding to the end of the buffer."); //NOI18N
422
public int addEx(E element) {
424
buffer[rawIndex] = element;
426
tail = (tail + 1) % buffer.length;
428
// XXX: hack, not very nice
429
if (buffer[head] instanceof Waypoint) {
430
((Waypoint) buffer[head]).dispose();
433
head = (head + 1) % buffer.length;
440
public E remove(int index) {
441
int rawIndex = getRawIndex(index);
443
if (rawIndex == head) {
444
head = (head + 1) % buffer.length;
446
int tailMinusOne = (tail - 1 + buffer.length) % buffer.length;
447
if (rawIndex == tailMinusOne) {
450
throw new UnsupportedOperationException("This ring buffer only allows removing at the beginning or end of the buffer."); //NOI18N
454
E old = buffer[rawIndex];
455
buffer[rawIndex] = null;
461
public E get(int index) {
462
return buffer[getRawIndex(index)];
466
return (tail - head + buffer.length) % buffer.length;
469
private int getRawIndex(int index) {
470
if (index >= 0 && index < size()) {
471
return (head + index) % buffer.length;
473
throw new IndexOutOfBoundsException("Index = " + index + ", size = " + size());
477
public int getIndex(int rawIndex) {
481
valid = (rawIndex >= 0 && rawIndex < tail) || (rawIndex >= head && rawIndex < buffer.length);
483
valid = rawIndex >= head && rawIndex < tail;
487
return (rawIndex - head + buffer.length) % buffer.length;
489
throw new IndexOutOfBoundsException("Invalid raw index. RawIndex = " + rawIndex + ", head = " + head + ", tail = " + tail);
492
} // End of RingBuffer class