# This patch file was generated by NetBeans IDE # Following Index: paths are relative to: D:\ws\main # This patch can be applied using context Tools: Patch action on respective folder. # It uses platform neutral UTF-8 encoding and \n newlines. # Above lines and this line are ignored by the patching process. Index: java.editor/nbproject/project.xml --- java.editor/nbproject/project.xml +++ java.editor/nbproject/project.xml @@ -228,6 +228,15 @@ + org.netbeans.modules.java.project + + + + 1 + 1.57 + + + org.netbeans.modules.java.source Index: java.editor/src/org/netbeans/modules/java/editor/hyperlink/ResourceHyperlinkProvider.java --- java.editor/src/org/netbeans/modules/java/editor/hyperlink/ResourceHyperlinkProvider.java +++ java.editor/src/org/netbeans/modules/java/editor/hyperlink/ResourceHyperlinkProvider.java @@ -0,0 +1,314 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2013 Sun Microsystems, Inc. + * Portions Copyrighted 2013 markiewb + */ +package org.netbeans.modules.java.editor.hyperlink; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.swing.JComboBox; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import org.netbeans.api.editor.mimelookup.MimeRegistration; +import org.netbeans.api.java.lexer.JavaTokenId; +import org.netbeans.api.java.project.JavaProjectConstants; +import org.netbeans.api.lexer.Token; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectUtils; +import org.netbeans.api.project.SourceGroup; +import org.netbeans.api.project.Sources; +import org.netbeans.editor.BaseDocument; +import org.netbeans.editor.Utilities; +import org.netbeans.lib.editor.hyperlink.spi.HyperlinkProviderExt; +import org.netbeans.lib.editor.hyperlink.spi.HyperlinkType; +import org.netbeans.modules.editor.NbEditorUtilities; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.cookies.EditCookie; +import org.openide.cookies.OpenCookie; +import org.openide.filesystems.FileObject; +import org.openide.loaders.DataObject; +import org.openide.loaders.DataObjectNotFoundException; +import org.openide.util.Exceptions; + +/** + * Hyperlink provider opening resources which are encoded in string literals + * within java files. + *

+ * For example: {@code "com/foo/Bar.java"} will be resolved in the source-roots + * of + * {@link JavaProjectConstants.SOURCES_TYPE_JAVA}, {@link JavaProjectConstants.SOURCES_TYPE_RESOURCES}, {@link JavaProjectConstants.SOURCES_HINT_TEST} + *

