001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.flatfile.dsl;
018
019import java.io.InputStream;
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.Map;
023
024import org.apache.commons.collections4.Transformer;
025import org.apache.commons.flatfile.DynamicField;
026import org.apache.commons.flatfile.Entity;
027import org.apache.commons.flatfile.EntityFactory;
028import org.apache.commons.flatfile.Field;
029import org.apache.commons.flatfile.util.ApplyOptions;
030import org.apache.commons.lang3.ObjectUtils;
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.apache.commons.pool.BasePoolableObjectFactory;
034import org.apache.commons.pool.ObjectPool;
035import org.apache.commons.pool.impl.StackObjectPool;
036
037import antlr.TokenBuffer;
038import antlr.collections.AST;
039
040/**
041 * Entity factory; provides access to named entities parsed from one or more description files.
042 * @version $Revision: 1766123 $ $Date: 2016-10-21 15:40:45 -0500 (Fri, 21 Oct 2016) $
043 */
044public class ParserEntityFactory implements EntityFactory {
045
046    /** Field option constant */
047    public static final String OPTION_FIELD = "field";
048    /** DynamicField option constant */
049    public static final String OPTION_DYNAMIC_FIELD = "dynamicField";
050
051    private static final Log LOG = LogFactory.getLog(ParserEntityFactory.class);
052    private static final EntityNameStrategy DEFAULT_NAME_STRATEGY = new DefaultEntityNameStrategy();
053
054    private class EntityHolder {
055
056        private final EntityDefinition definition;
057        private Entity prototype;
058
059        /**
060         * Create a new EntityHolder instance.
061         * @param definition base
062         */
063        EntityHolder(EntityDefinition definition) {
064            this.definition = definition;
065        }
066
067        /**
068         * Get the prototypical entity from this {@link EntityHolder}.
069         * @return Entity
070         */
071        Entity getPrototype() {
072            synchronized (this) {
073                if (prototype == null) {
074                    prototype = doWithPooledTreeParser(new Transformer<EntityTreeParser, Entity>() {
075
076                        public Entity transform(EntityTreeParser input) {
077                            return input.createEntity(definition);
078                        }
079                    });
080                }
081            }
082            return prototype;
083        }
084    }
085
086    private EntityNameStrategy entityNameStrategy;
087    private Map<String, EntityHolder> entityMap;
088    private final Map<String, Map<String, ?>> defaultOptionMaps = new HashMap<String, Map<String, ?>>();
089    private boolean checked;
090    private InputStream[] sources;
091    private EntityFactory parent;
092
093    private final ObjectPool<EntityTreeParser> treeParserPool =
094        new StackObjectPool<EntityTreeParser>(new BasePoolableObjectFactory<EntityTreeParser>() {
095
096            @Override
097            public EntityTreeParser makeObject() throws Exception {
098                EntityTreeParser result = new EntityTreeParser();
099                result.setEntityFactory(ParserEntityFactory.this);
100                return result;
101            }
102        }, 1);
103
104    /**
105     * Create a new ParserEntityFactory.
106     */
107    public ParserEntityFactory() {
108    }
109
110    /**
111     * Create a new ParserEntityFactory.
112     * @param source to read
113     */
114    public ParserEntityFactory(InputStream source) {
115        setSource(source);
116    }
117
118    /**
119     * Create a new ParserEntityFactory.
120     * @param sources to read
121     */
122    public ParserEntityFactory(InputStream[] sources) {
123        setSources(sources);
124    }
125
126    /**
127     * {@inheritDoc}
128     */
129    public final Entity getEntity(Object cue) {
130        EntityHolder holder = getEntityMap().get(getEntityNameStrategy().getEntityName(cue));
131        if (holder != null) {
132            return holder.getPrototype().clone();
133        }
134        return getParent() == null ? null : getParent().getEntity(cue);
135    }
136
137    /**
138     * Validate all defined entities.
139     */
140    public final synchronized void validate() {
141        if (checked) {
142            return;
143        }
144        for (String name : getEntityMap().keySet()) {
145            getEntity(name);
146        }
147    }
148
149    /**
150     * Get the entityNameStrategy.
151     * @return EntityNameStrategy.
152     */
153    public synchronized EntityNameStrategy getEntityNameStrategy() {
154        return entityNameStrategy == null ? DEFAULT_NAME_STRATEGY : entityNameStrategy;
155    }
156
157    /**
158     * Set the entityNameStrategy.
159     * @param entityNameStrategy The EntityNameStrategy entityNameStrategy to set.
160     */
161    public synchronized void setEntityNameStrategy(EntityNameStrategy entityNameStrategy) {
162        this.entityNameStrategy = entityNameStrategy;
163    }
164
165    /**
166     * Get the InputStream[] sources.
167     * @return InputStream[]
168     */
169    public InputStream[] getSources() {
170        return sources;
171    }
172
173    /**
174     * Set the InputStream[] sources.
175     * @param sources InputStream[]
176     */
177    public void setSources(InputStream[] sources) {
178        this.sources = sources;
179    }
180
181    /**
182     * Convenience setter for a single InputStream source.
183     * @param source to set
184     */
185    public void setSource(InputStream source) {
186        setSources(new InputStream[] { source });
187    }
188
189    /**
190     * Get the parent.
191     * @return EntityFactory
192     */
193    public EntityFactory getParent() {
194        return parent;
195    }
196
197    /**
198     * Set the parent.
199     * @param parent EntityFactory
200     */
201    public void setParent(EntityFactory parent) {
202        this.parent = parent;
203    }
204
205    /**
206     * Add an EntityDefinition
207     * @param name to associate
208     * @param def to add
209     */
210    void add(String name, EntityDefinition def) {
211        entityMap.put(name, new EntityHolder(def));
212    }
213
214    /**
215     * Set the default options for a given entity "type".
216     * @param type String key
217     * @param options option Map
218     */
219    void setDefaultOptions(String type, Map<String, ? extends Object> options) {
220        Map<String, ? extends Object> old = defaultOptionMaps.put(type, options);
221        if (!ObjectUtils.equals(old, options)) {
222            LOG.warn("Overriding " + type + " options");
223        }
224    }
225
226    /**
227     * Get the default option map for a given entity "type".
228     * @param type String key
229     * @return option Map
230     */
231    Map<String, ? extends Object> getDefaultOptions(String type) {
232        return defaultOptionMaps.get(type);
233    }
234
235    /**
236     * Create a Field of a particular length, applying default options.
237     * @param length of field
238     * @return Field
239     */
240    Field createField(int length) {
241        return applyDefaultOptions(new Field(length), OPTION_FIELD);
242    }
243
244    /**
245     * Create a dynamic field, applying default options.
246     * @return DynamicField
247     */
248    DynamicField createDynamicField() {
249        return applyDefaultOptions(new DynamicField(), OPTION_DYNAMIC_FIELD);
250    }
251
252    /**
253     * Create a field of a particular value, applying default options.
254     * @param value of the field.
255     * @return Field
256     */
257    Field createField(byte[] value) {
258        return applyDefaultOptions(new Field(value), OPTION_FIELD);
259    }
260
261    /**
262     * Apply the default options of the specified type to the specified Entity.
263     * @param <E> Entity subtype
264     * @param e E
265     * @param type associated field type
266     * @return e
267     */
268    private <E extends Entity> E applyDefaultOptions(E e, String type) {
269        Map<String, ? extends Object> m = defaultOptionMaps.get(type);
270        return m == null ? e : ApplyOptions.apply(e, m);
271    }
272
273    /**
274     * Return our entity map, initializing if necessary.
275     * @return Map<String, EntityProxy> - value may be an EntityDefinition or an Entity.
276     */
277    private synchronized Map<String, EntityHolder> getEntityMap() {
278        if (entityMap == null) {
279            entityMap = new HashMap<String, EntityHolder>();
280            try {
281                EntityParser p = null;
282                final ArrayList<AST> trees = new ArrayList<AST>();
283                InputStream[] is = getSources();
284                for (InputStream element : is) {
285                    TokenBuffer tb = new TokenBuffer(new EntityLexer(element));
286                    if (p == null) {
287                        p = new EntityParser(tb);
288                    } else {
289                        p.setTokenBuffer(tb);
290                    }
291                    p.parse();
292                    trees.add(p.getAST());
293                }
294                doWithPooledTreeParser(new Transformer<EntityTreeParser, Void>() {
295
296                    public Void transform(EntityTreeParser input) {
297                        for (AST ast : trees) {
298                            try {
299                                input.load(ast);
300                            } catch (Exception e) {
301                                throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
302                            }
303                        }
304                        return null;
305                    }
306                });
307            } catch (Exception e) {
308                throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
309            }
310        }
311        return entityMap;
312    }
313
314    /**
315     * Perform some function against a borrowed {@link EntityTreeParser} instance from the pool.
316     * @param xform function
317     * @param <T> expected result type of {@code xform}
318     * @return result from invoking {@code xform.transform(entityTreeParser)}
319     */
320    private <T> T doWithPooledTreeParser(Transformer<EntityTreeParser, T> xform) {
321        EntityTreeParser entityTreeParser;
322        try {
323            entityTreeParser = treeParserPool.borrowObject();
324        } catch (Exception e) {
325            throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
326        }
327        T result = xform.transform(entityTreeParser);
328        try {
329            treeParserPool.returnObject(entityTreeParser);
330        } catch (Exception e) {
331            LOG.error("Error returning EntityTreeParser to pool", e);
332        }
333        return result;
334    }
335}