You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ant.apache.org by mb...@apache.org on 2022/02/10 01:06:07 UTC

[ant] branch master updated (97cc23e -> b4a91c7)

This is an automated email from the ASF dual-hosted git repository.

mbenson pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/ant.git.


    from 97cc23e  test pathconvert setonempty=false
     new 71f4424  add static BuildException factory method
     new b4a91c7  refactor attribute introspection to support Optional* types

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/main/org/apache/tools/ant/BuildException.java  |  12 ++
 .../org/apache/tools/ant/IntrospectionHelper.java  | 235 +++++++++++++--------
 ...trospectionHelperSetOptionalAttributesTest.java | 171 +++++++++++++++
 3 files changed, 330 insertions(+), 88 deletions(-)
 create mode 100644 src/tests/junit/org/apache/tools/ant/IntrospectionHelperSetOptionalAttributesTest.java

[ant] 02/02: refactor attribute introspection to support Optional* types

Posted by mb...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mbenson pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ant.git

commit b4a91c7fb325080126b4b9692537766fd396157b
Author: Matt Benson <mb...@apache.org>
AuthorDate: Wed Feb 9 19:05:55 2022 -0600

    refactor attribute introspection to support Optional* types
---
 .../org/apache/tools/ant/IntrospectionHelper.java  | 235 +++++++++++++--------
 ...trospectionHelperSetOptionalAttributesTest.java | 171 +++++++++++++++
 2 files changed, 318 insertions(+), 88 deletions(-)

diff --git a/src/main/org/apache/tools/ant/IntrospectionHelper.java b/src/main/org/apache/tools/ant/IntrospectionHelper.java
index ff32f95..b61a6de 100644
--- a/src/main/org/apache/tools/ant/IntrospectionHelper.java
+++ b/src/main/org/apache/tools/ant/IntrospectionHelper.java
@@ -21,6 +21,8 @@ import java.io.File;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Enumeration;
@@ -29,6 +31,11 @@ import java.util.Hashtable;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
+import java.util.OptionalDouble;
+import java.util.OptionalInt;
+import java.util.OptionalLong;
+import java.util.function.Supplier;
 
 import org.apache.tools.ant.taskdefs.PreSetDef;
 import org.apache.tools.ant.types.EnumeratedAttribute;
