001package ball.databind.ant.taskdefs;
002/*-
003 * ##########################################################################
004 * Data Binding Utilities
005 * $Id: ObjectMapperTask.java 5285 2020-02-05 04:23:21Z ball $
006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-databind/trunk/src/main/java/ball/databind/ant/taskdefs/ObjectMapperTask.java $
007 * %%
008 * Copyright (C) 2016 - 2020 Allen D. Ball
009 * %%
010 * Licensed under the Apache License, Version 2.0 (the "License");
011 * you may not use this file except in compliance with the License.
012 * You may obtain a copy of the License at
013 *
014 *      http://www.apache.org/licenses/LICENSE-2.0
015 *
016 * Unless required by applicable law or agreed to in writing, software
017 * distributed under the License is distributed on an "AS IS" BASIS,
018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
019 * See the License for the specific language governing permissions and
020 * limitations under the License.
021 * ##########################################################################
022 */
023import ball.databind.ObjectMapperFeature;
024import ball.util.ant.taskdefs.AnnotatedAntTask;
025import ball.util.ant.taskdefs.AntTask;
026import ball.util.ant.taskdefs.ClasspathDelegateAntTask;
027import ball.util.ant.taskdefs.ConfigurableAntTask;
028import ball.util.ant.taskdefs.NotNull;
029import ball.util.ant.types.StringAttributeType;
030import com.fasterxml.jackson.databind.JavaType;
031import com.fasterxml.jackson.databind.ObjectMapper;
032import com.fasterxml.jackson.databind.type.TypeFactory;
033import java.io.File;
034import java.util.ArrayList;
035import java.util.Arrays;
036import java.util.Collection;
037import java.util.Map;
038import java.util.SortedMap;
039import java.util.TreeMap;
040import lombok.Getter;
041import lombok.NoArgsConstructor;
042import lombok.Setter;
043import lombok.ToString;
044import lombok.experimental.Accessors;
045import org.apache.tools.ant.BuildException;
046import org.apache.tools.ant.PropertyHelper;
047import org.apache.tools.ant.Task;
048import org.apache.tools.ant.util.ClasspathUtils;
049
050import static ball.databind.ObjectMapperFeature.MAP;
051import static java.util.Objects.requireNonNull;
052import static org.apache.commons.lang3.StringUtils.isEmpty;
053
054/**
055 * Abstract {@link.uri http://ant.apache.org/ Ant} base {@link Task} for
056 * {@link ObjectMapper} tasks.
057 *
058 * {@ant.task}
059 *
060 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
061 * @version $Revision: 5285 $
062 */
063public abstract class ObjectMapperTask extends Task
064                                       implements AnnotatedAntTask,
065                                                  ClasspathDelegateAntTask,
066                                                  ConfigurableAntTask {
067    @Getter @Setter @Accessors(chain = true, fluent = true)
068    private ClasspathUtils.Delegate delegate = null;
069    @Getter @Setter
070    private boolean registerModules = false;
071    private final ArrayList<Setting> settings = new ArrayList<>();
072    protected final ObjectMapper mapper;
073
074    /**
075     * No-argument constructor.
076     */
077    protected ObjectMapperTask() { this(new ObjectMapper()); }
078
079    /**
080     * Protected constructor to allow subclasses to specify the
081     * {@link ObjectMapper}.
082     *
083     * @param   mapper          The {@link ObjectMapper}.
084     */
085    protected ObjectMapperTask(ObjectMapper mapper) {
086        super();
087
088        this.mapper = requireNonNull(mapper, "mapper");
089    }
090
091    public void addConfiguredConfigure(Setting setting) {
092        settings.add(setting);
093    }
094
095    @Override
096    public void init() throws BuildException {
097        super.init();
098        ClasspathDelegateAntTask.super.init();
099        ConfigurableAntTask.super.init();
100
101        try {
102            if (isRegisterModules()) {
103                mapper.registerModules(ObjectMapper.findModules(getClassLoader()));
104            }
105
106            for (Map.Entry<String,Object> entry :
107                     getProject().getProperties().entrySet()) {
108                if (MAP.containsKey(entry.getKey())) {
109                    ObjectMapperFeature.configure(mapper,
110                                                  MAP.get(entry.getKey()),
111                                                  PropertyHelper.toBoolean(entry.getValue()));
112                }
113            }
114
115            for (Setting setting : settings) {
116                ObjectMapperFeature.configure(mapper,
117                                              setting.getEnum(),
118                                              setting.booleanValue());
119            }
120        } catch (BuildException exception) {
121            throw exception;
122        } catch (Throwable throwable) {
123            throw new BuildException(throwable);
124        }
125    }
126
127    @Override
128    public void execute() throws BuildException {
129        super.execute();
130        AnnotatedAntTask.super.execute();
131    }
132
133    /**
134     * {@link ObjectMapper} configuration setting.
135     *
136     * {@bean.info}
137     */
138    @NoArgsConstructor @ToString
139    public static class Setting extends StringAttributeType {
140
141        /**
142         * Method to get the feature {@link Enum}.
143         *
144         * @return      The feature {@link Enum}.
145         */
146        public Enum<?> getEnum() { return MAP.get(getName()); }
147
148        /**
149         * Method to get the feature boolean value.
150         *
151         * @return      The feature boolean value.
152         */
153        public boolean booleanValue() {
154            return (isEmpty(getValue())
155                        ? true
156                        : PropertyHelper.toBoolean(getValue()));
157        }
158    }
159
160    /**
161     * {@link.uri http://ant.apache.org/ Ant}
162     * {@link org.apache.tools.ant.Task} to invoke
163     * {@link ObjectMapper#readValue(File,Class)}.
164     *
165     * {@ant.task}
166     */
167    @AntTask("om-read-value")
168    @ToString
169    public static class ReadValue extends ObjectMapperTask {
170        @NotNull @Getter @Setter
171        private File file = null;
172        @NotNull @Getter @Setter
173        private String type = null;
174        @Getter @Setter
175        private String collection = null;
176
177        /**
178         * No-argument constructor.
179         */
180        public ReadValue() { this(new ObjectMapper()); }
181
182        /**
183         * Protected constructor to allow subclasses to specify the
184         * {@link ObjectMapper}.
185         *
186         * @param       mapper  The {@link ObjectMapper}.
187         */
188        protected ReadValue(ObjectMapper mapper) { super(mapper); }
189
190        /**
191         * Method to construct a {@link JavaType} from {@code getType()} and
192         * {@code getCollection()}.
193         *
194         * @return      The {@link JavaType}.
195         *
196         * @throws      Exception       If the {@link JavaType} cannot be
197         *                              constructed.
198         */
199        protected JavaType getJavaType() throws Exception {
200            TypeFactory factory = mapper.getTypeFactory();
201            JavaType type = null;
202
203            if (! isEmpty(getCollection())) {
204                type =
205                    factory
206                    .constructCollectionType(getClassForName(getCollection())
207                                             .asSubclass(Collection.class),
208                                             getClassForName(getType()));
209            } else {
210                type =
211                    factory
212                    .constructSimpleType(getClassForName(getType()),
213                                         new JavaType[] { });
214            }
215
216            return type;
217        }
218
219        @Override
220        public void execute() throws BuildException {
221            super.execute();
222
223            try {
224                Object value = mapper.readValue(getFile(), getJavaType());
225
226                log(String.valueOf(value));
227            } catch (BuildException exception) {
228                throw exception;
229            } catch (Throwable throwable) {
230                throw new BuildException(throwable);
231            }
232        }
233    }
234}