+ * If there are multiple matches a dialog will pop up and let the user choose. + * + * @author markiewb + */ +@MimeRegistration(mimeType = "text/x-java", service = HyperlinkProviderExt.class) +public class ResourceHyperlinkProvider implements HyperlinkProviderExt { + + public static void openInEditor(FileObject fileToOpen) { + DataObject fileDO; + try { + fileDO = DataObject.find(fileToOpen); + if (fileDO != null) { + EditCookie editCookie = fileDO.getLookup().lookup(EditCookie.class); + if (editCookie != null) { + editCookie.edit(); + } else { + OpenCookie openCookie = fileDO.getLookup().lookup(OpenCookie.class); + if (openCookie != null) { + openCookie.open(); + } + } + } + } catch (DataObjectNotFoundException e) { + Exceptions.printStackTrace(e); + } + + } + + @Override + public int[] getHyperlinkSpan(Document doc, int offset, HyperlinkType type) { + ResultTO matches = findResources(doc, offset); + if (matches.isValid()) { + return new int[]{matches.startOffsetInLiteral, matches.endOffsetInLiteral}; + } else { + return new int[]{-1, -1}; + } + } + + @Override + public Set getSupportedHyperlinkTypes() { + return EnumSet.of(HyperlinkType.GO_TO_DECLARATION); + } + + @Override + public String getTooltipText(Document doc, int offset, HyperlinkType type) { + ResultTO result = findResources(doc, offset); + if (!result.isValid()) { + return null; + } + + Collection findMatches = result.foundFiles; + if (findMatches.size() < 0) { + return null; + } + return MessageFormat.format("Open {0}{1,choice,0#|1#|1< ({1} matches)}", result.linkTarget, findMatches.size()); + } + + @Override + public boolean isHyperlinkPoint(Document document, int offset, HyperlinkType type) { + ResultTO matches = findResources(document, offset); + return matches.isValid(); + } + + @Override + public void performClickAction(Document doc, int position, HyperlinkType type) { + ResultTO matches = findResources(doc, position); + if (matches.isValid()) { + Collection foundMatches = matches.foundFiles; + final Project project = FileOwnerQuery.getOwner(NbEditorUtilities.getFileObject(doc)); + FileObject fileToOpen = getSingleMatchOrAskForUserChoice(foundMatches, project); + + if (fileToOpen == null) { +// StatusDisplayer.getDefault().setStatusText("Invalid path: " + findMatches.linkTarget); + return; + } + openInEditor(fileToOpen); + } + } + + private Set findFiles(Document doc, String path) { + //TODO cache the results for the same path, because checking for existence is an IO-operation (markiewb) + + //fallback to search in all source roots + FileObject docFO = NbEditorUtilities.getFileObject(doc); + Set matches = new HashSet<>(); + + matches.addAll(getMatchingFilesFromSourceRoots(FileOwnerQuery.getOwner(docFO), path)); + + FileObject fileInCurrentDirectory = getMatchingFileInCurrentDirectory(doc, path); + if (null != fileInCurrentDirectory) { + matches.add(fileInCurrentDirectory); + } + return matches; + } + + private ResultTO findResources(Document document, int offset) { + if (!(document instanceof BaseDocument)) { + return ResultTO.createEmpty(); + } + + BaseDocument doc = (BaseDocument) document; + JTextComponent target = Utilities.getFocusedComponent(); + + if ((target == null) || (target.getDocument() != doc)) { + return ResultTO.createEmpty(); + } + + try { + TokenHierarchy hi = TokenHierarchy.create(doc.getText(0, doc.getLength()), JavaTokenId.language()); + TokenSequence ts = hi.tokenSequence(JavaTokenId.language()); + + ts.move(offset); + boolean lastTokenInDocument = !ts.moveNext(); + if (lastTokenInDocument) { + // end of the document + return ResultTO.createEmpty(); + } + while (ts.token() == null || ts.token().id() == JavaTokenId.WHITESPACE) { + ts.movePrevious(); + } + + Token resourceToken = ts.offsetToken(); + if (null == resourceToken + || resourceToken.length() <= 2) { + return ResultTO.createEmpty(); + } + + if (resourceToken.id() == JavaTokenId.STRING_LITERAL // identified must be string + && resourceToken.length() > 2) { // identifier must be longer than "" string + int startOffset = resourceToken.offset(hi) + 1; + + final String wholeText = resourceToken.text().subSequence(1, resourceToken.length() - 1).toString(); + + int endOffset = startOffset + wholeText.length(); + String linkTarget = wholeText; + +// StatusDisplayer.getDefault().setStatusText("Path :" + startOffset + "/" + endOffset + "/" + offset + "//" + (offset - startOffset) + "=" + innerSelectedText); + Set findFiles = findFiles(doc, linkTarget); + return ResultTO.create(startOffset, endOffset, linkTarget, findFiles); + } + + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + return ResultTO.createEmpty(); + } + + private FileObject getMatchingFileInCurrentDirectory(Document doc, String path) { + FileObject docFO = NbEditorUtilities.getFileObject(doc); + FileObject currentDir = docFO.getParent(); + return currentDir.getFileObject(path); + } + + private List getMatchingFilesFromSourceRoots(Project p, String path) { + List list = new ArrayList<>(); + List foundMatches = new ArrayList<>(); + final Sources sources = ProjectUtils.getSources(p); + list.addAll(Arrays.asList(sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA))); + list.addAll(Arrays.asList(sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_RESOURCES))); + list.addAll(Arrays.asList(sources.getSourceGroups(JavaProjectConstants.SOURCES_HINT_TEST))); + for (SourceGroup sourceGroup : list) { + FileObject fileObject = sourceGroup.getRootFolder().getFileObject(path); + if (fileObject != null) { + foundMatches.add(fileObject); + } + } + return foundMatches; + } + + private FileObject getSingleMatchOrAskForUserChoice(Collection foundMatches, Project project) { + if (foundMatches.size() == 1) { + return foundMatches.iterator().next(); + } + List indexedFilePaths = new ArrayList<>(foundMatches); + + if (foundMatches.size() >= 2) { + List collector = new ArrayList<>(); + + for (FileObject fileObject : indexedFilePaths) { + //convert absolute path to relative regarding the project + String path1 = fileObject.getPath().substring(project.getProjectDirectory().getPath().length()); + collector.add(path1); + } + + //TODO replace with floating listbox like "Open implementations" (markiewb) + final JComboBox jList = new JComboBox<>(collector.toArray(new String[collector.size()])); + if (DialogDisplayer.getDefault().notify(new DialogDescriptor(jList, "Multiple files found. Please choose:")) == NotifyDescriptor.OK_OPTION) { + return indexedFilePaths.get(jList.getSelectedIndex()); + } + } + return null; + } + + private static class ResultTO { + + int startOffsetInLiteral; + int endOffsetInLiteral; + + String linkTarget; + + ResultTO(int startOffset, int endOffset, String linkTarget, Collection foundFiles) { + this.startOffsetInLiteral = startOffset; + this.endOffsetInLiteral = endOffset; + this.linkTarget = linkTarget; + this.foundFiles = foundFiles; + } + Collection foundFiles; + + boolean isValid() { + return !foundFiles.isEmpty(); + } + + static ResultTO createEmpty() { + return new ResultTO(-1, -1, null, Collections.emptySet()); + } + + static ResultTO create(int startOffset, int endOffset, String linkTarget, Collection foundFiles) { + return new ResultTO(startOffset, endOffset, linkTarget, foundFiles); + } + + } + +}