# HG changeset patch # Parent 8261224fd6078bd4a922685f57e8bae5f8995c0c diff --git a/ant.grammar/nbproject/project.xml b/ant.grammar/nbproject/project.xml --- a/ant.grammar/nbproject/project.xml +++ b/ant.grammar/nbproject/project.xml @@ -100,7 +100,7 @@ 2 - 1.9.0 + 1.28.0 diff --git a/ant.grammar/src/org/netbeans/modules/ant/grammar/AntGrammar.java b/ant.grammar/src/org/netbeans/modules/ant/grammar/AntGrammar.java --- a/ant.grammar/src/org/netbeans/modules/ant/grammar/AntGrammar.java +++ b/ant.grammar/src/org/netbeans/modules/ant/grammar/AntGrammar.java @@ -45,6 +45,7 @@ package org.netbeans.modules.ant.grammar; import java.io.IOException; +import java.net.URI; import java.net.URL; import java.text.Collator; import java.util.ArrayList; @@ -66,6 +67,7 @@ import org.netbeans.modules.xml.api.model.GrammarResult; import org.netbeans.modules.xml.api.model.HintContext; import org.netbeans.modules.xml.spi.dom.AbstractNode; +import org.netbeans.modules.xml.api.model.DescriptionSource; import org.openide.filesystems.FileObject; import org.openide.filesystems.URLMapper; import org.openide.util.Enumerations; @@ -395,24 +397,22 @@ case PROJECT: case TARGET: list.add(new MyElement(element) { - @Override public String getDescription() { + private URL manpage; + + { ClassLoader cl = Lookup.getDefault().lookup(ClassLoader.class); - URL manpage = cl.getResource("org/apache/tools/ant/module/docs/ant-docs/Tasks/" + element + ".html"); + manpage = cl.getResource("org/apache/tools/ant/module/docs/ant-docs/Tasks/" + element + ".html"); if (manpage == null) { manpage = cl.getResource("org/apache/tools/ant/module/docs/ant-docs/Types/" + element + ".html"); } - if (manpage != null) { - FileObject f = URLMapper.findFileObject(manpage); - if (f != null) { - try { - return new String(f.asBytes(), FileEncodingQuery.getEncoding(f)); - } catch (IOException x) { - LOG.log(Level.INFO, "Could not load " + manpage, x); - } - } - } - return null; } + + @Override + public URL getContentURL() { + return manpage; + } + + }); break; default: @@ -827,7 +827,7 @@ } - private static class MyElement extends AbstractResultNode implements Element { + private static class MyElement extends AbstractResultNode implements Element, DescriptionSource { private String name; @@ -850,7 +850,21 @@ public @Override String toString() { return name; } + + @Override + public DescriptionSource resolveLink(String link) { + return null; + } + + @Override + public boolean isExternal() { + return false; + } + @Override + public URL getContentURL() { + return null; + } } private static class MyAttr extends AbstractResultNode implements Attr { diff --git a/xml.core/nbproject/project.properties b/xml.core/nbproject/project.properties --- a/xml.core/nbproject/project.properties +++ b/xml.core/nbproject/project.properties @@ -42,7 +42,7 @@ javac.compilerargs=-Xlint -Xlint:-serial javac.source=1.6 -spec.version.base=1.27.0 +spec.version.base=1.28.0 is.autoload=true javadoc.packages=\ diff --git a/xml.core/nbproject/project.xml b/xml.core/nbproject/project.xml --- a/xml.core/nbproject/project.xml +++ b/xml.core/nbproject/project.xml @@ -50,6 +50,15 @@ org.netbeans.modules.xml.core + org.netbeans.api.annotations.common + + + + 1 + 1.11 + + + org.netbeans.api.xml diff --git a/xml.core/src/org/netbeans/modules/xml/api/model/DescriptionSource.java b/xml.core/src/org/netbeans/modules/xml/api/model/DescriptionSource.java new file mode 100644 --- /dev/null +++ b/xml.core/src/org/netbeans/modules/xml/api/model/DescriptionSource.java @@ -0,0 +1,106 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2011 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 2011 Sun Microsystems, Inc. + */ +package org.netbeans.modules.xml.api.model; + +import java.net.URI; +import java.net.URL; +import org.netbeans.api.annotations.common.CheckForNull; + +/** + * Optional mixin interface, which can be implemented together with the + * {@link GrammarResult} interface on the returned completion item. + * It allows to resolve links and provide description contents. + *

