/* * Licensed Materials - Property of Perforce Software, Inc. * © Copyright Perforce Software, Inc. 2014, 2021 * © Copyright IBM Corp. 2009, 2014 * © Copyright ILOG 1996, 2009 * All Rights Reserved. * * Note to U.S. Government Users Restricted Rights: * The Software and Documentation were developed at private expense and * are "Commercial Items" as that term is defined at 48 CFR 2.101, * consisting of "Commercial Computer Software" and * "Commercial Computer Software Documentation", as such terms are * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4, * as applicable. */ package lod; import java.io.DataInputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.util.LinkedList; import ilog.views.chart.IlvDataInterval; import ilog.views.chart.data.IlvDataPoints; import ilog.views.chart.data.lod.IlvDataTile; import ilog.views.chart.data.lod.IlvDataTileLoader; /** * A data tile loader that reads data from a binary file. * <p> * The <code>DataTileLoader</code> class implements a threaded loading mechanism * that uses random file access to retrieve the data for a given tile. */ public class DataTileLoader implements IlvDataTileLoader { private IlvDataInterval xRange = new IlvDataInterval(); private IlvDataInterval yRange = new IlvDataInterval(); private LinkedList<IlvDataTile> tileQueue = new LinkedList<IlvDataTile>(); private File file; private LoadThread loadThread; private long lag; /** * Initializes a new loader with the specified data file. * * @param file * The file from which data is read. */ public DataTileLoader(File file) { parseDataFile(file); this.file = file; loadThread = new LoadThread(); loadThread.start(); } /** * Parses the data file to compute the limits of the x and y values. */ private void parseDataFile(File file) { DataInputStream in = null; try { in = new DataInputStream(new FileInputStream(file)); int len = in.available(); int min = Integer.MAX_VALUE; int max = Integer.MIN_VALUE; byte[] bytes = new byte[len]; in.read(bytes); int val; for (int i = 0; i < len; i += 4) { val = ((bytes[i] & 0xff) << 24) + ((bytes[i + 1] & 0xff) << 16) + ((bytes[i + 2] & 0xff) << 8) + ((bytes[i + 3] & 0xff)); if (val > max) max = val; else if (val < min) min = val; } yRange.set(min, max); xRange.set(0, len / 4); } catch (Exception x) { x.printStackTrace(); } finally { try { if (in != null) in.close(); } catch (Exception x) { x.printStackTrace(); } } } /** * The thread that handles data reading. */ private class LoadThread extends Thread { RandomAccessFile rf = null; IlvDataTile tile; LoadThread() { super("TileLoadingThread"); } /** * Cancels the loading of a tile. */ void cancelLoading() { // The thread can be in a waiting state, so we interrupt it. // If it is not in such a state, the interrupt flag of the // thread will be set and we will be able to test it in the run method. interrupt(); if (rf != null) { // The thread can be blocked in an IO request (for example: reading). // To interrupt it, we close the file, which should throw an // IOException in the run method. try { rf.close(); } catch (Exception x) { x.printStackTrace(); } } } /** * Waits for tile loading requests and executes them. */ Override public void run() { while (true) { try { rf = null; tile = popTile(); if (tile == null) continue; if (DataTileLoader.this.getLag() > 0) sleep(DataTileLoader.this.getLag()); rf = new RandomAccessFile(DataTileLoader.this.file, "r"); IlvDataInterval itv = tile.getRange(); long start = (long) Math.floor(itv.getMin()); if (start < 0) continue; rf.seek(start * 4); int count = (int) itv.getLength() + 1; IlvDataPoints dataPts = new IlvDataPoints(count); int byteCount = count * 4; byte[] data = new byte[byteCount]; // For better performances, we read all the bytes at once // instead of using the readInt() method. rf.read(data); if (Thread.interrupted()) continue; int val; for (int i = 0; i < byteCount; i += 4) { val = ((data[i] & 0xff) << 24) | ((data[i + 1] & 0xff) << 16) | ((data[i + 2] & 0xff) << 8) | ((data[i + 3] & 0xff)); dataPts.add(start++, val); } if (Thread.interrupted()) continue; tile.setData(dataPts); dataPts.dispose(); tile.loadComplete(); tile = null; } catch (EOFException e) { System.err.println("End of file reached"); } catch (InterruptedException e) { // Can be because of cancelLoading. // e.printStackTrace(); } catch (IOException e) { // Can be because of cancelLoading. // e.printStackTrace(); } catch (Exception x) { x.printStackTrace(); } finally { try { if (rf != null) rf.close(); } catch (Exception x) { x.printStackTrace(); } } } } } /** * Pops the first tile from the queue. */ private synchronized IlvDataTile popTile() { try { while (tileQueue.size() == 0) wait(); // Put loading thread in waiting state. } catch (InterruptedException x) { return null; } return tileQueue.removeFirst(); } /** * Puts a tile at the end of the queue. */ private synchronized void pushTile(IlvDataTile tile) { tileQueue.addLast(tile); if (tileQueue.size() == 1) notify(); // Wake up loading thread. } /** * Loads the contents of the specified tile. This method appends the tile to * the tile queue. */ Override public synchronized void load(IlvDataTile tile) throws Exception { pushTile(tile); } /** * Invoked by the tile controller when a tile is released. * <p> * This method removes the tile from the tile queue, or cancels the loading if * the tile is currently being processed. */ Override public synchronized void release(IlvDataTile tile) { if (loadThread.tile == tile) { // The tile is currently processed by the loading thread. loadThread.cancelLoading(); } else { // Just remove the tile from the queue. tileQueue.remove(tile); } tile.setData(null); } /** * Specifies a fictional duration during which the loading thread sleeps, thus * simulating a lag that can occur when dealing with network connections. */ public void setLag(long lag) { this.lag = lag; } /** * Returns the lag. * * @see #setLag */ public final long getLag() { return lag; } /** * Returns the limits of the x values that will be provided by the loader. */ Override public IlvDataInterval getXRange() { return new IlvDataInterval(xRange); } /** * Returns the limits of the y values that will be provided by the loader. */ Override public IlvDataInterval getYRange() { return new IlvDataInterval(yRange); } }