Hyperlink in a Plain Text File

Let's say you want to create a hyperlink in a plain text file, like this:

Here's how:

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.StyledDocument;
import org.netbeans.api.editor.EditorRegistry;
import org.netbeans.lib.editor.hyperlink.spi.HyperlinkProvider;
import org.openide.util.Exceptions;

public class RoutesHyperlinkProvider implements HyperlinkProvider {

    private static String ROUTES_IDENTIFIER = "Application.index";
    private int startOffset;
    private int endOffset;

    public boolean isHyperlinkPoint(Document doc, int offset) {
        Pattern p = Pattern.compile(ROUTES_IDENTIFIER);
        try {
            Matcher m = p.matcher(doc.getText(0, doc.getLength()));
            while (m.find() == true){
                startOffset = m.start();
                endOffset = m.end();
                return true;
        } catch (BadLocationException ex) {
        return false;

    public int[] getHyperlinkSpan(Document dcmnt, int i) {
        JTextComponent target = EditorRegistry.lastFocusedComponent();
        final StyledDocument styledDoc = (StyledDocument) target.getDocument();
        if (styledDoc == null) {
            return null;
        // Return the position which was set in the isHyperlink method:
        return new int[]{startOffset, endOffset};

    public void performClickAction(Document dcmnt, int i) {
        //do something when the user clicks the hyperlink


It seems you code only supports a single ROUTES_IDENTIFIER in the document -- what happens if there are more ?

If it's only one, then the 'while' in 'isHyperlinkPoint' might as well be replaced with an 'if'.

Also, not sure why you are using 'EditorRegistry.lastFocusedComponent()' in 'getHyperlinkSpan' when it seems you are given a 'Document dcmnt' argument.

You are also given an 'offset' which you never use and should probably make matching faster if you just get a substring from the document and not the whole text. Another trick might be to cache the compiled pattern.

Well, that was my code review so far :-)

Posted by Emilian Bold on November 23, 2010 at 07:42 PM PST #

That's exactly the problem I have, i.e., I only support a single ROUTES_IDENITIFIER. Can someone give me a tip what I should do to support more?

Posted by Geertjan Wielenga on November 23, 2010 at 08:00 PM PST #

This could be a prototype for a more general implementation:

public abstract class AbstractHyperlinkProvider implements HyperlinkProvider {

private transient int startOffset, endOffset;
private transient final String identifier;

public abstract void performClickAction(Document document, int i);

public AbstractHyperlinkProvider(String identifier) {
this.identifier = identifier;

public boolean isHyperlinkPoint(Document document, int offset) {
boolean result = false;
Matcher matcher = null;
try {
matcher = Pattern.compile(identifier).matcher(document.getText(0, document.getLength()));
} catch (BadLocationException ex) {
while (matcher.find()) {
startOffset = matcher.start();
endOffset = matcher.end();
result = true;
return result;

public int[] getHyperlinkSpan(Document document, int i) {
int[] result = null;
if ((StyledDocument) EditorRegistry.lastFocusedComponent().getDocument() != null) {
// Return the position which was set in the isHyperlink method:
result = new int[]{startOffset, endOffset};
return result;

Posted by Carlos Hoces on November 24, 2010 at 06:12 PM PST #

Post a Comment:
  • HTML Syntax: NOT allowed

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.


« March 2015