All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.maven.hyperlinks.HyperlinkProviderImpl Maven / Gradle / Ivy

There is a newer version: RELEASE240
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.maven.hyperlinks;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.Document;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.InputLocation;
import org.apache.maven.model.InputSource;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.editor.mimelookup.MimeRegistrations;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.api.lexer.TokenUtilities;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.xml.lexer.XMLTokenId;
import org.netbeans.lib.editor.hyperlink.spi.HyperlinkProviderExt;
import org.netbeans.lib.editor.hyperlink.spi.HyperlinkType;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.netbeans.modules.maven.api.Constants;
import org.netbeans.modules.maven.api.FileUtilities;
import org.netbeans.modules.maven.api.ModelUtils;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.modules.maven.api.PluginPropertyUtils;
import org.netbeans.modules.maven.embedder.EmbedderFactory;
import org.netbeans.modules.maven.embedder.MavenEmbedder;
import org.netbeans.modules.maven.grammar.POMDataObject;
import org.openide.awt.HtmlBrowser;
import org.openide.cookies.EditCookie;
import org.openide.cookies.LineCookie;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.text.Line;
import org.openide.util.NbBundle.Messages;
import static org.netbeans.modules.maven.hyperlinks.Bundle.*;
import org.netbeans.modules.maven.spi.nodes.NodeUtils;
import org.openide.filesystems.FileUtil;

/**
 * adds hyperlinking support to pom.xml files..
 *
 * @author mkleint
 */
@MimeRegistrations({
    @MimeRegistration(mimeType = Constants.POM_MIME_TYPE, service = HyperlinkProviderExt.class, position = 1000)
    ,
    @MimeRegistration(mimeType = POMDataObject.SETTINGS_MIME_TYPE, service = HyperlinkProviderExt.class, position = 1000)
})
public class HyperlinkProviderImpl implements HyperlinkProviderExt {

    private static final Logger LOG = Logger.getLogger(HyperlinkProviderImpl.class.getName());

    @Override
    public boolean isHyperlinkPoint(final Document doc, final int offset, HyperlinkType type) {
        final PomHyperlinkInfo hyperLinkInfo = new PomHyperlinkInfo(doc, offset);
        doc.render(new PomParserRunnable(hyperLinkInfo, doc, offset));

        return hyperLinkInfo.isHyperlinkPoint();
    }

    @Override
    public int[] getHyperlinkSpan(final Document doc, final int offset, HyperlinkType type) {
        final PomHyperlinkInfo hyperLinkInfo = new PomHyperlinkInfo(doc, offset);
        doc.render(new PomParserRunnable(hyperLinkInfo, doc, offset));

        return hyperLinkInfo.getHyperLinkSpan();
    }

    @Override
    public void performClickAction(final Document doc, final int offset, HyperlinkType type) {
        final PomHyperlinkInfo hyperLinkInfo = new PomHyperlinkInfo(doc, offset);
        doc.render(new PomParserRunnable(hyperLinkInfo, doc, offset));

        hyperLinkInfo.performClickAction();
    }

    @Override
    public Set getSupportedHyperlinkTypes() {
        return Collections.singleton(HyperlinkType.GO_TO_DECLARATION);
    }

    @Override
    @Messages({
        "# {0} - property name",
        "# {1} - resolved value",
        "Hint_prop_resolution={0} resolves to ''{1}''\nNavigate to definition.",
        "Hint_prop_cannot=Cannot resolve expression\nNavigates to definition."})
    public String getTooltipText(final Document doc, final int offset, HyperlinkType type) {
        final PomHyperlinkInfo hyperLinkInfo = new PomHyperlinkInfo(doc, offset);
        doc.render(new PomParserRunnable(hyperLinkInfo, doc, offset));
        String[] tooltip = hyperLinkInfo.getTooltipText();

        if (tooltip == null) {
            return Hint_prop_cannot();
        } else if (tooltip.length == 2) {
            return Hint_prop_resolution(tooltip[0], tooltip[1]);
        } else {
            return tooltip[0];
        }
    }

    public static void openAtSource(InputLocation location) {
        InputSource source = location.getSource();
        if (source != null && source.getLocation() != null) {
            FileObject fobj = FileUtilities.convertStringToFileObject(source.getLocation());
            if (fobj != null) {
                try {
                    DataObject dobj = DataObject.find(NodeUtils.readOnlyLocalRepositoryFile(fobj));
                    EditCookie edit = dobj.getLookup().lookup(EditCookie.class);
                    if (edit != null) {
                        edit.edit();
                    }
                    LineCookie lc = dobj.getLookup().lookup(LineCookie.class);
                    lc.getLineSet().getOriginal(location.getLineNumber() - 1).show(Line.ShowOpenType.REUSE, Line.ShowVisibilityType.FOCUS, location.getColumnNumber() - 1);
                } catch (DataObjectNotFoundException ex) {
                    LOG.log(Level.FINE, "dataobject not found", ex);
                }
            }
        }
    }

    private static class Tuple {

        final int spanStart;
        final int spanEnd;
        final String value;

        public Tuple(String val, int start, int end) {
            this.value = val;
            this.spanStart = start;
            this.spanEnd = end;
        }
    }

