--- a/api.intent/arch.xml +++ a/api.intent/arch.xml @@ -0,0 +1,1176 @@ + + +]> + + + + &api-questions; + + + +

+ Intent is an description of some intended operation. It is specified + by action type (String) and a URI. When some Intent is executed, + proper handler is chosen and it performs the actual operation. +

+

+ Intents provide loose coupling between modules that may request an + operation and modules that can perform it. They can be easily + serialized, so they are suitable for distributed heterogenous + systems. +

+ + +

+ API for describing and executing intended operations. +

+
+ +

+ SPI for handlers that are able to invoke proper operation for + specified intents. +

+
+
+ + + + + +

+ The code is checked by unit tests. +

+
+ + + + + +

+ Done. +

+
+ + + + + + +
+    Future<Object> result = new Intent(Intent.ACTION_VIEW, new URI("scheme://path/")).execute();
+    Object value = result.get();
+            
+
+ + +
+    new Intent(Intent.ACTION_VIEW, new URI("scheme://path/")).execute(new Callback() {
+      void success(Object result) {
+        // use the result somehow
+      }
+      void failure(Exception exception) {
+        // report the failure somehow
+      }
+    });
+            
+
+ + +
+    @IntentHandlerRegistration(
+                displayName = "Show my item in MyEditor",
+                position = 800,
+                uriPattern = "myscheme://.*",
+                actions = {Intent.ACTION_VIEW, Intent.ACTION_EDIT}
+    )
+    public static Object handleIntent(Intent intent) {
+        SomeType result = parseAndPerformIntentSomehow(intent);
+        return result;
+    }
+            
+
+ + +
+    @IntentHandlerRegistration(
+                displayName = "Show my item in MyEditor",
+                position = 800,
+                uriPattern = "myscheme://.*",
+                actions = "*"
+    )
+    public static void handleIntent(final Intent intent, final Result result) {
+        // Move the execution to another thread. Do not wait for the result
+        // here, just pass the result object.
+        EventQueue.invokeLater(new Runnable() {
+            public void run() {
+                try {
+                    Object result = doSomethingInEDT(intent);
+                    result.setResult(e);
+                } catch (Exception e) {
+                    result.setException(e);
+                }
+            }
+        });
+    }
+            
+
+
+ + + + + +

+ This module provides a contract between API clients that can express + some intention to invoke an operation and SPI providers that can + handle that intention. +

+

+ This is useful in client-server environments, where the intention + can be constructed on server-side, but handled on client-side. The + objects that describe the intention should be easy to construct, + transfer and interpret. +

+
+ + + + + + + + + + + + +

+ No deprecation needed. +

+
+ + + + + +

+ Yes. +

+
+ + + + + +

+ No standards. +

+
+ + + + + +

+ No settings are read or written. +

+
+ + + + + +

+ 1.6 +

+
+ + + + + +

+ JRE +

+
+ + + + + + + + + + + + +

+ No non-NB dependencies. +

+
+ + + + + +

+ Any platform. +

+
+ + + + + +

+ Standard module dependency is sufficient. +

+
+ + + + + +

+ Just module JAR. +

+
+ + + + + +

+ Yes. +

+
+ + + + + +

+ Only API and SPI packages are public. +

+
+ + + + + +

+ Installation location does not matter. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ The API is threadsafe. SPI implementations should ensure proper + synchronization. +

+
+ + + + + +

+ No clipboard access. +

+
+ + + + + +

+ No Drag & Drop support. +

+
+ + + + + +

+ No files are read or written by this module. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ The annotation processor is registered using ServiceProvider. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ Very little memory consumed. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ Code in the module is very simple. Performance is incluenced mosly + by SPI implementations, which should run quite quickly. +

+
+ + + + + +

