001package ball.databind.ant.taskdefs;
002/*-
003 * ##########################################################################
004 * Data Binding Utilities
005 * $Id: ObjectMapperTask.java 7215 2021-01-03 18:39:51Z 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 - 2021 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.NonNull;
043import lombok.RequiredArgsConstructor;
044import lombok.Setter;
045import lombok.ToString;
046import lombok.experimental.Accessors;
047import org.apache.tools.ant.BuildException;
048import org.apache.tools.ant.PropertyHelper;
049import org.apache.tools.ant.Task;
050import org.apache.tools.ant.util.ClasspathUtils;
051
052import static ball.databind.ObjectMapperFeature.MAP;
053import static lombok.AccessLevel.PROTECTED;
054import static org.apache.commons.lang3.StringUtils.isEmpty;
055
056/**
057 * Abstract {@link.uri http://ant.apache.org/ Ant} base {@link Task} for
058 * {@link ObjectMapper} tasks.
059 *
060 * {@ant.task}
061 *
062 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
063 * @version $Revision: 7215 $
064 */
065@RequiredArgsConstructor(access = PROTECTED)
066public abstract class ObjectMapperTask extends Task
067                                       implements AnnotatedAntTask,
068                                                  ClasspathDelegateAntTask,
069                                                  ConfigurableAntTask {
070    @Getter @Setter @Accessors(chain = true, fluent = true)
071    private ClasspathUtils.Delegate delegate = null;
072    @Getter @Setter
073    private boolean registerModules = false;
074    private final ArrayList<Setting> settings = new ArrayList<>();
075    @NonNull
076    protected final ObjectMapper mapper;
077
078    /**
079     * No-argument constructor.
080     */
081    protected ObjectMapperTask() { this(new ObjectMapper()); }
082
083    public void addConfiguredConfigure(Setting setting) {
084        settings.add(setting);
085    }
086
087    @Override
088    public void init() throws BuildException {
089        super.init();
090        ClasspathDelegateAntTask.super.init();
091        ConfigurableAntTask.super.init();
092
093        try {
094            if (isRegisterModules()) {
095                mapper.registerModules(ObjectMapper.findModules(getClassLoader()));
096            }
097
098            for (Map.Entry<String,Object> entry :
099                     getProject().getProperties().entrySet()) {
100                if (MAP.containsKey(entry.getKey())) {
101                    ObjectMapperFeature.configure(mapper,
102                                                  MAP.get(entry.getKey()),
103                                                  PropertyHelper.toBoolean(entry.getValue()));
104                }
105            }
106
107            for (Setting setting : settings) {
108                ObjectMapperFeature.configure(mapper,
109                                              setting.getEnum(),
110                                              setting.booleanValue());
111            }
112        } catch (BuildException exception) {
113            throw exception;
114        } catch (Throwable throwable) {
115            throw new BuildException(throwable);
116        }
117    }
118
119    @Override
120    public void execute() throws BuildException {
121        super.execute();
122        AnnotatedAntTask.super.execute();
123    }
124
125    /**
126     * {@link ObjectMapper} configuration setting.
127     *
128     * {@bean.info}
129     */
130    @NoArgsConstructor @ToString
131    public static class Setting extends StringAttributeType {
132
133        /**
134         * Method to get the feature {@link Enum}.
135         *
136         * @return      The feature {@link Enum}.
137         */
138        public Enum<?> getEnum() { return MAP.get(getName()); }
139
140        /**
141         * Method to get the feature boolean value.
142         *
143         * @return      The feature boolean value.
144         */
145        public boolean booleanValue() {
146            return (isEmpty(getValue())
147                        ? true
148                        : PropertyHelper.toBoolean(getValue()));
149        }
150    }
151
152    /**
153     * {@link.uri http://ant.apache.org/ Ant}
154     * {@link org.apache.tools.ant.Task} to invoke
155     * {@link ObjectMapper#readValue(File,Class)}.
156     *
157     * {@ant.task}
158     */
159    @AntTask("om-read-value")
160    @ToString
161    public static class ReadValue extends ObjectMapperTask {
162        @NotNull @Getter @Setter
163        private File file = null;
164        @NotNull @Getter @Setter
165        private String type = null;
166        @Getter @Setter
167        private String collection = null;
168
169        /**
170         * No-argument constructor.
171         */
172        public ReadValue() { this(new ObjectMapper()); }
173
174        /**
175         * Protected constructor to allow subclasses to specify the
176         * {@link ObjectMapper}.
177         *
178         * @param       mapper  The {@link ObjectMapper}.
179         */
180        protected ReadValue(ObjectMapper mapper) { super(mapper); }
181
182        /**
183         * Method to construct a {@link JavaType} from {@code getType()} and
184         * {@code getCollection()}.
185         *
186         * @return      The {@link JavaType}.
187         *
188         * @throws      Exception       If the {@link JavaType} cannot be
189         *                              constructed.
190         */
191        protected JavaType getJavaType() throws Exception {
192            TypeFactory factory = mapper.getTypeFactory();
193            JavaType type = null;
194
195            if (! isEmpty(getCollection())) {
196                type =
197                    factory
198                    .constructCollectionType(getClassForName(getCollection())
199                                             .asSubclass(Collection.class),
200                                             getClassForName(getType()));
201            } else {
202                type =
203                    factory
204                    .constructSimpleType(getClassForName(getType()),
205                                         new JavaType[] { });
206            }
207
208            return type;
209        }
210
211        @Override
212        public void execute() throws BuildException {
213            super.execute();
214
215            try {
216                Object value = mapper.readValue(getFile(), getJavaType());
217
218                log(String.valueOf(value));
219            } catch (BuildException exception) {
220                throw exception;
221            } catch (Throwable throwable) {
222                throw new BuildException(throwable);
223            }
224        }
225    }
226}