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     */
017    package org.apache.commons.flatfile;
018    
019    import java.io.IOException;
020    import java.io.InputStream;
021    import java.io.ObjectInputStream;
022    import java.io.ObjectOutputStream;
023    import java.io.OutputStream;
024    import java.io.Serializable;
025    import java.util.Arrays;
026    
027    import org.apache.commons.io.IOUtils;
028    import org.apache.commons.io.output.ByteArrayOutputStream;
029    import org.apache.commons.lang.ArrayUtils;
030    import org.apache.commons.lang.ObjectUtils;
031    import org.apache.commons.lang.Validate;
032    import org.apache.commons.lang.builder.HashCodeBuilder;
033    
034    import org.apache.commons.flatfile.util.ThresholdingInputStream;
035    
036    /**
037     * Dynamically-resizable field. Supports pad/justify, but only relevant when an assigned value is too small for the
038     * field AND underflow == IGNORE.
039     * @version $Revision: 758023 $ $Date: 2009-03-24 16:09:19 -0500 (Tue, 24 Mar 2009) $
040     */
041    public class DynamicField extends PadJustifyFieldSupport {
042        /**
043         * Bounds
044         */
045        public static class Bounds implements Serializable {
046            /**
047             * Immutable range of all valid lengths.
048             */
049            public static final Bounds ALL_VALID = new Bounds() {
050                private static final long serialVersionUID = -2039931927562380883L;
051    
052                {
053                    this.minimum = 0;
054                    this.maximum = Integer.MAX_VALUE;
055                }
056    
057                /**
058                 * {@inheritDoc}
059                 */
060                public void setMinimum(int minimum) {
061                }
062    
063                /**
064                 * {@inheritDoc}
065                 */
066                public void setMaximum(int maximum) {
067                }
068            };
069    
070            /**
071             * Default bounds.
072             */
073            public static final Bounds DEFAULT = ALL_VALID;
074    
075            private static final long serialVersionUID = 1L;
076    
077            /** Minimum */
078            protected int minimum;
079    
080            /** Maximum */
081            protected int maximum;
082    
083            /**
084             * Create a new Bounds.
085             * @param minimum size
086             * @param maximum size
087             */
088            public Bounds(int minimum, int maximum) {
089                setMinimum(minimum);
090                setMaximum(maximum);
091            }
092    
093            /**
094             * Create a new Bounds instance.
095             * @param example to copy
096             */
097            private Bounds(Bounds example) {
098                this(example.getMinimum(), example.getMaximum());
099            }
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 > "
121                        + ALL_VALID.getMaximum(), maximum);
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 < "
139                        + ALL_VALID.getMinimum(), minimum);
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            Validate.notNull(bounds, "bounds cannot be null");
339            this.bounds = bounds;
340        }
341    
342        /**
343         * Serialization
344         * @param out to write to
345         * @throws IOException on error
346         */
347        private synchronized void writeObject(ObjectOutputStream out) throws IOException {
348            out.writeObject(getBounds());
349            initialize(ArrayUtils.EMPTY_BYTE_ARRAY);
350            out.write(buffer.size());
351            buffer.writeTo(out);
352        }
353    
354        /**
355         * Serialization
356         * @param in to read from
357         * @throws IOException on error
358         * @throws ClassNotFoundException if Object class not found
359         */
360        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
361            setBounds((Bounds) in.readObject());
362            int n = in.read();
363            initialize();
364            if (n > 0) {
365                IOUtils.copy(new ThresholdingInputStream(in, n), buffer);
366            }
367        }
368    
369        /**
370         * Intitialize this {@link DynamicField}.
371         */
372        private void initialize() {
373            initialize(null);
374        }
375    
376        /**
377         * Initialize this {@link DynamicField} from a given byte[].
378         * @param value byte[]
379         */
380        private synchronized void initialize(byte[] value) {
381            if (buffer == null) {
382                buffer = new ByteArrayOutputStream();
383                if (value != null) {
384                    iSetValue(value);
385                }
386            }
387        }
388    
389    }