@@ -1031,19 +1038,71 @@ public final class IntrospectionHelper {
     private AttributeSetter createAttributeSetter(final Method m,
                                                   final Class<?> arg,
                                                   final String attrName) {
+        if (Optional.class.equals(arg)) {
+            Type gpt = m.getGenericParameterTypes()[0];
+            Class<?> payload = Object.class;
+            if (gpt instanceof ParameterizedType ) {
+                Type ata = ((ParameterizedType) gpt).getActualTypeArguments()[0];
+                if (ata instanceof Class<?>) {
+                    payload = (Class<?>) ata;
+                } else if (ata instanceof ParameterizedType) {
+                    payload = (Class<?>) ((ParameterizedType) ata).getRawType();
+                }
+            }
+            final AttributeSetter wrapped = createAttributeSetter(m, payload, attrName);
+            return new AttributeSetter(m, arg, Optional::empty) {
+                @Override
+                Optional<?> toTargetType(Project project, String value)
+                    throws BuildException {
+                    return Optional.ofNullable(wrapped.toTargetType(project, value));
+                }
+            };
+        }
+        if (OptionalInt.class.equals(arg)) {
+            final AttributeSetter wrapped = createAttributeSetter(m, Integer.class, attrName);
+            return new AttributeSetter(m, arg, OptionalInt::empty) {
+                @Override
+                OptionalInt toTargetType(Project project, String value)
+                    throws BuildException {
+                    return Optional.ofNullable((Integer) wrapped.toTargetType(project, value))
+                        .map(OptionalInt::of).orElseGet(OptionalInt::empty);
+                }
+            };
+        }
+        if (OptionalLong.class.equals(arg)) {
+            final AttributeSetter wrapped = createAttributeSetter(m, Long.class, attrName);
+            return new AttributeSetter(m, arg, OptionalLong::empty) {
+                @Override
+                OptionalLong toTargetType(Project project, String value)
+                    throws BuildException {
+                    return Optional.ofNullable((Long) wrapped.toTargetType(project, value))
+                        .map(OptionalLong::of).orElseGet(OptionalLong::empty);
+                }
+            };
+        }
+        if (OptionalDouble.class.equals(arg)) {
+            final AttributeSetter wrapped = createAttributeSetter(m, Double.class, attrName);
+            return new AttributeSetter(m, arg, OptionalDouble::empty) {
+                @Override
+                Object toTargetType(Project project, String value)
+                    throws BuildException {
+                    return Optional.ofNullable((Double) wrapped.toTargetType(project, value))
+                        .map(OptionalDouble::of).orElseGet(OptionalDouble::empty);
+                }
+            };
+        }
         // use wrappers for primitive classes, e.g. int and
         // Integer are treated identically
         final Class<?> reflectedArg = PRIMITIVE_TYPE_MAP.getOrDefault(arg, arg);
 
         // Object.class - it gets handled differently by AttributeSetter
-        if (java.lang.Object.class == reflectedArg) {
+        if (Object.class == reflectedArg) {
             return new AttributeSetter(m, arg) {
                 @Override
-                public void set(final Project p, final Object parent, final String value)
-                        throws InvocationTargetException,
-                    IllegalAccessException {
+                Object toTargetType(Project project, String value)
+                    throws BuildException {
                     throw new BuildException(
-                        "Internal ant problem - this should not get called");
+                            "Internal ant problem - this should not get called");
                 }
             };
         }
@@ -1051,58 +1110,53 @@ public final class IntrospectionHelper {
         if (String.class.equals(reflectedArg)) {
             return new AttributeSetter(m, arg) {
                 @Override
-                public void set(final Project p, final Object parent, final String value)
-                        throws InvocationTargetException, IllegalAccessException {
-                    m.invoke(parent, (Object[]) new String[] {value});
+                public String toTargetType(Project project, String t) {
+                    return t;
                 }
             };
         }
         // char and Character get special treatment - take the first character
-        if (java.lang.Character.class.equals(reflectedArg)) {
+        if (Character.class.equals(reflectedArg)) {
             return new AttributeSetter(m, arg) {
                 @Override
-                public void set(final Project p, final Object parent, final String value)
-                        throws InvocationTargetException, IllegalAccessException {
+                public Character toTargetType(Project project, String value) {
                     if (value.isEmpty()) {
                         throw new BuildException("The value \"\" is not a "
                                 + "legal value for attribute \"" + attrName + "\"");
                     }
-                    m.invoke(parent, (Object[]) new Character[] {value.charAt(0)});
+                    return Character.valueOf(value.charAt(0));
                 }
             };
         }
         // boolean and Boolean get special treatment because we have a nice method in Project
-        if (java.lang.Boolean.class.equals(reflectedArg)) {
+        if (Boolean.class.equals(reflectedArg)) {
             return new AttributeSetter(m, arg) {
                 @Override
-                public void set(final Project p, final Object parent, final String value)
-                        throws InvocationTargetException, IllegalAccessException {
-                    m.invoke(parent, (Object[]) new Boolean[] {
-                            Project.toBoolean(value) ? Boolean.TRUE : Boolean.FALSE });
+                public Boolean toTargetType(Project project, String value) {
+                    return Boolean.valueOf(Project.toBoolean(value));
                 }
             };
         }
         // Class doesn't have a String constructor but a decent factory method
-        if (java.lang.Class.class.equals(reflectedArg)) {
+        if (Class.class.equals(reflectedArg)) {
             return new AttributeSetter(m, arg) {
                 @Override
-                public void set(final Project p, final Object parent, final String value)
-                        throws InvocationTargetException, IllegalAccessException, BuildException {
+                public Class<?> toTargetType(Project project, String value) {
                     try {
-                        m.invoke(parent, Class.forName(value));
-                    } catch (final ClassNotFoundException ce) {
-                        throw new BuildException(ce);
+                        return Class.forName(value);
+                    } catch (ClassNotFoundException e) {
+                        throw new BuildException(e);
                     }
                 }
             };
         }
         // resolve relative paths through Project
-        if (java.io.File.class.equals(reflectedArg)) {
+        if (File.class.equals(reflectedArg)) {
             return new AttributeSetter(m, arg) {
                 @Override
-                public void set(final Project p, final Object parent, final String value)
-                        throws InvocationTargetException, IllegalAccessException {
-                    m.invoke(parent, p.resolveFile(value));
+                Object toTargetType(Project project, String value)
+                    throws BuildException {
+                    return project.resolveFile(value);
                 }
             };
         }
@@ -1110,20 +1164,19 @@ public final class IntrospectionHelper {
         if (java.nio.file.Path.class.equals(reflectedArg)) {
             return new AttributeSetter(m, arg) {
                 @Override
-                public void set(final Project p, final Object parent, final String value)
-                        throws InvocationTargetException, IllegalAccessException {
-                    m.invoke(parent, p.resolveFile(value).toPath());
+                Object toTargetType(Project project, String value)
+                    throws BuildException {
+                    return project.resolveFile(value).toPath();
                 }
             };
         }
-
         // resolve Resources/FileProviders as FileResources relative to Project:
         if (Resource.class.equals(reflectedArg) || FileProvider.class.equals(reflectedArg)) {
             return new AttributeSetter(m, arg) {
                 @Override
-                void set(final Project p, final Object parent, final String value)
-                        throws InvocationTargetException, IllegalAccessException, BuildException {
-                    m.invoke(parent, new FileResource(p, p.resolveFile(value)));
+                Object toTargetType(Project project, String value)
+                    throws BuildException {
+                    return new FileResource(project.resolveFile(value));
                 }
             };
         }
@@ -1131,38 +1184,34 @@ public final class IntrospectionHelper {
         if (EnumeratedAttribute.class.isAssignableFrom(reflectedArg)) {
             return new AttributeSetter(m, arg) {
                 @Override
-                public void set(final Project p, final Object parent, final String value)
-                        throws InvocationTargetException, IllegalAccessException, BuildException {
+                public EnumeratedAttribute toTargetType(Project project, String value) {
+                    EnumeratedAttribute ea;
                     try {
-                        final EnumeratedAttribute ea =
-                                (EnumeratedAttribute) reflectedArg.getDeclaredConstructor().newInstance();
-                        ea.setValue(value);
-                        m.invoke(parent, ea);
-                    } catch (final InstantiationException | NoSuchMethodException ie) {
-                        throw new BuildException(ie);
+                        ea = (EnumeratedAttribute) reflectedArg.getDeclaredConstructor().newInstance();
+                    } catch (InstantiationException | IllegalAccessException
+                            | IllegalArgumentException | InvocationTargetException
+                            | NoSuchMethodException | SecurityException e) {
+                        throw BuildException.of(e);
                     }
+                    ea.setValue(value);
+                    return ea;
                 }
             };
         }
-
         final AttributeSetter setter = getEnumSetter(reflectedArg, m, arg);
         if (setter != null) {
             return setter;
         }
-
-        if (java.lang.Long.class.equals(reflectedArg)) {
+        if (Long.class.equals(reflectedArg)) {
             return new AttributeSetter(m, arg) {
                 @Override
-                public void set(final Project p, final Object parent, final String value)
-                        throws InvocationTargetException, IllegalAccessException, BuildException {
+                public Long toTargetType(Project project, String value) {
                     try {
-                        m.invoke(parent, StringUtils.parseHumanSizes(value));
+                        return Long.valueOf(StringUtils.parseHumanSizes(value));
                     } catch (final NumberFormatException e) {
-                        throw new BuildException("Can't assign non-numeric"
-                                                 + " value '" + value + "' to"
-                                                 + " attribute " + attrName);
-                    } catch (final InvocationTargetException | IllegalAccessException e) {
-                        throw e;
+                        throw new BuildException(
+                            String.format("Can't assign non-numeric value '%s' to attribute %s",
+                                value, attrName));
                     } catch (final Exception e) {
                         throw new BuildException(e);
                     }
@@ -1194,30 +1243,32 @@ public final class IntrospectionHelper {
 
         return new AttributeSetter(m, arg) {
             @Override
-            public void set(final Project p, final Object parent, final String value)
-                    throws InvocationTargetException, IllegalAccessException, BuildException {
+            public Object toTargetType(Project project, String value) {
                 try {
                     final Object[] args = finalIncludeProject
-                            ? new Object[] {p, value} : new Object[] {value};
+                            ? new Object[] {project, value} : new Object[] {value};
 
                     final Object attribute = finalConstructor.newInstance(args);
-                    if (p != null) {
-                        p.setProjectReference(attribute);
+                    if (project != null) {
+                        project.setProjectReference(attribute);
                     }
-                    m.invoke(parent, attribute);
-                } catch (final InvocationTargetException e) {
-                    final Throwable cause = e.getCause();
-                    if (cause instanceof IllegalArgumentException) {
-                        throw new BuildException("Can't assign value '" + value
-                                                 + "' to attribute " + attrName
-                                                 + ", reason: "
-                                                 + cause.getClass()
-                                                 + " with message '"
-                                                 + cause.getMessage() + "'");
+                    return attribute;
+                } catch (final Exception e) {
+                    Throwable thw = e;
+                    while (true) {
+                        if (thw instanceof IllegalArgumentException) {
+                            throw new BuildException(String.format(
+                                "Can't convert value '%s' to type %s, reason: %s with message '%s'",
+                                value, reflectedArg, thw.getClass(), thw.getMessage()));
+                        }
+                        final Throwable _thw = thw;
+                        Optional<Throwable> next = Optional.of(thw).map(Throwable::getCause).filter(t -> t != _thw);
+                        if (!next.isPresent()) {
+                            break;
+                        }
+                        thw = next.get();
                     }
-                    throw e;
-                } catch (final InstantiationException ie) {
-                    throw new BuildException(ie);
+                    throw BuildException.of(e);
                 }
             }
         };
@@ -1228,22 +1279,18 @@ public final class IntrospectionHelper {
         if (reflectedArg.isEnum()) {
             return new AttributeSetter(m, arg) {
                 @Override
-                public void set(final Project p, final Object parent, final String value)
-                    throws InvocationTargetException, IllegalAccessException,
-                    BuildException {
-                    Enum<?> setValue;
+                public Enum<?> toTargetType(Project project, String value) {
                     try {
                         @SuppressWarnings({ "unchecked", "rawtypes" })
-                        final Enum<?> enumValue = Enum.valueOf((Class<? extends Enum>) reflectedArg,
-                                value);
-                        setValue = enumValue;
+                        final Enum<?> result =
+                            Enum.valueOf((Class<? extends Enum>) reflectedArg, value);
+                        return result;
                     } catch (final IllegalArgumentException e) {
                         // there is a specific logic here for the value
                         // being out of the allowed set of enumerations.
                         throw new BuildException("'" + value + "' is not a permitted value for "
                                 + reflectedArg.getName());
                     }
-                    m.invoke(parent, setValue);
                 }
             };
         }
@@ -1480,32 +1527,44 @@ public final class IntrospectionHelper {
     private abstract static class AttributeSetter {
         private final Method method; // the method called to set the attribute
         private final Class<?> type;
+        private final Supplier<?> supplyWhenNull;
+
         protected AttributeSetter(final Method m, final Class<?> type) {
-            method = m;
+            this(m, type, () -> null);
+        }
+
+        protected AttributeSetter(final Method method, final Class<?> type,
+            final Supplier<?> supplyWhenNull) {
+            this.method = method;
             this.type = type;
+            this.supplyWhenNull = supplyWhenNull;
         }
-        void setObject(final Project p, final Object parent, final Object value)
+
+        final void setObject(final Project p, final Object parent, Object value)
                 throws InvocationTargetException, IllegalAccessException, BuildException {
             if (type != null) {
                 Class<?> useType = type;
                 if (type.isPrimitive()) {
                     if (value == null) {
-                        throw new BuildException(
-                            "Attempt to set primitive "
-                            + getPropertyName(method.getName(), "set")
-                            + " to null on " + parent);
+                        throw new BuildException("Attempt to set primitive %s to null on %s",
+                            getPropertyName(method.getName(), "set"), parent);
                     }
                     useType = PRIMITIVE_TYPE_MAP.get(type);
                 }
+                if (value == null ) {
+                    value = supplyWhenNull.get();
+                }
                 if (value == null || useType.isInstance(value)) {
                     method.invoke(parent, value);
                     return;
                 }
             }
-            set(p, parent, value.toString());
+            method.invoke(parent, toTargetType(p, value.toString()));
+        }
+
+        Object toTargetType(Project project, String value) {
+            throw new UnsupportedOperationException();
         }
-        abstract void set(Project p, Object parent, String value)
-                throws InvocationTargetException, IllegalAccessException, BuildException;
     }
 
     /**
diff --git a/src/tests/junit/org/apache/tools/ant/IntrospectionHelperSetOptionalAttributesTest.java b/src/tests/junit/org/apache/tools/ant/IntrospectionHelperSetOptionalAttributesTest.java
new file mode 100644
index 0000000..25c028e
--- /dev/null
+++ b/src/tests/junit/org/apache/tools/ant/IntrospectionHelperSetOptionalAttributesTest.java
@@ -0,0 +1,171 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tools.ant;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+
+import java.io.File;
+import java.util.Optional;
+import java.util.OptionalDouble;
+import java.util.OptionalInt;
+import java.util.OptionalLong;
+
+import org.apache.tools.ant.util.StringUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+public class IntrospectionHelperSetOptionalAttributesTest {
+    public static class HavingOptionals {
+        private Optional<String> foo;
+        private Optional<File> bar;
+        @SuppressWarnings("rawtypes")
+        private Optional baz;
+        private OptionalInt a;
+        private OptionalLong b;
+        private OptionalDouble c;
+
+        public Optional<String> getFoo() {
+            return foo;
+        }
+
+        public void setFoo(Optional<String> foo) {
+            this.foo = foo;
+        }
+
+        public Optional<File> getBar() {
+            return bar;
+        }
+
+        public void setBar(Optional<File> bar) {
+            this.bar = bar;
+        }
+
+        public OptionalInt getA() {
+            return a;
+        }
+
+        public void setA(OptionalInt a) {
+            this.a = a;
+        }
+
+        public OptionalLong getB() {
+            return b;
+        }
+
+        public void setB(OptionalLong b) {
+            this.b = b;
+        }
+
+        public OptionalDouble getC() {
+            return c;
+        }
+
+        public void setC(OptionalDouble c) {
+            this.c = c;
+        }
+
+        @SuppressWarnings("rawtypes")
+        public Optional getBaz() {
+            return baz;
+        }
+
+        @SuppressWarnings("rawtypes")
+        public void setBaz(Optional baz) {
+            this.baz = baz;
+        }
+    }
+
+    private Project p;
+    private IntrospectionHelper ih;
+    private HavingOptionals subject;
+
+    @Before
+    public void setup() {
+        p = new Project();
+        p.setBasedir(File.separator);
+        ih = IntrospectionHelper.getHelper(HavingOptionals.class);
+        subject = new HavingOptionals();
+    }
+
+    @Test
+    public void testOptionalString() {
+        ih.setAttribute(p, subject, "foo", "fooValue");
+        assertEquals("fooValue", subject.getFoo().get());
+    }
+
+    @Test
+    public void testEmptyOptionalString() {
+        ih.setAttribute(p, subject, "foo", null);
+        assertFalse(subject.getFoo().isPresent());
+    }
+
+    @Test
+    public void testOptionalFile() {
+        ih.setAttribute(p, subject, "bar", "barFile");
+        assertEquals(p.resolveFile("barFile"), subject.getBar().get());
+    }
+
+    @Test
+    public void testEmptyOptionalFile() {
+        ih.setAttribute(p, subject, "bar", null);
+        assertFalse(subject.getBar().isPresent());
+    }
+
+    @Test
+    public void testOptionalRaw() {
+        assertThrows(BuildException.class, () -> ih.setAttribute(p, subject, "baz", "bazValue"));
+    }
+
+    @Test
+    public void testOptionalInt() {
+        ih.setAttribute(p, subject, "a", "6");
+        assertEquals(6, subject.getA().getAsInt());
+    }
+
+    @Test
+    public void testEmptyOptionalInt() {
+        ih.setAttribute(p, subject, "a", null);
+        assertFalse(subject.getA().isPresent());
+    }
+
+    @Test
+    public void testOptionalLong() throws Exception {
+        ih.setAttribute(p, subject, "b", "6K");
+        assertEquals(StringUtils.parseHumanSizes("6K"), subject.getB().getAsLong());
+    }
+
+    @Test
+    public void testEmptyOptionalLong() throws Exception {
+        ih.setAttribute(p, subject, "b", null);
+        assertFalse(subject.getB().isPresent());
+    }
+
+    @Test
+    public void testOptionalDouble() {
+        ih.setAttribute(p, subject, "c", "6.66");
+        assertEquals(6.66, subject.getC().getAsDouble(), 0.00001);
+    }
+
+    @Test
+    public void testEmptyOptionalDouble() {
+        ih.setAttribute(p, subject, "c", null);
+        assertFalse(subject.getC().isPresent());
+    }
+}

[ant] 01/02: add static BuildException factory method

Posted by mb...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mbenson pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ant.git

commit 71f44247d449f7f3fa2928e8de76e3107f6497f5
Author: Matt Benson <mb...@apache.org>
AuthorDate: Wed Feb 9 19:05:33 2022 -0600

    add static BuildException factory method
---
 src/main/org/apache/tools/ant/BuildException.java | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/src/main/org/apache/tools/ant/BuildException.java b/src/main/org/apache/tools/ant/BuildException.java
index 6dc2c12..996043a 100644
--- a/src/main/org/apache/tools/ant/BuildException.java
+++ b/src/main/org/apache/tools/ant/BuildException.java
@@ -24,6 +24,18 @@ public class BuildException extends RuntimeException {
 
     private static final long serialVersionUID = -5419014565354664240L;
 
+    /**
+     * Get a {@link BuildException} for the specified {@link Throwable}.
+     * @param t
+     * @return {@link BuildException}
+     */
+    public static BuildException of(Throwable t) {
+        if (t instanceof BuildException) {
+            return (BuildException) t;
+        }
+        return new BuildException(t);
+    }
+
     /** Location in the build file where the exception occurred */
     private Location location = Location.UNKNOWN_LOCATION;