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;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.io.ObjectInputStream;
022import java.io.ObjectOutputStream;
023import java.io.OutputStream;
024import java.io.Serializable;
025import java.util.Arrays;
026
027import org.apache.commons.io.IOUtils;
028import org.apache.commons.io.output.ByteArrayOutputStream;
029import org.apache.commons.lang3.ArrayUtils;
030import org.apache.commons.lang3.ObjectUtils;
031import org.apache.commons.lang3.Validate;
032import org.apache.commons.lang3.builder.HashCodeBuilder;
033
034import 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: 1301248 $ $Date: 2012-03-15 17:20:49 -0500 (Thu, 15 Mar 2012) $
040 */
041public 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 %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}