#270154: CommandLine.create(Object[]) allows one to pre-create instances of the processors and directly register them SendOpts270154
authorJaroslav Tulach <jtulach@netbeans.org>
Thu, 23 Mar 2017 14:37:45 +0100
branchSendOpts270154
changeset 31485269cb1b2f29c8
parent 314851 93c16431b222
child 314853 e2878ebf739d
#270154: CommandLine.create(Object[]) allows one to pre-create instances of the processors and directly register them
sendopts/apichanges.xml
sendopts/src/org/netbeans/api/sendopts/CommandLine.java
sendopts/src/org/netbeans/modules/sendopts/DefaultProcessor.java
sendopts/test/unit/src/org/netbeans/modules/sendopts/InstancesTest.java
     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 +