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;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.ObjectInputStream;
22  import java.io.ObjectOutputStream;
23  import java.io.OutputStream;
24  import java.io.Serializable;
25  import java.util.Arrays;
26  
27  import org.apache.commons.io.IOUtils;
28  import org.apache.commons.io.output.ByteArrayOutputStream;
29  import org.apache.commons.lang3.ArrayUtils;
30  import org.apache.commons.lang3.ObjectUtils;
31  import org.apache.commons.lang3.Validate;
32  import org.apache.commons.lang3.builder.HashCodeBuilder;
33  
34  import org.apache.commons.flatfile.util.ThresholdingInputStream;
35  
36  /**
37   * Dynamically-resizable field. Supports pad/justify, but only relevant when an assigned value is too small for the
38   * field AND underflow == IGNORE.
39   * @version $Revision: 1301248 $ $Date: 2012-03-15 17:20:49 -0500 (Thu, 15 Mar 2012) $
40   */
41  public class DynamicField extends PadJustifyFieldSupport {
42      /**
43       * Bounds
44       */
45      public static class Bounds implements Serializable {
46          /**
47           * Immutable range of all valid lengths.
48           */
49          public static final Bounds ALL_VALID = new Bounds() {
50              private static final long serialVersionUID = -2039931927562380883L;
51  
52              {
53                  this.minimum = 0;
54                  this.maximum = Integer.MAX_VALUE;
55              }
56  
57              /**
58               * {@inheritDoc}
59               */
60              public void setMinimum(int minimum) {
61              }
62  
63              /**
64               * {@inheritDoc}
65               */
66              public void setMaximum(int maximum) {
67              }
68          };
69  
70          /**
71           * Default bounds.
72           */
73          public static final Bounds DEFAULT = ALL_VALID;
74  
75          private static final long serialVersionUID = 1L;
76  
77          /** Minimum */
78          protected int minimum;
79  
80          /** Maximum */
81          protected int maximum;
82  
83          /**
84           * Create a new Bounds.
85           * @param minimum size
86           * @param maximum size
87           */
88          public Bounds(int minimum, int maximum) {
89              setMinimum(minimum);
90              setMaximum(maximum);
91          }
92  
93          /**
94           * Create a new Bounds instance.
95           * @param example to copy
96           */
97          private Bounds(Bounds example) {
98              this(example.getMinimum(), example.getMaximum());
99          }
100 
101         /**
102          * Create a new Bounds instance.
103          */
104         private Bounds() {
105         }
106 
107         /**
108          * Get the maximum.
109          * @return int
110          */
111         public int getMaximum() {
112             return maximum;
113         }
114 
115         /**
116          * Set the maximum.
117          * @param maximum int
118          */
119         public void setMaximum(int maximum) {
120             Validate.isTrue(maximum <= ALL_VALID.getMaximum(), "maximum value %d > %d", maximum,
121                 ALL_VALID.getMaximum());
122             this.maximum = maximum;
123         }
124 
125         /**
126          * Get the minimum.
127          * @return int
128          */
129         public int getMinimum() {
130             return minimum;
131         }
132 
133         /**
134          * Set the minimum.
135          * @param minimum int
136          */
137         public void setMinimum(int minimum) {
138             Validate.isTrue(minimum >= ALL_VALID.getMinimum(), "minimum value %s < %s", minimum,
139                 ALL_VALID.getMinimum());
140             this.minimum = minimum;
141         }
142 
143         /**
144          * {@inheritDoc}
145          */
146         public boolean equals(Object o) {
147             if (o == this) {
148                 return true;
149             }
150             if (!(o instanceof Bounds)) {
151                 return false;
152             }
153             Bounds ob = (Bounds) o;
154             return ob.getMaximum() == getMaximum() && ob.getMinimum() == getMinimum();
155         }
156 
157         /**
158          * {@inheritDoc}
159          */
160         public int hashCode() {
161             return new HashCodeBuilder().append(minimum).append(maximum).toHashCode();
162         }
163 
164         /**
165          * Validate a field and value against these Bounds.
166          * @param value to validate
167          * @param df field to validate
168          */
169         public void validate(byte[] value, DynamicField df) {
170             df.getUnderflow().check(value, getMinimum());
171             df.getOverflow().check(value, getMaximum());
172         }
173     }
174 
175     /** Serialization version */
176     private static final long serialVersionUID = -319053179741813727L;
177 
178     private Bounds bounds;
179     private Overflow overflow;
180     private Underflow underflow;
181     private transient ByteArrayOutputStream buffer;
182 
183     /**
184      * Create a new DynamicField.
185      */
186     public DynamicField() {
187         this(new Bounds(Bounds.DEFAULT));
188     }
189 
190     /**
191      * Create a new DynamicField.
192      * @param bounds bounds
193      */
194     public DynamicField(Bounds bounds) {
195         setBounds(bounds);
196     }
197 
198     /**
199      * {@inheritDoc}
200      */
201     public synchronized byte[] getValue() {
202         initialize(ArrayUtils.EMPTY_BYTE_ARRAY);
203         return buffer.toByteArray();
204     }
205 
206     /**
207      * {@inheritDoc}
208      */
209     public int length() {
210         initialize(ArrayUtils.EMPTY_BYTE_ARRAY);
211         return buffer.size();
212     }
213 
214     /**
215      * Read up to <code>maximumLength</code> bytes from the specified InputStream or stop at EOF. This will rarely be
216      * what you want. Instead, consider using InputFilteringDynamicField. In the case that &lt;
217      * <code>minimumLength</code> bytes are available from <code>is</code> the <code>justify</code> and
218      * <code>pad</code> options come into play.
219      * @param is the InputStream from which to read data.
220      * @throws IOException on problems with I/O, duh...
221      */
222     public synchronized void readFrom(InputStream is) throws IOException {
223         initialize();
224         buffer.reset();
225         IOUtils.copy(new ThresholdingInputStream(is, getBounds().getMaximum()), buffer);
226     }
227 
228     /**
229      * {@inheritDoc}
230      */
231     public synchronized void setValue(byte[] b) {
232         getBounds().validate(b, this);
233         initialize();
234         iSetValue(b);
235     }
236 
237     /**
238      * {@inheritDoc}
239      */
240     public synchronized void writeTo(OutputStream os) throws IOException {
241         initialize(ArrayUtils.EMPTY_BYTE_ARRAY);
242         buffer.writeTo(os);
243     }
244 
245     /**
246      * Get the overflow.
247      * @return Overflow
248      */
249     public Overflow getOverflow() {
250         return overflow == null ? Overflow.ERROR : overflow;
251     }
252 
253     /**
254      * Set the overflow.
255      * @param overflow Overflow
256      */
257     public void setOverflow(Overflow overflow) {
258         this.overflow = overflow;
259     }
260 
261     /**
262      * Get the underflow.
263      * @return Underflow
264      */
265     public Underflow getUnderflow() {
266         return underflow == null ? Underflow.ERROR : underflow;
267     }
268 
269     /**
270      * Set the underflow.
271      * @param underflow Underflow
272      */
273     public void setUnderflow(Underflow underflow) {
274         this.underflow = underflow;
275     }
276 
277     /**
278      * {@inheritDoc}
279      */
280     public int hashCode() {
281         return new HashCodeBuilder().append(getBounds()).append(getValue()).toHashCode();
282     }
283 
284     /**
285      * {@inheritDoc}
286      */
287     public boolean equals(Object other) {
288         if (other == this) {
289             return true;
290         }
291         if (!(other instanceof DynamicField)) {
292             return false;
293         }
294         DynamicField odf = (DynamicField) other;
295         return ObjectUtils.equals(odf.getBounds(), getBounds())
296                 && Arrays.equals(odf.getValue(), getValue());
297     }
298 
299     /**
300      * {@inheritDoc}
301      */
302     protected int getPadJustifyLength() {
303         return getBounds().getMinimum();
304     }
305 
306     /**
307      * Protected inner setValue
308      * @param value byte[]
309      */
310     protected synchronized void iSetValue(byte[] value) {
311         super.setValue(value);
312     }
313 
314     /**
315      * {@inheritDoc}
316      */
317     public DynamicField clone() {
318         DynamicField result = (DynamicField) super.clone();
319         result.setBounds(new Bounds(getBounds()));
320         result.buffer = null;
321         result.setValue(getValue());
322         return result;
323     }
324 
325     /**
326      * Get the bounds.
327      * @return Bounds
328      */
329     public Bounds getBounds() {
330         return bounds;
331     }
332 
333     /**
334      * Set the bounds.
335      * @param bounds Bounds
336      */
337     public void setBounds(Bounds bounds) {
338         this.bounds = Validate.notNull(bounds, "bounds cannot be null");
339     }
340 
341     /**
342      * Serialization
343      * @param out to write to
344      * @throws IOException on error
345      */
346     private synchronized void writeObject(ObjectOutputStream out) throws IOException {
347         out.writeObject(getBounds());
348         initialize(ArrayUtils.EMPTY_BYTE_ARRAY);
349         out.write(buffer.size());
350         buffer.writeTo(out);
351     }
352 
353     /**
354      * Serialization
355      * @param in to read from
356      * @throws IOException on error
357      * @throws ClassNotFoundException if Object class not found
358      */
359     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
360         setBounds((Bounds) in.readObject());
361         int n = in.read();
362         initialize();
363         if (n > 0) {
364             IOUtils.copy(new ThresholdingInputStream(in, n), buffer);
365         }
366     }
367 
368     /**
369      * Intitialize this {@link DynamicField}.
370      */
371     private void initialize() {
372         initialize(null);
373     }
374 
375     /**
376      * Initialize this {@link DynamicField} from a given byte[].
377      * @param value byte[]
378      */
379     private synchronized void initialize(byte[] value) {
380         if (buffer == null) {
381             buffer = new ByteArrayOutputStream();
382             if (value != null) {
383                 iSetValue(value);
384             }
385         }
386     }
387 
388 }