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