View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.flatfile.dsl;
18  
19  import java.io.InputStream;
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.Map;
23  
24  import org.apache.commons.collections4.Transformer;
25  import org.apache.commons.flatfile.DynamicField;
26  import org.apache.commons.flatfile.Entity;
27  import org.apache.commons.flatfile.EntityFactory;
28  import org.apache.commons.flatfile.Field;
29  import org.apache.commons.flatfile.util.ApplyOptions;
30  import org.apache.commons.lang3.ObjectUtils;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.commons.pool.BasePoolableObjectFactory;
34  import org.apache.commons.pool.ObjectPool;
35  import org.apache.commons.pool.impl.StackObjectPool;
36  
37  import antlr.TokenBuffer;
38  import antlr.collections.AST;
39  
40  /**
41   * Entity factory; provides access to named entities parsed from one or more description files.
42   * @version $Revision: 1766123 $ $Date: 2016-10-21 15:40:45 -0500 (Fri, 21 Oct 2016) $
43   */
44  public class ParserEntityFactory implements EntityFactory {
45  
46      /** Field option constant */
47      public static final String OPTION_FIELD = "field";
48      /** DynamicField option constant */
49      public static final String OPTION_DYNAMIC_FIELD = "dynamicField";
50  
51      private static final Log LOG = LogFactory.getLog(ParserEntityFactory.class);
52      private static final EntityNameStrategy DEFAULT_NAME_STRATEGY = new DefaultEntityNameStrategy();
53  
54      private class EntityHolder {
55  
56          private final EntityDefinition definition;
57          private Entity prototype;
58  
59          /**
60           * Create a new EntityHolder instance.
61           * @param definition base
62           */
63          EntityHolder(EntityDefinition definition) {
64              this.definition = definition;
65          }
66  
67          /**
68           * Get the prototypical entity from this {@link EntityHolder}.
69           * @return Entity
70           */
71          Entity getPrototype() {
72              synchronized (this) {
73                  if (prototype == null) {
74                      prototype = doWithPooledTreeParser(new Transformer<EntityTreeParser, Entity>() {
75  
76                          public Entity transform(EntityTreeParser input) {
77                              return input.createEntity(definition);
78                          }
79                      });
80                  }
81              }
82              return prototype;
83          }
84      }
85  
86      private EntityNameStrategy entityNameStrategy;
87      private Map<String, EntityHolder> entityMap;
88      private final Map<String, Map<String, ?>> defaultOptionMaps = new HashMap<String, Map<String, ?>>();
89      private boolean checked;
90      private InputStream[] sources;
91      private EntityFactory parent;
92  
93      private final ObjectPool<EntityTreeParser> treeParserPool =
94          new StackObjectPool<EntityTreeParser>(new BasePoolableObjectFactory<EntityTreeParser>() {
95  
96              @Override
97              public EntityTreeParser makeObject() throws Exception {
98                  EntityTreeParser result = new EntityTreeParser();
99                  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 }