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 <
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 }