ScatteringStoreDataAccessor.java :  » Database-DBMS » mckoi » com » mckoi » store » Java Open Source

Java Open Source » Database DBMS » mckoi 
mckoi » com » mckoi » store » ScatteringStoreDataAccessor.java
/**
 * com.mckoi.store.ScatteringStoreDataAccessor  13 Jun 2003
 *
 * Mckoi SQL Database ( http://www.mckoi.com/database )
 * Copyright (C) 2000, 2001, 2002  Diehl and Associates, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * Version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License Version 2 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * Version 2 along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Change Log:
 * 
 * 
 */

package com.mckoi.store;

import java.util.ArrayList;
import java.io.*;

/**
 * An implementation of StoreDataAccessor that scatters the addressible
 * data resource across multiple files in the file system.  When one store
 * data resource reaches a certain threshold size, the content 'flows' over
 * to the next file.
 *
 * @author Tobias Downer
 */

public class ScatteringStoreDataAccessor implements StoreDataAccessor {

//  /**
//   * True if the data accessor is read only.
//   */
//  private boolean read_only;

  /**
   * The path of this store in the file system.
   */
  private final File path;

  /**
   * The name of the file in the file system minus the extension.
   */
  private final String file_name;

  /**
   * The extension of the first file in the sliced set.
   */
  private final String first_ext;

  /**
   * The maximum size a file slice can grow too before a new slice is created.
   */
  private final long max_slice_size;
  
  /**
   * The list of RandomAccessFile objects for each file that represents a
   * slice of the store.  (FileSlice objects)
   */
  private ArrayList slice_list;

  /**
   * The current actual physical size of the store data on disk.
   */
  private long true_file_length;

  /**
   * A lock when modifying the true_data_size, and slice_list.
   */
  private final Object lock = new Object();;

  /**
   * Set when the store is openned.
   */
  private boolean open = false;
  


  /**
   * Constructs the store data accessor.
   */
  public ScatteringStoreDataAccessor(File path, String file_name,
                                     String first_ext, long max_slice_size) {
    slice_list = new ArrayList();
    this.path = path;
    this.file_name = file_name;
    this.first_ext = first_ext;
    this.max_slice_size = max_slice_size;
  }

  /**
   * Given a file, this will convert to a scattering file store with files
   * no larger than the maximum slice size.
   */
  public void convertToScatteringStore(File f) throws IOException {

    int BUFFER_SIZE = 65536;

    RandomAccessFile src = new RandomAccessFile(f, "rw");
    long file_size = f.length();
    long current_p = max_slice_size;
    long to_write = Math.min(file_size - current_p, max_slice_size);
    int write_to_part = 1;

    byte[] copy_buffer = new byte[BUFFER_SIZE];

    while (to_write > 0) {

      src.seek(current_p);

      File to_f = slicePartFile(write_to_part);
      if (to_f.exists()) {
        throw new IOException("Copy error, slice already exists.");
      }
      FileOutputStream to_raf = new FileOutputStream(to_f);

      while (to_write > 0) {
        int size_to_copy = (int) Math.min(BUFFER_SIZE, to_write);

        src.readFully(copy_buffer, 0, size_to_copy);
        to_raf.write(copy_buffer, 0, size_to_copy);

        current_p += size_to_copy;
        to_write -= size_to_copy;
      }

      to_raf.flush();
      to_raf.close();

      to_write = Math.min(file_size - current_p, max_slice_size);
      ++write_to_part;
    }

    // Truncate the source file
    if (file_size > max_slice_size) {
      src.seek(0);
      src.setLength(max_slice_size);
    }
    src.close();

  }

  /**
   * Given an index value, this will return a File object for the nth slice in
   * the file system.  For example, given '4' will return [file name].004,
   * given 1004 will return [file name].1004, etc.
   */
  private File slicePartFile(int i) {
    if (i == 0) {
      return new File(path, file_name + "." + first_ext);
    }
    StringBuffer fn = new StringBuffer();
    fn.append(file_name);
    fn.append(".");
    if (i < 10) {
      fn.append("00");
    }
    else if (i < 100) {
      fn.append("0");
    }
    fn.append(i);
    return new File(path, fn.toString());
  }

  /**
   * Counts the number of files in the file store that represent this store.
   */
  private int countStoreFiles() {
    int i = 0;
    File f = slicePartFile(i);
    while (f.exists()) {
      ++i;
      f = slicePartFile(i);
    }
    return i;
  }    

  /**
   * Creates a StoreDataAccessor object for accessing a given slice.
   */
  private StoreDataAccessor createSliceDataAccessor(File file) {
    // Currently we only support an IOStoreDataAccessor object.
    return new IOStoreDataAccessor(file);
  }

  /**
   * Discovers the size of the data resource (doesn't require the file to be
   * open).
   */
  private long discoverSize() throws IOException {
    long running_total = 0;
    
    synchronized (lock) {
      // Does the file exist?
      int i = 0;
      File f = slicePartFile(i);
      while (f.exists()) {
        running_total += createSliceDataAccessor(f).getSize();

        ++i;
        f = slicePartFile(i);
      }
    }

    return running_total;
  }
  
  // ---------- Implemented from StoreDataAccessor ----------
  
  public void open(boolean read_only) throws IOException {
    long running_length;
    
    synchronized (lock) {
      slice_list = new ArrayList();
      
      // Does the file exist?
      File f = slicePartFile(0);
      boolean open_existing = f.exists();
      
      // If the file already exceeds the threshold and there isn't a secondary
      // file then we need to convert the file.
      if (open_existing && f.length() > max_slice_size) {
        File f2 = slicePartFile(1);
        if (f2.exists()) {
          throw new IOException(
                          "File length exceeds maximum slice size setting.");
        }
        // We need to scatter the file.
        if (!read_only) {
          convertToScatteringStore(f);
        }
        else {
          throw new IOException(
                "Unable to convert to a scattered store because read-only.");
        }
      }
  
      // Setup the first file slice
      FileSlice slice = new FileSlice();
      slice.data = createSliceDataAccessor(f);
      slice.data.open(read_only);
      
      slice_list.add(slice);
      running_length = slice.data.getSize();
  
      // If we are opening a store that exists already, there may be other
      // slices we need to setup.
      if (open_existing) {
        int i = 1;
        File slice_part = slicePartFile(i);
        while (slice_part.exists()) {
          // Create the new slice information for this part of the file.
          slice = new FileSlice();
          slice.data = createSliceDataAccessor(slice_part);
          slice.data.open(read_only);
  
          slice_list.add(slice);
          running_length += slice.data.getSize();
  
          ++i;
          slice_part = slicePartFile(i);
        }
      }
  
      true_file_length = running_length;
      
      open = true;
    }
  }
  
  public void close() throws IOException {
    synchronized (lock) {
      int sz = slice_list.size();
      for (int i = 0; i < sz; ++i) {
        FileSlice slice = (FileSlice) slice_list.get(i);
        slice.data.close();
      }
      slice_list = null;
      open = false;
    }
  }

  public boolean delete() {
    // The number of files
    int count_files = countStoreFiles();
    // Delete each file from back to front
    for (int i = count_files - 1; i >= 0; --i) {
      File f = slicePartFile(i);
      boolean delete_success = createSliceDataAccessor(f).delete();
      if (!delete_success) {
        return false;
      }
    }
    return true;
  }

  public boolean exists() {
    return slicePartFile(0).exists();
  }
  
  
  public void read(long position, byte[] buf, int off, int len)
                                                        throws IOException {
    // Reads the array (potentially across multiple slices).
    while (len > 0) {
      int file_i = (int) (position / max_slice_size);
      long file_p = (position % max_slice_size);
      int file_len = (int) Math.min((long) len, max_slice_size - file_p);

      FileSlice slice;
      synchronized (lock) {
        // Return if out of bounds.
        if (file_i < 0 || file_i >= slice_list.size()) {
          return;
        }
        slice = (FileSlice) slice_list.get(file_i);
      }
      slice.data.read(file_p, buf, off, file_len);
      
      position += file_len;
      off += file_len;
      len -= file_len;
    }
  }

  public void write(long position, byte[] buf, int off, int len)
                                                        throws IOException {
    // Writes the array (potentially across multiple slices).
    while (len > 0) {
      int file_i = (int) (position / max_slice_size);
      long file_p = (position % max_slice_size);
      int file_len = (int) Math.min((long) len, max_slice_size - file_p);

      FileSlice slice;
      synchronized (lock) {
        // Return if out of bounds.
        if (file_i < 0 || file_i >= slice_list.size()) {
          return;
        }
        slice = (FileSlice) slice_list.get(file_i);
      }
      slice.data.write(file_p, buf, off, file_len);

      position += file_len;
      off += file_len;
      len -= file_len;
    }
  }

  public void setSize(long length) throws IOException {
    synchronized (lock) {
      // The size we need to grow the data area
      long total_size_to_grow = length - true_file_length;
      // Assert that we aren't shrinking the data area size.
      if (total_size_to_grow < 0) {
        throw new IOException("Unable to make the data area size " +
                              "smaller for this type of store.");
      }

      while (total_size_to_grow > 0) {
        // Grow the last slice by this size
        int last = slice_list.size() - 1;
        FileSlice slice = (FileSlice) slice_list.get(last);
        final long old_slice_length = slice.data.getSize();
        long to_grow = Math.min(total_size_to_grow,
                                (max_slice_size - old_slice_length));

        // Flush the buffer and set the length of the file
        slice.data.setSize(old_slice_length + to_grow);
        // Synchronize the file change.  XP appears to defer a file size change
        // and it can result in errors if the JVM is terminated.
        slice.data.synch();

        total_size_to_grow -= to_grow;
        // Create a new empty slice if we need to extend the data area
        if (total_size_to_grow > 0) {
          File slice_file = slicePartFile(last + 1);

          slice = new FileSlice();
          slice.data = createSliceDataAccessor(slice_file);
          slice.data.open(false);

          slice_list.add(slice);
        }
      }
      true_file_length = length;
    }

  }

  public long getSize() throws IOException {
    synchronized (lock) {
      if (open) {
        return true_file_length;
      }
      else {
        return discoverSize();
      }
    }
  }
  
  public void synch() throws IOException {
    synchronized (lock) {
      int sz = slice_list.size();
      for (int i = 0; i < sz; ++i) {
        FileSlice slice = (FileSlice) slice_list.get(i);
        slice.data.synch();
      }
    }
  }

  // ---------- Inner classes ----------
  
  /**
   * An object that contains information about a file slice.  The information
   * includes the name of the file, the RandomAccessFile that represents the
   * slice, and the size of the file.
   */
  private static class FileSlice {

    StoreDataAccessor data;
    
  }
  
}

java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.