This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 237902
Collapse All | Expand All

(-)java.editor/nbproject/project.xml (+9 lines)
Lines 228-233 Link Here
228
                    </run-dependency>
228
                    </run-dependency>
229
                </dependency>
229
                </dependency>
230
                <dependency>
230
                <dependency>
231
                    <code-name-base>org.netbeans.modules.java.project</code-name-base>
232
                    <build-prerequisite/>
233
                    <compile-dependency/>
234
                    <run-dependency>
235
                        <release-version>1</release-version>
236
                        <specification-version>1.57</specification-version>
237
                    </run-dependency>
238
                </dependency>
239
                <dependency>
231
                    <code-name-base>org.netbeans.modules.java.source</code-name-base>
240
                    <code-name-base>org.netbeans.modules.java.source</code-name-base>
232
                    <build-prerequisite/>
241
                    <build-prerequisite/>
233
                    <compile-dependency/>
242
                    <compile-dependency/>
(-)java.editor/src/org/netbeans/modules/java/editor/hyperlink/ResourceHyperlinkProvider.java (+314 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2013 Sun Microsystems, Inc.
41
 * Portions Copyrighted 2013 markiewb
42
 */
43
package org.netbeans.modules.java.editor.hyperlink;
44
45
import java.text.MessageFormat;
46
import java.util.ArrayList;
47
import java.util.Arrays;
48
import java.util.Collection;
49
import java.util.Collections;
50
import java.util.EnumSet;
51
import java.util.HashSet;
52
import java.util.List;
53
import java.util.Set;
54
import javax.swing.JComboBox;
55
import javax.swing.text.BadLocationException;
56
import javax.swing.text.Document;
57
import javax.swing.text.JTextComponent;
58
import org.netbeans.api.editor.mimelookup.MimeRegistration;
59
import org.netbeans.api.java.lexer.JavaTokenId;
60
import org.netbeans.api.java.project.JavaProjectConstants;
61
import org.netbeans.api.lexer.Token;
62
import org.netbeans.api.lexer.TokenHierarchy;
63
import org.netbeans.api.lexer.TokenSequence;
64
import org.netbeans.api.project.FileOwnerQuery;
65
import org.netbeans.api.project.Project;
66
import org.netbeans.api.project.ProjectUtils;
67
import org.netbeans.api.project.SourceGroup;
68
import org.netbeans.api.project.Sources;
69
import org.netbeans.editor.BaseDocument;
70
import org.netbeans.editor.Utilities;
71
import org.netbeans.lib.editor.hyperlink.spi.HyperlinkProviderExt;
72
import org.netbeans.lib.editor.hyperlink.spi.HyperlinkType;
73
import org.netbeans.modules.editor.NbEditorUtilities;
74
import org.openide.DialogDescriptor;
75
import org.openide.DialogDisplayer;
76
import org.openide.NotifyDescriptor;
77
import org.openide.cookies.EditCookie;
78
import org.openide.cookies.OpenCookie;
79
import org.openide.filesystems.FileObject;
80
import org.openide.loaders.DataObject;
81
import org.openide.loaders.DataObjectNotFoundException;
82
import org.openide.util.Exceptions;
83
84
/**
85
 * Hyperlink provider opening resources which are encoded in string literals
86
 * within java files.
87
 * <p>
88
 * For example: {@code "com/foo/Bar.java"} will be resolved in the source-roots
89
 * of
90
 * {@link JavaProjectConstants.SOURCES_TYPE_JAVA}, {@link JavaProjectConstants.SOURCES_TYPE_RESOURCES}, {@link JavaProjectConstants.SOURCES_HINT_TEST}
91
 * </p>
92
 * If there are multiple matches a dialog will pop up and let the user choose.
93
 *
94
 * @author markiewb
95
 */
96
@MimeRegistration(mimeType = "text/x-java", service = HyperlinkProviderExt.class)
97
public class ResourceHyperlinkProvider implements HyperlinkProviderExt {
98
99
    public static void openInEditor(FileObject fileToOpen) {
100
        DataObject fileDO;
101
        try {
102
            fileDO = DataObject.find(fileToOpen);
103
            if (fileDO != null) {
104
                EditCookie editCookie = fileDO.getLookup().lookup(EditCookie.class);
105
                if (editCookie != null) {
106
                    editCookie.edit();
107
                } else {
108
                    OpenCookie openCookie = fileDO.getLookup().lookup(OpenCookie.class);
109
                    if (openCookie != null) {
110
                        openCookie.open();
111
                    }
112
                }
113
            }
114
        } catch (DataObjectNotFoundException e) {
115
            Exceptions.printStackTrace(e);
116
        }
117
118
    }
119
120
    @Override
121
    public int[] getHyperlinkSpan(Document doc, int offset, HyperlinkType type) {
122
        ResultTO matches = findResources(doc, offset);
123
        if (matches.isValid()) {
124
            return new int[]{matches.startOffsetInLiteral, matches.endOffsetInLiteral};
125
        } else {
126
            return new int[]{-1, -1};
127
        }
128
    }
129
130
    @Override
131
    public Set<HyperlinkType> getSupportedHyperlinkTypes() {
132
        return EnumSet.of(HyperlinkType.GO_TO_DECLARATION);
133
    }
134
135
    @Override
136
    public String getTooltipText(Document doc, int offset, HyperlinkType type) {
137
        ResultTO result = findResources(doc, offset);
138
        if (!result.isValid()) {
139
            return null;
140
        }
141
142
        Collection<FileObject> findMatches = result.foundFiles;
143
        if (findMatches.size() < 0) {
144
            return null;
145
        }
146
        return MessageFormat.format("<html>Open <b>{0}</b>{1,choice,0#|1#|1< ({1} matches)}", result.linkTarget, findMatches.size());
147
    }
148
149
    @Override
150
    public boolean isHyperlinkPoint(Document document, int offset, HyperlinkType type) {
151
        ResultTO matches = findResources(document, offset);
152
        return matches.isValid();
153
    }
154
155
    @Override
156
    public void performClickAction(Document doc, int position, HyperlinkType type) {
157
        ResultTO matches = findResources(doc, position);
158
        if (matches.isValid()) {
159
            Collection<FileObject> foundMatches = matches.foundFiles;
160
            final Project project = FileOwnerQuery.getOwner(NbEditorUtilities.getFileObject(doc));
161
            FileObject fileToOpen = getSingleMatchOrAskForUserChoice(foundMatches, project);
162
163
            if (fileToOpen == null) {
164
//                StatusDisplayer.getDefault().setStatusText("Invalid path: " + findMatches.linkTarget);
165
                return;
166
            }
167
            openInEditor(fileToOpen);
168
        }
169
    }
170
171
    private Set<FileObject> findFiles(Document doc, String path) {
172
        //TODO cache the results for the same path, because checking for existence is an IO-operation (markiewb)
173
174
        //fallback to search in all source roots
175
        FileObject docFO = NbEditorUtilities.getFileObject(doc);
176
        Set<FileObject> matches = new HashSet<>();
177
178
        matches.addAll(getMatchingFilesFromSourceRoots(FileOwnerQuery.getOwner(docFO), path));
179
180
        FileObject fileInCurrentDirectory = getMatchingFileInCurrentDirectory(doc, path);
181
        if (null != fileInCurrentDirectory) {
182
            matches.add(fileInCurrentDirectory);
183
        }
184
        return matches;
185
    }
186
187
    private ResultTO findResources(Document document, int offset) {
188
        if (!(document instanceof BaseDocument)) {
189
            return ResultTO.createEmpty();
190
        }
191
192
        BaseDocument doc = (BaseDocument) document;
193
        JTextComponent target = Utilities.getFocusedComponent();
194
195
        if ((target == null) || (target.getDocument() != doc)) {
196
            return ResultTO.createEmpty();
197
        }
198
199
        try {
200
            TokenHierarchy<String> hi = TokenHierarchy.create(doc.getText(0, doc.getLength()), JavaTokenId.language());
201
            TokenSequence<JavaTokenId> ts = hi.tokenSequence(JavaTokenId.language());
202
203
            ts.move(offset);
204
            boolean lastTokenInDocument = !ts.moveNext();
205
            if (lastTokenInDocument) {
206
                // end of the document
207
                return ResultTO.createEmpty();
208
            }
209
            while (ts.token() == null || ts.token().id() == JavaTokenId.WHITESPACE) {
210
                ts.movePrevious();
211
            }
212
213
            Token<JavaTokenId> resourceToken = ts.offsetToken();
214
            if (null == resourceToken
215
                    || resourceToken.length() <= 2) {
216
                return ResultTO.createEmpty();
217
            }
218
219
            if (resourceToken.id() == JavaTokenId.STRING_LITERAL // identified must be string
220
                    && resourceToken.length() > 2) { // identifier must be longer than "" string
221
                int startOffset = resourceToken.offset(hi) + 1;
222
223
                final String wholeText = resourceToken.text().subSequence(1, resourceToken.length() - 1).toString();
224
225
                int endOffset = startOffset + wholeText.length();
226
                String linkTarget = wholeText;
227
228
//                StatusDisplayer.getDefault().setStatusText("Path :" + startOffset + "/" + endOffset + "/" + offset + "//" + (offset - startOffset) + "=" + innerSelectedText);
229
                Set<FileObject> findFiles = findFiles(doc, linkTarget);
230
                return ResultTO.create(startOffset, endOffset, linkTarget, findFiles);
231
            }
232
233
        } catch (BadLocationException ex) {
234
            Exceptions.printStackTrace(ex);
235
        }
236
        return ResultTO.createEmpty();
237
    }
238
239
    private FileObject getMatchingFileInCurrentDirectory(Document doc, String path) {
240
        FileObject docFO = NbEditorUtilities.getFileObject(doc);
241
        FileObject currentDir = docFO.getParent();
242
        return currentDir.getFileObject(path);
243
    }
244
245
    private List<FileObject> getMatchingFilesFromSourceRoots(Project p, String path) {
246
        List<SourceGroup> list = new ArrayList<>();
247
        List<FileObject> foundMatches = new ArrayList<>();
248
        final Sources sources = ProjectUtils.getSources(p);
249
        list.addAll(Arrays.asList(sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA)));
250
        list.addAll(Arrays.asList(sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_RESOURCES)));
251
        list.addAll(Arrays.asList(sources.getSourceGroups(JavaProjectConstants.SOURCES_HINT_TEST)));
252
        for (SourceGroup sourceGroup : list) {
253
            FileObject fileObject = sourceGroup.getRootFolder().getFileObject(path);
254
            if (fileObject != null) {
255
                foundMatches.add(fileObject);
256
            }
257
        }
258
        return foundMatches;
259
    }