    private Tuple findProperty(String textToken, int tokenOffset, int currentOffset) {
        if (textToken == null) {
            return null;
        }
        int ff = currentOffset - tokenOffset;

        if (ff > -1 && ff < textToken.length()) {
            String before = textToken.substring(0, ff);
            String after = textToken.substring(ff);
            int bo = before.lastIndexOf("${");
            int bc = before.lastIndexOf("}");
            int ao = after.indexOf("${");
            int ac = after.indexOf("}");
            if (bo > bc && ac > -1 && (ac < ao || ao == -1)) { //case where currentOffset is on property
                return new Tuple(textToken.substring(bo, before.length() + ac + 1), tokenOffset + bo, tokenOffset + ff + ac + 1);
            }

            if (before.length() == 0 && ao == 0 && ac > 0) { //case where currentOffset is at beginning
                return new Tuple(textToken.substring(0, ac + 1), tokenOffset, tokenOffset + ac + 1);
            }

        }
        return null;
    }

    private FileObject getProjectDir(Document doc) {
        DataObject dObject = NbEditorUtilities.getDataObject(doc);
        if (dObject != null) {
            return dObject.getPrimaryFile().getParent();
        }
        return null;
    }

    private NbMavenProject getNbMavenProject(Document doc) {
        Project prj = getProject(doc);
        if (prj != null) {
            return prj.getLookup().lookup(NbMavenProject.class);
        }
        return null;
    }

    private Project getProject(Document doc) {
        DataObject dobj = NbEditorUtilities.getDataObject(doc);
        if (dobj != null) {
            return FileOwnerQuery.getOwner(dobj.getPrimaryFile());
        }
        return null;
    }

    private FileObject getPath(FileObject parent, String path) {
        // TODO more substitutions necessary probably..
        if (path.startsWith("${basedir}/")) { //NOI18N
            path = path.substring("${basedir}/".length()); //NOI18N
        }
        while (path.startsWith("../") && parent.getParent() != null) { //NOI18N
            path = path.substring("../".length()); //NOI18N
            parent = parent.getParent();
        }
        return parent.getFileObject(path);
    }

    private class PomParserRunnable implements Runnable {

        private final PomHyperlinkInfo hyperLinkInfo;
        private final Document document;
        private final int offset;

        public PomParserRunnable(PomHyperlinkInfo hyperLinkInfo, Document document, int offset) {
            this.hyperLinkInfo = hyperLinkInfo;
            this.document = document;
            this.offset = offset;
        }

        @Override
        public void run() {
            TokenHierarchy th = TokenHierarchy.get(document);
            TokenSequence xml = th.tokenSequence(XMLTokenId.language());
            xml.move(offset);
            xml.moveNext();
            Token token = xml.token();

            // when it's not a value -> do nothing.
            if (token == null) {
                return;
            }

            if (token.id() == XMLTokenId.TEXT) {
                hyperLinkInfo.calculateInfo(token, xml);
            }
        }
    }

    private class PomHyperlinkInfo {

        final Document doc;
        final int documentOffset;
        final FileObject projectFileObject;
        boolean isText;
        int ftokenOff;
        String ftext;

        String artifactId;
        String groupId;
        String version;
        String type;

        public PomHyperlinkInfo(Document doc, int documentOffset) {
            this.doc = doc;
            this.documentOffset = documentOffset;
            this.projectFileObject = getProjectDir(doc);
        }

        boolean isHyperlinkUrl() {
            return ftext != null
                    && (ftext.startsWith("http://")
                    || //NOI18N
                    ftext.startsWith("https://")); //NOI18N;
        }

        private FileObject getFileSystemLinkObject() {
            FileObject fo = getProjectDir(doc);
            if (fo != null && ftext != null) {
                return getPath(fo, ftext) ;
            }
            return null;
        }

        boolean isMavenProperty() {
            if (ftext != null) {
                int ff = documentOffset - ftokenOff;
                if (ff > -1 && ff < ftext.length()) {
                    String before = ftext.substring(0, ff);
                    String after = ftext.substring(ff);
                    int bo = before.lastIndexOf("${");//NOI18N
                    int bc = before.lastIndexOf("}");//NOI18N
                    int ao = after.indexOf("${");//NOI18N
                    int ac = after.indexOf("}");//NOI18N
                    if (bo > bc && ac > -1 && (ac < ao || ao == -1)) {
                        return true;
                    }
                }
            }
            return false;
        }

        boolean isMavenDependency() {
            return artifactId != null && groupId != null && version != null;
        }

        boolean isHyperlinkPoint() {
            return (isHyperlinkUrl() || getFileSystemLinkObject() != null || 
                    isMavenProperty() || (isMavenDependency() && getMavenArtifactAbsolutePomPath() != null));
        }

        private void calculateInfo(Token token, TokenSequence xml) {
            isText = token.id() == XMLTokenId.TEXT;
            if (isText) {
                ftokenOff = xml.offset();
                ftext = token.text().toString();

                if (projectFileObject != null && getPath(projectFileObject, ftext) != null) {
                    xml.movePrevious();
                    token = xml.token();
                    if (token != null && token.id().equals(XMLTokenId.TAG) && TokenUtilities.equals(token.text(), ">")) {//NOI18N
                        xml.movePrevious();
                        token = xml.token();
                        if (token != null && token.id().equals(XMLTokenId.TAG)) {
                            if (TokenUtilities.equals(token.text(), "")) { //NOI18N
                        xml.movePrevious();
                        String tokenString = xml.token().text().toString();
                        if (" xml) {
            while (!TokenUtilities.equals(" xml, XMLTokenId tokenId) {
            while (xml.token() != null && !xml.token().id().equals(tokenId)) {
                xml.moveNext();
            }
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy