001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.io;
020
021import java.io.*;
022import java.util.*;
023import java.rmi.server.UID;
024import java.security.MessageDigest;
025import org.apache.commons.logging.*;
026import org.apache.hadoop.util.Options;
027import org.apache.hadoop.fs.*;
028import org.apache.hadoop.fs.Options.CreateOpts;
029import org.apache.hadoop.io.compress.CodecPool;
030import org.apache.hadoop.io.compress.CompressionCodec;
031import org.apache.hadoop.io.compress.CompressionInputStream;
032import org.apache.hadoop.io.compress.CompressionOutputStream;
033import org.apache.hadoop.io.compress.Compressor;
034import org.apache.hadoop.io.compress.Decompressor;
035import org.apache.hadoop.io.compress.DefaultCodec;
036import org.apache.hadoop.io.compress.GzipCodec;
037import org.apache.hadoop.io.compress.zlib.ZlibFactory;
038import org.apache.hadoop.io.serializer.Deserializer;
039import org.apache.hadoop.io.serializer.Serializer;
040import org.apache.hadoop.io.serializer.SerializationFactory;
041import org.apache.hadoop.classification.InterfaceAudience;
042import org.apache.hadoop.classification.InterfaceStability;
043import org.apache.hadoop.conf.*;
044import org.apache.hadoop.util.Progressable;
045import org.apache.hadoop.util.Progress;
046import org.apache.hadoop.util.ReflectionUtils;
047import org.apache.hadoop.util.NativeCodeLoader;
048import org.apache.hadoop.util.MergeSort;
049import org.apache.hadoop.util.PriorityQueue;
050import org.apache.hadoop.util.Time;
051
052/** 
053 * <code>SequenceFile</code>s are flat files consisting of binary key/value 
054 * pairs.
055 * 
056 * <p><code>SequenceFile</code> provides {@link SequenceFile.Writer},
057 * {@link SequenceFile.Reader} and {@link Sorter} classes for writing,
058 * reading and sorting respectively.</p>
059 * 
060 * There are three <code>SequenceFile</code> <code>Writer</code>s based on the 
061 * {@link CompressionType} used to compress key/value pairs:
062 * <ol>
063 *   <li>
064 *   <code>Writer</code> : Uncompressed records.
065 *   </li>
066 *   <li>
067 *   <code>RecordCompressWriter</code> : Record-compressed files, only compress 
068 *                                       values.
069 *   </li>
070 *   <li>
071 *   <code>BlockCompressWriter</code> : Block-compressed files, both keys & 
072 *                                      values are collected in 'blocks' 
073 *                                      separately and compressed. The size of 
074 *                                      the 'block' is configurable.
075 * </ol>
076 * 
077 * <p>The actual compression algorithm used to compress key and/or values can be
078 * specified by using the appropriate {@link CompressionCodec}.</p>
079 * 
080 * <p>The recommended way is to use the static <tt>createWriter</tt> methods
081 * provided by the <code>SequenceFile</code> to chose the preferred format.</p>
082 *
083 * <p>The {@link SequenceFile.Reader} acts as the bridge and can read any of the
084 * above <code>SequenceFile</code> formats.</p>
085 *
086 * <h4 id="Formats">SequenceFile Formats</h4>
087 * 
088 * <p>Essentially there are 3 different formats for <code>SequenceFile</code>s
089 * depending on the <code>CompressionType</code> specified. All of them share a
090 * <a href="#Header">common header</a> described below.
091 * 
092 * <h5 id="Header">SequenceFile Header</h5>
093 * <ul>
094 *   <li>
095 *   version - 3 bytes of magic header <b>SEQ</b>, followed by 1 byte of actual 
096 *             version number (e.g. SEQ4 or SEQ6)
097 *   </li>
098 *   <li>
099 *   keyClassName -key class
100 *   </li>
101 *   <li>
102 *   valueClassName - value class
103 *   </li>
104 *   <li>
105 *   compression - A boolean which specifies if compression is turned on for 
106 *                 keys/values in this file.
107 *   </li>
108 *   <li>
109 *   blockCompression - A boolean which specifies if block-compression is 
110 *                      turned on for keys/values in this file.
111 *   </li>
112 *   <li>
113 *   compression codec - <code>CompressionCodec</code> class which is used for  
114 *                       compression of keys and/or values (if compression is 
115 *                       enabled).
116 *   </li>
117 *   <li>
118 *   metadata - {@link Metadata} for this file.
119 *   </li>
120 *   <li>
121 *   sync - A sync marker to denote end of the header.
122 *   </li>
123 * </ul>
124 * 
125 * <h5 id="#UncompressedFormat">Uncompressed SequenceFile Format</h5>
126 * <ul>
127 * <li>
128 * <a href="#Header">Header</a>
129 * </li>
130 * <li>
131 * Record
132 *   <ul>
133 *     <li>Record length</li>
134 *     <li>Key length</li>
135 *     <li>Key</li>
136 *     <li>Value</li>
137 *   </ul>
138 * </li>
139 * <li>
140 * A sync-marker every few <code>100</code> bytes or so.
141 * </li>
142 * </ul>
143 *
144 * <h5 id="#RecordCompressedFormat">Record-Compressed SequenceFile Format</h5>
145 * <ul>
146 * <li>
147 * <a href="#Header">Header</a>
148 * </li>
149 * <li>
150 * Record
151 *   <ul>
152 *     <li>Record length</li>
153 *     <li>Key length</li>
154 *     <li>Key</li>
155 *     <li><i>Compressed</i> Value</li>
156 *   </ul>
157 * </li>
158 * <li>
159 * A sync-marker every few <code>100</code> bytes or so.
160 * </li>
161 * </ul>
162 * 
163 * <h5 id="#BlockCompressedFormat">Block-Compressed SequenceFile Format</h5>
164 * <ul>
165 * <li>
166 * <a href="#Header">Header</a>
167 * </li>
168 * <li>
169 * Record <i>Block</i>
170 *   <ul>
171 *     <li>Uncompressed number of records in the block</li>
172 *     <li>Compressed key-lengths block-size</li>
173 *     <li>Compressed key-lengths block</li>
174 *     <li>Compressed keys block-size</li>
175 *     <li>Compressed keys block</li>
176 *     <li>Compressed value-lengths block-size</li>
177 *     <li>Compressed value-lengths block</li>
178 *     <li>Compressed values block-size</li>
179 *     <li>Compressed values block</li>
180 *   </ul>
181 * </li>
182 * <li>
183 * A sync-marker every block.
184 * </li>
185 * </ul>
186 * 
187 * <p>The compressed blocks of key lengths and value lengths consist of the 
188 * actual lengths of individual keys/values encoded in ZeroCompressedInteger 
189 * format.</p>
190 * 
191 * @see CompressionCodec
192 */
193@InterfaceAudience.Public
194@InterfaceStability.Stable
195public class SequenceFile {
196  private static final Log LOG = LogFactory.getLog(SequenceFile.class);
197
198  private SequenceFile() {}                         // no public ctor
199
200  private static final byte BLOCK_COMPRESS_VERSION = (byte)4;
201  private static final byte CUSTOM_COMPRESS_VERSION = (byte)5;
202  private static final byte VERSION_WITH_METADATA = (byte)6;
203  private static byte[] VERSION = new byte[] {
204    (byte)'S', (byte)'E', (byte)'Q', VERSION_WITH_METADATA
205  };
206
207  private static final int SYNC_ESCAPE = -1;      // "length" of sync entries
208  private static final int SYNC_HASH_SIZE = 16;   // number of bytes in hash 
209  private static final int SYNC_SIZE = 4+SYNC_HASH_SIZE; // escape + hash
210
211  /** The number of bytes between sync points.*/
212  public static final int SYNC_INTERVAL = 100*SYNC_SIZE; 
213
214  /** 
215   * The compression type used to compress key/value pairs in the 
216   * {@link SequenceFile}.
217   * 
218   * @see SequenceFile.Writer
219   */
220  public static enum CompressionType {
221    /** Do not compress records. */
222    NONE, 
223    /** Compress values only, each separately. */
224    RECORD,
225    /** Compress sequences of records together in blocks. */
226    BLOCK
227  }
228
229  /**
230   * Get the compression type for the reduce outputs
231   * @param job the job config to look in
232   * @return the kind of compression to use
233   */
234  static public CompressionType getDefaultCompressionType(Configuration job) {
235    String name = job.get("io.seqfile.compression.type");
236    return name == null ? CompressionType.RECORD : 
237      CompressionType.valueOf(name);
238  }
239  
240  /**
241   * Set the default compression type for sequence files.
242   * @param job the configuration to modify
243   * @param val the new compression type (none, block, record)
244   */
245  static public void setDefaultCompressionType(Configuration job, 
246                                               CompressionType val) {
247    job.set("io.seqfile.compression.type", val.toString());
248  }
249
250  /**
251   * Create a new Writer with the given options.
252   * @param conf the configuration to use
253   * @param opts the options to create the file with
254   * @return a new Writer
255   * @throws IOException
256   */
257  public static Writer createWriter(Configuration conf, Writer.Option... opts
258                                    ) throws IOException {
259    Writer.CompressionOption compressionOption = 
260      Options.getOption(Writer.CompressionOption.class, opts);
261    CompressionType kind;
262    if (compressionOption != null) {
263      kind = compressionOption.getValue();
264    } else {
265      kind = getDefaultCompressionType(conf);
266      opts = Options.prependOptions(opts, Writer.compression(kind));
267    }
268    switch (kind) {
269      default:
270      case NONE:
271        return new Writer(conf, opts);
272      case RECORD:
273        return new RecordCompressWriter(conf, opts);
274      case BLOCK:
275        return new BlockCompressWriter(conf, opts);
276    }
277  }
278
279  /**
280   * Construct the preferred type of SequenceFile Writer.
281   * @param fs The configured filesystem. 
282   * @param conf The configuration.
283   * @param name The name of the file. 
284   * @param keyClass The 'key' type.
285   * @param valClass The 'value' type.
286   * @return Returns the handle to the constructed SequenceFile Writer.
287   * @throws IOException
288   * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)}
289   *     instead.
290   */
291  @Deprecated
292  public static Writer 
293    createWriter(FileSystem fs, Configuration conf, Path name, 
294                 Class keyClass, Class valClass) throws IOException {
295    return createWriter(conf, Writer.filesystem(fs),
296                        Writer.file(name), Writer.keyClass(keyClass),
297                        Writer.valueClass(valClass));
298  }
299  
300  /**
301   * Construct the preferred type of SequenceFile Writer.
302   * @param fs The configured filesystem. 
303   * @param conf The configuration.
304   * @param name The name of the file. 
305   * @param keyClass The 'key' type.
306   * @param valClass The 'value' type.
307   * @param compressionType The compression type.
308   * @return Returns the handle to the constructed SequenceFile Writer.
309   * @throws IOException
310   * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)}
311   *     instead.
312   */
313  @Deprecated
314  public static Writer 
315    createWriter(FileSystem fs, Configuration conf, Path name, 
316                 Class keyClass, Class valClass, 
317                 CompressionType compressionType) throws IOException {
318    return createWriter(conf, Writer.filesystem(fs),
319                        Writer.file(name), Writer.keyClass(keyClass),
320                        Writer.valueClass(valClass), 
321                        Writer.compression(compressionType));
322  }
323  
324  /**
325   * Construct the preferred type of SequenceFile Writer.
326   * @param fs The configured filesystem. 
327   * @param conf The configuration.
328   * @param name The name of the file. 
329   * @param keyClass The 'key' type.
330   * @param valClass The 'value' type.
331   * @param compressionType The compression type.
332   * @param progress The Progressable object to track progress.
333   * @return Returns the handle to the constructed SequenceFile Writer.
334   * @throws IOException
335   * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)}
336   *     instead.
337   */
338  @Deprecated
339  public static Writer
340    createWriter(FileSystem fs, Configuration conf, Path name, 
341                 Class keyClass, Class valClass, CompressionType compressionType,
342                 Progressable progress) throws IOException {
343    return createWriter(conf, Writer.file(name),
344                        Writer.filesystem(fs),
345                        Writer.keyClass(keyClass),
346                        Writer.valueClass(valClass), 
347                        Writer.compression(compressionType),
348                        Writer.progressable(progress));
349  }
350
351  /**
352   * Construct the preferred type of SequenceFile Writer.
353   * @param fs The configured filesystem. 
354   * @param conf The configuration.
355   * @param name The name of the file. 
356   * @param keyClass The 'key' type.
357   * @param valClass The 'value' type.
358   * @param compressionType The compression type.
359   * @param codec The compression codec.
360   * @return Returns the handle to the constructed SequenceFile Writer.
361   * @throws IOException
362   * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)}
363   *     instead.
364   */
365  @Deprecated
366  public static Writer 
367    createWriter(FileSystem fs, Configuration conf, Path name, 
368                 Class keyClass, Class valClass, CompressionType compressionType, 
369                 CompressionCodec codec) throws IOException {
370    return createWriter(conf, Writer.file(name),
371                        Writer.filesystem(fs),
372                        Writer.keyClass(keyClass),
373                        Writer.valueClass(valClass), 
374                        Writer.compression(compressionType, codec));
375  }
376  
377  /**
378   * Construct the preferred type of SequenceFile Writer.
379   * @param fs The configured filesystem. 
380   * @param conf The configuration.
381   * @param name The name of the file. 
382   * @param keyClass The 'key' type.
383   * @param valClass The 'value' type.
384   * @param compressionType The compression type.
385   * @param codec The compression codec.
386   * @param progress The Progressable object to track progress.
387   * @param metadata The metadata of the file.
388   * @return Returns the handle to the constructed SequenceFile Writer.
389   * @throws IOException
390   * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)}
391   *     instead.
392   */
393  @Deprecated
394  public static Writer
395    createWriter(FileSystem fs, Configuration conf, Path name, 
396                 Class keyClass, Class valClass, 
397                 CompressionType compressionType, CompressionCodec codec,
398                 Progressable progress, Metadata metadata) throws IOException {
399    return createWriter(conf, Writer.file(name),
400                        Writer.filesystem(fs),
401                        Writer.keyClass(keyClass),
402                        Writer.valueClass(valClass),
403                        Writer.compression(compressionType, codec),
404                        Writer.progressable(progress),
405                        Writer.metadata(metadata));
406  }
407
408  /**
409   * Construct the preferred type of SequenceFile Writer.
410   * @param fs The configured filesystem.
411   * @param conf The configuration.
412   * @param name The name of the file.
413   * @param keyClass The 'key' type.
414   * @param valClass The 'value' type.
415   * @param bufferSize buffer size for the underlaying outputstream.
416   * @param replication replication factor for the file.
417   * @param blockSize block size for the file.
418   * @param compressionType The compression type.
419   * @param codec The compression codec.
420   * @param progress The Progressable object to track progress.
421   * @param metadata The metadata of the file.
422   * @return Returns the handle to the constructed SequenceFile Writer.
423   * @throws IOException
424   * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)}
425   *     instead.
426   */
427  @Deprecated
428  public static Writer
429    createWriter(FileSystem fs, Configuration conf, Path name,
430                 Class keyClass, Class valClass, int bufferSize,
431                 short replication, long blockSize,
432                 CompressionType compressionType, CompressionCodec codec,
433                 Progressable progress, Metadata metadata) throws IOException {
434    return createWriter(conf, Writer.file(name),
435                        Writer.filesystem(fs),
436                        Writer.keyClass(keyClass),
437                        Writer.valueClass(valClass), 
438                        Writer.bufferSize(bufferSize), 
439                        Writer.replication(replication),
440                        Writer.blockSize(blockSize),
441                        Writer.compression(compressionType, codec),
442                        Writer.progressable(progress),
443                        Writer.metadata(metadata));
444  }
445
446  /**
447   * Construct the preferred type of SequenceFile Writer.
448   * @param fs The configured filesystem.
449   * @param conf The configuration.
450   * @param name The name of the file.
451   * @param keyClass The 'key' type.
452   * @param valClass The 'value' type.
453   * @param bufferSize buffer size for the underlaying outputstream.
454   * @param replication replication factor for the file.
455   * @param blockSize block size for the file.
456   * @param createParent create parent directory if non-existent
457   * @param compressionType The compression type.
458   * @param codec The compression codec.
459   * @param metadata The metadata of the file.
460   * @return Returns the handle to the constructed SequenceFile Writer.
461   * @throws IOException
462   */
463  @Deprecated
464  public static Writer
465  createWriter(FileSystem fs, Configuration conf, Path name,
466               Class keyClass, Class valClass, int bufferSize,
467               short replication, long blockSize, boolean createParent,
468               CompressionType compressionType, CompressionCodec codec,
469               Metadata metadata) throws IOException {
470    return createWriter(FileContext.getFileContext(fs.getUri(), conf),
471        conf, name, keyClass, valClass, compressionType, codec,
472        metadata, EnumSet.of(CreateFlag.CREATE,CreateFlag.OVERWRITE),
473        CreateOpts.bufferSize(bufferSize),
474        createParent ? CreateOpts.createParent()
475                     : CreateOpts.donotCreateParent(),
476        CreateOpts.repFac(replication),
477        CreateOpts.blockSize(blockSize)
478      );
479  }
480
481  /**
482   * Construct the preferred type of SequenceFile Writer.
483   * @param fc The context for the specified file.
484   * @param conf The configuration.
485   * @param name The name of the file.
486   * @param keyClass The 'key' type.
487   * @param valClass The 'value' type.
488   * @param compressionType The compression type.
489   * @param codec The compression codec.
490   * @param metadata The metadata of the file.
491   * @param createFlag gives the semantics of create: overwrite, append etc.
492   * @param opts file creation options; see {@link CreateOpts}.
493   * @return Returns the handle to the constructed SequenceFile Writer.
494   * @throws IOException
495   */
496  public static Writer
497  createWriter(FileContext fc, Configuration conf, Path name,
498               Class keyClass, Class valClass,
499               CompressionType compressionType, CompressionCodec codec,
500               Metadata metadata,
501               final EnumSet<CreateFlag> createFlag, CreateOpts... opts)
502               throws IOException {
503    return createWriter(conf, fc.create(name, createFlag, opts),
504          keyClass, valClass, compressionType, codec, metadata).ownStream();
505  }
506
507  /**
508   * Construct the preferred type of SequenceFile Writer.
509   * @param fs The configured filesystem. 
510   * @param conf The configuration.
511   * @param name The name of the file. 
512   * @param keyClass The 'key' type.
513   * @param valClass The 'value' type.
514   * @param compressionType The compression type.
515   * @param codec The compression codec.
516   * @param progress The Progressable object to track progress.
517   * @return Returns the handle to the constructed SequenceFile Writer.
518   * @throws IOException
519   * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)}
520   *     instead.
521   */
522  @Deprecated
523  public static Writer
524    createWriter(FileSystem fs, Configuration conf, Path name, 
525                 Class keyClass, Class valClass, 
526                 CompressionType compressionType, CompressionCodec codec,
527                 Progressable progress) throws IOException {
528    return createWriter(conf, Writer.file(name),
529                        Writer.filesystem(fs),
530                        Writer.keyClass(keyClass),
531                        Writer.valueClass(valClass),
532                        Writer.compression(compressionType, codec),
533                        Writer.progressable(progress));
534  }
535
536  /**
537   * Construct the preferred type of 'raw' SequenceFile Writer.
538   * @param conf The configuration.
539   * @param out The stream on top which the writer is to be constructed.
540   * @param keyClass The 'key' type.
541   * @param valClass The 'value' type.
542   * @param compressionType The compression type.
543   * @param codec The compression codec.
544   * @param metadata The metadata of the file.
545   * @return Returns the handle to the constructed SequenceFile Writer.
546   * @throws IOException
547   * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)}
548   *     instead.
549   */
550  @Deprecated
551  public static Writer
552    createWriter(Configuration conf, FSDataOutputStream out, 
553                 Class keyClass, Class valClass,
554                 CompressionType compressionType,
555                 CompressionCodec codec, Metadata metadata) throws IOException {
556    return createWriter(conf, Writer.stream(out), Writer.keyClass(keyClass),
557                        Writer.valueClass(valClass), 
558                        Writer.compression(compressionType, codec),
559                        Writer.metadata(metadata));
560  }
561  
562  /**
563   * Construct the preferred type of 'raw' SequenceFile Writer.
564   * @param conf The configuration.
565   * @param out The stream on top which the writer is to be constructed.
566   * @param keyClass The 'key' type.
567   * @param valClass The 'value' type.
568   * @param compressionType The compression type.
569   * @param codec The compression codec.
570   * @return Returns the handle to the constructed SequenceFile Writer.
571   * @throws IOException
572   * @deprecated Use {@link #createWriter(Configuration, Writer.Option...)}
573   *     instead.
574   */
575  @Deprecated
576  public static Writer
577    createWriter(Configuration conf, FSDataOutputStream out, 
578                 Class keyClass, Class valClass, CompressionType compressionType,
579                 CompressionCodec codec) throws IOException {
580    return createWriter(conf, Writer.stream(out), Writer.keyClass(keyClass),
581                        Writer.valueClass(valClass),
582                        Writer.compression(compressionType, codec));
583  }
584  
585
586  /** The interface to 'raw' values of SequenceFiles. */
587  public static interface ValueBytes {
588
589    /** Writes the uncompressed bytes to the outStream.
590     * @param outStream : Stream to write uncompressed bytes into.
591     * @throws IOException
592     */
593    public void writeUncompressedBytes(DataOutputStream outStream)
594      throws IOException;
595
596    /** Write compressed bytes to outStream. 
597     * Note: that it will NOT compress the bytes if they are not compressed.
598     * @param outStream : Stream to write compressed bytes into.
599     */
600    public void writeCompressedBytes(DataOutputStream outStream) 
601      throws IllegalArgumentException, IOException;
602
603    /**
604     * Size of stored data.
605     */
606    public int getSize();
607  }
608  
609  private static class UncompressedBytes implements ValueBytes {
610    private int dataSize;
611    private byte[] data;
612    
613    private UncompressedBytes() {
614      data = null;
615      dataSize = 0;
616    }
617    
618    private void reset(DataInputStream in, int length) throws IOException {
619      if (data == null) {
620        data = new byte[length];
621      } else if (length > data.length) {
622        data = new byte[Math.max(length, data.length * 2)];
623      }
624      dataSize = -1;
625      in.readFully(data, 0, length);
626      dataSize = length;
627    }
628    
629    @Override
630    public int getSize() {
631      return dataSize;
632    }
633    
634    @Override
635    public void writeUncompressedBytes(DataOutputStream outStream)
636      throws IOException {
637      outStream.write(data, 0, dataSize);
638    }
639
640    @Override
641    public void writeCompressedBytes(DataOutputStream outStream) 
642      throws IllegalArgumentException, IOException {
643      throw 
644        new IllegalArgumentException("UncompressedBytes cannot be compressed!");
645    }
646
647  } // UncompressedBytes
648  
649  private static class CompressedBytes implements ValueBytes {
650    private int dataSize;
651    private byte[] data;
652    DataInputBuffer rawData = null;
653    CompressionCodec codec = null;
654    CompressionInputStream decompressedStream = null;
655
656    private CompressedBytes(CompressionCodec codec) {
657      data = null;
658      dataSize = 0;
659      this.codec = codec;
660    }
661
662    private void reset(DataInputStream in, int length) throws IOException {
663      if (data == null) {
664        data = new byte[length];
665      } else if (length > data.length) {
666        data = new byte[Math.max(length, data.length * 2)];
667      } 
668      dataSize = -1;
669      in.readFully(data, 0, length);
670      dataSize = length;
671    }
672    
673    @Override
674    public int getSize() {
675      return dataSize;
676    }
677    
678    @Override
679    public void writeUncompressedBytes(DataOutputStream outStream)
680      throws IOException {
681      if (decompressedStream == null) {
682        rawData = new DataInputBuffer();
683        decompressedStream = codec.createInputStream(rawData);
684      } else {
685        decompressedStream.resetState();
686      }
687      rawData.reset(data, 0, dataSize);
688
689      byte[] buffer = new byte[8192];
690      int bytesRead = 0;
691      while ((bytesRead = decompressedStream.read(buffer, 0, 8192)) != -1) {
692        outStream.write(buffer, 0, bytesRead);
693      }
694    }
695
696    @Override
697    public void writeCompressedBytes(DataOutputStream outStream) 
698      throws IllegalArgumentException, IOException {
699      outStream.write(data, 0, dataSize);
700    }
701
702  } // CompressedBytes
703  
704  /**
705   * The class encapsulating with the metadata of a file.
706   * The metadata of a file is a list of attribute name/value
707   * pairs of Text type.
708   *
709   */
710  public static class Metadata implements Writable {
711
712    private TreeMap<Text, Text> theMetadata;
713    
714    public Metadata() {
715      this(new TreeMap<Text, Text>());
716    }
717    
718    public Metadata(TreeMap<Text, Text> arg) {
719      if (arg == null) {
720        this.theMetadata = new TreeMap<Text, Text>();
721      } else {
722        this.theMetadata = arg;
723      }
724    }
725    
726    public Text get(Text name) {
727      return this.theMetadata.get(name);
728    }
729    
730    public void set(Text name, Text value) {
731      this.theMetadata.put(name, value);
732    }
733    
734    public TreeMap<Text, Text> getMetadata() {
735      return new TreeMap<Text, Text>(this.theMetadata);
736    }
737    
738    @Override
739    public void write(DataOutput out) throws IOException {
740      out.writeInt(this.theMetadata.size());
741      Iterator<Map.Entry<Text, Text>> iter =
742        this.theMetadata.entrySet().iterator();
743      while (iter.hasNext()) {
744        Map.Entry<Text, Text> en = iter.next();
745        en.getKey().write(out);
746        en.getValue().write(out);
747      }
748    }
749
750    @Override
751    public void readFields(DataInput in) throws IOException {
752      int sz = in.readInt();
753      if (sz < 0) throw new IOException("Invalid size: " + sz + " for file metadata object");
754      this.theMetadata = new TreeMap<Text, Text>();
755      for (int i = 0; i < sz; i++) {
756        Text key = new Text();
757        Text val = new Text();
758        key.readFields(in);
759        val.readFields(in);
760        this.theMetadata.put(key, val);
761      }    
762    }
763
764    @Override
765    public boolean equals(Object other) {
766      if (other == null) {
767        return false;
768      }
769      if (other.getClass() != this.getClass()) {
770        return false;
771      } else {
772        return equals((Metadata)other);
773      }
774    }
775    
776    public boolean equals(Metadata other) {
777      if (other == null) return false;
778      if (this.theMetadata.size() != other.theMetadata.size()) {
779        return false;
780      }
781      Iterator<Map.Entry<Text, Text>> iter1 =
782        this.theMetadata.entrySet().iterator();
783      Iterator<Map.Entry<Text, Text>> iter2 =
784        other.theMetadata.entrySet().iterator();
785      while (iter1.hasNext() && iter2.hasNext()) {
786        Map.Entry<Text, Text> en1 = iter1.next();
787        Map.Entry<Text, Text> en2 = iter2.next();
788        if (!en1.getKey().equals(en2.getKey())) {
789          return false;
790        }
791        if (!en1.getValue().equals(en2.getValue())) {
792          return false;
793        }
794      }
795      if (iter1.hasNext() || iter2.hasNext()) {
796        return false;
797      }
798      return true;
799    }
800
801    @Override
802    public int hashCode() {
803      assert false : "hashCode not designed";
804      return 42; // any arbitrary constant will do 
805    }
806    
807    @Override
808    public String toString() {
809      StringBuilder sb = new StringBuilder();
810      sb.append("size: ").append(this.theMetadata.size()).append("\n");
811      Iterator<Map.Entry<Text, Text>> iter =
812        this.theMetadata.entrySet().iterator();
813      while (iter.hasNext()) {
814        Map.Entry<Text, Text> en = iter.next();
815        sb.append("\t").append(en.getKey().toString()).append("\t").append(en.getValue().toString());
816        sb.append("\n");
817      }
818      return sb.toString();
819    }
820  }
821  
822  /** Write key/value pairs to a sequence-format file. */
823  public static class Writer implements java.io.Closeable, Syncable {
824    private Configuration conf;
825    FSDataOutputStream out;
826    boolean ownOutputStream = true;
827    DataOutputBuffer buffer = new DataOutputBuffer();
828
829    Class keyClass;
830    Class valClass;
831
832    private final CompressionType compress;
833    CompressionCodec codec = null;
834    CompressionOutputStream deflateFilter = null;
835    DataOutputStream deflateOut = null;
836    Metadata metadata = null;
837    Compressor compressor = null;
838
839    private boolean appendMode = false;
840
841    protected Serializer keySerializer;
842    protected Serializer uncompressedValSerializer;
843    protected Serializer compressedValSerializer;
844    
845    // Insert a globally unique 16-byte value every few entries, so that one
846    // can seek into the middle of a file and then synchronize with record
847    // starts and ends by scanning for this value.
848    long lastSyncPos;                     // position of last sync
849    byte[] sync;                          // 16 random bytes
850    {
851      try {                                       
852        MessageDigest digester = MessageDigest.getInstance("MD5");
853        long time = Time.now();
854        digester.update((new UID()+"@"+time).getBytes());
855        sync = digester.digest();
856      } catch (Exception e) {
857        throw new RuntimeException(e);
858      }
859    }
860
861    public static interface Option {}
862    
863    static class FileOption extends Options.PathOption 
864                                    implements Option {
865      FileOption(Path path) {
866        super(path);
867      }
868    }
869
870    /**
871     * @deprecated only used for backwards-compatibility in the createWriter methods
872     * that take FileSystem.
873     */
874    @Deprecated
875    private static class FileSystemOption implements Option {
876      private final FileSystem value;
877      protected FileSystemOption(FileSystem value) {
878        this.value = value;
879      }
880      public FileSystem getValue() {
881        return value;
882      }
883    }
884
885    static class StreamOption extends Options.FSDataOutputStreamOption 
886                              implements Option {
887      StreamOption(FSDataOutputStream stream) {
888        super(stream);
889      }
890    }
891
892    static class BufferSizeOption extends Options.IntegerOption
893                                  implements Option {
894      BufferSizeOption(int value) {
895        super(value);
896      }
897    }
898    
899    static class BlockSizeOption extends Options.LongOption implements Option {
900      BlockSizeOption(long value) {
901        super(value);
902      }
903    }
904
905    static class ReplicationOption extends Options.IntegerOption
906                                   implements Option {
907      ReplicationOption(int value) {
908        super(value);
909      }
910    }
911
912    static class AppendIfExistsOption extends Options.BooleanOption implements
913        Option {
914      AppendIfExistsOption(boolean value) {
915        super(value);
916      }
917    }
918
919    static class KeyClassOption extends Options.ClassOption implements Option {
920      KeyClassOption(Class<?> value) {
921        super(value);
922      }
923    }
924
925    static class ValueClassOption extends Options.ClassOption
926                                          implements Option {
927      ValueClassOption(Class<?> value) {
928        super(value);
929      }
930    }
931
932    static class MetadataOption implements Option {
933      private final Metadata value;
934      MetadataOption(Metadata value) {
935        this.value = value;
936      }
937      Metadata getValue() {
938        return value;
939      }
940    }
941
942    static class ProgressableOption extends Options.ProgressableOption
943                                    implements Option {
944      ProgressableOption(Progressable value) {
945        super(value);
946      }
947    }
948
949    private static class CompressionOption implements Option {
950      private final CompressionType value;
951      private final CompressionCodec codec;
952      CompressionOption(CompressionType value) {
953        this(value, null);
954      }
955      CompressionOption(CompressionType value, CompressionCodec codec) {
956        this.value = value;
957        this.codec = (CompressionType.NONE != value && null == codec)
958          ? new DefaultCodec()
959          : codec;
960      }
961      CompressionType getValue() {
962        return value;
963      }
964      CompressionCodec getCodec() {
965        return codec;
966      }
967    }
968
969    public static Option file(Path value) {
970      return new FileOption(value);
971    }
972
973    /**
974     * @deprecated only used for backwards-compatibility in the createWriter methods
975     * that take FileSystem.
976     */
977    @Deprecated
978    private static Option filesystem(FileSystem fs) {
979      return new SequenceFile.Writer.FileSystemOption(fs);
980    }
981    
982    public static Option bufferSize(int value) {
983      return new BufferSizeOption(value);
984    }
985    
986    public static Option stream(FSDataOutputStream value) {
987      return new StreamOption(value);
988    }
989    
990    public static Option replication(short value) {
991      return new ReplicationOption(value);
992    }
993    
994    public static Option appendIfExists(boolean value) {
995      return new AppendIfExistsOption(value);
996    }
997
998    public static Option blockSize(long value) {
999      return new BlockSizeOption(value);
1000    }
1001    
1002    public static Option progressable(Progressable value) {
1003      return new ProgressableOption(value);
1004    }
1005
1006    public static Option keyClass(Class<?> value) {
1007      return new KeyClassOption(value);
1008    }
1009    
1010    public static Option valueClass(Class<?> value) {
1011      return new ValueClassOption(value);
1012    }
1013    
1014    public static Option metadata(Metadata value) {
1015      return new MetadataOption(value);
1016    }
1017
1018    public static Option compression(CompressionType value) {
1019      return new CompressionOption(value);
1020    }
1021
1022    public static Option compression(CompressionType value,
1023        CompressionCodec codec) {
1024      return new CompressionOption(value, codec);
1025    }
1026    
1027    /**
1028     * Construct a uncompressed writer from a set of options.
1029     * @param conf the configuration to use
1030     * @param options the options used when creating the writer
1031     * @throws IOException if it fails
1032     */
1033    Writer(Configuration conf, 
1034           Option... opts) throws IOException {
1035      BlockSizeOption blockSizeOption = 
1036        Options.getOption(BlockSizeOption.class, opts);
1037      BufferSizeOption bufferSizeOption = 
1038        Options.getOption(BufferSizeOption.class, opts);
1039      ReplicationOption replicationOption = 
1040        Options.getOption(ReplicationOption.class, opts);
1041      ProgressableOption progressOption = 
1042        Options.getOption(ProgressableOption.class, opts);
1043      FileOption fileOption = Options.getOption(FileOption.class, opts);
1044      AppendIfExistsOption appendIfExistsOption = Options.getOption(
1045          AppendIfExistsOption.class, opts);
1046      FileSystemOption fsOption = Options.getOption(FileSystemOption.class, opts);
1047      StreamOption streamOption = Options.getOption(StreamOption.class, opts);
1048      KeyClassOption keyClassOption = 
1049        Options.getOption(KeyClassOption.class, opts);
1050      ValueClassOption valueClassOption = 
1051        Options.getOption(ValueClassOption.class, opts);
1052      MetadataOption metadataOption = 
1053        Options.getOption(MetadataOption.class, opts);
1054      CompressionOption compressionTypeOption =
1055        Options.getOption(CompressionOption.class, opts);
1056      // check consistency of options
1057      if ((fileOption == null) == (streamOption == null)) {
1058        throw new IllegalArgumentException("file or stream must be specified");
1059      }
1060      if (fileOption == null && (blockSizeOption != null ||
1061                                 bufferSizeOption != null ||
1062                                 replicationOption != null ||
1063                                 progressOption != null)) {
1064        throw new IllegalArgumentException("file modifier options not " +
1065                                           "compatible with stream");
1066      }
1067
1068      FSDataOutputStream out;
1069      boolean ownStream = fileOption != null;
1070      if (ownStream) {
1071        Path p = fileOption.getValue();
1072        FileSystem fs;
1073        if (fsOption != null) {
1074          fs = fsOption.getValue();
1075        } else {
1076          fs = p.getFileSystem(conf);
1077        }
1078        int bufferSize = bufferSizeOption == null ? getBufferSize(conf) :
1079          bufferSizeOption.getValue();
1080        short replication = replicationOption == null ? 
1081          fs.getDefaultReplication(p) :
1082          (short) replicationOption.getValue();
1083        long blockSize = blockSizeOption == null ? fs.getDefaultBlockSize(p) :
1084          blockSizeOption.getValue();
1085        Progressable progress = progressOption == null ? null :
1086          progressOption.getValue();
1087
1088        if (appendIfExistsOption != null && appendIfExistsOption.getValue()
1089            && fs.exists(p)) {
1090
1091          // Read the file and verify header details
1092          SequenceFile.Reader reader = new SequenceFile.Reader(conf,
1093              SequenceFile.Reader.file(p), new Reader.OnlyHeaderOption());
1094          try {
1095
1096            if (keyClassOption.getValue() != reader.getKeyClass()
1097                || valueClassOption.getValue() != reader.getValueClass()) {
1098              throw new IllegalArgumentException(
1099                  "Key/value class provided does not match the file");
1100            }
1101
1102            if (reader.getVersion() != VERSION[3]) {
1103              throw new VersionMismatchException(VERSION[3],
1104                  reader.getVersion());
1105            }
1106
1107            if (metadataOption != null) {
1108              LOG.info("MetaData Option is ignored during append");
1109            }
1110            metadataOption = (MetadataOption) SequenceFile.Writer
1111                .metadata(reader.getMetadata());
1112
1113            CompressionOption readerCompressionOption = new CompressionOption(
1114                reader.getCompressionType(), reader.getCompressionCodec());
1115
1116            if (readerCompressionOption.value != compressionTypeOption.value
1117                || !readerCompressionOption.codec.getClass().getName()
1118                    .equals(compressionTypeOption.codec.getClass().getName())) {
1119              throw new IllegalArgumentException(
1120                  "Compression option provided does not match the file");
1121            }
1122
1123            sync = reader.getSync();
1124
1125          } finally {
1126            reader.close();
1127          }
1128
1129          out = fs.append(p, bufferSize, progress);
1130          this.appendMode = true;
1131        } else {
1132          out = fs
1133              .create(p, true, bufferSize, replication, blockSize, progress);
1134        }
1135      } else {
1136        out = streamOption.getValue();
1137      }
1138      Class<?> keyClass = keyClassOption == null ?
1139          Object.class : keyClassOption.getValue();
1140      Class<?> valueClass = valueClassOption == null ?
1141          Object.class : valueClassOption.getValue();
1142      Metadata metadata = metadataOption == null ?
1143          new Metadata() : metadataOption.getValue();
1144      this.compress = compressionTypeOption.getValue();
1145      final CompressionCodec codec = compressionTypeOption.getCodec();
1146      if (codec != null &&
1147          (codec instanceof GzipCodec) &&
1148          !NativeCodeLoader.isNativeCodeLoaded() &&
1149          !ZlibFactory.isNativeZlibLoaded(conf)) {
1150        throw new IllegalArgumentException("SequenceFile doesn't work with " +
1151                                           "GzipCodec without native-hadoop " +
1152                                           "code!");
1153      }
1154      init(conf, out, ownStream, keyClass, valueClass, codec, metadata);
1155    }
1156
1157    /** Create the named file.
1158     * @deprecated Use 
1159     *   {@link SequenceFile#createWriter(Configuration, Writer.Option...)} 
1160     *   instead.
1161     */
1162    @Deprecated
1163    public Writer(FileSystem fs, Configuration conf, Path name, 
1164                  Class keyClass, Class valClass) throws IOException {
1165      this.compress = CompressionType.NONE;
1166      init(conf, fs.create(name), true, keyClass, valClass, null, 
1167           new Metadata());
1168    }
1169    
1170    /** Create the named file with write-progress reporter.
1171     * @deprecated Use 
1172     *   {@link SequenceFile#createWriter(Configuration, Writer.Option...)} 
1173     *   instead.
1174     */
1175    @Deprecated
1176    public Writer(FileSystem fs, Configuration conf, Path name, 
1177                  Class keyClass, Class valClass,
1178                  Progressable progress, Metadata metadata) throws IOException {
1179      this.compress = CompressionType.NONE;
1180      init(conf, fs.create(name, progress), true, keyClass, valClass,
1181           null, metadata);
1182    }
1183    
1184    /** Create the named file with write-progress reporter. 
1185     * @deprecated Use 
1186     *   {@link SequenceFile#createWriter(Configuration, Writer.Option...)} 
1187     *   instead.
1188     */
1189    @Deprecated
1190    public Writer(FileSystem fs, Configuration conf, Path name,
1191                  Class keyClass, Class valClass,
1192                  int bufferSize, short replication, long blockSize,
1193                  Progressable progress, Metadata metadata) throws IOException {
1194      this.compress = CompressionType.NONE;
1195      init(conf,
1196           fs.create(name, true, bufferSize, replication, blockSize, progress),
1197           true, keyClass, valClass, null, metadata);
1198    }
1199
1200    boolean isCompressed() { return compress != CompressionType.NONE; }
1201    boolean isBlockCompressed() { return compress == CompressionType.BLOCK; }
1202    
1203    Writer ownStream() { this.ownOutputStream = true; return this;  }
1204
1205    /** Write and flush the file header. */
1206    private void writeFileHeader() 
1207      throws IOException {
1208      out.write(VERSION);
1209      Text.writeString(out, keyClass.getName());
1210      Text.writeString(out, valClass.getName());
1211      
1212      out.writeBoolean(this.isCompressed());
1213      out.writeBoolean(this.isBlockCompressed());
1214      
1215      if (this.isCompressed()) {
1216        Text.writeString(out, (codec.getClass()).getName());
1217      }
1218      this.metadata.write(out);
1219      out.write(sync);                       // write the sync bytes
1220      out.flush();                           // flush header
1221    }
1222
1223    /** Initialize. */
1224    @SuppressWarnings("unchecked")
1225    void init(Configuration conf, FSDataOutputStream out, boolean ownStream,
1226              Class keyClass, Class valClass,
1227              CompressionCodec codec, Metadata metadata) 
1228      throws IOException {
1229      this.conf = conf;
1230      this.out = out;
1231      this.ownOutputStream = ownStream;
1232      this.keyClass = keyClass;
1233      this.valClass = valClass;
1234      this.codec = codec;
1235      this.metadata = metadata;
1236      SerializationFactory serializationFactory = new SerializationFactory(conf);
1237      this.keySerializer = serializationFactory.getSerializer(keyClass);
1238      if (this.keySerializer == null) {
1239        throw new IOException(
1240            "Could not find a serializer for the Key class: '"
1241                + keyClass.getCanonicalName() + "'. "
1242                + "Please ensure that the configuration '" +
1243                CommonConfigurationKeys.IO_SERIALIZATIONS_KEY + "' is "
1244                + "properly configured, if you're using"
1245                + "custom serialization.");
1246      }
1247      this.keySerializer.open(buffer);
1248      this.uncompressedValSerializer = serializationFactory.getSerializer(valClass);
1249      if (this.uncompressedValSerializer == null) {
1250        throw new IOException(
1251            "Could not find a serializer for the Value class: '"
1252                + valClass.getCanonicalName() + "'. "
1253                + "Please ensure that the configuration '" +
1254                CommonConfigurationKeys.IO_SERIALIZATIONS_KEY + "' is "
1255                + "properly configured, if you're using"
1256                + "custom serialization.");
1257      }
1258      this.uncompressedValSerializer.open(buffer);
1259      if (this.codec != null) {
1260        ReflectionUtils.setConf(this.codec, this.conf);
1261        this.compressor = CodecPool.getCompressor(this.codec);
1262        this.deflateFilter = this.codec.createOutputStream(buffer, compressor);
1263        this.deflateOut = 
1264          new DataOutputStream(new BufferedOutputStream(deflateFilter));
1265        this.compressedValSerializer = serializationFactory.getSerializer(valClass);
1266        if (this.compressedValSerializer == null) {
1267          throw new IOException(
1268              "Could not find a serializer for the Value class: '"
1269                  + valClass.getCanonicalName() + "'. "
1270                  + "Please ensure that the configuration '" +
1271                  CommonConfigurationKeys.IO_SERIALIZATIONS_KEY + "' is "
1272                  + "properly configured, if you're using"
1273                  + "custom serialization.");
1274        }
1275        this.compressedValSerializer.open(deflateOut);
1276      }
1277
1278      if (appendMode) {
1279        sync();
1280      } else {
1281        writeFileHeader();
1282      }
1283    }
1284    
1285    /** Returns the class of keys in this file. */
1286    public Class getKeyClass() { return keyClass; }
1287
1288    /** Returns the class of values in this file. */
1289    public Class getValueClass() { return valClass; }
1290
1291    /** Returns the compression codec of data in this file. */
1292    public CompressionCodec getCompressionCodec() { return codec; }
1293    
1294    /** create a sync point */
1295    public void sync() throws IOException {
1296      if (sync != null && lastSyncPos != out.getPos()) {
1297        out.writeInt(SYNC_ESCAPE);                // mark the start of the sync
1298        out.write(sync);                          // write sync
1299        lastSyncPos = out.getPos();               // update lastSyncPos
1300      }
1301    }
1302
1303    /**
1304     * flush all currently written data to the file system
1305     * @deprecated Use {@link #hsync()} or {@link #hflush()} instead
1306     */
1307    @Deprecated
1308    public void syncFs() throws IOException {
1309      if (out != null) {
1310        out.sync();                               // flush contents to file system
1311      }
1312    }
1313
1314    @Override
1315    public void hsync() throws IOException {
1316      if (out != null) {
1317        out.hsync();
1318      }
1319    }
1320
1321    @Override
1322    public void hflush() throws IOException {
1323      if (out != null) {
1324        out.hflush();
1325      }
1326    }
1327    
1328    /** Returns the configuration of this file. */
1329    Configuration getConf() { return conf; }
1330    
1331    /** Close the file. */
1332    @Override
1333    public synchronized void close() throws IOException {
1334      keySerializer.close();
1335      uncompressedValSerializer.close();
1336      if (compressedValSerializer != null) {
1337        compressedValSerializer.close();
1338      }
1339
1340      CodecPool.returnCompressor(compressor);
1341      compressor = null;
1342      
1343      if (out != null) {
1344        
1345        // Close the underlying stream iff we own it...
1346        if (ownOutputStream) {
1347          out.close();
1348        } else {
1349          out.flush();
1350        }
1351        out = null;
1352      }
1353    }
1354
1355    synchronized void checkAndWriteSync() throws IOException {
1356      if (sync != null &&
1357          out.getPos() >= lastSyncPos+SYNC_INTERVAL) { // time to emit sync
1358        sync();
1359      }
1360    }
1361
1362    /** Append a key/value pair. */
1363    public void append(Writable key, Writable val)
1364      throws IOException {
1365      append((Object) key, (Object) val);
1366    }
1367
1368    /** Append a key/value pair. */
1369    @SuppressWarnings("unchecked")
1370    public synchronized void append(Object key, Object val)
1371      throws IOException {
1372      if (key.getClass() != keyClass)
1373        throw new IOException("wrong key class: "+key.getClass().getName()
1374                              +" is not "+keyClass);
1375      if (val.getClass() != valClass)
1376        throw new IOException("wrong value class: "+val.getClass().getName()
1377                              +" is not "+valClass);
1378
1379      buffer.reset();
1380
1381      // Append the 'key'
1382      keySerializer.serialize(key);
1383      int keyLength = buffer.getLength();
1384      if (keyLength < 0)
1385        throw new IOException("negative length keys not allowed: " + key);
1386
1387      // Append the 'value'
1388      if (compress == CompressionType.RECORD) {
1389        deflateFilter.resetState();
1390        compressedValSerializer.serialize(val);
1391        deflateOut.flush();
1392        deflateFilter.finish();
1393      } else {
1394        uncompressedValSerializer.serialize(val);
1395      }
1396
1397      // Write the record out
1398      checkAndWriteSync();                                // sync
1399      out.writeInt(buffer.getLength());                   // total record length
1400      out.writeInt(keyLength);                            // key portion length
1401      out.write(buffer.getData(), 0, buffer.getLength()); // data
1402    }
1403
1404    public synchronized void appendRaw(byte[] keyData, int keyOffset,
1405        int keyLength, ValueBytes val) throws IOException {
1406      if (keyLength < 0)
1407        throw new IOException("negative length keys not allowed: " + keyLength);
1408
1409      int valLength = val.getSize();
1410
1411      checkAndWriteSync();
1412      
1413      out.writeInt(keyLength+valLength);          // total record length
1414      out.writeInt(keyLength);                    // key portion length
1415      out.write(keyData, keyOffset, keyLength);   // key
1416      val.writeUncompressedBytes(out);            // value
1417    }
1418
1419    /** Returns the current length of the output file.
1420     *
1421     * <p>This always returns a synchronized position.  In other words,
1422     * immediately after calling {@link SequenceFile.Reader#seek(long)} with a position
1423     * returned by this method, {@link SequenceFile.Reader#next(Writable)} may be called.  However
1424     * the key may be earlier in the file than key last written when this
1425     * method was called (e.g., with block-compression, it may be the first key
1426     * in the block that was being written when this method was called).
1427     */
1428    public synchronized long getLength() throws IOException {
1429      return out.getPos();
1430    }
1431
1432  } // class Writer
1433
1434  /** Write key/compressed-value pairs to a sequence-format file. */
1435  static class RecordCompressWriter extends Writer {
1436    
1437    RecordCompressWriter(Configuration conf, 
1438                         Option... options) throws IOException {
1439      super(conf, options);
1440    }
1441
1442    /** Append a key/value pair. */
1443    @Override
1444    @SuppressWarnings("unchecked")
1445    public synchronized void append(Object key, Object val)
1446      throws IOException {
1447      if (key.getClass() != keyClass)
1448        throw new IOException("wrong key class: "+key.getClass().getName()
1449                              +" is not "+keyClass);
1450      if (val.getClass() != valClass)
1451        throw new IOException("wrong value class: "+val.getClass().getName()
1452                              +" is not "+valClass);
1453
1454      buffer.reset();
1455
1456      // Append the 'key'
1457      keySerializer.serialize(key);
1458      int keyLength = buffer.getLength();
1459      if (keyLength < 0)
1460        throw new IOException("negative length keys not allowed: " + key);
1461
1462      // Compress 'value' and append it
1463      deflateFilter.resetState();
1464      compressedValSerializer.serialize(val);
1465      deflateOut.flush();
1466      deflateFilter.finish();
1467
1468      // Write the record out
1469      checkAndWriteSync();                                // sync
1470      out.writeInt(buffer.getLength());                   // total record length
1471      out.writeInt(keyLength);                            // key portion length
1472      out.write(buffer.getData(), 0, buffer.getLength()); // data
1473    }
1474
1475    /** Append a key/value pair. */
1476    @Override
1477    public synchronized void appendRaw(byte[] keyData, int keyOffset,
1478        int keyLength, ValueBytes val) throws IOException {
1479
1480      if (keyLength < 0)
1481        throw new IOException("negative length keys not allowed: " + keyLength);
1482
1483      int valLength = val.getSize();
1484      
1485      checkAndWriteSync();                        // sync
1486      out.writeInt(keyLength+valLength);          // total record length
1487      out.writeInt(keyLength);                    // key portion length
1488      out.write(keyData, keyOffset, keyLength);   // 'key' data
1489      val.writeCompressedBytes(out);              // 'value' data
1490    }
1491    
1492  } // RecordCompressionWriter
1493
1494  /** Write compressed key/value blocks to a sequence-format file. */
1495  static class BlockCompressWriter extends Writer {
1496    
1497    private int noBufferedRecords = 0;
1498    
1499    private DataOutputBuffer keyLenBuffer = new DataOutputBuffer();
1500    private DataOutputBuffer keyBuffer = new DataOutputBuffer();
1501
1502    private DataOutputBuffer valLenBuffer = new DataOutputBuffer();
1503    private DataOutputBuffer valBuffer = new DataOutputBuffer();
1504
1505    private final int compressionBlockSize;
1506    
1507    BlockCompressWriter(Configuration conf,
1508                        Option... options) throws IOException {
1509      super(conf, options);
1510      compressionBlockSize = 
1511        conf.getInt("io.seqfile.compress.blocksize", 1000000);
1512      keySerializer.close();
1513      keySerializer.open(keyBuffer);
1514      uncompressedValSerializer.close();
1515      uncompressedValSerializer.open(valBuffer);
1516    }
1517
1518    /** Workhorse to check and write out compressed data/lengths */
1519    private synchronized 
1520      void writeBuffer(DataOutputBuffer uncompressedDataBuffer) 
1521      throws IOException {
1522      deflateFilter.resetState();
1523      buffer.reset();
1524      deflateOut.write(uncompressedDataBuffer.getData(), 0, 
1525                       uncompressedDataBuffer.getLength());
1526      deflateOut.flush();
1527      deflateFilter.finish();
1528      
1529      WritableUtils.writeVInt(out, buffer.getLength());
1530      out.write(buffer.getData(), 0, buffer.getLength());
1531    }
1532    
1533    /** Compress and flush contents to dfs */
1534    @Override
1535    public synchronized void sync() throws IOException {
1536      if (noBufferedRecords > 0) {
1537        super.sync();
1538        
1539        // No. of records
1540        WritableUtils.writeVInt(out, noBufferedRecords);
1541        
1542        // Write 'keys' and lengths
1543        writeBuffer(keyLenBuffer);
1544        writeBuffer(keyBuffer);
1545        
1546        // Write 'values' and lengths
1547        writeBuffer(valLenBuffer);
1548        writeBuffer(valBuffer);
1549        
1550        // Flush the file-stream
1551        out.flush();
1552        
1553        // Reset internal states
1554        keyLenBuffer.reset();
1555        keyBuffer.reset();
1556        valLenBuffer.reset();
1557        valBuffer.reset();
1558        noBufferedRecords = 0;
1559      }
1560      
1561    }
1562    
1563    /** Close the file. */
1564    @Override
1565    public synchronized void close() throws IOException {
1566      if (out != null) {
1567        sync();
1568      }
1569      super.close();
1570    }
1571
1572    /** Append a key/value pair. */
1573    @Override
1574    @SuppressWarnings("unchecked")
1575    public synchronized void append(Object key, Object val)
1576      throws IOException {
1577      if (key.getClass() != keyClass)
1578        throw new IOException("wrong key class: "+key+" is not "+keyClass);
1579      if (val.getClass() != valClass)
1580        throw new IOException("wrong value class: "+val+" is not "+valClass);
1581
1582      // Save key/value into respective buffers 
1583      int oldKeyLength = keyBuffer.getLength();
1584      keySerializer.serialize(key);
1585      int keyLength = keyBuffer.getLength() - oldKeyLength;
1586      if (keyLength < 0)
1587        throw new IOException("negative length keys not allowed: " + key);
1588      WritableUtils.writeVInt(keyLenBuffer, keyLength);
1589
1590      int oldValLength = valBuffer.getLength();
1591      uncompressedValSerializer.serialize(val);
1592      int valLength = valBuffer.getLength() - oldValLength;
1593      WritableUtils.writeVInt(valLenBuffer, valLength);
1594      
1595      // Added another key/value pair
1596      ++noBufferedRecords;
1597      
1598      // Compress and flush?
1599      int currentBlockSize = keyBuffer.getLength() + valBuffer.getLength();
1600      if (currentBlockSize >= compressionBlockSize) {
1601        sync();
1602      }
1603    }
1604    
1605    /** Append a key/value pair. */
1606    @Override
1607    public synchronized void appendRaw(byte[] keyData, int keyOffset,
1608        int keyLength, ValueBytes val) throws IOException {
1609      
1610      if (keyLength < 0)
1611        throw new IOException("negative length keys not allowed");
1612
1613      int valLength = val.getSize();
1614      
1615      // Save key/value data in relevant buffers
1616      WritableUtils.writeVInt(keyLenBuffer, keyLength);
1617      keyBuffer.write(keyData, keyOffset, keyLength);
1618      WritableUtils.writeVInt(valLenBuffer, valLength);
1619      val.writeUncompressedBytes(valBuffer);
1620
1621      // Added another key/value pair
1622      ++noBufferedRecords;
1623
1624      // Compress and flush?
1625      int currentBlockSize = keyBuffer.getLength() + valBuffer.getLength(); 
1626      if (currentBlockSize >= compressionBlockSize) {
1627        sync();
1628      }
1629    }
1630  
1631  } // BlockCompressionWriter
1632
1633  /** Get the configured buffer size */
1634  private static int getBufferSize(Configuration conf) {
1635    return conf.getInt("io.file.buffer.size", 4096);
1636  }
1637
1638  /** Reads key/value pairs from a sequence-format file. */
1639  public static class Reader implements java.io.Closeable {
1640    private String filename;
1641    private FSDataInputStream in;
1642    private DataOutputBuffer outBuf = new DataOutputBuffer();
1643
1644    private byte version;
1645
1646    private String keyClassName;
1647    private String valClassName;
1648    private Class keyClass;
1649    private Class valClass;
1650
1651    private CompressionCodec codec = null;
1652    private Metadata metadata = null;
1653    
1654    private byte[] sync = new byte[SYNC_HASH_SIZE];
1655    private byte[] syncCheck = new byte[SYNC_HASH_SIZE];
1656    private boolean syncSeen;
1657
1658    private long headerEnd;
1659    private long end;
1660    private int keyLength;
1661    private int recordLength;
1662
1663    private boolean decompress;
1664    private boolean blockCompressed;
1665    
1666    private Configuration conf;
1667
1668    private int noBufferedRecords = 0;
1669    private boolean lazyDecompress = true;
1670    private boolean valuesDecompressed = true;
1671    
1672    private int noBufferedKeys = 0;
1673    private int noBufferedValues = 0;
1674    
1675    private DataInputBuffer keyLenBuffer = null;
1676    private CompressionInputStream keyLenInFilter = null;
1677    private DataInputStream keyLenIn = null;
1678    private Decompressor keyLenDecompressor = null;
1679    private DataInputBuffer keyBuffer = null;
1680    private CompressionInputStream keyInFilter = null;
1681    private DataInputStream keyIn = null;
1682    private Decompressor keyDecompressor = null;
1683
1684    private DataInputBuffer valLenBuffer = null;
1685    private CompressionInputStream valLenInFilter = null;
1686    private DataInputStream valLenIn = null;
1687    private Decompressor valLenDecompressor = null;
1688    private DataInputBuffer valBuffer = null;
1689    private CompressionInputStream valInFilter = null;
1690    private DataInputStream valIn = null;
1691    private Decompressor valDecompressor = null;
1692    
1693    private Deserializer keyDeserializer;
1694    private Deserializer valDeserializer;
1695
1696    /**
1697     * A tag interface for all of the Reader options
1698     */
1699    public static interface Option {}
1700    
1701    /**
1702     * Create an option to specify the path name of the sequence file.
1703     * @param value the path to read
1704     * @return a new option
1705     */
1706    public static Option file(Path value) {
1707      return new FileOption(value);
1708    }
1709    
1710    /**
1711     * Create an option to specify the stream with the sequence file.
1712     * @param value the stream to read.
1713     * @return a new option
1714     */
1715    public static Option stream(FSDataInputStream value) {
1716      return new InputStreamOption(value);
1717    }
1718    
1719    /**
1720     * Create an option to specify the starting byte to read.
1721     * @param value the number of bytes to skip over
1722     * @return a new option
1723     */
1724    public static Option start(long value) {
1725      return new StartOption(value);
1726    }
1727    
1728    /**
1729     * Create an option to specify the number of bytes to read.
1730     * @param value the number of bytes to read
1731     * @return a new option
1732     */
1733    public static Option length(long value) {
1734      return new LengthOption(value);
1735    }
1736    
1737    /**
1738     * Create an option with the buffer size for reading the given pathname.
1739     * @param value the number of bytes to buffer
1740     * @return a new option
1741     */
1742    public static Option bufferSize(int value) {
1743      return new BufferSizeOption(value);
1744    }
1745
1746    private static class FileOption extends Options.PathOption 
1747                                    implements Option {
1748      private FileOption(Path value) {
1749        super(value);
1750      }
1751    }
1752    
1753    private static class InputStreamOption
1754        extends Options.FSDataInputStreamOption 
1755        implements Option {
1756      private InputStreamOption(FSDataInputStream value) {
1757        super(value);
1758      }
1759    }
1760
1761    private static class StartOption extends Options.LongOption
1762                                     implements Option {
1763      private StartOption(long value) {
1764        super(value);
1765      }
1766    }
1767
1768    private static class LengthOption extends Options.LongOption
1769                                      implements Option {
1770      private LengthOption(long value) {
1771        super(value);
1772      }
1773    }
1774
1775    private static class BufferSizeOption extends Options.IntegerOption
1776                                      implements Option {
1777      private BufferSizeOption(int value) {
1778        super(value);
1779      }
1780    }
1781
1782    // only used directly
1783    private static class OnlyHeaderOption extends Options.BooleanOption 
1784                                          implements Option {
1785      private OnlyHeaderOption() {
1786        super(true);
1787      }
1788    }
1789
1790    public Reader(Configuration conf, Option... opts) throws IOException {
1791      // Look up the options, these are null if not set
1792      FileOption fileOpt = Options.getOption(FileOption.class, opts);
1793      InputStreamOption streamOpt = 
1794        Options.getOption(InputStreamOption.class, opts);
1795      StartOption startOpt = Options.getOption(StartOption.class, opts);
1796      LengthOption lenOpt = Options.getOption(LengthOption.class, opts);
1797      BufferSizeOption bufOpt = Options.getOption(BufferSizeOption.class,opts);
1798      OnlyHeaderOption headerOnly = 
1799        Options.getOption(OnlyHeaderOption.class, opts);
1800      // check for consistency
1801      if ((fileOpt == null) == (streamOpt == null)) {
1802        throw new 
1803          IllegalArgumentException("File or stream option must be specified");
1804      }
1805      if (fileOpt == null && bufOpt != null) {
1806        throw new IllegalArgumentException("buffer size can only be set when" +
1807                                           " a file is specified.");
1808      }
1809      // figure out the real values
1810      Path filename = null;
1811      FSDataInputStream file;
1812      final long len;
1813      if (fileOpt != null) {
1814        filename = fileOpt.getValue();
1815        FileSystem fs = filename.getFileSystem(conf);
1816        int bufSize = bufOpt == null ? getBufferSize(conf): bufOpt.getValue();
1817        len = null == lenOpt
1818          ? fs.getFileStatus(filename).getLen()
1819          : lenOpt.getValue();
1820        file = openFile(fs, filename, bufSize, len);
1821      } else {
1822        len = null == lenOpt ? Long.MAX_VALUE : lenOpt.getValue();
1823        file = streamOpt.getValue();
1824      }
1825      long start = startOpt == null ? 0 : startOpt.getValue();
1826      // really set up
1827      initialize(filename, file, start, len, conf, headerOnly != null);
1828    }
1829
1830    /**
1831     * Construct a reader by opening a file from the given file system.
1832     * @param fs The file system used to open the file.
1833     * @param file The file being read.
1834     * @param conf Configuration
1835     * @throws IOException
1836     * @deprecated Use Reader(Configuration, Option...) instead.
1837     */
1838    @Deprecated
1839    public Reader(FileSystem fs, Path file, 
1840                  Configuration conf) throws IOException {
1841      this(conf, file(file.makeQualified(fs)));
1842    }
1843
1844    /**
1845     * Construct a reader by the given input stream.
1846     * @param in An input stream.
1847     * @param buffersize unused
1848     * @param start The starting position.
1849     * @param length The length being read.
1850     * @param conf Configuration
1851     * @throws IOException
1852     * @deprecated Use Reader(Configuration, Reader.Option...) instead.
1853     */
1854    @Deprecated
1855    public Reader(FSDataInputStream in, int buffersize,
1856        long start, long length, Configuration conf) throws IOException {
1857      this(conf, stream(in), start(start), length(length));
1858    }
1859
1860    /** Common work of the constructors. */
1861    private void initialize(Path filename, FSDataInputStream in,
1862                            long start, long length, Configuration conf,
1863                            boolean tempReader) throws IOException {
1864      if (in == null) {
1865        throw new IllegalArgumentException("in == null");
1866      }
1867      this.filename = filename == null ? "<unknown>" : filename.toString();
1868      this.in = in;
1869      this.conf = conf;
1870      boolean succeeded = false;
1871      try {
1872        seek(start);
1873        this.end = this.in.getPos() + length;
1874        // if it wrapped around, use the max
1875        if (end < length) {
1876          end = Long.MAX_VALUE;
1877        }
1878        init(tempReader);
1879        succeeded = true;
1880      } finally {
1881        if (!succeeded) {
1882          IOUtils.cleanup(LOG, this.in);
1883        }
1884      }
1885    }
1886
1887    /**
1888     * Override this method to specialize the type of
1889     * {@link FSDataInputStream} returned.
1890     * @param fs The file system used to open the file.
1891     * @param file The file being read.
1892     * @param bufferSize The buffer size used to read the file.
1893     * @param length The length being read if it is >= 0.  Otherwise,
1894     *               the length is not available.
1895     * @return The opened stream.
1896     * @throws IOException
1897     */
1898    protected FSDataInputStream openFile(FileSystem fs, Path file,
1899        int bufferSize, long length) throws IOException {
1900      return fs.open(file, bufferSize);
1901    }
1902    
1903    /**
1904     * Initialize the {@link Reader}
1905     * @param tmpReader <code>true</code> if we are constructing a temporary
1906     *                  reader {@link SequenceFile.Sorter.cloneFileAttributes}, 
1907     *                  and hence do not initialize every component; 
1908     *                  <code>false</code> otherwise.
1909     * @throws IOException
1910     */
1911    private void init(boolean tempReader) throws IOException {
1912      byte[] versionBlock = new byte[VERSION.length];
1913      String exceptionMsg = this + " not a SequenceFile";
1914
1915      // Try to read sequence file header.
1916      try {
1917        in.readFully(versionBlock);
1918      } catch (EOFException e) {
1919        throw new EOFException(exceptionMsg);
1920      }
1921
1922      if ((versionBlock[0] != VERSION[0]) ||
1923          (versionBlock[1] != VERSION[1]) ||
1924          (versionBlock[2] != VERSION[2])) {
1925        throw new IOException(this + " not a SequenceFile");
1926      }
1927
1928      // Set 'version'
1929      version = versionBlock[3];
1930      if (version > VERSION[3]) {
1931        throw new VersionMismatchException(VERSION[3], version);
1932      }
1933
1934      if (version < BLOCK_COMPRESS_VERSION) {
1935        UTF8 className = new UTF8();
1936
1937        className.readFields(in);
1938        keyClassName = className.toStringChecked(); // key class name
1939
1940        className.readFields(in);
1941        valClassName = className.toStringChecked(); // val class name
1942      } else {
1943        keyClassName = Text.readString(in);
1944        valClassName = Text.readString(in);
1945      }
1946
1947      if (version > 2) {                          // if version > 2
1948        this.decompress = in.readBoolean();       // is compressed?
1949      } else {
1950        decompress = false;
1951      }
1952
1953      if (version >= BLOCK_COMPRESS_VERSION) {    // if version >= 4
1954        this.blockCompressed = in.readBoolean();  // is block-compressed?
1955      } else {
1956        blockCompressed = false;
1957      }
1958      
1959      // if version >= 5
1960      // setup the compression codec
1961      if (decompress) {
1962        if (version >= CUSTOM_COMPRESS_VERSION) {
1963          String codecClassname = Text.readString(in);
1964          try {
1965            Class<? extends CompressionCodec> codecClass
1966              = conf.getClassByName(codecClassname).asSubclass(CompressionCodec.class);
1967            this.codec = ReflectionUtils.newInstance(codecClass, conf);
1968          } catch (ClassNotFoundException cnfe) {
1969            throw new IllegalArgumentException("Unknown codec: " + 
1970                                               codecClassname, cnfe);
1971          }
1972        } else {
1973          codec = new DefaultCodec();
1974          ((Configurable)codec).setConf(conf);
1975        }
1976      }
1977      
1978      this.metadata = new Metadata();
1979      if (version >= VERSION_WITH_METADATA) {    // if version >= 6
1980        this.metadata.readFields(in);
1981      }
1982      
1983      if (version > 1) {                          // if version > 1
1984        in.readFully(sync);                       // read sync bytes
1985        headerEnd = in.getPos();                  // record end of header
1986      }
1987      
1988      // Initialize... *not* if this we are constructing a temporary Reader
1989      if (!tempReader) {
1990        valBuffer = new DataInputBuffer();
1991        if (decompress) {
1992          valDecompressor = CodecPool.getDecompressor(codec);
1993          valInFilter = codec.createInputStream(valBuffer, valDecompressor);
1994          valIn = new DataInputStream(valInFilter);
1995        } else {
1996          valIn = valBuffer;
1997        }
1998
1999        if (blockCompressed) {
2000          keyLenBuffer = new DataInputBuffer();
2001          keyBuffer = new DataInputBuffer();
2002          valLenBuffer = new DataInputBuffer();
2003
2004          keyLenDecompressor = CodecPool.getDecompressor(codec);
2005          keyLenInFilter = codec.createInputStream(keyLenBuffer, 
2006                                                   keyLenDecompressor);
2007          keyLenIn = new DataInputStream(keyLenInFilter);
2008
2009          keyDecompressor = CodecPool.getDecompressor(codec);
2010          keyInFilter = codec.createInputStream(keyBuffer, keyDecompressor);
2011          keyIn = new DataInputStream(keyInFilter);
2012
2013          valLenDecompressor = CodecPool.getDecompressor(codec);
2014          valLenInFilter = codec.createInputStream(valLenBuffer, 
2015                                                   valLenDecompressor);
2016          valLenIn = new DataInputStream(valLenInFilter);
2017        }
2018        
2019        SerializationFactory serializationFactory =
2020          new SerializationFactory(conf);
2021        this.keyDeserializer =
2022          getDeserializer(serializationFactory, getKeyClass());
2023        if (this.keyDeserializer == null) {
2024          throw new IOException(
2025              "Could not find a deserializer for the Key class: '"
2026                  + getKeyClass().getCanonicalName() + "'. "
2027                  + "Please ensure that the configuration '" +
2028                  CommonConfigurationKeys.IO_SERIALIZATIONS_KEY + "' is "
2029                  + "properly configured, if you're using "
2030                  + "custom serialization.");
2031        }
2032        if (!blockCompressed) {
2033          this.keyDeserializer.open(valBuffer);
2034        } else {
2035          this.keyDeserializer.open(keyIn);
2036        }
2037        this.valDeserializer =
2038          getDeserializer(serializationFactory, getValueClass());
2039        if (this.valDeserializer == null) {
2040          throw new IOException(
2041              "Could not find a deserializer for the Value class: '"
2042                  + getValueClass().getCanonicalName() + "'. "
2043                  + "Please ensure that the configuration '" +
2044                  CommonConfigurationKeys.IO_SERIALIZATIONS_KEY + "' is "
2045                  + "properly configured, if you're using "
2046                  + "custom serialization.");
2047        }
2048        this.valDeserializer.open(valIn);
2049      }
2050    }
2051    
2052    @SuppressWarnings("unchecked")
2053    private Deserializer getDeserializer(SerializationFactory sf, Class c) {
2054      return sf.getDeserializer(c);
2055    }
2056    
2057    /** Close the file. */
2058    @Override
2059    public synchronized void close() throws IOException {
2060      // Return the decompressors to the pool
2061      CodecPool.returnDecompressor(keyLenDecompressor);
2062      CodecPool.returnDecompressor(keyDecompressor);
2063      CodecPool.returnDecompressor(valLenDecompressor);
2064      CodecPool.returnDecompressor(valDecompressor);
2065      keyLenDecompressor = keyDecompressor = null;
2066      valLenDecompressor = valDecompressor = null;
2067      
2068      if (keyDeserializer != null) {
2069        keyDeserializer.close();
2070      }
2071      if (valDeserializer != null) {
2072        valDeserializer.close();
2073      }
2074      
2075      // Close the input-stream
2076      in.close();
2077    }
2078
2079    /** Returns the name of the key class. */
2080    public String getKeyClassName() {
2081      return keyClassName;
2082    }
2083
2084    /** Returns the class of keys in this file. */
2085    public synchronized Class<?> getKeyClass() {
2086      if (null == keyClass) {
2087        try {
2088          keyClass = WritableName.getClass(getKeyClassName(), conf);
2089        } catch (IOException e) {
2090          throw new RuntimeException(e);
2091        }
2092      }
2093      return keyClass;
2094    }
2095
2096    /** Returns the name of the value class. */
2097    public String getValueClassName() {
2098      return valClassName;
2099    }
2100
2101    /** Returns the class of values in this file. */
2102    public synchronized Class<?> getValueClass() {
2103      if (null == valClass) {
2104        try {
2105          valClass = WritableName.getClass(getValueClassName(), conf);
2106        } catch (IOException e) {
2107          throw new RuntimeException(e);
2108        }
2109      }
2110      return valClass;
2111    }
2112
2113    /** Returns true if values are compressed. */
2114    public boolean isCompressed() { return decompress; }
2115    
2116    /** Returns true if records are block-compressed. */
2117    public boolean isBlockCompressed() { return blockCompressed; }
2118    
2119    /** Returns the compression codec of data in this file. */
2120    public CompressionCodec getCompressionCodec() { return codec; }
2121    
2122    private byte[] getSync() {
2123      return sync;
2124    }
2125
2126    private byte getVersion() {
2127      return version;
2128    }
2129
2130    /**
2131     * Get the compression type for this file.
2132     * @return the compression type
2133     */
2134    public CompressionType getCompressionType() {
2135      if (decompress) {
2136        return blockCompressed ? CompressionType.BLOCK : CompressionType.RECORD;
2137      } else {
2138        return CompressionType.NONE;
2139      }
2140    }
2141
2142    /** Returns the metadata object of the file */
2143    public Metadata getMetadata() {
2144      return this.metadata;
2145    }
2146    
2147    /** Returns the configuration used for this file. */
2148    Configuration getConf() { return conf; }
2149    
2150    /** Read a compressed buffer */
2151    private synchronized void readBuffer(DataInputBuffer buffer, 
2152                                         CompressionInputStream filter) throws IOException {
2153      // Read data into a temporary buffer
2154      DataOutputBuffer dataBuffer = new DataOutputBuffer();
2155
2156      try {
2157        int dataBufferLength = WritableUtils.readVInt(in);
2158        dataBuffer.write(in, dataBufferLength);
2159      
2160        // Set up 'buffer' connected to the input-stream
2161        buffer.reset(dataBuffer.getData(), 0, dataBuffer.getLength());
2162      } finally {
2163        dataBuffer.close();
2164      }
2165
2166      // Reset the codec
2167      filter.resetState();
2168    }
2169    
2170    /** Read the next 'compressed' block */
2171    private synchronized void readBlock() throws IOException {
2172      // Check if we need to throw away a whole block of 
2173      // 'values' due to 'lazy decompression' 
2174      if (lazyDecompress && !valuesDecompressed) {
2175        in.seek(WritableUtils.readVInt(in)+in.getPos());
2176        in.seek(WritableUtils.readVInt(in)+in.getPos());
2177      }
2178      
2179      // Reset internal states
2180      noBufferedKeys = 0; noBufferedValues = 0; noBufferedRecords = 0;
2181      valuesDecompressed = false;
2182
2183      //Process sync
2184      if (sync != null) {
2185        in.readInt();
2186        in.readFully(syncCheck);                // read syncCheck
2187        if (!Arrays.equals(sync, syncCheck))    // check it
2188          throw new IOException("File is corrupt!");
2189      }
2190      syncSeen = true;
2191
2192      // Read number of records in this block
2193      noBufferedRecords = WritableUtils.readVInt(in);
2194      
2195      // Read key lengths and keys
2196      readBuffer(keyLenBuffer, keyLenInFilter);
2197      readBuffer(keyBuffer, keyInFilter);
2198      noBufferedKeys = noBufferedRecords;
2199      
2200      // Read value lengths and values
2201      if (!lazyDecompress) {
2202        readBuffer(valLenBuffer, valLenInFilter);
2203        readBuffer(valBuffer, valInFilter);
2204        noBufferedValues = noBufferedRecords;
2205        valuesDecompressed = true;
2206      }
2207    }
2208
2209    /** 
2210     * Position valLenIn/valIn to the 'value' 
2211     * corresponding to the 'current' key 
2212     */
2213    private synchronized void seekToCurrentValue() throws IOException {
2214      if (!blockCompressed) {
2215        if (decompress) {
2216          valInFilter.resetState();
2217        }
2218        valBuffer.reset();
2219      } else {
2220        // Check if this is the first value in the 'block' to be read
2221        if (lazyDecompress && !valuesDecompressed) {
2222          // Read the value lengths and values
2223          readBuffer(valLenBuffer, valLenInFilter);
2224          readBuffer(valBuffer, valInFilter);
2225          noBufferedValues = noBufferedRecords;
2226          valuesDecompressed = true;
2227        }
2228        
2229        // Calculate the no. of bytes to skip
2230        // Note: 'current' key has already been read!
2231        int skipValBytes = 0;
2232        int currentKey = noBufferedKeys + 1;          
2233        for (int i=noBufferedValues; i > currentKey; --i) {
2234          skipValBytes += WritableUtils.readVInt(valLenIn);
2235          --noBufferedValues;
2236        }
2237        
2238        // Skip to the 'val' corresponding to 'current' key
2239        if (skipValBytes > 0) {
2240          if (valIn.skipBytes(skipValBytes) != skipValBytes) {
2241            throw new IOException("Failed to seek to " + currentKey + 
2242                                  "(th) value!");
2243          }
2244        }
2245      }
2246    }
2247
2248    /**
2249     * Get the 'value' corresponding to the last read 'key'.
2250     * @param val : The 'value' to be read.
2251     * @throws IOException
2252     */
2253    public synchronized void getCurrentValue(Writable val) 
2254      throws IOException {
2255      if (val instanceof Configurable) {
2256        ((Configurable) val).setConf(this.conf);
2257      }
2258
2259      // Position stream to 'current' value
2260      seekToCurrentValue();
2261
2262      if (!blockCompressed) {
2263        val.readFields(valIn);
2264        
2265        if (valIn.read() > 0) {
2266          LOG.info("available bytes: " + valIn.available());
2267          throw new IOException(val+" read "+(valBuffer.getPosition()-keyLength)
2268                                + " bytes, should read " +
2269                                (valBuffer.getLength()-keyLength));
2270        }
2271      } else {
2272        // Get the value
2273        int valLength = WritableUtils.readVInt(valLenIn);
2274        val.readFields(valIn);
2275        
2276        // Read another compressed 'value'
2277        --noBufferedValues;
2278        
2279        // Sanity check
2280        if ((valLength < 0) && LOG.isDebugEnabled()) {
2281          LOG.debug(val + " is a zero-length value");
2282        }
2283      }
2284
2285    }
2286    
2287    /**
2288     * Get the 'value' corresponding to the last read 'key'.
2289     * @param val : The 'value' to be read.
2290     * @throws IOException
2291     */
2292    public synchronized Object getCurrentValue(Object val) 
2293      throws IOException {
2294      if (val instanceof Configurable) {
2295        ((Configurable) val).setConf(this.conf);
2296      }
2297
2298      // Position stream to 'current' value
2299      seekToCurrentValue();
2300
2301      if (!blockCompressed) {
2302        val = deserializeValue(val);
2303        
2304        if (valIn.read() > 0) {
2305          LOG.info("available bytes: " + valIn.available());
2306          throw new IOException(val+" read "+(valBuffer.getPosition()-keyLength)
2307                                + " bytes, should read " +
2308                                (valBuffer.getLength()-keyLength));
2309        }
2310      } else {
2311        // Get the value
2312        int valLength = WritableUtils.readVInt(valLenIn);
2313        val = deserializeValue(val);
2314        
2315        // Read another compressed 'value'
2316        --noBufferedValues;
2317        
2318        // Sanity check
2319        if ((valLength < 0) && LOG.isDebugEnabled()) {
2320          LOG.debug(val + " is a zero-length value");
2321        }
2322      }
2323      return val;
2324
2325    }
2326
2327    @SuppressWarnings("unchecked")
2328    private Object deserializeValue(Object val) throws IOException {
2329      return valDeserializer.deserialize(val);
2330    }
2331    
2332    /** Read the next key in the file into <code>key</code>, skipping its
2333     * value.  True if another entry exists, and false at end of file. */
2334    public synchronized boolean next(Writable key) throws IOException {
2335      if (key.getClass() != getKeyClass())
2336        throw new IOException("wrong key class: "+key.getClass().getName()
2337                              +" is not "+keyClass);
2338
2339      if (!blockCompressed) {
2340        outBuf.reset();
2341        
2342        keyLength = next(outBuf);
2343        if (keyLength < 0)
2344          return false;
2345        
2346        valBuffer.reset(outBuf.getData(), outBuf.getLength());
2347        
2348        key.readFields(valBuffer);
2349        valBuffer.mark(0);
2350        if (valBuffer.getPosition() != keyLength)
2351          throw new IOException(key + " read " + valBuffer.getPosition()
2352                                + " bytes, should read " + keyLength);
2353      } else {
2354        //Reset syncSeen
2355        syncSeen = false;
2356        
2357        if (noBufferedKeys == 0) {
2358          try {
2359            readBlock();
2360          } catch (EOFException eof) {
2361            return false;
2362          }
2363        }
2364        
2365        int keyLength = WritableUtils.readVInt(keyLenIn);
2366        
2367        // Sanity check
2368        if (keyLength < 0) {
2369          return false;
2370        }
2371        
2372        //Read another compressed 'key'
2373        key.readFields(keyIn);
2374        --noBufferedKeys;
2375      }
2376
2377      return true;
2378    }
2379
2380    /** Read the next key/value pair in the file into <code>key</code> and
2381     * <code>val</code>.  Returns true if such a pair exists and false when at
2382     * end of file */
2383    public synchronized boolean next(Writable key, Writable val)
2384      throws IOException {
2385      if (val.getClass() != getValueClass())
2386        throw new IOException("wrong value class: "+val+" is not "+valClass);
2387
2388      boolean more = next(key);
2389      
2390      if (more) {
2391        getCurrentValue(val);
2392      }
2393
2394      return more;
2395    }
2396    
2397    /**
2398     * Read and return the next record length, potentially skipping over 
2399     * a sync block.
2400     * @return the length of the next record or -1 if there is no next record
2401     * @throws IOException
2402     */
2403    private synchronized int readRecordLength() throws IOException {
2404      if (in.getPos() >= end) {
2405        return -1;
2406      }      
2407      int length = in.readInt();
2408      if (version > 1 && sync != null &&
2409          length == SYNC_ESCAPE) {              // process a sync entry
2410        in.readFully(syncCheck);                // read syncCheck
2411        if (!Arrays.equals(sync, syncCheck))    // check it
2412          throw new IOException("File is corrupt!");
2413        syncSeen = true;
2414        if (in.getPos() >= end) {
2415          return -1;
2416        }
2417        length = in.readInt();                  // re-read length
2418      } else {
2419        syncSeen = false;
2420      }
2421      
2422      return length;
2423    }
2424    
2425    /** Read the next key/value pair in the file into <code>buffer</code>.
2426     * Returns the length of the key read, or -1 if at end of file.  The length
2427     * of the value may be computed by calling buffer.getLength() before and
2428     * after calls to this method. */
2429    /** @deprecated Call {@link #nextRaw(DataOutputBuffer,SequenceFile.ValueBytes)}. */
2430    @Deprecated
2431    synchronized int next(DataOutputBuffer buffer) throws IOException {
2432      // Unsupported for block-compressed sequence files
2433      if (blockCompressed) {
2434        throw new IOException("Unsupported call for block-compressed" +
2435                              " SequenceFiles - use SequenceFile.Reader.next(DataOutputStream, ValueBytes)");
2436      }
2437      try {
2438        int length = readRecordLength();
2439        if (length == -1) {
2440          return -1;
2441        }
2442        int keyLength = in.readInt();
2443        buffer.write(in, length);
2444        return keyLength;
2445      } catch (ChecksumException e) {             // checksum failure
2446        handleChecksumException(e);
2447        return next(buffer);
2448      }
2449    }
2450
2451    public ValueBytes createValueBytes() {
2452      ValueBytes val = null;
2453      if (!decompress || blockCompressed) {
2454        val = new UncompressedBytes();
2455      } else {
2456        val = new CompressedBytes(codec);
2457      }
2458      return val;
2459    }
2460
2461    /**
2462     * Read 'raw' records.
2463     * @param key - The buffer into which the key is read
2464     * @param val - The 'raw' value
2465     * @return Returns the total record length or -1 for end of file
2466     * @throws IOException
2467     */
2468    public synchronized int nextRaw(DataOutputBuffer key, ValueBytes val) 
2469      throws IOException {
2470      if (!blockCompressed) {
2471        int length = readRecordLength();
2472        if (length == -1) {
2473          return -1;
2474        }
2475        int keyLength = in.readInt();
2476        int valLength = length - keyLength;
2477        key.write(in, keyLength);
2478        if (decompress) {
2479          CompressedBytes value = (CompressedBytes)val;
2480          value.reset(in, valLength);
2481        } else {
2482          UncompressedBytes value = (UncompressedBytes)val;
2483          value.reset(in, valLength);
2484        }
2485        
2486        return length;
2487      } else {
2488        //Reset syncSeen
2489        syncSeen = false;
2490        
2491        // Read 'key'
2492        if (noBufferedKeys == 0) {
2493          if (in.getPos() >= end) 
2494            return -1;
2495
2496          try { 
2497            readBlock();
2498          } catch (EOFException eof) {
2499            return -1;
2500          }
2501        }
2502        int keyLength = WritableUtils.readVInt(keyLenIn);
2503        if (keyLength < 0) {
2504          throw new IOException("zero length key found!");
2505        }
2506        key.write(keyIn, keyLength);
2507        --noBufferedKeys;
2508        
2509        // Read raw 'value'
2510        seekToCurrentValue();
2511        int valLength = WritableUtils.readVInt(valLenIn);
2512        UncompressedBytes rawValue = (UncompressedBytes)val;
2513        rawValue.reset(valIn, valLength);
2514        --noBufferedValues;
2515        
2516        return (keyLength+valLength);
2517      }
2518      
2519    }
2520
2521    /**
2522     * Read 'raw' keys.
2523     * @param key - The buffer into which the key is read
2524     * @return Returns the key length or -1 for end of file
2525     * @throws IOException
2526     */
2527    public synchronized int nextRawKey(DataOutputBuffer key) 
2528      throws IOException {
2529      if (!blockCompressed) {
2530        recordLength = readRecordLength();
2531        if (recordLength == -1) {
2532          return -1;
2533        }
2534        keyLength = in.readInt();
2535        key.write(in, keyLength);
2536        return keyLength;
2537      } else {
2538        //Reset syncSeen
2539        syncSeen = false;
2540        
2541        // Read 'key'
2542        if (noBufferedKeys == 0) {
2543          if (in.getPos() >= end) 
2544            return -1;
2545
2546          try { 
2547            readBlock();
2548          } catch (EOFException eof) {
2549            return -1;
2550          }
2551        }
2552        int keyLength = WritableUtils.readVInt(keyLenIn);
2553        if (keyLength < 0) {
2554          throw new IOException("zero length key found!");
2555        }
2556        key.write(keyIn, keyLength);
2557        --noBufferedKeys;
2558        
2559        return keyLength;
2560      }
2561      
2562    }
2563
2564    /** Read the next key in the file, skipping its
2565     * value.  Return null at end of file. */
2566    public synchronized Object next(Object key) throws IOException {
2567      if (key != null && key.getClass() != getKeyClass()) {
2568        throw new IOException("wrong key class: "+key.getClass().getName()
2569                              +" is not "+keyClass);
2570      }
2571
2572      if (!blockCompressed) {
2573        outBuf.reset();
2574        
2575        keyLength = next(outBuf);
2576        if (keyLength < 0)
2577          return null;
2578        
2579        valBuffer.reset(outBuf.getData(), outBuf.getLength());
2580        
2581        key = deserializeKey(key);
2582        valBuffer.mark(0);
2583        if (valBuffer.getPosition() != keyLength)
2584          throw new IOException(key + " read " + valBuffer.getPosition()
2585                                + " bytes, should read " + keyLength);
2586      } else {
2587        //Reset syncSeen
2588        syncSeen = false;
2589        
2590        if (noBufferedKeys == 0) {
2591          try {
2592            readBlock();
2593          } catch (EOFException eof) {
2594            return null;
2595          }
2596        }
2597        
2598        int keyLength = WritableUtils.readVInt(keyLenIn);
2599        
2600        // Sanity check
2601        if (keyLength < 0) {
2602          return null;
2603        }
2604        
2605        //Read another compressed 'key'
2606        key = deserializeKey(key);
2607        --noBufferedKeys;
2608      }
2609
2610      return key;
2611    }
2612
2613    @SuppressWarnings("unchecked")
2614    private Object deserializeKey(Object key) throws IOException {
2615      return keyDeserializer.deserialize(key);
2616    }
2617
2618    /**
2619     * Read 'raw' values.
2620     * @param val - The 'raw' value
2621     * @return Returns the value length
2622     * @throws IOException
2623     */
2624    public synchronized int nextRawValue(ValueBytes val) 
2625      throws IOException {
2626      
2627      // Position stream to current value
2628      seekToCurrentValue();
2629 
2630      if (!blockCompressed) {
2631        int valLength = recordLength - keyLength;
2632        if (decompress) {
2633          CompressedBytes value = (CompressedBytes)val;
2634          value.reset(in, valLength);
2635        } else {
2636          UncompressedBytes value = (UncompressedBytes)val;
2637          value.reset(in, valLength);
2638        }
2639         
2640        return valLength;
2641      } else {
2642        int valLength = WritableUtils.readVInt(valLenIn);
2643        UncompressedBytes rawValue = (UncompressedBytes)val;
2644        rawValue.reset(valIn, valLength);
2645        --noBufferedValues;
2646        return valLength;
2647      }
2648      
2649    }
2650
2651    private void handleChecksumException(ChecksumException e)
2652      throws IOException {
2653      if (this.conf.getBoolean("io.skip.checksum.errors", false)) {
2654        LOG.warn("Bad checksum at "+getPosition()+". Skipping entries.");
2655        sync(getPosition()+this.conf.getInt("io.bytes.per.checksum", 512));
2656      } else {
2657        throw e;
2658      }
2659    }
2660
2661    /** disables sync. often invoked for tmp files */
2662    synchronized void ignoreSync() {
2663      sync = null;
2664    }
2665    
2666    /** Set the current byte position in the input file.
2667     *
2668     * <p>The position passed must be a position returned by {@link
2669     * SequenceFile.Writer#getLength()} when writing this file.  To seek to an arbitrary
2670     * position, use {@link SequenceFile.Reader#sync(long)}.
2671     */
2672    public synchronized void seek(long position) throws IOException {
2673      in.seek(position);
2674      if (blockCompressed) {                      // trigger block read
2675        noBufferedKeys = 0;
2676        valuesDecompressed = true;
2677      }
2678    }
2679
2680    /** Seek to the next sync mark past a given position.*/
2681    public synchronized void sync(long position) throws IOException {
2682      if (position+SYNC_SIZE >= end) {
2683        seek(end);
2684        return;
2685      }
2686
2687      if (position < headerEnd) {
2688        // seek directly to first record
2689        in.seek(headerEnd);
2690        // note the sync marker "seen" in the header
2691        syncSeen = true;
2692        return;
2693      }
2694
2695      try {
2696        seek(position+4);                         // skip escape
2697        in.readFully(syncCheck);
2698        int syncLen = sync.length;
2699        for (int i = 0; in.getPos() < end; i++) {
2700          int j = 0;
2701          for (; j < syncLen; j++) {
2702            if (sync[j] != syncCheck[(i+j)%syncLen])
2703              break;
2704          }
2705          if (j == syncLen) {
2706            in.seek(in.getPos() - SYNC_SIZE);     // position before sync
2707            return;
2708          }
2709          syncCheck[i%syncLen] = in.readByte();
2710        }
2711      } catch (ChecksumException e) {             // checksum failure
2712        handleChecksumException(e);
2713      }
2714    }
2715
2716    /** Returns true iff the previous call to next passed a sync mark.*/
2717    public synchronized boolean syncSeen() { return syncSeen; }
2718
2719    /** Return the current byte position in the input file. */
2720    public synchronized long getPosition() throws IOException {
2721      return in.getPos();
2722    }
2723
2724    /** Returns the name of the file. */
2725    @Override
2726    public String toString() {
2727      return filename;
2728    }
2729
2730  }
2731
2732  /** Sorts key/value pairs in a sequence-format file.
2733   *
2734   * <p>For best performance, applications should make sure that the {@link
2735   * Writable#readFields(DataInput)} implementation of their keys is
2736   * very efficient.  In particular, it should avoid allocating memory.
2737   */
2738  public static class Sorter {
2739
2740    private RawComparator comparator;
2741
2742    private MergeSort mergeSort; //the implementation of merge sort
2743    
2744    private Path[] inFiles;                     // when merging or sorting
2745
2746    private Path outFile;
2747
2748    private int memory; // bytes
2749    private int factor; // merged per pass
2750
2751    private FileSystem fs = null;
2752
2753    private Class keyClass;
2754    private Class valClass;
2755
2756    private Configuration conf;
2757    private Metadata metadata;
2758    
2759    private Progressable progressable = null;
2760
2761    /** Sort and merge files containing the named classes. */
2762    public Sorter(FileSystem fs, Class<? extends WritableComparable> keyClass,
2763                  Class valClass, Configuration conf)  {
2764      this(fs, WritableComparator.get(keyClass, conf), keyClass, valClass, conf);
2765    }
2766
2767    /** Sort and merge using an arbitrary {@link RawComparator}. */
2768    public Sorter(FileSystem fs, RawComparator comparator, Class keyClass, 
2769                  Class valClass, Configuration conf) {
2770      this(fs, comparator, keyClass, valClass, conf, new Metadata());
2771    }
2772
2773    /** Sort and merge using an arbitrary {@link RawComparator}. */
2774    public Sorter(FileSystem fs, RawComparator comparator, Class keyClass,
2775                  Class valClass, Configuration conf, Metadata metadata) {
2776      this.fs = fs;
2777      this.comparator = comparator;
2778      this.keyClass = keyClass;
2779      this.valClass = valClass;
2780      this.memory = conf.getInt("io.sort.mb", 100) * 1024 * 1024;
2781      this.factor = conf.getInt("io.sort.factor", 100);
2782      this.conf = conf;
2783      this.metadata = metadata;
2784    }
2785
2786    /** Set the number of streams to merge at once.*/
2787    public void setFactor(int factor) { this.factor = factor; }
2788
2789    /** Get the number of streams to merge at once.*/
2790    public int getFactor() { return factor; }
2791
2792    /** Set the total amount of buffer memory, in bytes.*/
2793    public void setMemory(int memory) { this.memory = memory; }
2794
2795    /** Get the total amount of buffer memory, in bytes.*/
2796    public int getMemory() { return memory; }
2797
2798    /** Set the progressable object in order to report progress. */
2799    public void setProgressable(Progressable progressable) {
2800      this.progressable = progressable;
2801    }
2802    
2803    /** 
2804     * Perform a file sort from a set of input files into an output file.
2805     * @param inFiles the files to be sorted
2806     * @param outFile the sorted output file
2807     * @param deleteInput should the input files be deleted as they are read?
2808     */
2809    public void sort(Path[] inFiles, Path outFile,
2810                     boolean deleteInput) throws IOException {
2811      if (fs.exists(outFile)) {
2812        throw new IOException("already exists: " + outFile);
2813      }
2814
2815      this.inFiles = inFiles;
2816      this.outFile = outFile;
2817
2818      int segments = sortPass(deleteInput);
2819      if (segments > 1) {
2820        mergePass(outFile.getParent());
2821      }
2822    }
2823
2824    /** 
2825     * Perform a file sort from a set of input files and return an iterator.
2826     * @param inFiles the files to be sorted
2827     * @param tempDir the directory where temp files are created during sort
2828     * @param deleteInput should the input files be deleted as they are read?
2829     * @return iterator the RawKeyValueIterator
2830     */
2831    public RawKeyValueIterator sortAndIterate(Path[] inFiles, Path tempDir, 
2832                                              boolean deleteInput) throws IOException {
2833      Path outFile = new Path(tempDir + Path.SEPARATOR + "all.2");
2834      if (fs.exists(outFile)) {
2835        throw new IOException("already exists: " + outFile);
2836      }
2837      this.inFiles = inFiles;
2838      //outFile will basically be used as prefix for temp files in the cases
2839      //where sort outputs multiple sorted segments. For the single segment
2840      //case, the outputFile itself will contain the sorted data for that
2841      //segment
2842      this.outFile = outFile;
2843
2844      int segments = sortPass(deleteInput);
2845      if (segments > 1)
2846        return merge(outFile.suffix(".0"), outFile.suffix(".0.index"), 
2847                     tempDir);
2848      else if (segments == 1)
2849        return merge(new Path[]{outFile}, true, tempDir);
2850      else return null;
2851    }
2852
2853    /**
2854     * The backwards compatible interface to sort.
2855     * @param inFile the input file to sort
2856     * @param outFile the sorted output file
2857     */
2858    public void sort(Path inFile, Path outFile) throws IOException {
2859      sort(new Path[]{inFile}, outFile, false);
2860    }
2861    
2862    private int sortPass(boolean deleteInput) throws IOException {
2863      if(LOG.isDebugEnabled()) {
2864        LOG.debug("running sort pass");
2865      }
2866      SortPass sortPass = new SortPass();         // make the SortPass
2867      sortPass.setProgressable(progressable);
2868      mergeSort = new MergeSort(sortPass.new SeqFileComparator());
2869      try {
2870        return sortPass.run(deleteInput);         // run it
2871      } finally {
2872        sortPass.close();                         // close it
2873      }
2874    }
2875
2876    private class SortPass {
2877      private int memoryLimit = memory/4;
2878      private int recordLimit = 1000000;
2879      
2880      private DataOutputBuffer rawKeys = new DataOutputBuffer();
2881      private byte[] rawBuffer;
2882
2883      private int[] keyOffsets = new int[1024];
2884      private int[] pointers = new int[keyOffsets.length];
2885      private int[] pointersCopy = new int[keyOffsets.length];
2886      private int[] keyLengths = new int[keyOffsets.length];
2887      private ValueBytes[] rawValues = new ValueBytes[keyOffsets.length];
2888      
2889      private ArrayList segmentLengths = new ArrayList();
2890      
2891      private Reader in = null;
2892      private FSDataOutputStream out = null;
2893      private FSDataOutputStream indexOut = null;
2894      private Path outName;
2895
2896      private Progressable progressable = null;
2897
2898      public int run(boolean deleteInput) throws IOException {
2899        int segments = 0;
2900        int currentFile = 0;
2901        boolean atEof = (currentFile >= inFiles.length);
2902        CompressionType compressionType;
2903        CompressionCodec codec = null;
2904        segmentLengths.clear();
2905        if (atEof) {
2906          return 0;
2907        }
2908        
2909        // Initialize
2910        in = new Reader(fs, inFiles[currentFile], conf);
2911        compressionType = in.getCompressionType();
2912        codec = in.getCompressionCodec();
2913        
2914        for (int i=0; i < rawValues.length; ++i) {
2915          rawValues[i] = null;
2916        }
2917        
2918        while (!atEof) {
2919          int count = 0;
2920          int bytesProcessed = 0;
2921          rawKeys.reset();
2922          while (!atEof && 
2923                 bytesProcessed < memoryLimit && count < recordLimit) {
2924
2925            // Read a record into buffer
2926            // Note: Attempt to re-use 'rawValue' as far as possible
2927            int keyOffset = rawKeys.getLength();       
2928            ValueBytes rawValue = 
2929              (count == keyOffsets.length || rawValues[count] == null) ? 
2930              in.createValueBytes() : 
2931              rawValues[count];
2932            int recordLength = in.nextRaw(rawKeys, rawValue);
2933            if (recordLength == -1) {
2934              in.close();
2935              if (deleteInput) {
2936                fs.delete(inFiles[currentFile], true);
2937              }
2938              currentFile += 1;
2939              atEof = currentFile >= inFiles.length;
2940              if (!atEof) {
2941                in = new Reader(fs, inFiles[currentFile], conf);
2942              } else {
2943                in = null;
2944              }
2945              continue;
2946            }
2947
2948            int keyLength = rawKeys.getLength() - keyOffset;
2949
2950            if (count == keyOffsets.length)
2951              grow();
2952
2953            keyOffsets[count] = keyOffset;                // update pointers
2954            pointers[count] = count;
2955            keyLengths[count] = keyLength;
2956            rawValues[count] = rawValue;
2957
2958            bytesProcessed += recordLength; 
2959            count++;
2960          }
2961
2962          // buffer is full -- sort & flush it
2963          if(LOG.isDebugEnabled()) {
2964            LOG.debug("flushing segment " + segments);
2965          }
2966          rawBuffer = rawKeys.getData();
2967          sort(count);
2968          // indicate we're making progress
2969          if (progressable != null) {
2970            progressable.progress();
2971          }
2972          flush(count, bytesProcessed, compressionType, codec, 
2973                segments==0 && atEof);
2974          segments++;
2975        }
2976        return segments;
2977      }
2978
2979      public void close() throws IOException {
2980        if (in != null) {
2981          in.close();
2982        }
2983        if (out != null) {
2984          out.close();
2985        }
2986        if (indexOut != null) {
2987          indexOut.close();
2988        }
2989      }
2990
2991      private void grow() {
2992        int newLength = keyOffsets.length * 3 / 2;
2993        keyOffsets = grow(keyOffsets, newLength);
2994        pointers = grow(pointers, newLength);
2995        pointersCopy = new int[newLength];
2996        keyLengths = grow(keyLengths, newLength);
2997        rawValues = grow(rawValues, newLength);
2998      }
2999
3000      private int[] grow(int[] old, int newLength) {
3001        int[] result = new int[newLength];
3002        System.arraycopy(old, 0, result, 0, old.length);
3003        return result;
3004      }
3005      
3006      private ValueBytes[] grow(ValueBytes[] old, int newLength) {
3007        ValueBytes[] result = new ValueBytes[newLength];
3008        System.arraycopy(old, 0, result, 0, old.length);
3009        for (int i=old.length; i < newLength; ++i) {
3010          result[i] = null;
3011        }
3012        return result;
3013      }
3014
3015      private void flush(int count, int bytesProcessed, 
3016                         CompressionType compressionType, 
3017                         CompressionCodec codec, 
3018                         boolean done) throws IOException {
3019        if (out == null) {
3020          outName = done ? outFile : outFile.suffix(".0");
3021          out = fs.create(outName);
3022          if (!done) {
3023            indexOut = fs.create(outName.suffix(".index"));
3024          }
3025        }
3026
3027        long segmentStart = out.getPos();
3028        Writer writer = createWriter(conf, Writer.stream(out), 
3029            Writer.keyClass(keyClass), Writer.valueClass(valClass),
3030            Writer.compression(compressionType, codec),
3031            Writer.metadata(done ? metadata : new Metadata()));
3032        
3033        if (!done) {
3034          writer.sync = null;                     // disable sync on temp files
3035        }
3036
3037        for (int i = 0; i < count; i++) {         // write in sorted order
3038          int p = pointers[i];
3039          writer.appendRaw(rawBuffer, keyOffsets[p], keyLengths[p], rawValues[p]);
3040        }
3041        writer.close();
3042        
3043        if (!done) {
3044          // Save the segment length
3045          WritableUtils.writeVLong(indexOut, segmentStart);
3046          WritableUtils.writeVLong(indexOut, (out.getPos()-segmentStart));
3047          indexOut.flush();
3048        }
3049      }
3050
3051      private void sort(int count) {
3052        System.arraycopy(pointers, 0, pointersCopy, 0, count);
3053        mergeSort.mergeSort(pointersCopy, pointers, 0, count);
3054      }
3055      class SeqFileComparator implements Comparator<IntWritable> {
3056        @Override
3057        public int compare(IntWritable I, IntWritable J) {
3058          return comparator.compare(rawBuffer, keyOffsets[I.get()], 
3059                                    keyLengths[I.get()], rawBuffer, 
3060                                    keyOffsets[J.get()], keyLengths[J.get()]);
3061        }
3062      }
3063      
3064      /** set the progressable object in order to report progress */
3065      public void setProgressable(Progressable progressable)
3066      {
3067        this.progressable = progressable;
3068      }
3069      
3070    } // SequenceFile.Sorter.SortPass
3071
3072    /** The interface to iterate over raw keys/values of SequenceFiles. */
3073    public static interface RawKeyValueIterator {
3074      /** Gets the current raw key
3075       * @return DataOutputBuffer
3076       * @throws IOException
3077       */
3078      DataOutputBuffer getKey() throws IOException; 
3079      /** Gets the current raw value
3080       * @return ValueBytes 
3081       * @throws IOException
3082       */
3083      ValueBytes getValue() throws IOException; 
3084      /** Sets up the current key and value (for getKey and getValue)
3085       * @return true if there exists a key/value, false otherwise 
3086       * @throws IOException
3087       */
3088      boolean next() throws IOException;
3089      /** closes the iterator so that the underlying streams can be closed
3090       * @throws IOException
3091       */
3092      void close() throws IOException;
3093      /** Gets the Progress object; this has a float (0.0 - 1.0) 
3094       * indicating the bytes processed by the iterator so far
3095       */
3096      Progress getProgress();
3097    }    
3098    
3099    /**
3100     * Merges the list of segments of type <code>SegmentDescriptor</code>
3101     * @param segments the list of SegmentDescriptors
3102     * @param tmpDir the directory to write temporary files into
3103     * @return RawKeyValueIterator
3104     * @throws IOException
3105     */
3106    public RawKeyValueIterator merge(List <SegmentDescriptor> segments, 
3107                                     Path tmpDir) 
3108      throws IOException {
3109      // pass in object to report progress, if present
3110      MergeQueue mQueue = new MergeQueue(segments, tmpDir, progressable);
3111      return mQueue.merge();
3112    }
3113
3114    /**
3115     * Merges the contents of files passed in Path[] using a max factor value
3116     * that is already set
3117     * @param inNames the array of path names
3118     * @param deleteInputs true if the input files should be deleted when 
3119     * unnecessary
3120     * @param tmpDir the directory to write temporary files into
3121     * @return RawKeyValueIteratorMergeQueue
3122     * @throws IOException
3123     */
3124    public RawKeyValueIterator merge(Path [] inNames, boolean deleteInputs,
3125                                     Path tmpDir) 
3126      throws IOException {
3127      return merge(inNames, deleteInputs, 
3128                   (inNames.length < factor) ? inNames.length : factor,
3129                   tmpDir);
3130    }
3131
3132    /**
3133     * Merges the contents of files passed in Path[]
3134     * @param inNames the array of path names
3135     * @param deleteInputs true if the input files should be deleted when 
3136     * unnecessary
3137     * @param factor the factor that will be used as the maximum merge fan-in
3138     * @param tmpDir the directory to write temporary files into
3139     * @return RawKeyValueIteratorMergeQueue
3140     * @throws IOException
3141     */
3142    public RawKeyValueIterator merge(Path [] inNames, boolean deleteInputs,
3143                                     int factor, Path tmpDir) 
3144      throws IOException {
3145      //get the segments from inNames
3146      ArrayList <SegmentDescriptor> a = new ArrayList <SegmentDescriptor>();
3147      for (int i = 0; i < inNames.length; i++) {
3148        SegmentDescriptor s = new SegmentDescriptor(0,
3149            fs.getFileStatus(inNames[i]).getLen(), inNames[i]);
3150        s.preserveInput(!deleteInputs);
3151        s.doSync();
3152        a.add(s);
3153      }
3154      this.factor = factor;
3155      MergeQueue mQueue = new MergeQueue(a, tmpDir, progressable);
3156      return mQueue.merge();
3157    }
3158
3159    /**
3160     * Merges the contents of files passed in Path[]
3161     * @param inNames the array of path names
3162     * @param tempDir the directory for creating temp files during merge
3163     * @param deleteInputs true if the input files should be deleted when 
3164     * unnecessary
3165     * @return RawKeyValueIteratorMergeQueue
3166     * @throws IOException
3167     */
3168    public RawKeyValueIterator merge(Path [] inNames, Path tempDir, 
3169                                     boolean deleteInputs) 
3170      throws IOException {
3171      //outFile will basically be used as prefix for temp files for the
3172      //intermediate merge outputs           
3173      this.outFile = new Path(tempDir + Path.SEPARATOR + "merged");
3174      //get the segments from inNames
3175      ArrayList <SegmentDescriptor> a = new ArrayList <SegmentDescriptor>();
3176      for (int i = 0; i < inNames.length; i++) {
3177        SegmentDescriptor s = new SegmentDescriptor(0,
3178            fs.getFileStatus(inNames[i]).getLen(), inNames[i]);
3179        s.preserveInput(!deleteInputs);
3180        s.doSync();
3181        a.add(s);
3182      }
3183      factor = (inNames.length < factor) ? inNames.length : factor;
3184      // pass in object to report progress, if present
3185      MergeQueue mQueue = new MergeQueue(a, tempDir, progressable);
3186      return mQueue.merge();
3187    }
3188
3189    /**
3190     * Clones the attributes (like compression of the input file and creates a 
3191     * corresponding Writer
3192     * @param inputFile the path of the input file whose attributes should be 
3193     * cloned
3194     * @param outputFile the path of the output file 
3195     * @param prog the Progressable to report status during the file write
3196     * @return Writer
3197     * @throws IOException
3198     */
3199    public Writer cloneFileAttributes(Path inputFile, Path outputFile, 
3200                                      Progressable prog) throws IOException {
3201      Reader reader = new Reader(conf,
3202                                 Reader.file(inputFile),
3203                                 new Reader.OnlyHeaderOption());
3204      CompressionType compress = reader.getCompressionType();
3205      CompressionCodec codec = reader.getCompressionCodec();
3206      reader.close();
3207
3208      Writer writer = createWriter(conf, 
3209                                   Writer.file(outputFile), 
3210                                   Writer.keyClass(keyClass), 
3211                                   Writer.valueClass(valClass), 
3212                                   Writer.compression(compress, codec), 
3213                                   Writer.progressable(prog));
3214      return writer;
3215    }
3216
3217    /**
3218     * Writes records from RawKeyValueIterator into a file represented by the 
3219     * passed writer
3220     * @param records the RawKeyValueIterator
3221     * @param writer the Writer created earlier 
3222     * @throws IOException
3223     */
3224    public void writeFile(RawKeyValueIterator records, Writer writer) 
3225      throws IOException {
3226      while(records.next()) {
3227        writer.appendRaw(records.getKey().getData(), 0, 
3228                         records.getKey().getLength(), records.getValue());
3229      }
3230      writer.sync();
3231    }
3232        
3233    /** Merge the provided files.
3234     * @param inFiles the array of input path names
3235     * @param outFile the final output file
3236     * @throws IOException
3237     */
3238    public void merge(Path[] inFiles, Path outFile) throws IOException {
3239      if (fs.exists(outFile)) {
3240        throw new IOException("already exists: " + outFile);
3241      }
3242      RawKeyValueIterator r = merge(inFiles, false, outFile.getParent());
3243      Writer writer = cloneFileAttributes(inFiles[0], outFile, null);
3244      
3245      writeFile(r, writer);
3246
3247      writer.close();
3248    }
3249
3250    /** sort calls this to generate the final merged output */
3251    private int mergePass(Path tmpDir) throws IOException {
3252      if(LOG.isDebugEnabled()) {
3253        LOG.debug("running merge pass");
3254      }
3255      Writer writer = cloneFileAttributes(
3256                                          outFile.suffix(".0"), outFile, null);
3257      RawKeyValueIterator r = merge(outFile.suffix(".0"), 
3258                                    outFile.suffix(".0.index"), tmpDir);
3259      writeFile(r, writer);
3260
3261      writer.close();
3262      return 0;
3263    }
3264
3265    /** Used by mergePass to merge the output of the sort
3266     * @param inName the name of the input file containing sorted segments
3267     * @param indexIn the offsets of the sorted segments
3268     * @param tmpDir the relative directory to store intermediate results in
3269     * @return RawKeyValueIterator
3270     * @throws IOException
3271     */
3272    private RawKeyValueIterator merge(Path inName, Path indexIn, Path tmpDir) 
3273      throws IOException {
3274      //get the segments from indexIn
3275      //we create a SegmentContainer so that we can track segments belonging to
3276      //inName and delete inName as soon as we see that we have looked at all
3277      //the contained segments during the merge process & hence don't need 
3278      //them anymore
3279      SegmentContainer container = new SegmentContainer(inName, indexIn);
3280      MergeQueue mQueue = new MergeQueue(container.getSegmentList(), tmpDir, progressable);
3281      return mQueue.merge();
3282    }
3283    
3284    /** This class implements the core of the merge logic */
3285    private class MergeQueue extends PriorityQueue 
3286      implements RawKeyValueIterator {
3287      private boolean compress;
3288      private boolean blockCompress;
3289      private DataOutputBuffer rawKey = new DataOutputBuffer();
3290      private ValueBytes rawValue;
3291      private long totalBytesProcessed;
3292      private float progPerByte;
3293      private Progress mergeProgress = new Progress();
3294      private Path tmpDir;
3295      private Progressable progress = null; //handle to the progress reporting object
3296      private SegmentDescriptor minSegment;
3297      
3298      //a TreeMap used to store the segments sorted by size (segment offset and
3299      //segment path name is used to break ties between segments of same sizes)
3300      private Map<SegmentDescriptor, Void> sortedSegmentSizes =
3301        new TreeMap<SegmentDescriptor, Void>();
3302            
3303      @SuppressWarnings("unchecked")
3304      public void put(SegmentDescriptor stream) throws IOException {
3305        if (size() == 0) {
3306          compress = stream.in.isCompressed();
3307          blockCompress = stream.in.isBlockCompressed();
3308        } else if (compress != stream.in.isCompressed() || 
3309                   blockCompress != stream.in.isBlockCompressed()) {
3310          throw new IOException("All merged files must be compressed or not.");
3311        } 
3312        super.put(stream);
3313      }
3314      
3315      /**
3316       * A queue of file segments to merge
3317       * @param segments the file segments to merge
3318       * @param tmpDir a relative local directory to save intermediate files in
3319       * @param progress the reference to the Progressable object
3320       */
3321      public MergeQueue(List <SegmentDescriptor> segments,
3322          Path tmpDir, Progressable progress) {
3323        int size = segments.size();
3324        for (int i = 0; i < size; i++) {
3325          sortedSegmentSizes.put(segments.get(i), null);
3326        }
3327        this.tmpDir = tmpDir;
3328        this.progress = progress;
3329      }
3330      @Override
3331      protected boolean lessThan(Object a, Object b) {
3332        // indicate we're making progress
3333        if (progress != null) {
3334          progress.progress();
3335        }
3336        SegmentDescriptor msa = (SegmentDescriptor)a;
3337        SegmentDescriptor msb = (SegmentDescriptor)b;
3338        return comparator.compare(msa.getKey().getData(), 0, 
3339                                  msa.getKey().getLength(), msb.getKey().getData(), 0, 
3340                                  msb.getKey().getLength()) < 0;
3341      }
3342      @Override
3343      public void close() throws IOException {
3344        SegmentDescriptor ms;                           // close inputs
3345        while ((ms = (SegmentDescriptor)pop()) != null) {
3346          ms.cleanup();
3347        }
3348        minSegment = null;
3349      }
3350      @Override
3351      public DataOutputBuffer getKey() throws IOException {
3352        return rawKey;
3353      }
3354      @Override
3355      public ValueBytes getValue() throws IOException {
3356        return rawValue;
3357      }
3358      @Override
3359      public boolean next() throws IOException {
3360        if (size() == 0)
3361          return false;
3362        if (minSegment != null) {
3363          //minSegment is non-null for all invocations of next except the first
3364          //one. For the first invocation, the priority queue is ready for use
3365          //but for the subsequent invocations, first adjust the queue 
3366          adjustPriorityQueue(minSegment);
3367          if (size() == 0) {
3368            minSegment = null;
3369            return false;
3370          }
3371        }
3372        minSegment = (SegmentDescriptor)top();
3373        long startPos = minSegment.in.getPosition(); // Current position in stream
3374        //save the raw key reference
3375        rawKey = minSegment.getKey();
3376        //load the raw value. Re-use the existing rawValue buffer
3377        if (rawValue == null) {
3378          rawValue = minSegment.in.createValueBytes();
3379        }
3380        minSegment.nextRawValue(rawValue);
3381        long endPos = minSegment.in.getPosition(); // End position after reading value
3382        updateProgress(endPos - startPos);
3383        return true;
3384      }
3385      
3386      @Override
3387      public Progress getProgress() {
3388        return mergeProgress; 
3389      }
3390
3391      private void adjustPriorityQueue(SegmentDescriptor ms) throws IOException{
3392        long startPos = ms.in.getPosition(); // Current position in stream
3393        boolean hasNext = ms.nextRawKey();
3394        long endPos = ms.in.getPosition(); // End position after reading key
3395        updateProgress(endPos - startPos);
3396        if (hasNext) {
3397          adjustTop();
3398        } else {
3399          pop();
3400          ms.cleanup();
3401        }
3402      }
3403
3404      private void updateProgress(long bytesProcessed) {
3405        totalBytesProcessed += bytesProcessed;
3406        if (progPerByte > 0) {
3407          mergeProgress.set(totalBytesProcessed * progPerByte);
3408        }
3409      }
3410      
3411      /** This is the single level merge that is called multiple times 
3412       * depending on the factor size and the number of segments
3413       * @return RawKeyValueIterator
3414       * @throws IOException
3415       */
3416      public RawKeyValueIterator merge() throws IOException {
3417        //create the MergeStreams from the sorted map created in the constructor
3418        //and dump the final output to a file
3419        int numSegments = sortedSegmentSizes.size();
3420        int origFactor = factor;
3421        int passNo = 1;
3422        LocalDirAllocator lDirAlloc = new LocalDirAllocator("io.seqfile.local.dir");
3423        do {
3424          //get the factor for this pass of merge
3425          factor = getPassFactor(passNo, numSegments);
3426          List<SegmentDescriptor> segmentsToMerge =
3427            new ArrayList<SegmentDescriptor>();
3428          int segmentsConsidered = 0;
3429          int numSegmentsToConsider = factor;
3430          while (true) {
3431            //extract the smallest 'factor' number of segment pointers from the 
3432            //TreeMap. Call cleanup on the empty segments (no key/value data)
3433            SegmentDescriptor[] mStream = 
3434              getSegmentDescriptors(numSegmentsToConsider);
3435            for (int i = 0; i < mStream.length; i++) {
3436              if (mStream[i].nextRawKey()) {
3437                segmentsToMerge.add(mStream[i]);
3438                segmentsConsidered++;
3439                // Count the fact that we read some bytes in calling nextRawKey()
3440                updateProgress(mStream[i].in.getPosition());
3441              }
3442              else {
3443                mStream[i].cleanup();
3444                numSegments--; //we ignore this segment for the merge
3445              }
3446            }
3447            //if we have the desired number of segments
3448            //or looked at all available segments, we break
3449            if (segmentsConsidered == factor || 
3450                sortedSegmentSizes.size() == 0) {
3451              break;
3452            }
3453              
3454            numSegmentsToConsider = factor - segmentsConsidered;
3455          }
3456          //feed the streams to the priority queue
3457          initialize(segmentsToMerge.size()); clear();
3458          for (int i = 0; i < segmentsToMerge.size(); i++) {
3459            put(segmentsToMerge.get(i));
3460          }
3461          //if we have lesser number of segments remaining, then just return the
3462          //iterator, else do another single level merge
3463          if (numSegments <= factor) {
3464            //calculate the length of the remaining segments. Required for 
3465            //calculating the merge progress
3466            long totalBytes = 0;
3467            for (int i = 0; i < segmentsToMerge.size(); i++) {
3468              totalBytes += segmentsToMerge.get(i).segmentLength;
3469            }
3470            if (totalBytes != 0) //being paranoid
3471              progPerByte = 1.0f / (float)totalBytes;
3472            //reset factor to what it originally was
3473            factor = origFactor;
3474            return this;
3475          } else {
3476            //we want to spread the creation of temp files on multiple disks if 
3477            //available under the space constraints
3478            long approxOutputSize = 0; 
3479            for (SegmentDescriptor s : segmentsToMerge) {
3480              approxOutputSize += s.segmentLength + 
3481                                  ChecksumFileSystem.getApproxChkSumLength(
3482                                  s.segmentLength);
3483            }
3484            Path tmpFilename = 
3485              new Path(tmpDir, "intermediate").suffix("." + passNo);
3486
3487            Path outputFile =  lDirAlloc.getLocalPathForWrite(
3488                                                tmpFilename.toString(),
3489                                                approxOutputSize, conf);
3490            if(LOG.isDebugEnabled()) { 
3491              LOG.debug("writing intermediate results to " + outputFile);
3492            }
3493            Writer writer = cloneFileAttributes(
3494                                                fs.makeQualified(segmentsToMerge.get(0).segmentPathName), 
3495                                                fs.makeQualified(outputFile), null);
3496            writer.sync = null; //disable sync for temp files
3497            writeFile(this, writer);
3498            writer.close();
3499            
3500            //we finished one single level merge; now clean up the priority 
3501            //queue
3502            this.close();
3503            
3504            SegmentDescriptor tempSegment = 
3505              new SegmentDescriptor(0,
3506                  fs.getFileStatus(outputFile).getLen(), outputFile);
3507            //put the segment back in the TreeMap
3508            sortedSegmentSizes.put(tempSegment, null);
3509            numSegments = sortedSegmentSizes.size();
3510            passNo++;
3511          }
3512          //we are worried about only the first pass merge factor. So reset the 
3513          //factor to what it originally was
3514          factor = origFactor;
3515        } while(true);
3516      }
3517  
3518      //Hadoop-591
3519      public int getPassFactor(int passNo, int numSegments) {
3520        if (passNo > 1 || numSegments <= factor || factor == 1) 
3521          return factor;
3522        int mod = (numSegments - 1) % (factor - 1);
3523        if (mod == 0)
3524          return factor;
3525        return mod + 1;
3526      }
3527      
3528      /** Return (& remove) the requested number of segment descriptors from the
3529       * sorted map.
3530       */
3531      public SegmentDescriptor[] getSegmentDescriptors(int numDescriptors) {
3532        if (numDescriptors > sortedSegmentSizes.size())
3533          numDescriptors = sortedSegmentSizes.size();
3534        SegmentDescriptor[] SegmentDescriptors = 
3535          new SegmentDescriptor[numDescriptors];
3536        Iterator iter = sortedSegmentSizes.keySet().iterator();
3537        int i = 0;
3538        while (i < numDescriptors) {
3539          SegmentDescriptors[i++] = (SegmentDescriptor)iter.next();
3540          iter.remove();
3541        }
3542        return SegmentDescriptors;
3543      }
3544    } // SequenceFile.Sorter.MergeQueue
3545
3546    /** This class defines a merge segment. This class can be subclassed to 
3547     * provide a customized cleanup method implementation. In this 
3548     * implementation, cleanup closes the file handle and deletes the file 
3549     */
3550    public class SegmentDescriptor implements Comparable {
3551      
3552      long segmentOffset; //the start of the segment in the file
3553      long segmentLength; //the length of the segment
3554      Path segmentPathName; //the path name of the file containing the segment
3555      boolean ignoreSync = true; //set to true for temp files
3556      private Reader in = null; 
3557      private DataOutputBuffer rawKey = null; //this will hold the current key
3558      private boolean preserveInput = false; //delete input segment files?
3559      
3560      /** Constructs a segment
3561       * @param segmentOffset the offset of the segment in the file
3562       * @param segmentLength the length of the segment
3563       * @param segmentPathName the path name of the file containing the segment
3564       */
3565      public SegmentDescriptor (long segmentOffset, long segmentLength, 
3566                                Path segmentPathName) {
3567        this.segmentOffset = segmentOffset;
3568        this.segmentLength = segmentLength;
3569        this.segmentPathName = segmentPathName;
3570      }
3571      
3572      /** Do the sync checks */
3573      public void doSync() {ignoreSync = false;}
3574      
3575      /** Whether to delete the files when no longer needed */
3576      public void preserveInput(boolean preserve) {
3577        preserveInput = preserve;
3578      }
3579
3580      public boolean shouldPreserveInput() {
3581        return preserveInput;
3582      }
3583      
3584      @Override
3585      public int compareTo(Object o) {
3586        SegmentDescriptor that = (SegmentDescriptor)o;
3587        if (this.segmentLength != that.segmentLength) {
3588          return (this.segmentLength < that.segmentLength ? -1 : 1);
3589        }
3590        if (this.segmentOffset != that.segmentOffset) {
3591          return (this.segmentOffset < that.segmentOffset ? -1 : 1);
3592        }
3593        return (this.segmentPathName.toString()).
3594          compareTo(that.segmentPathName.toString());
3595      }
3596
3597      @Override
3598      public boolean equals(Object o) {
3599        if (!(o instanceof SegmentDescriptor)) {
3600          return false;
3601        }
3602        SegmentDescriptor that = (SegmentDescriptor)o;
3603        if (this.segmentLength == that.segmentLength &&
3604            this.segmentOffset == that.segmentOffset &&
3605            this.segmentPathName.toString().equals(
3606              that.segmentPathName.toString())) {
3607          return true;
3608        }
3609        return false;
3610      }
3611
3612      @Override
3613      public int hashCode() {
3614        return 37 * 17 + (int) (segmentOffset^(segmentOffset>>>32));
3615      }
3616
3617      /** Fills up the rawKey object with the key returned by the Reader
3618       * @return true if there is a key returned; false, otherwise
3619       * @throws IOException
3620       */
3621      public boolean nextRawKey() throws IOException {
3622        if (in == null) {
3623          int bufferSize = getBufferSize(conf); 
3624          Reader reader = new Reader(conf,
3625                                     Reader.file(segmentPathName), 
3626                                     Reader.bufferSize(bufferSize),
3627                                     Reader.start(segmentOffset), 
3628                                     Reader.length(segmentLength));
3629        
3630          //sometimes we ignore syncs especially for temp merge files
3631          if (ignoreSync) reader.ignoreSync();
3632
3633          if (reader.getKeyClass() != keyClass)
3634            throw new IOException("wrong key class: " + reader.getKeyClass() +
3635                                  " is not " + keyClass);
3636          if (reader.getValueClass() != valClass)
3637            throw new IOException("wrong value class: "+reader.getValueClass()+
3638                                  " is not " + valClass);
3639          this.in = reader;
3640          rawKey = new DataOutputBuffer();
3641        }
3642        rawKey.reset();
3643        int keyLength = 
3644          in.nextRawKey(rawKey);
3645        return (keyLength >= 0);
3646      }
3647
3648      /** Fills up the passed rawValue with the value corresponding to the key
3649       * read earlier
3650       * @param rawValue
3651       * @return the length of the value
3652       * @throws IOException
3653       */
3654      public int nextRawValue(ValueBytes rawValue) throws IOException {
3655        int valLength = in.nextRawValue(rawValue);
3656        return valLength;
3657      }
3658      
3659      /** Returns the stored rawKey */
3660      public DataOutputBuffer getKey() {
3661        return rawKey;
3662      }
3663      
3664      /** closes the underlying reader */
3665      private void close() throws IOException {
3666        this.in.close();
3667        this.in = null;
3668      }
3669
3670      /** The default cleanup. Subclasses can override this with a custom 
3671       * cleanup 
3672       */
3673      public void cleanup() throws IOException {
3674        close();
3675        if (!preserveInput) {
3676          fs.delete(segmentPathName, true);
3677        }
3678      }
3679    } // SequenceFile.Sorter.SegmentDescriptor
3680    
3681    /** This class provisions multiple segments contained within a single
3682     *  file
3683     */
3684    private class LinkedSegmentsDescriptor extends SegmentDescriptor {
3685
3686      SegmentContainer parentContainer = null;
3687
3688      /** Constructs a segment
3689       * @param segmentOffset the offset of the segment in the file
3690       * @param segmentLength the length of the segment
3691       * @param segmentPathName the path name of the file containing the segment
3692       * @param parent the parent SegmentContainer that holds the segment
3693       */
3694      public LinkedSegmentsDescriptor (long segmentOffset, long segmentLength, 
3695                                       Path segmentPathName, SegmentContainer parent) {
3696        super(segmentOffset, segmentLength, segmentPathName);
3697        this.parentContainer = parent;
3698      }
3699      /** The default cleanup. Subclasses can override this with a custom 
3700       * cleanup 
3701       */
3702      @Override
3703      public void cleanup() throws IOException {
3704        super.close();
3705        if (super.shouldPreserveInput()) return;
3706        parentContainer.cleanup();
3707      }
3708      
3709      @Override
3710      public boolean equals(Object o) {
3711        if (!(o instanceof LinkedSegmentsDescriptor)) {
3712          return false;
3713        }
3714        return super.equals(o);
3715      }
3716    } //SequenceFile.Sorter.LinkedSegmentsDescriptor
3717
3718    /** The class that defines a container for segments to be merged. Primarily
3719     * required to delete temp files as soon as all the contained segments
3720     * have been looked at */
3721    private class SegmentContainer {
3722      private int numSegmentsCleanedUp = 0; //track the no. of segment cleanups
3723      private int numSegmentsContained; //# of segments contained
3724      private Path inName; //input file from where segments are created
3725      
3726      //the list of segments read from the file
3727      private ArrayList <SegmentDescriptor> segments = 
3728        new ArrayList <SegmentDescriptor>();
3729      /** This constructor is there primarily to serve the sort routine that 
3730       * generates a single output file with an associated index file */
3731      public SegmentContainer(Path inName, Path indexIn) throws IOException {
3732        //get the segments from indexIn
3733        FSDataInputStream fsIndexIn = fs.open(indexIn);
3734        long end = fs.getFileStatus(indexIn).getLen();
3735        while (fsIndexIn.getPos() < end) {
3736          long segmentOffset = WritableUtils.readVLong(fsIndexIn);
3737          long segmentLength = WritableUtils.readVLong(fsIndexIn);
3738          Path segmentName = inName;
3739          segments.add(new LinkedSegmentsDescriptor(segmentOffset, 
3740                                                    segmentLength, segmentName, this));
3741        }
3742        fsIndexIn.close();
3743        fs.delete(indexIn, true);
3744        numSegmentsContained = segments.size();
3745        this.inName = inName;
3746      }
3747
3748      public List <SegmentDescriptor> getSegmentList() {
3749        return segments;
3750      }
3751      public void cleanup() throws IOException {
3752        numSegmentsCleanedUp++;
3753        if (numSegmentsCleanedUp == numSegmentsContained) {
3754          fs.delete(inName, true);
3755        }
3756      }
3757    } //SequenceFile.Sorter.SegmentContainer
3758
3759  } // SequenceFile.Sorter
3760
3761} // SequenceFile