260
261
    private FileObject getSingleMatchOrAskForUserChoice(Collection<FileObject> foundMatches, Project project) {
262
        if (foundMatches.size() == 1) {
263
            return foundMatches.iterator().next();
264
        }
265
        List<FileObject> indexedFilePaths = new ArrayList<>(foundMatches);
266
267
        if (foundMatches.size() >= 2) {
268
            List<String> collector = new ArrayList<>();
269
270
            for (FileObject fileObject : indexedFilePaths) {
271
                //convert absolute path to relative regarding the project
272
                String path1 = fileObject.getPath().substring(project.getProjectDirectory().getPath().length());
273
                collector.add(path1);
274
            }
275
276
            //TODO replace with floating listbox like "Open implementations" (markiewb)
277
            final JComboBox<String> jList = new JComboBox<>(collector.toArray(new String[collector.size()]));
278
            if (DialogDisplayer.getDefault().notify(new DialogDescriptor(jList, "Multiple files found. Please choose:")) == NotifyDescriptor.OK_OPTION) {
279
                return indexedFilePaths.get(jList.getSelectedIndex());
280
            }
281
        }
282
        return null;
283
    }
284
285
    private static class ResultTO {
286
287
        int startOffsetInLiteral;
288
        int endOffsetInLiteral;
289
290
        String linkTarget;
291
292
        ResultTO(int startOffset, int endOffset, String linkTarget, Collection<FileObject> foundFiles) {
293
            this.startOffsetInLiteral = startOffset;
294
            this.endOffsetInLiteral = endOffset;
295
            this.linkTarget = linkTarget;
296
            this.foundFiles = foundFiles;
297
        }
298
        Collection<FileObject> foundFiles;
299
300
        boolean isValid() {
301
            return !foundFiles.isEmpty();
302
        }
303
304
        static ResultTO createEmpty() {
305
            return new ResultTO(-1, -1, null, Collections.<FileObject>emptySet());
306
        }
307
308
        static ResultTO create(int startOffset, int endOffset, String linkTarget, Collection<FileObject> foundFiles) {
309
            return new ResultTO(startOffset, endOffset, linkTarget, foundFiles);
310
        }
311
312
    }
313
314
}

Return to bug 237902