#270154: CommandLine.create(Object[]) allows one to pre-create instances of the processors and directly register them
1.1 --- a/sendopts/apichanges.xml Tue Mar 21 15:09:19 2017 +0100
1.2 +++ b/sendopts/apichanges.xml Thu Mar 23 14:37:45 2017 +0100
1.3 @@ -52,6 +52,22 @@
1.4 <apidef name="sendoptsspi">SendOpts SPI</apidef>
1.5 </apidefs>
1.6 <changes>
1.7 + <change id="instances">
1.8 + <api name="sendoptsapi"/>
1.9 + <summary>Command line with instances</summary>
1.10 + <version major="2" minor="37"/>
1.11 + <date day="23" month="3" year="2017"/>
1.12 + <author login="jtulach"/>
1.13 + <compatibility addition="yes" binary="compatible" source="compatible" semantic="compatible"/>
1.14 + <description>
1.15 + <a href="@TOP@/org/netbeans/api/sendopts/CommandLine.html#create-java.lang.Object...-">Create</a>
1.16 + command line with instances of
1.17 + <a href="@TOP@/org/netbeans/spi/sendopts/OptionProcessor.html">OptionProcessor</a>
1.18 + and
1.19 + <a href="@TOP@/org/netbeans/spi/sendopts/ArgsProcessor.html">ArgsProcessor</a>.
1.20 + </description>
1.21 + <class package="org.netbeans.api.sendopts" name="CommandLine"/>
1.22 + </change>
1.23 <change id="usage-into">
1.24 <api name="sendoptsspi"/>
1.25 <summary>Print Usage into Caller's Stream</summary>
2.1 --- a/sendopts/src/org/netbeans/api/sendopts/CommandLine.java Tue Mar 21 15:09:19 2017 +0100
2.2 +++ b/sendopts/src/org/netbeans/api/sendopts/CommandLine.java Thu Mar 23 14:37:45 2017 +0100
2.3 @@ -96,18 +96,52 @@
2.4 * @since 2.20
2.5 */
2.6 public static CommandLine create(Class<?>... classes) {
2.7 + return createImpl(classes);
2.8 + }
2.9 +
2.10 + /** Creates new command line processor based on options defined in
2.11 + * the provided <code>objects</code>. The objects can implement
2.12 + * the {@link org.netbeans.spi.sendopts.OptionProcessor processor}
2.13 + * interface or (if they don't) their classes are scanned
2.14 + * for fields annotated with {@code @}{@link org.netbeans.spi.sendopts.Arg}
2.15 + * annotation. Alternatively one can register {@link Class} instances with
2.16 + * default constructor - such classes will be instantiated and then
2.17 + * processed as described above.
2.18 + *
2.19 + * @param instances objects that declare the command line options
2.20 + * @return new command line object that contains options declared in the
2.21 + * provided classes
2.22 + * @since 2.37
2.23 + */
2.24 + public static CommandLine create(Object... instances) {
2.25 + return createImpl(instances);
2.26 + }
2.27 +
2.28 + private static CommandLine createImpl(Object[] instances) {
2.29 List<OptionProcessor> arr = new ArrayList<OptionProcessor>();
2.30 - for (Class<?> c : classes) {
2.31 + for (Object o : instances) {
2.32 + Class<?> c;
2.33 + Object instance;
2.34 + if (o instanceof Class<?>) {
2.35 + c = (Class<?>)o;
2.36 + instance = null;
2.37 + } else {
2.38 + c = o.getClass();
2.39 + instance = o;
2.40 + }
2.41 if (OptionProcessor.class.isAssignableFrom(c)) {
2.42 try {
2.43 - arr.add((OptionProcessor) c.newInstance());
2.44 + if (instance == null) {
2.45 + instance = c.newInstance();
2.46 + }
2.47 + arr.add((OptionProcessor) instance);
2.48 } catch (InstantiationException ex) {
2.49 throw new IllegalStateException(ex);
2.50 } catch (IllegalAccessException ex) {
2.51 throw new IllegalStateException(ex);
2.52 }
2.53 } else {
2.54 - arr.add(DefaultProcessor.create(c));
2.55 + arr.add(DefaultProcessor.create(c, instance));
2.56 }
2.57 }
2.58 return new CommandLine(arr);
3.1 --- a/sendopts/src/org/netbeans/modules/sendopts/DefaultProcessor.java Tue Mar 21 15:09:19 2017 +0100
3.2 +++ b/sendopts/src/org/netbeans/modules/sendopts/DefaultProcessor.java Thu Mar 23 14:37:45 2017 +0100
3.3 @@ -64,12 +64,14 @@
3.4 public final class DefaultProcessor extends OptionProcessor {
3.5 private static final Option defArgs = Option.defaultArguments();
3.6 private final String clazz;
3.7 + private final Object instance;
3.8 private final Set<Option> options;
3.9
3.10 private DefaultProcessor(
3.11 - String clazz, Set<Option> arr
3.12 + String clazz, Object instance, Set<Option> arr
3.13 ) {
3.14 this.clazz = clazz;
3.15 + this.instance = instance;
3.16 this.options = Collections.unmodifiableSet(arr);
3.17 }
3.18
3.19 @@ -96,9 +98,12 @@
3.20 return o;
3.21 }
3.22
3.23 - public static OptionProcessor create(Class<?> clazz) {
3.24 + public static OptionProcessor create(Class<?> clazz, Object instance) {
3.25 Map<String,Object> map = new HashMap<String, Object>();
3.26 map.put("class", clazz.getName());
3.27 + if (instance != null) {
3.28 + map.put("instance", instance);
3.29 + }
3.30 int cnt = 1;
3.31 for (Field e : clazz.getFields()) {
3.32 Arg o = e.getAnnotation(Arg.class);
3.33 @@ -156,7 +161,8 @@
3.34 arr.add(defArgs);
3.35 }
3.36 }
3.37 - return new DefaultProcessor(c, arr);
3.38 + Object instance = map.get("instance"); // NOI18N
3.39 + return new DefaultProcessor(c, instance, arr);
3.40 }
3.41
3.42
3.43 @@ -169,8 +175,15 @@
3.44 protected void process(Env env, Map<Option, String[]> optionValues) throws CommandException {
3.45 try {
3.46 ClassLoader l = findClassLoader();
3.47 - Class<?> realClazz = Class.forName(clazz, true, l);
3.48 - Object instance = realClazz.newInstance();
3.49 + Class<?> realClazz;
3.50 + Object inst;
3.51 + if (instance == null) {
3.52 + realClazz = Class.forName(clazz, true, l);
3.53 + inst = realClazz.newInstance();
3.54 + } else {
3.55 + realClazz = instance.getClass();
3.56 + inst = instance;
3.57 + }
3.58 Map<Option,Field> map = processFields(realClazz, options);
3.59 for (Map.Entry<Option, String[]> entry : optionValues.entrySet()) {
3.60 final Option option = entry.getKey();
3.61 @@ -179,27 +192,27 @@
3.62 assert f != null : "No field for option: " + option;
3.63 switch (type) {
3.64 case withoutArgument:
3.65 - f.setBoolean(instance, true); break;
3.66 + f.setBoolean(inst, true); break;
3.67 case requiredArgument:
3.68 - f.set(instance, entry.getValue()[0]); break;
3.69 + f.set(inst, entry.getValue()[0]); break;
3.70 case optionalArgument:
3.71 if (entry.getValue().length == 1) {
3.72 - f.set(instance, entry.getValue()[0]);
3.73 + f.set(inst, entry.getValue()[0]);
3.74 } else {
3.75 - f.set(instance, f.getAnnotation(Arg.class).defaultValue());
3.76 + f.set(inst, f.getAnnotation(Arg.class).defaultValue());
3.77 }
3.78 break;
3.79 case additionalArguments:
3.80 - f.set(instance, entry.getValue()); break;
3.81 + f.set(inst, entry.getValue()); break;
3.82 case defaultArguments:
3.83 - f.set(instance, entry.getValue()); break;
3.84 + f.set(inst, entry.getValue()); break;
3.85 }
3.86 }
3.87 - if (instance instanceof Runnable) {
3.88 - ((Runnable)instance).run();
3.89 + if (inst instanceof Runnable) {
3.90 + ((Runnable)inst).run();
3.91 }
3.92 - if (instance instanceof ArgsProcessor) {
3.93 - ((ArgsProcessor)instance).process(env);
3.94 + if (inst instanceof ArgsProcessor) {
3.95 + ((ArgsProcessor)inst).process(env);
3.96 }
3.97 } catch (Exception exception) {
3.98 throw (CommandException)new CommandException(10, exception.getLocalizedMessage()).initCause(exception);
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/sendopts/test/unit/src/org/netbeans/modules/sendopts/InstancesTest.java Thu Mar 23 14:37:45 2017 +0100
4.3 @@ -0,0 +1,159 @@
4.4 +/*
4.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4.6 + *
4.7 + * Copyright 2010 Oracle and/or its affiliates. All rights reserved.
4.8 + *
4.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
4.10 + * Other names may be trademarks of their respective owners.
4.11 + *
4.12 + * The contents of this file are subject to the terms of either the GNU
4.13 + * General Public License Version 2 only ("GPL") or the Common
4.14 + * Development and Distribution License("CDDL") (collectively, the
4.15 + * "License"). You may not use this file except in compliance with the
4.16 + * License. You can obtain a copy of the License at
4.17 + * http://www.netbeans.org/cddl-gplv2.html
4.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
4.19 + * specific language governing permissions and limitations under the
4.20 + * License. When distributing the software, include this License Header
4.21 + * Notice in each file and include the License file at
4.22 + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
4.23 + * particular file as subject to the "Classpath" exception as provided
4.24 + * by Oracle in the GPL Version 2 section of the License file that
4.25 + * accompanied this code. If applicable, add the following below the
4.26 + * License Header, with the fields enclosed by brackets [] replaced by
4.27 + * your own identifying information:
4.28 + * "Portions Copyrighted [year] [name of copyright owner]"
4.29 + *
4.30 + * If you wish your version of this file to be governed by only the CDDL
4.31 + * or only the GPL Version 2, indicate your decision by adding
4.32 + * "[Contributor] elects to include this software in this distribution
4.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
4.34 + * single choice of license, a recipient has the option to distribute
4.35 + * your version of this file under either the CDDL, the GPL Version 2 or
4.36 + * to extend the choice of license to its licensees as provided above.
4.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
4.38 + * Version 2 license, then the option applies only if the new code is
4.39 + * made subject to such option by the copyright holder.
4.40 + *
4.41 + * Contributor(s):
4.42 + *
4.43 + * Portions Copyrighted 2010 Sun Microsystems, Inc.
4.44 + */
4.45 +
4.46 +package org.netbeans.modules.sendopts;
4.47 +
4.48 +import java.util.Collections;
4.49 +import java.util.Map;
4.50 +import java.util.Set;
4.51 +import org.netbeans.api.sendopts.CommandException;
4.52 +import org.netbeans.spi.sendopts.ArgsProcessor;
4.53 +import org.netbeans.spi.sendopts.Arg;
4.54 +import org.netbeans.api.sendopts.CommandLine;
4.55 +import org.netbeans.junit.NbTestCase;
4.56 +import org.netbeans.spi.sendopts.Env;
4.57 +import org.netbeans.spi.sendopts.Option;
4.58 +import org.netbeans.spi.sendopts.OptionProcessor;
4.59 +
4.60 +/**
4.61 + *
4.62 + * @author Jaroslav Tulach <jtulach@netbeans.org>
4.63 + */
4.64 +public class InstancesTest extends NbTestCase {
4.65 + private CommandLine cmd;
4.66 + private SampleOptions1 sample1;
4.67 + private String[] arr;
4.68 + private Options1 options1;
4.69 +
4.70 + public InstancesTest(String n) {
4.71 + super(n);
4.72 + }
4.73 +
4.74 + @Override
4.75 + protected void setUp() throws Exception {
4.76 + sample1 = new SampleOptions1();
4.77 + arr = new String[1];
4.78 + options1 = new Options1(arr);
4.79 + cmd = CommandLine.create(sample1, options1, Options2.class, SampleOptions2.class);
4.80 + methodCalled = null;
4.81 + methodEnv = null;
4.82 + }
4.83 +
4.84 + public void testParsingWithInstances() throws Exception {
4.85 + cmd.process("--with", "value", "--without", "--runMe", "--tellmehow");
4.86 +
4.87 + assertTrue(sample1.runCalled);
4.88 + assertEquals("value", arr[0]);
4.89 + assertNotNull(methodCalled);
4.90 + assertTrue(methodCalled.tellMeHow);
4.91 + assertTrue(Options2.called);
4.92 + }
4.93 +
4.94 + private static final class Options1 extends OptionProcessor {
4.95 + private static final Option WITH = Option.requiredArgument(Option.NO_SHORT_NAME, "with");
4.96 +
4.97 + private final String[] holder;
4.98 +
4.99 + Options1(String[] holder) {
4.100 + this.holder = holder;
4.101 + }
4.102 +
4.103 + @Override
4.104 + protected Set<Option> getOptions() {
4.105 + return Collections.singleton(WITH);
4.106 + }
4.107 +
4.108 + @Override
4.109 + protected void process(Env env, Map<Option, String[]> optionValues) throws CommandException {
4.110 + holder[0] = optionValues.get(WITH)[0];
4.111 + }
4.112 + }
4.113 +
4.114 + public static final class Options2 extends OptionProcessor {
4.115 + private static final Option WITHOUT = Option.withoutArgument(Option.NO_SHORT_NAME, "without");
4.116 +
4.117 + static boolean called;
4.118 +
4.119 + @Override
4.120 + protected Set<Option> getOptions() {
4.121 + return Collections.singleton(WITHOUT);
4.122 + }
4.123 +
4.124 + @Override
4.125 + protected void process(Env env, Map<Option, String[]> optionValues) throws CommandException {
4.126 + called = optionValues.containsKey(WITHOUT);
4.127 + }
4.128 + }
4.129 +
4.130 +
4.131 + public static final class SampleOptions1 implements Runnable {
4.132 + @Arg(longName = "runMe")
4.133 + public boolean runMe;
4.134 +
4.135 + public SampleOptions1() {
4.136 + }
4.137 +
4.138 + boolean runCalled;
4.139 +
4.140 + @Override
4.141 + public void run() {
4.142 + assertTrue(runMe);
4.143 + runCalled = true;
4.144 + }
4.145 + }
4.146 + public static final class SampleOptions2 implements ArgsProcessor {
4.147 + @Arg(longName = "tellmehow")
4.148 + public boolean tellMeHow;
4.149 +
4.150 + @Override
4.151 + public void process(Env env) {
4.152 + assertTrue(tellMeHow);
4.153 + methodEnv = env;
4.154 + methodCalled = this;
4.155 + }
4.156 + }
4.157 +
4.158 + private static Env methodEnv;
4.159 + private static SampleOptions2 methodCalled;
4.160 +}
4.161 +
4.162 +