+ No performance criteria are enforced. The plugged-in code is invoked + by a dedicated executor. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ +
--- a/api.intent/build.xml +++ a/api.intent/build.xml @@ -0,0 +1,5 @@ + + + Builds, tests, and runs the project org.netbeans.api.intent + + --- a/api.intent/manifest.mf +++ a/api.intent/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +AutoUpdate-Show-In-Client: true +OpenIDE-Module: org.netbeans.api.intent +OpenIDE-Module-Localizing-Bundle: org/netbeans/api/intent/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + --- a/api.intent/nbproject/project.properties +++ a/api.intent/nbproject/project.properties @@ -0,0 +1,4 @@ +is.autoload=true +javac.source=1.7 +javac.compilerargs=-Xlint -Xlint:-serial +javadoc.arch=${basedir}/arch.xml --- a/api.intent/nbproject/project.xml +++ a/api.intent/nbproject/project.xml @@ -0,0 +1,61 @@ + + + org.netbeans.modules.apisupport.project + + + org.netbeans.api.intent + + + org.netbeans.api.annotations.common + + + + 1 + 1.25 + + + + org.openide.filesystems + + + + 9.1 + + + + org.openide.util + + + + 9.3 + + + + org.openide.util.lookup + + + + 8.26 + + + + + + unit + + org.netbeans.libs.junit4 + + + + org.netbeans.modules.nbjunit + + + + + + org.netbeans.api.intent + org.netbeans.spi.intent + + + + --- a/api.intent/src/org/netbeans/api/intent/Bundle.properties +++ a/api.intent/src/org/netbeans/api/intent/Bundle.properties @@ -0,0 +1,7 @@ +OpenIDE-Module-Display-Category=Infrastructure +OpenIDE-Module-Long-Description=\ + API for invoking intended operations (described by suitable type of object, e.g. \ + URI) by some of registered handlers.\n\ + SPI for handlers of possible intended operations. +OpenIDE-Module-Name=Intent API +OpenIDE-Module-Short-Description=Performing intended operations by suitable handlers --- a/api.intent/src/org/netbeans/api/intent/Callback.java +++ a/api.intent/src/org/netbeans/api/intent/Callback.java @@ -0,0 +1,72 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 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 2014 Sun Microsystems, Inc. + */ +package org.netbeans.api.intent; + +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; + +/** + * Callback invoked when an intent action has finished. It is run in a dedicated + * thread in background. + * + * @see Intent#execute(org.netbeans.api.intent.Callback) + * + * @author jhavlin + */ +public interface Callback { + + /** + * Invoked when the intent action has completed successfully. The type of + * result depends on implementation of chosen intent handler, it can be + * null. + * + * @param result Result value. + */ + public void success(@NullAllowed Object result); + + /** + * Invoked when the intent action has failed. + * + * @param exception Encountered exception. + */ + public void failure(@NonNull Exception exception); +} --- a/api.intent/src/org/netbeans/api/intent/Intent.java +++ a/api.intent/src/org/netbeans/api/intent/Intent.java @@ -0,0 +1,290 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 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 2014 Sun Microsystems, Inc. + */ +package org.netbeans.api.intent; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.modules.intent.CallbackResult; +import org.netbeans.modules.intent.IntentHandler; +import org.netbeans.modules.intent.SettableResult; +import org.netbeans.spi.intent.Result; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Parameters; + +/** + * Description of some intended operation. The operation is described by an + * action and a URI. + * + *

+ * If the intent is executed, proper registered handler is chosen to perform the + * actual operation. + *

+ *

+ * For example, the following code can be used to open a file in editor (if the + * environment is suitable for such operation). + *

+ * + * new Intent(Intent.ACTION_VIEW, new URI("file://path/file.txt")).execute(); + * + * + * @author jhavlin + */ +public final class Intent { + + private static final Logger LOG = Logger.getLogger(Intent.class.getName()); + + /** + * Standard VIEW action type. + */ + public static final String ACTION_VIEW = "VIEW"; //NOI18N + + /** + * Standard EDIT action type. + */ + public static final String ACTION_EDIT = "EDIT"; //NOI18N + + private final String action; + private final URI uri; + + /** + * Constructor for an intended operation. + * + * @param action Action type to perform. It is recommended to use either + * standard actions predefined in Intent class (see {@link #ACTION_EDIT}, + * {@link #ACTION_VIEW}), or strings similar to fully qualified field names + * (e.g. "org.some.package.ClassName.ACTION_CUSTOM"). + * + * @param uri URI specifying the operation. + */ + public Intent(@NonNull String action, @NonNull URI uri) { + Parameters.notNull("action", action); + Parameters.notNull("uri", uri); + this.action = action; + this.uri = uri; + } + + /** + * Get action type. + * + * @return The action type. + */ + public @NonNull String getAction() { + return action; + } + + /** + * Get URI specifying this intent. + * + * @return The URI. + */ + public @NonNull URI getUri() { + return uri; + } + + /** + * Execute the intent. The operation will be run asynchronously. + *

+ * If the result is ignored, it's recommended to use + * {@code intent.execute(null);} + *

+ * + * @return {@link Future} Future for result of the action. The type of + * result depends on implementation of chosen intent handler, it can be + * null. + */ + public @NonNull Future execute() { + return IntentHandler.RP.submit(new Callable() { + + @Override + public Object call() throws Exception { + SettableResult sr = new SettableResult(); + invoke(Intent.this, sr); + if (sr.getException() != null) { + throw sr.getException(); + } + return sr.getResult(); + } + }); + } + + /** + * Execute the intent. The operation will be run asynchronously. + * + * @param callback Callback object that will be notified when the execution + * completes. If callback is null, the result will be ignored. + */ + public void execute(@NullAllowed final Callback callback) { + IntentHandler.RP.post(new Runnable() { + + @Override + public void run() { + invoke(Intent.this, callback == null + ? null + : new CallbackResult(callback)); + } + }); + } + + /** + * Get available actions for the intent. + * + * @return Immutable set of available actions, sorted by priority. + */ + public @NonNull SortedSet getIntentActions() { + SortedSet intentHandlers = getIntentHandlers(this); + SortedSet actions = new TreeSet<>( + new Comparator() { + + @Override + public int compare(IntentAction o1, IntentAction o2) { + return o1.getPosition() - o2.getPosition(); + } + }); + for (IntentHandler ih : intentHandlers) { + actions.add(new IntentAction(this, ih)); + } + return Collections.unmodifiableSortedSet(actions); + } + + private static SortedSet getIntentHandlers( + Intent intent) { + + FileObject f = FileUtil.getConfigFile("Services/Intent/Handlers"); + if (f == null) { + return null; + } + SortedSet candidates = new TreeSet<>(); + for (FileObject fo : f.getChildren()) { + if ("instance".equals(fo.getExt())) { + Object pattern = fo.getAttribute("uriPattern"); + Object displayName = fo.getAttribute("displayName"); + Object position = fo.getAttribute("position"); + Object actions = fo.getAttribute("actions"); + if (pattern instanceof String && displayName instanceof String + && position instanceof Integer + && actions instanceof String) { + if (canSupport((String) pattern, (String) actions, intent)) { + try { + IntentHandler ih = FileUtil.getConfigObject( + fo.getPath(), IntentHandler.class); + candidates.add(ih); + } catch (Exception e) { + LOG.log(Level.INFO, + "Cannot instantiate handler for " //NOI18N + + fo.getPath(), e); + } + } + } else { + LOG.log(Level.FINE, "Invalid URI handler {0}", fo.getPath()); + } + } + } + return candidates; + } + + /** + * Check whether an intent is supported by a handler specified by a URI + * pattern and action list. + * + * @param uriPattern Pattern for the URI. + * @param actions Comma-separated actions. + * @param intent Intent to check. + * @return True if the intent matches the URI pattern and action list. + */ + private static boolean canSupport(String uriPattern, String actions, + Intent intent) { + Pattern p = Pattern.compile(uriPattern); + if (p.matcher(intent.getUri().toString()).matches()) { + if ("*".equals(actions)) { + return true; + } else { + List actionList = Arrays.asList(actions.split(",")); + return actionList.contains(intent.getAction()); + } + } else { + return false; + } + } + + private static void invoke(Intent intent, Result resultOrNull) { + + Throwable lastException = null; + boolean handled = false; + + for (IntentHandler h : getIntentHandlers(intent)) { + try { + h.handle(intent, resultOrNull); + handled = true; + break; + } catch (Exception e) { + lastException = e; + LOG.log(Level.WARNING, null, e); + } + } + if (!handled) { + if (resultOrNull != null) { + resultOrNull.setException(lastException == null + ? new NoAvailableHandlerException(intent) + : new NoAvailableHandlerException(intent, lastException)); + } + LOG.log(Level.INFO, "Intent {0} cannot be handled", intent);//NOI18N + } + } + + @Override + public String toString() { + return "Intent{" + "action=" + action + ", uri=" + uri + '}'; + } +} --- a/api.intent/src/org/netbeans/api/intent/IntentAction.java +++ a/api.intent/src/org/netbeans/api/intent/IntentAction.java @@ -0,0 +1,134 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 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 2014 Sun Microsystems, Inc. + */ +package org.netbeans.api.intent; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.modules.intent.CallbackResult; +import org.netbeans.modules.intent.IntentHandler; +import org.netbeans.modules.intent.SettableResult; +import org.netbeans.spi.intent.Result; + +/** + * Actual action for an Intent. Pair of an Intent and one of its handlers. + * + * @see Intent#getIntentActions() + * @author jhavlin + */ +public final class IntentAction { + + private final Intent intent; + private final IntentHandler delegate; + + IntentAction(Intent intent, IntentHandler delegate) { + this.intent = intent; + this.delegate = delegate; + } + + int getPosition() { + return delegate.getPosition(); + } + + /** + * Execute the intent action. The operation will be run asynchronously. + * + * @param callback Callback object that will be notified when the execution + * completes. If callback is null, the result will be ignored. + */ + public void execute(@NullAllowed final Callback callback) { + IntentHandler.RP.post(new Runnable() { + @Override + public void run() { + Result result = callback == null + ? null + : new CallbackResult(callback); + delegate.handle(intent, result); + } + }); + } + + /** + * Execute the intent action. The operation will be run asynchronously. + *

+ * If the result is ignored, it's recommended to use + * {@code intentAction.execute(null);} + *

+ * + * @return Future for result of the action. The type of result depends on + * implementation of chosen intent handler, it can be null. + */ + public @NonNull Future execute() { + + return IntentHandler.RP.submit(new Callable() { + @Override + public Object call() throws Exception { + SettableResult result = new SettableResult(); + delegate.handle(intent, result); + if (result.getException() != null) { + throw result.getException(); + } + return result.getResult(); + } + }); + } + + /** + * Get display name of this action. + * + * @return The localized display name. + */ + public @NonNull String getDisplayName() { + return delegate.getDisplayName(); + } + + /** + * Get icon of this action. + * + * @return Some resource identifier, e.g. icon id, path or URI. + * Depends on the platform. If not available, empty string is returned. + */ + public @NonNull String getIcon() { + return delegate.getIcon(); + } +} --- a/api.intent/src/org/netbeans/api/intent/NoAvailableHandlerException.java +++ a/api.intent/src/org/netbeans/api/intent/NoAvailableHandlerException.java @@ -0,0 +1,64 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 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 2014 Sun Microsystems, Inc. + */ +package org.netbeans.api.intent; + +/** + * Exception thrown when no handler is available for the Intent, or when + * invocation of all handlers fails. In the latter case, exception thrown by the + * last handler is set as the init cause of this exception. + * + * @author jhavlin + */ +public class NoAvailableHandlerException extends Exception { + + public NoAvailableHandlerException(Intent intent) { + super(messageForIntent(intent)); + } + + public NoAvailableHandlerException(Intent intent, Throwable cause) { + super(messageForIntent(intent), cause); + } + + private static String messageForIntent(Intent intent) { + return "No available handler for intent " + intent; + } +} --- a/api.intent/src/org/netbeans/api/intent/package-info.java +++ a/api.intent/src/org/netbeans/api/intent/package-info.java @@ -0,0 +1,72 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 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 2014 Sun Microsystems, Inc. + */ + +/** + * API for working with {@link org.netbeans.api.intent.Intent}s, abstract descriptions of intended + * operations. + *

+ * Intents can be used when we want to perform some standard operation and we + * believe that the environment (the IDE, some application) is capable of + * finding and choosing correct action for it. + *

+ *

+ * The operations are specified as pair of action type and a URI. See example: + *

+ * + * {@link org.netbeans.api.intent.Intent} i = new {@link org.netbeans.api.intent.Intent}(Intent.ACTION_VIEW, new URI("file://path/file.txt")); + * + *

+ * We can execute an Intent to let the system choose to most appropriate + * action for the intent and invoke it: + *

+ * + * i.{@link org.netbeans.api.intent.Intent#execute() execute()}; + * + *

+ * Or we can get list of all available actions, display them somehow, and let + * the user select one of them: + *

+ * + * Set<IntentAction> available = i.{@link org.netbeans.api.intent.Intent#getIntentActions() getIntentActions()}; + * + */ +package org.netbeans.api.intent; --- a/api.intent/src/org/netbeans/modules/intent/CallbackResult.java +++ a/api.intent/src/org/netbeans/modules/intent/CallbackResult.java @@ -0,0 +1,88 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 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 2014 Sun Microsystems, Inc. + */ +package org.netbeans.modules.intent; + +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.api.intent.Callback; +import org.netbeans.spi.intent.Result; +import org.openide.util.Parameters; +import org.openide.util.RequestProcessor; + +/** + * + * @author jhavlin + */ +public class CallbackResult implements Result { + + private static final RequestProcessor RP + = new RequestProcessor("Intent Callbacks"); + + private final Callback callback; + + public CallbackResult(@NonNull Callback callback) { + this.callback = callback; + } + + @Override + public void setResult(final Object result) { + RP.post(new Runnable() { + + @Override + public void run() { + callback.success(result); + } + }); + } + + @Override + public void setException(final Exception exception) { + Parameters.notNull("exception", exception); //NOI18N + RP.post(new Runnable() { + + @Override + public void run() { + callback.failure(exception); + } + }); + } +} --- a/api.intent/src/org/netbeans/modules/intent/IntentHandler.java +++ a/api.intent/src/org/netbeans/modules/intent/IntentHandler.java @@ -0,0 +1,163 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 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 2014 Sun Microsystems, Inc. + */ +package org.netbeans.modules.intent; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import org.netbeans.api.intent.Intent; +import org.netbeans.spi.intent.Result; +import org.openide.filesystems.FileObject; +import org.openide.util.Lookup; +import org.openide.util.RequestProcessor; + +/** + * + * @author jhavlin + */ +public class IntentHandler implements Comparable { + + public static final RequestProcessor RP = new RequestProcessor( + IntentHandler.class); + + private static final Result IGNORING_RESULT = new Result() { + + @Override + public void setResult(Object result) { + } + + @Override + public void setException(Exception exception) { + } + }; + + @Override + public int compareTo(IntentHandler o) { + return this.getPosition() - o.getPosition(); + } + + private enum Type { + RETURN, SETBACK + } + + private final String className; + private final String methodName; + private final String displayName; + private final String icon; + private final Type type; + private final int position; + + public static IntentHandler create(FileObject fo) { + String n = fo.getName(); + int lastDash = n.lastIndexOf('-'); + if (lastDash <= 0 || lastDash + 1 >= n.length()) { + throw new IllegalArgumentException("Invalid handler file"); //NOI18N + } + String className = n.substring(0, lastDash).replace('-', '.'); + String methodName = n.substring(lastDash + 1); + String displayName = (String) fo.getAttribute("displayName"); //NOI18N + String icon = (String) fo.getAttribute("icon"); //NOI18N + int position = (Integer) fo.getAttribute("position"); //NOI18N + Type type = Type.valueOf((String) fo.getAttribute("type")); //NOI18N + + return new IntentHandler(className, methodName, displayName, icon, + type, position); + } + + private IntentHandler(String className, String methodName, + String displayName, String icon, Type type, int position) { + + this.className = className; + this.methodName = methodName; + this.displayName = displayName; + this.icon = icon; + this.type = type; + this.position = position; + } + + public String getDisplayName() { + return displayName; + } + + public String getIcon() { + return icon; + } + + public int getPosition() { + return position; + } + + public boolean isSetBack() { + return type == Type.SETBACK; + } + + public void handle(Intent intent, Result resultOrNull) { + + Result result = resultOrNull == null ? IGNORING_RESULT : resultOrNull; + + ClassLoader cls = Lookup.getDefault().lookup(ClassLoader.class); + if (cls == null) { + throw new IllegalStateException("Classloader not found"); //NOI18N + } else { + try { + Class c = Class.forName(className, true, cls); + if (isSetBack()) { + Method m = c.getDeclaredMethod(methodName, Intent.class, + Result.class); + m.invoke(null, intent, result); + } else { + Method m = c.getDeclaredMethod(methodName, Intent.class); + Object res = m.invoke(null, intent); + result.setResult(res); + } + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof Exception) { + result.setException((Exception) cause); + } else { + result.setException(new Exception(cause)); + } + } catch (ReflectiveOperationException e) { + result.setException(e); + } + } + } +} --- a/api.intent/src/org/netbeans/modules/intent/OpenUriHandlerProcessor.java +++ a/api.intent/src/org/netbeans/modules/intent/OpenUriHandlerProcessor.java @@ -0,0 +1,215 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 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 2014 Sun Microsystems, Inc. + */ +package org.netbeans.modules.intent; + +import java.util.Set; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import org.netbeans.api.intent.Intent; +import org.netbeans.spi.intent.IntentHandlerRegistration; +import org.netbeans.spi.intent.Result; +import org.openide.filesystems.annotations.LayerBuilder; +import org.openide.filesystems.annotations.LayerBuilder.File; +import org.openide.filesystems.annotations.LayerGeneratingProcessor; +import org.openide.filesystems.annotations.LayerGenerationException; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author jhavlin + */ +@ServiceProvider(service = Processor.class) +@SupportedSourceVersion(SourceVersion.RELEASE_7) +@SupportedAnnotationTypes("org.netbeans.spi.intent.IntentHandlerRegistration") +public class OpenUriHandlerProcessor extends LayerGeneratingProcessor { + + @Override + protected boolean handleProcess( + Set annotations, + RoundEnvironment roundEnv) throws LayerGenerationException { + + for (Element e : roundEnv.getElementsAnnotatedWith( + IntentHandlerRegistration.class)) { + IntentHandlerRegistration r = e.getAnnotation( + IntentHandlerRegistration.class); + registerHandler(e, r); + } + return true; + } + + private static final String SUFFIX = ".instance"; //NOI18N + + private void registerHandler(Element e, IntentHandlerRegistration r) + throws LayerGenerationException { + + TypeElement intentTypeElement = getTypeElement(Intent.class); + TypeElement objectTypeElement = getTypeElement(Object.class); + TypeElement resultTypeElement = getTypeElement(Result.class); + + if (!ElementKind.METHOD.equals(e.getKind())) { + throw error(e, "The annotation can be applied only to" //NOI18N + + " a method.");//NOI18N + } + if (!e.getModifiers().contains(Modifier.STATIC)) { + throw error(e, "The annotated method must be static."); //NOI18N + } + + ExecutableElement ee; + if (e instanceof ExecutableElement) { + ee = (ExecutableElement) e; + } else { + throw error(e, "Annotated element must be an " //NOI18N + + "ExecutableElement"); //NOI18N + + } + + String type; + if (ee.getParameters().size() == 1 + && hasParameter(ee, 0, intentTypeElement) + && hasResultType(ee, objectTypeElement)) { + type = "RETURN"; + } else if (ee.getParameters().size() == 2 + && hasParameter(ee, 0, intentTypeElement) + && hasParameter(ee, 1, resultTypeElement) + && hasVoidResultType(ee)) { + type = "SETBACK"; + } else { + throw error(e, "The handler method must take a " //NOI18N + + "single argument of type " //NOI18N + + "org.netbeans.api.intent.Intent and return Object"//NOI18N + + "; or take two arguments of types" //NOI18N + + "org.netbeans.api.intent.Intent" //NOI18N + + "and org.netbeans.spi.intent.Result" //NOI18N + + " and return void."); //NOI18N + } + + boolean takeAll = false; + boolean empty = true; + StringBuilder sb = new StringBuilder(); + for (String action: r.actions()) { + if ("*".equals(action)) { + takeAll = true; + break; + } else { + if (!empty) { + sb.append(','); + } + sb.append(action); + empty = false; + } + } + String actions = takeAll ? "*" : sb.toString(); + + final LayerBuilder b = layer(e); + File f = b.file("Services/Intent/Handlers/" //NOI18N + + getName(e).replace('.', '-') + SUFFIX); + f.position(r.position()); + f.stringvalue("instanceClass", //NOI18N + IntentHandler.class.getCanonicalName()); + f.methodvalue("instanceCreate", IntentHandler.class.getCanonicalName(), + "create"); //NOI18N + f.bundlevalue("displayName", r.displayName()); //NOI18N + f.stringvalue("uriPattern", r.uriPattern()); //NOI18N + f.stringvalue("icon", r.icon()); //NOI18N + f.stringvalue("type", type); //NOI18N + f.stringvalue("actions", actions); //NOI18N + f.write(); + } + + private String getName(Element e) { + if (e.getKind().isClass() || e.getKind().isInterface()) { + return processingEnv.getElementUtils().getBinaryName( + (TypeElement) e).toString(); + } else if (e.getKind() == ElementKind.PACKAGE) { + return e.getSimpleName().toString(); + } else { + return getName(e.getEnclosingElement()) + '.' + e.getSimpleName(); + } + } + + private boolean hasParameter(ExecutableElement ee, int pos, + Element typeElement) { + return processingEnv.getTypeUtils().asElement( + ee.getParameters().get(pos).asType()).equals( + typeElement); + } + + private boolean hasVoidResultType(ExecutableElement ee) { + TypeMirror returnType = ee.getReturnType(); + Element returnTypeElement = processingEnv.getTypeUtils().asElement( + returnType); + return returnTypeElement == null; + } + + private boolean hasResultType(ExecutableElement ee, Element typeElement) { + TypeMirror returnType = ee.getReturnType(); + Element returnTypeElement = processingEnv.getTypeUtils().asElement( + returnType); + return returnTypeElement.equals(typeElement); + } + + private TypeElement getTypeElement(Class cls) { + TypeElement typeElement = processingEnv.getElementUtils() + .getTypeElement(cls.getCanonicalName()); + return typeElement; + } + + private IllegalArgumentException error(Element e, String msg) { + StringBuilder sb = new StringBuilder(); + sb.append(e.getEnclosingElement().toString()); + sb.append("."); //NOI18N + sb.append(e.getSimpleName()); + sb.append(":"); //NOI18N + sb.append(System.lineSeparator()); + sb.append(msg); + return new IllegalArgumentException(sb.toString()); + } +} --- a/api.intent/src/org/netbeans/modules/intent/SettableResult.java +++ a/api.intent/src/org/netbeans/modules/intent/SettableResult.java @@ -0,0 +1,91 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 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 2014 Sun Microsystems, Inc. + */ +package org.netbeans.modules.intent; + +import java.util.concurrent.CountDownLatch; +import org.netbeans.spi.intent.Result; +import org.openide.util.Parameters; + +/** + * + * @author jhavlin + */ +public final class SettableResult implements Result { + + private final CountDownLatch latch = new CountDownLatch(1); + + private Object result = null; + private Exception exception = null; + + public Object getResult() { + try { + latch.await(); + } catch (InterruptedException ex) { + setException(ex); + latch.countDown(); + } + return result; + } + + public synchronized Exception getException() { + try { + latch.await(); + } catch (InterruptedException ex) { + setException(ex); + latch.countDown(); + } + return exception; + } + + @Override + public synchronized void setResult(Object result) { + this.result = result; + latch.countDown(); + } + + @Override + public synchronized void setException(Exception exception) { + Parameters.notNull("exception", exception); //NOI18N + this.exception = exception; + latch.countDown(); + } +} --- a/api.intent/src/org/netbeans/spi/intent/IntentHandlerRegistration.java +++ a/api.intent/src/org/netbeans/spi/intent/IntentHandlerRegistration.java @@ -0,0 +1,90 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 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 2014 Sun Microsystems, Inc. + */ +package org.netbeans.spi.intent; + +import java.util.regex.Pattern; + +/** + * Annotation for registering Intent handlers into the application. + * See {@link org.netbeans.spi.intent} for more info and examples. + * + * @see org.netbeans.spi.intent + */ +public @interface IntentHandlerRegistration { + + /** + * Position of the handler. The lesser value, the bigger priority. + * + * @return The position. + */ + int position(); + + /** + * List of supported action types. To support all actions, use "*" wildcard. + * + * @return List of supported action types. If some of contained values + * equals "*", all actions are supported. + */ + String[] actions(); + + /** + * Pattern of supported URIs. See {@link Pattern}. Examples: To handle + * all URIs, use ".*", to handle http adresses, use "http://.*". + * + * @return The URI pattern. + */ + String uriPattern(); + + /** + * Display name of this handler. Bundle keys can be used here. + * + * @return The display name. + */ + String displayName(); + + /** + * Identifier for an icon, e.g. path or URI. + * + * @return Icon identifier. + */ + String icon() default ""; +} --- a/api.intent/src/org/netbeans/spi/intent/Result.java +++ a/api.intent/src/org/netbeans/spi/intent/Result.java @@ -0,0 +1,67 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 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 2014 Sun Microsystems, Inc. + */ +package org.netbeans.spi.intent; + +import org.netbeans.api.annotations.common.NullAllowed; + +/** + * Object passed to intent handler which should be notified about computed + * result. + * + * @author jhavlin + */ +public interface Result { + + /** + * Set computed result. + * + * @param result The result. + */ + public void setResult(@NullAllowed Object result); + + /** + * Set encountered exception. + * + * @param exception The exception. + */ + public void setException(Exception exception); +} --- a/api.intent/src/org/netbeans/spi/intent/package-info.java +++ a/api.intent/src/org/netbeans/spi/intent/package-info.java @@ -0,0 +1,105 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 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 2014 Sun Microsystems, Inc. + */ +/** + * SPI for Intent handlers. + *

+ * Handling some type of Intents is as simple as registering a method using + * annotation {@link org.netbeans.spi.intent.IntentHandlerRegistration}. + *

+ *

+ * Currently two types of handling methods are supported: + *

+ *
    + *
  • + * Public static method taking {@link org.netbeans.api.intent.Intent} and + * returning {@link java.lang.Object}. This method will be invoked in a + * background thread. It is suitable if no waiting for asynchronous operations + * is needed and when the method will finish reasonably quickly (so that it + * will not block execution of other intents). + *
  • + *
  • + * Public static method taking {@link org.netbeans.api.intent.Intent} and + * {@link org.netbeans.spi.intent.Result} with no return type (void). It will be + * invoked in a background thread, but it can simply pass the result object to + * other threads. When the computation is finished, either + * {@link org.netbeans.spi.intent.Result#setException(java.lang.Exception)} or + * {@link org.netbeans.spi.intent.Result#setResult(java.lang.Object)} + * MUST be called on the result object. + *
  • + *
+ *

See examples:

+ *

Basic handler:

+ *
+ *   @{@link org.netbeans.spi.intent.IntentHandlerRegistration}(
+ *               displayName = "Show my item in MyEditor",
+ *               position = 800,
+ *               uriPattern = "myscheme://.*",
+ *               actions = {Intent.ACTION_VIEW, Intent.ACTION_EDIT}
+ *   )
+ *   public static Object handleIntent({@link org.netbeans.api.intent.Intent} intent) {
+ *       SomeType result = parseAndPerformIntentSomehow(intent);
+ *       return result;
+ *   }
+ * 
+ *

Handler that uses {@link org.netbeans.spi.intent.Result}:

+ *
+ *   @{@link org.netbeans.spi.intent.IntentHandlerRegistration}(
+ *               displayName = "Show my item in MyEditor",
+ *               position = 800,
+ *               uriPattern = "myscheme://.*",
+ *               actions = "*"
+ *   )
+ *   public static void handleIntent(final {@link org.netbeans.api.intent.Intent} intent, final {@link org.netbeans.spi.intent.Result} result) {
+ *       EventQueue.invokeLater(new Runnable() {
+ *           public void run() {
+ *               try {
+ *                   Object value = doSomethingInEDT(intent);
+ *                   result.setResult(value);
+ *               } catch (Exception e) {
+ *                   result.setException(e);
+ *               }
+ *           }
+ *       });
+ *   }
+ * 
+ */ +package org.netbeans.spi.intent; --- a/api.intent/test/unit/src/org/netbeans/api/intent/IntentTest.java +++ a/api.intent/test/unit/src/org/netbeans/api/intent/IntentTest.java @@ -0,0 +1,326 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 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 2014 Sun Microsystems, Inc. + */ +package org.netbeans.api.intent; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.Before; +import org.netbeans.spi.intent.IntentHandlerRegistration; +import org.netbeans.spi.intent.Result; +import org.openide.util.Exceptions; + +/** + * + * @author jhavlin + */ +public class IntentTest { + + private static boolean handled = false; + + private static final String HANDLED_BY_TEST = "Greetings from Test"; + private static final String HANDLED_BY_NB = "Greetings from NB"; + private static final String HANDLED_BY_NB_PARAM + = "Greetings from NB parametrized"; + private static final String HANDLED_BY_BROKEN = ""; + private static final String HANDLED_BY_BROKEN_SB = ""; + private static final String HANDLED_BY_SETBACK = "Greetings from setback"; + + @Before + public void setUp() { + handled = false; + } + + @Test + public void testOpenUri() throws URISyntaxException, InterruptedException, + ExecutionException { + + Future res3 = new Intent("TEST", + new URI("scheme://x/y/z/")).execute(); + assertNotNull(res3.get()); + + assertFalse(handled); + Future res2 = new Intent(Intent.ACTION_VIEW, + new URI("test://a/b/c/")).execute(); + Exception e = null; + try { + assertNotNull(res2.get()); + } catch (InterruptedException | ExecutionException ex) { + e = ex; + } + assertNotNull(e); + } + + @Test + @SuppressWarnings("ThrowableResultIgnored") + public void testSelectAppropriateHandler() + throws URISyntaxException, InterruptedException, + ExecutionException { + + Future x0 = new Intent("NONE", + new URI("unsupported://resource")).execute(); + + Exception e = null; + try { + x0.get(); + } catch (InterruptedException | ExecutionException ex) { + e = ex; + } + assertTrue(e instanceof ExecutionException + && (e.getCause() instanceof NoAvailableHandlerException)); + + Future x1 = new Intent("NONE", + new URI("broken://resource")).execute(); + try { + x1.get(); + } catch (InterruptedException | ExecutionException ex) { + e = ex; + } + assertTrue(e instanceof ExecutionException); + + Future x2 = new Intent("NONE", + new URI("brokensb://resource")).execute(); + try { + x2.get(); + } catch (InterruptedException | ExecutionException ex) { + e = ex; + } + assertTrue(e instanceof ExecutionException); + + Future f0 = new Intent("TEST", + new URI("unsupported://resource")).execute(); + assertEquals(HANDLED_BY_TEST, f0.get()); + + Future f1 = new Intent(Intent.ACTION_VIEW, + new URI("netbeans://resource")).execute(); + assertEquals(HANDLED_BY_NB, f1.get()); + + Future f2 = new Intent(Intent.ACTION_VIEW, + new URI("netbeans://resource?someParam=x")).execute(); + assertEquals(HANDLED_BY_NB, f2.get()); + + Future f3 = new Intent(Intent.ACTION_VIEW, + new URI("netbeans://resource?x=y&requiredParam=123")).execute(); + assertEquals(HANDLED_BY_NB_PARAM, f3.get()); + + Future f4 = new Intent(Intent.ACTION_VIEW, + new URI("setback://resource")).execute(); + assertEquals(HANDLED_BY_SETBACK, f4.get()); + } + + @Test + public void testExecutionWithCallback() throws URISyntaxException, InterruptedException, ExecutionException { + + class CheckingCallback implements Callback { + + private final Semaphore s = new Semaphore(0); + + private Exception lastException = null; + private Object lastResult = null; + + @Override + public void success(Object result) { + lastException = null; + lastResult = result; + s.release(); + } + + @Override + public void failure(Exception exception) { + lastException = exception; + lastResult = null; + s.release(); + } + + public void checkLastResult(Object expectedResult) { + try { + s.acquire(); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + } + assertEquals(expectedResult, lastResult); + } + + public void checkLastFailure(Class ec) { + try { + s.acquire(); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + } + assertNotNull(lastException); + assertEquals(ec, lastException.getClass()); + } + } + + CheckingCallback cb = new CheckingCallback(); + + new Intent("NONE", new URI("broken://resource")).execute(cb); + cb.checkLastFailure(RuntimeException.class); + + new Intent("NONE", new URI("brokensb://resource")).execute(cb); + cb.checkLastFailure(RuntimeException.class); + + new Intent("NONE", new URI("unsupported://resource")).execute(cb); + cb.checkLastFailure(NoAvailableHandlerException.class); + + new Intent("TEST", + new URI("unsupported://resource")).execute(cb); + cb.checkLastResult(HANDLED_BY_TEST); + + new Intent(Intent.ACTION_VIEW, + new URI("netbeans://resource")).execute(cb); + cb.checkLastResult(HANDLED_BY_NB); + + new Intent(Intent.ACTION_VIEW, + new URI("netbeans://resource?someParam=x")).execute(cb); + cb.checkLastResult(HANDLED_BY_NB); + + new Intent(Intent.ACTION_VIEW, + new URI("netbeans://resource?x=y&requiredParam=123")).execute(cb); + cb.checkLastResult(HANDLED_BY_NB_PARAM); + + new Intent(Intent.ACTION_VIEW, + new URI("setback://resource")).execute(cb); + cb.checkLastResult(HANDLED_BY_SETBACK); + } + + /** + * Handler that claims to support all URI patterns, but that actually + * accepts only scheme "test". + * + * @param intent + * @return + */ + @SuppressWarnings("PublicInnerClass") + @IntentHandlerRegistration( + displayName = "Test", + position = 999, + uriPattern = ".*", + actions = "TEST") + public static Object handleIntent(Intent intent) { + return HANDLED_BY_TEST; + } + + /** + * Handler for URIs with scheme "netbeans". + * + * @param intent + * @return + */ + @SuppressWarnings("PublicInnerClass") + @IntentHandlerRegistration( + displayName = HANDLED_BY_NB, + position = 998, + uriPattern = "netbeans://.*", + actions = "*") + public static Object handleNetBeansIntent(Intent intent) { + return HANDLED_BY_NB; + } + + /** + * Handler for URIs with scheme "netbeans" and parameter "requiredParam". + * + * @param intent + * @return + */ + @SuppressWarnings("PublicInnerClass") + @IntentHandlerRegistration( + displayName = HANDLED_BY_NB_PARAM, + position = 997, + uriPattern = "netbeans://.*[?&]requiredParam=.+", + actions = {Intent.ACTION_VIEW, Intent.ACTION_EDIT}) + public static Object handleParametrizedNetBeansIntent(Intent intent) { + return HANDLED_BY_NB_PARAM; + } + + /** + * Handler for URIs with scheme "broken". + * + * @param intent + * @return + */ + @SuppressWarnings("PublicInnerClass") + @IntentHandlerRegistration( + displayName = HANDLED_BY_BROKEN, + position = 997, + uriPattern = "broken://.*", + actions = "*") + public static Object handleBroken(Intent intent) { + throw new RuntimeException("Intentionally broken"); + } + + /** + * Handler for URIs with scheme "brokensb". + * + * @param intent + * @param result + */ + @SuppressWarnings("PublicInnerClass") + @IntentHandlerRegistration( + displayName = HANDLED_BY_BROKEN_SB, + position = 997, + uriPattern = "brokensb://.*", + actions = "*") + public static void handleBrokenSb(Intent intent, Result result) { + result.setException(new RuntimeException("Intentionally broken")); + } + + /** + * Handler for URIs with scheme "setback". + * + * @param intent + * @param result + */ + @SuppressWarnings("PublicInnerClass") + @IntentHandlerRegistration( + displayName = HANDLED_BY_SETBACK, + position = 997, + uriPattern = "setback://.*", + actions = "*") + public static void handleSetBack(Intent intent, Result result) { + result.setResult(HANDLED_BY_SETBACK); + } +} --- a/nbbuild/build.properties +++ a/nbbuild/build.properties @@ -100,6 +100,7 @@ config.javadoc.stable=\ api.annotations.common,\ api.html4j,\ + api.intent,\ api.io,\ api.maven,\ api.templates,\ --- a/nbbuild/cluster.properties +++ a/nbbuild/cluster.properties @@ -195,6 +195,7 @@ nb.cluster.platform=\ api.annotations.common,\ api.html4j,\ + api.intent,\ api.io,\ api.progress,\ api.progress.compat8,\ --- a/nbbuild/javadoctools/links.xml +++ a/nbbuild/javadoctools/links.xml @@ -240,5 +240,6 @@ + --- a/nbbuild/javadoctools/properties.xml +++ a/nbbuild/javadoctools/properties.xml @@ -239,5 +239,6 @@ + --- a/nbbuild/javadoctools/replaces.xml +++ a/nbbuild/javadoctools/replaces.xml @@ -239,5 +239,6 @@ +