+ * Implementation may return {@code null} from {@link #getDescription} to + * indicate that the system should load contents from the URL supplied by {@link #getContentURL()}. + *

+ * The implementation may choose to provide the content URL to open within the IDE + * or in an external browser. Any links will be resolved relatively to that URL. + * For special cases, the implementation may resolve a link to another instance + * of DescriptionSource by implementing {@link #resolveLink}. + *

+ * If both {@link #getDescription} and {@link #getContentURL} return null, there's + * no description at all for the {@link GrammarResult}. + * + * @author sdedic + * @since 1.28 + */ +public interface DescriptionSource { + /** + * Provides text of the description. If the method returns null, the + * IDE will try to load contents of the {@link #getContentURL()} and use that + * as a description. + * + * @return Description contents or {@code null}, if contents should be loaded + * from {@link #getContentURL}. + */ + @CheckForNull + public String getDescription(); + + /** + * True, if the description can be opened by an external browser, following the + * {@link #getContentURL}. + * + * @return true, if the content URL can be used outside of the IDE + */ + public boolean isExternal(); + + /** + * Returns URL for the content, so it can be retrieved. If this URL is provided, + * any link not resolved by {@link #resolveLink} will be treated + * as relative to this content URL. + * + * @return URL of the description content + */ + @CheckForNull + public URL getContentURL(); + + /** + * Resolves a link in the documentation to another DescriptionSource. Can + * return null, if the link cannot be resolved. + * + * @param link link found in the text + * @return resolved description, or {@code null} + */ + @CheckForNull + public DescriptionSource resolveLink(String link); +} diff --git a/xml.core/src/org/netbeans/modules/xml/api/model/GrammarResult.java b/xml.core/src/org/netbeans/modules/xml/api/model/GrammarResult.java --- a/xml.core/src/org/netbeans/modules/xml/api/model/GrammarResult.java +++ b/xml.core/src/org/netbeans/modules/xml/api/model/GrammarResult.java @@ -46,6 +46,7 @@ import org.w3c.dom.Node; import javax.swing.Icon; +import org.netbeans.api.annotations.common.CheckForNull; /** * It represents additonal properties of a result option. @@ -77,9 +78,28 @@ String getDisplayName(); /** + * Returns contents of a description, text suitable for displaying as a tooltip + * that simplifies decision. {@code null} may be returned if no description is available. + * The decription is interpreted as HTML markup. If the markup contains relative + * links or special URIs, implement also {@link DescriptionSource} to resolve + * those links. + *

+ * If {@link DescriptionSource} is implemented on the same object, this method + * may return null to indicate the content should be loaded by the infrastructure + * from the URL returned by {@link DescriptionSource#getContentURL()}. If both + * {@code getDescription()} and {@code DescriptionSource.getContentURL()} return + * null, no description is displayed in the tooltip. + *

+ * Implementors may prefer implementing the {@code DescriptionSource} and + * loading from the {@link DescriptionSource#getContentURL()} if the + * description resides in a separate file included in the JAR or in the XML layer. + * * @return provide additional information simplifing decision - * (suitable for tooltip) or null + * (suitable for tooltip) or {@code null}. + * + * @since 1.28 - DescriptionSource extension */ + @CheckForNull String getDescription(); /** diff --git a/xml.text/nbproject/project.xml b/xml.text/nbproject/project.xml --- a/xml.text/nbproject/project.xml +++ b/xml.text/nbproject/project.xml @@ -184,12 +184,21 @@ + org.netbeans.modules.queries + + + + 1 + 1.25 + + + org.netbeans.modules.xml.core 2 - 1.14 + 1.28 diff --git a/xml.text/src/org/netbeans/modules/xml/text/completion/XMLResultItem.java b/xml.text/src/org/netbeans/modules/xml/text/completion/XMLResultItem.java --- a/xml.text/src/org/netbeans/modules/xml/text/completion/XMLResultItem.java +++ b/xml.text/src/org/netbeans/modules/xml/text/completion/XMLResultItem.java @@ -49,7 +49,11 @@ import java.awt.Font; import java.awt.Graphics; import java.awt.event.KeyEvent; +import java.io.IOException; +import java.net.MalformedURLException; import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.Action; import javax.swing.text.*; import javax.swing.Icon; @@ -57,13 +61,18 @@ import org.netbeans.editor.*; import javax.swing.JLabel; import org.netbeans.api.editor.completion.Completion; +import org.netbeans.api.queries.FileEncodingQuery; import org.netbeans.modules.xml.api.model.GrammarResult; +import org.netbeans.modules.xml.api.model.DescriptionSource; import org.netbeans.modules.xml.text.api.XMLDefaultTokenContext; import org.netbeans.modules.xml.text.syntax.XMLSyntaxSupport; import org.netbeans.spi.editor.completion.CompletionDocumentation; import org.netbeans.spi.editor.completion.CompletionItem; import org.netbeans.spi.editor.completion.CompletionResultSet; import org.netbeans.spi.editor.completion.CompletionTask; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.URLMapper; +import org.openide.util.Exceptions; /** * This class carries result information required by NetBeans Editor module. @@ -72,6 +81,7 @@ * @author Sandeep Randhawa */ class XMLResultItem implements CompletionItem { + private static final Logger LOG = Logger.getLogger(XMLResultItem.class.getName()); // text to be diplayed to user public final String displayText; @@ -304,25 +314,160 @@ protected CompletionTask doCreateDocumentationTask(final GrammarResult res) { return new CompletionTask() { public void query(CompletionResultSet resultSet) { - if (res != null && res.getDescription() != null) { - resultSet.setDocumentation(new Docum(res.getDescription())); - + CompletionDocumentation cd = create(); + if (cd != null) { + resultSet.setDocumentation(cd); } resultSet.finish(); } public void refresh(CompletionResultSet resultSet) { - if (res != null && res.getDescription() != null) { - resultSet.setDocumentation(new Docum(res.getDescription())); - } - resultSet.finish(); + query(resultSet); } public void cancel() {} + + + private CompletionDocumentation create() { + String doc; + + doc = res.getDescription(); + if (!(res instanceof DescriptionSource)) { + if (doc == null) { + return null; + } + return new Docum(doc); + } else { + DescriptionSource ds = (DescriptionSource)res; + if (doc == null && ds.getContentURL() == null) { + return null; + } + return new ExtDocum(ds, doc); + } + } }; } + + /** + * Extended documentation, based on the {@link DescriptionSource} SPI. + */ + private static class ExtDocum extends URLDocum implements CompletionDocumentation { + private DescriptionSource src; + private String doc; + + ExtDocum(DescriptionSource src, String doc) { + super(src.getContentURL(), src.isExternal()); + this.src = src; + this.doc = doc; + } + + @Override + public String getText() { + if (doc == null) { + doc = src.getDescription(); + if (doc == null) { + doc = super.getText(); + } + } + return doc; + } + + @Override + public CompletionDocumentation resolveLink(String link) { + try { + DescriptionSource target = src.resolveLink(link); + if (target != null) { + return new ExtDocum(target, null); + } + + URL base = src.getContentURL(); + if (base == null) { + // sorry, cannot resolve. + return null; + } + + URL targetURL = new URL(base, link); + + // leave the VM as soon as possible. This hack uses URLMappers + // to find out whether URL (converted to FO and back) can be + // represented outside the VM + boolean external = true; + FileObject f = URLMapper.findFileObject(targetURL); + if (f != null) { + external = URLMapper.findURL(f, URLMapper.EXTERNAL) != null; + } + return new URLDocum(targetURL, external); + } catch (MalformedURLException ex) { + Exceptions.printStackTrace(ex); + return null; + } + } + + @Override + public Action getGotoSourceAction() { + return null; + } + } + + /** + * Pure URL documentation item. Resolves links, if original URL was able + * to open externally, the derived URLs do it as well. + */ + private static class URLDocum implements CompletionDocumentation { + URL content; + boolean external; + + URLDocum(URL content, boolean external) { + this.content = content; + this.external = external; + } + + URLDocum(boolean external) { + this.external = external; + } + + @Override + public Action getGotoSourceAction() { + return null; + } + + @Override + public String getText() { + if (content == null) { + return null; + } + FileObject f = URLMapper.findFileObject(content); + if (f != null) { + try { + return new String(f.asBytes(), FileEncodingQuery.getEncoding(f)); + } catch (IOException x) { + LOG.log(Level.INFO, "Could not load " + content, x); + } + } + return null; + } + + @Override + public URL getURL() { + return external ? content : null; + } + + @Override + public CompletionDocumentation resolveLink(String link) { + if (content == null) { + return null; + } + try { + return new URLDocum(new URL(content, link), external); + } catch (MalformedURLException ex) { + Exceptions.printStackTrace(ex); + return null; + } + } + + } private static class Docum implements CompletionDocumentation { private String doc; - + private Docum(String doc) { this.doc = doc; } diff --git a/xml.text/test/unit/src/org/netbeans/modules/xml/text/completion/ElementResultItemDocumentationTest.java b/xml.text/test/unit/src/org/netbeans/modules/xml/text/completion/ElementResultItemDocumentationTest.java new file mode 100644 --- /dev/null +++ b/xml.text/test/unit/src/org/netbeans/modules/xml/text/completion/ElementResultItemDocumentationTest.java @@ -0,0 +1,382 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2011 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 2011 Sun Microsystems, Inc. + */ +package org.netbeans.modules.xml.text.completion; + +import java.lang.reflect.Constructor; +import java.net.URL; +import javax.swing.Icon; +import org.netbeans.junit.NbTestCase; +import org.netbeans.junit.NbTestSuite; + +import org.netbeans.modules.editor.completion.CompletionImpl; +import org.netbeans.modules.editor.completion.CompletionResultSetImpl; +import org.netbeans.modules.xml.api.EncodingUtil; +import org.netbeans.modules.xml.api.model.DescriptionSource; +import org.netbeans.modules.xml.api.model.GrammarResult; +import org.netbeans.modules.xml.spi.dom.AbstractNode; +import org.netbeans.spi.editor.completion.CompletionDocumentation; +import org.netbeans.spi.editor.completion.CompletionProvider; +import org.netbeans.spi.editor.completion.CompletionResultSet; +import org.netbeans.spi.editor.completion.CompletionTask; + +/** + * Checks behaviour of {@link GrammarResult} support in the ElementResultItem. + * plain CompletionDocumentation must work unchanged. If mixin interface is implemented, + * explicit values (content, resolved links) must take precedence. + *

+ * If contentURL is provided, links must be resolved and content loaded by the + * ElementResultItem support even though the supplied CompletionDocumentation itself does not + * provide content/links. + * + * @author sdedic + */ +public class ElementResultItemDocumentationTest extends NbTestCase { + + public static NbTestSuite suite() { + NbTestSuite suite = new NbTestSuite(); + suite.addTestSuite(ElementResultItemDocumentationTest.class); + return suite; + } + + public ElementResultItemDocumentationTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + private CompletionResultSetImpl rsImpl; + + + private CompletionResultSet resultSetFor(CompletionTask task, int type) throws Exception { + rsImpl = createCompletionResultImpl(task, type); + return rsImpl.getResultSet(); + } + + private CompletionResultSetImpl createCompletionResultImpl(CompletionTask task, int type) throws Exception { + Class clazz = CompletionResultSetImpl.class; + Constructor ctor = clazz.getDeclaredConstructor(CompletionImpl.class, Object.class, CompletionTask.class, Integer.TYPE); + ctor.setAccessible(true); + return (CompletionResultSetImpl)ctor.newInstance(CompletionImpl.get(), "", task, type); + } + + /** + * Check the previou state, result items without the mixin interface. + * Should return the description 'as is' + * + * @throws Exception + */ + private static final String PLAIN_DESCRIPTION_TEXT = "Plain description"; + + public void testPlainContent() throws Exception { + MockGrammarResult r = new MockGrammarResult(); + + r.setDescription(PLAIN_DESCRIPTION_TEXT); + ElementResultItem item = new ElementResultItem(0, r); + CompletionTask task = item.doCreateDocumentationTask(r); + + CompletionResultSet rs = resultSetFor(task, CompletionProvider.DOCUMENTATION_QUERY_TYPE); + task.query(rs); + + assertTrue(rs.isFinished()); + + assertEquals(PLAIN_DESCRIPTION_TEXT, rsImpl.getDocumentation().getText()); + + // should return null from the url. + assertNull(rsImpl.getDocumentation().getURL()); + + // should not be able to resolve links + assertNull(rsImpl.getDocumentation().resolveLink("link")); + } + + private URL createResourceName(String n) { + return ElementResultItemDocumentationTest.class.getResource(n); + } + + private CompletionDocumentation createDocResourceResultSet(MockUrlGrammarResult r) throws Exception { + if (r == null) { + r = new MockUrlGrammarResult(); + r.setContentURL(createResourceName("res/docResource.html")); + r.setExternal(true); + } + + ElementResultItem item = new ElementResultItem(0, r); + CompletionTask task = item.doCreateDocumentationTask(r); + CompletionResultSet rs = resultSetFor(task, CompletionProvider.DOCUMENTATION_QUERY_TYPE); + task.query(rs); + + assertTrue(rs.isFinished()); + + return rsImpl.getDocumentation(); + } + + /** + * Checks that custom contents overrides the one supplied by XMLResultItem + * + * @throws Exception + */ + public void testCustomContent() throws Exception { + URL res = EncodingUtil.class.getResource("/org/netbeans/modules/xml/core/resources/Bundle.properties"); + MockUrlGrammarResult test = new MockUrlGrammarResult(); + test.setContentURL(res); + test.setExternal(false); + test.setDescription(PLAIN_DESCRIPTION_TEXT); + + CompletionDocumentation doc = createDocResourceResultSet(test); + assertNull(doc.getURL()); + assertEquals("Invalid content", PLAIN_DESCRIPTION_TEXT, doc.getText()); + } + + /** + * Checks documentation bundled in a JAR - not openable by external browser. + * Need a resource within a JAR + * + * @throws Exception + */ + public void testInternalUrlDocumentation() throws Exception { + URL res = EncodingUtil.class.getResource("/org/netbeans/modules/xml/core/resources/Bundle.properties"); + MockUrlGrammarResult test = new MockUrlGrammarResult(); + test.setContentURL(res); + test.setExternal(false); + + CompletionDocumentation doc = createDocResourceResultSet(test); + assertNull(doc.getURL()); + assertTrue(doc.getText().contains("OpenIDE-Module-Name=XML Core")); + + // check that relative links still resolve to internal doc + CompletionDocumentation internal = doc.resolveLink("mf-layer.xml"); + assertNotNull("Relative links must be resolvable", internal); + assertNull("Relative links cannot be opened in extbrowser", doc.getURL()); + assertTrue("Content must be accessible", + internal.getText().contains("org-netbeans-modules-xml-dtd-grammar-DTDGrammarQueryProvider.instance")); + + // check that absolute links resolve to external doc + CompletionDocumentation external = doc.resolveLink("http://www.netbeans.org"); + assertNotNull("Absolute links must be resolvable", external); + assertEquals("Absolute links must have URL", external.getURL(), new URL("http://www.netbeans.org")); + } + + /** + * Checks that file-based documentation reverts to external as soon as possible + * @throws Exception + */ + public void testFileDocumentation() throws Exception { + URL res = EncodingUtil.class.getResource("/org/netbeans/modules/xml/core/resources/Bundle.properties"); + MockUrlGrammarResult test = new MockUrlGrammarResult(); + test.setContentURL(res); + test.setExternal(false); + + CompletionDocumentation doc = createDocResourceResultSet(test); + assertNull(doc.getURL()); + assertTrue("Invalid content", doc.getText().contains("OpenIDE-Module-Name=XML Core")); + + // check that resolve of file-based URL turns the doc to external: + + URL url = createResourceName("res/docResource.html"); + CompletionDocumentation file = doc.resolveLink(url.toString()); + assertNotNull(file); + assertEquals("URL must be openable in browser", url, file.getURL()); + assertTrue("Invalid content of the linked doc", file.getText().startsWith("This is an URL resource with relative link and +absolute link diff --git a/xml.text/test/unit/src/org/netbeans/modules/xml/text/completion/res/relativeLink1.html b/xml.text/test/unit/src/org/netbeans/modules/xml/text/completion/res/relativeLink1.html new file mode 100644 --- /dev/null +++ b/xml.text/test/unit/src/org/netbeans/modules/xml/text/completion/res/relativeLink1.html @@ -0,0 +1,1 @@ +Resource referenced by relative link \ No newline at end of file