//
// Util.java
//

/*
VisAD system for interactive analysis and visualization of numerical
data.  Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This library 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
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/

package visad.util;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.image.BufferedImage;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.util.Calendar;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.Group;
import javax.media.j3d.Node;
import javax.media.j3d.SceneGraphObject;
import javax.media.j3d.VirtualUniverse;

import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;

import ncsa.hdf.hdf5lib.H5;

import visad.ConstantMap;
import visad.Display;
import visad.DisplayImpl;
import visad.VisADException;

import visad.data.bio.LociForm;
import visad.data.mcidas.AreaForm;
import visad.data.mcidas.MapForm;

import com.sun.j3d.utils.universe.SimpleUniverse;

import java.util.logging.Formatter;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;


/**
 * A hodge-podge of general utility methods.
 */
public class Util {

  /**
   * Determine whether two numbers are roughly the same.
   *
   * @param a First number
   * @param b Second number
   * @param epsilon Absolute amount by which they can differ.
   *
   * @return true if they're approximately equal.
   */
  public static boolean isApproximatelyEqual(float a, float b,
                                             float epsilon) {
    // deal with NaNs first
    if (Float.isNaN(a)) {
      return Float.isNaN(b);
    }
    else if (Float.isNaN(b)) {
      return false;
    }

    if (Float.isInfinite(a)) {
      if (Float.isInfinite(b)) {
        return ((a < 0.0 && b < 0.0) || (a > 0.0 && b > 0.0));
      }

      return false;
    }
    else if (Float.isInfinite(b)) {
      return false;
    }

    if (Math.abs(a - b) < epsilon) {
      return true;
    }

    if (a == 0.0 || b == 0.0) {
      return false;
    }

    final float FLT_EPSILON = 1.192092896E-07F;

    return Math.abs(1 - a / b) < FLT_EPSILON;
  }

  /**
   * Determine whether two numbers are roughly the same.
   *
   * @param a First number
   * @param b Second number
   *
   * @return true if they're approximately equal.
   */
  public static boolean isApproximatelyEqual(float a, float b) {
    return isApproximatelyEqual(a, b, 0.00001);
  }

  /**
   * Determine whether two numbers are roughly the same.
   *
   * @param a First number
   * @param b Second number
   * @param epsilon Absolute amount by which they can differ.
   *
   * @return true if they're approximately equal.
   */
  public static boolean isApproximatelyEqual(double a, double b,
                                             double epsilon) {
    // deal with NaNs first
    if (Double.isNaN(a)) {
      return Double.isNaN(b);
    }
    else if (Double.isNaN(b)) {
      return false;
    }

    if (Double.isInfinite(a)) {
      if (Double.isInfinite(b)) {
        return ((a < 0.0 && b < 0.0) || (a > 0.0 && b > 0.0));
      }

      return false;
    }
    else if (Double.isInfinite(b)) {
      return false;
    }

    if (Math.abs(a - b) < epsilon) {
      return true;
    }

    if (a == 0.0 || b == 0.0) {
      return false;
    }

    final double DBL_EPSILON = 2.2204460492503131E-16;

    return Math.abs(1 - a / b) < DBL_EPSILON;
  }

  /**
   * Determine whether two numbers are roughly the same.
   *
   * @param a First number
   * @param b Second number
   *
   * @return true if they're approximately equal.
   */
  public static boolean isApproximatelyEqual(double a, double b) {
    return isApproximatelyEqual(a, b, 0.000000001);
  }

  /**
   * Return a string representation of VisAD's build date and time.
   *
   * @return VisAD build date and time
   */
  
  public static String getVersionDate() {
    try {
      InputStream is = Util.class.getResourceAsStream("/DATE");
      BufferedReader in = new BufferedReader(new InputStreamReader(is));
      String date = in.readLine();
      in.close();
      return date;
    }
    catch (IOException exc) {
      return null;
    }
  }

  /**
   * Print Java3D properties.
   * @param str Where to print properties.
   * @param canvas The Canvas3D to get properties from. If null Canvas3D
   *  properties are skipped.
   *
   * @see #printJ3DProperties(Canvas3D)
   */
  public static void printJ3DProperties(PrintStream str, Canvas3D canvas) {
    Map map = VirtualUniverse.getProperties();
    for (Object key : map.keySet()) {
      str.println(String.format("%s=%s", key, map.get(key)));
    }

    if (canvas == null) {
      canvas = new Canvas3D(SimpleUniverse.getPreferredConfiguration());
    }
    str.println("== Canvas3D properties ==");
    map = canvas.queryProperties();
    for (Object key : map.keySet()) {
      str.println(String.format("%s=%s", key, map.get(key).toString()));
    }
  }

  /**
   * Print Java3D global and Canvas3D properties to System.err.
   * @param canvas The Canvas3D to get properties from. If null Canvas3D
   *  properties are skipped.
   *
   * @see javax.media.j3d.VirtualUniverse#getProperties()
   * @see javax.media.j3d.Canvas3D#queryProperties()
   */
  public static void printJ3DProperties(Canvas3D canvas) {
    printJ3DProperties(System.err, canvas);
  }

  /**
   * 
   *
   * @param node 
   */
  public static void printSceneGraph(Node node) {
    if (node == null) return;
    printSceneGraph(node, 0);
  }

  /**
   * 
   *
   * @param node 
   * @param lvl 
   */
  private static void printSceneGraph(Node node, int lvl) {
    StringBuffer buf = new StringBuffer();
    for (int i = 0; i < lvl; i++) {
      buf.append("    ");
    }
    lvl++;
    System.err.println(buf.toString() + node.toString());
    if (node instanceof Group) {
      Group group = (Group)node;
      Enumeration children = group.getAllChildren();
      while(children.hasMoreElements()) {
        printSceneGraph((Node)children.nextElement(), lvl);
      }
    }
  }

  // Map
  //private static final Map SGO_NAMES = new HashMap();
  private static final Method SGO_GET_NAME;
  private static final Method SGO_SET_NAME;
  static {
    Method sgoGetName = null, sgoSetName = null;
    try {
      sgoGetName = SceneGraphObject.class.getDeclaredMethod("getName",
        new Class[0]);
      sgoSetName = SceneGraphObject.class.getDeclaredMethod("setName",
        new Class[] {String.class});
    }
    catch (SecurityException e) {
    }
    catch (NoSuchMethodException e) {
    }
    SGO_GET_NAME = sgoGetName;
    SGO_SET_NAME = sgoSetName;
  }

  /**
   * Gets the name of the given {@link SceneGraphObject}.
   *
   * This method exists to avoid a compile-time
   * dependency on Java3D 1.4+.
   */
  public static String getName(SceneGraphObject obj) {
    if (SGO_GET_NAME != null) {
      try {
        return (String) SGO_GET_NAME.invoke(obj);
      }
      catch (IllegalAccessException exc) {
      }
      catch (InvocationTargetException exc) {
      }
    }
    else {
      // no SceneGraphObject.getName method; retrieve name from Map instead
      //return (String) SGO_NAMES.get(obj);
    }
    return null;
  }

  /**
   * Sets the name of the given {@link SceneGraphObject}.
   *
   * This method exists to avoid a compile-time
   * dependency on Java3D 1.4+.
   */
  public static void setName(SceneGraphObject obj, String name) {
    if (SGO_SET_NAME != null) {
      try {
        SGO_SET_NAME.invoke(obj, name);
      }
      catch (IllegalAccessException exc) {
      }
      catch (InvocationTargetException exc) {
      }
    }
    else {
      // no SceneGraphObject.setName method; save name to Map instead
      //SGO_NAMES.put(obj, name);
    }
  }

  /**
   * Convert 10-bit data to 2-byte data
   * @param input
   * @param output
   * @return 0 if no errors
   */
  public static int tenBitToTwoByte(byte [] input, short [] output) {

	  int total = output.length;
	  int index = 0;
	  int temp = 0;
	  int skip = 0;
	  int outputIndex = 0;

	  index = skip / 8;
	  skip = skip % 8;
	  //input = (unsigned char *) inp;

	  while (total > 0)
	  {
		  total--;

		  /*
		   * enumerated to avoid the more general need
		   * to always access 3 bytes
		   * which in reality is needed only for case 7
		   */
		  switch (skip)
		  {
		  case 0:
			  temp = 4 * (int) (input[index] & 0xFF) +  (int) (input[index + 1] & 0xFF) / 64;
			  break;
		  case 1:
			  temp = 8 * (int) (input[index] & 0xFF) +  (int) (input[index + 1] & 0xFF) / 32;
			  break;
		  case 2:
			  temp = 16 * (int) (input[index] & 0xFF) +  (int) (input[index + 1] & 0xFF) / 16;
			  break;
		  case 3:
			  temp = 32 * (int) (input[index] & 0xFF) +  (int) (input[index + 1] & 0xFF) / 8;
			  break;
		  case 4:
			  temp = 64 * (int) (input[index] & 0xFF) +  (int) (input[index + 1] & 0xFF) / 4;
			  break;
		  case 5:
			  temp = 128 * (int) (input[index] & 0xFF) +  (int) (input[index + 1] & 0xFF) / 2;
			  break;
		  case 6:
			  temp = 256 * (int) (input[index] & 0xFF) +  (int) (input[index + 1] & 0xFF);
			  break;
		  case 7:
			  temp = 512 *(1& (int) (input[index] & 0xFF)) + 2 * (int) (input[index + 1] & 0xFF)
			                                        + ( (int) (input[index + 2] & 0xFF) > 127 ? 1 : 0);
			  break;
		  }

		  output[outputIndex] = (short) (temp & 0x3ff);
		  outputIndex++;

		  /*
		   * these two statements together increment 10 bits on the input
		   */
		  index++;
		  skip += 2;

		  /*
		   * now normalize skip
		   */
		  if (skip > 7)
		  {
			  index++;
			  skip -= 8;
		  }
	  }

	  return 0;
  }
  
  /**
   * Return a string representation of the current date and time.
   *
   * @return string timestamp for current time
   */
  
  public static String getTimestamp() {
    StringBuffer sb = new StringBuffer();
    Calendar cal = Calendar.getInstance();
    int year = cal.get(Calendar.YEAR);
    int month = cal.get(Calendar.MONTH);
    int day = cal.get(Calendar.DAY_OF_MONTH);
    int hour = cal.get(Calendar.HOUR_OF_DAY);
    int min = cal.get(Calendar.MINUTE);
    int sec = cal.get(Calendar.SECOND);
    int milli = cal.get(Calendar.MILLISECOND);
    sb.append(year);
    sb.append("/");
    if (month < 9) sb.append("0");
    sb.append(month + 1);
    sb.append("/");
    if (day < 10) sb.append("0");
    sb.append(day);
    sb.append(", ");
    if (hour < 10) sb.append("0");
    sb.append(hour);
    sb.append(":");
    if (min < 10) sb.append("0");
    sb.append(min);
    sb.append(":");
    if (sec < 10) sb.append("0");
    sb.append(sec);
    sb.append(".");
    if (milli < 100) sb.append("0");
    if (milli < 10) sb.append("0");
    sb.append(milli);
    return sb.toString();
  }

  /**
   * Return a JFileChooser that recognizes supported VisAD file types.
   *
   * @return file chooser which recognizes supported VisAD file types
   */
  
  public static JFileChooser getVisADFileChooser() {
    JFileChooser dialog = new JFileChooser(System.getProperty("user.dir"));
    Vector filters = new Vector();
    boolean jai = canDoJAI();

    // Amanda F2000 - amanda/F2000Form
    FileFilter f2000 = new ExtensionFileFilter("r", "Amanda F2000");
    filters.add(f2000);

    // ASCII text - text/TextForm
    FileFilter text = new ExtensionFileFilter(new String[] {"csv", "tsv",
            "bsv", "txt"}, "ASCII text");
    filters.add(text);

    // DEM - gis/DemFamily
    FileFilter dem = new ExtensionFileFilter("dem",
                                             "Digital Elevation Model");
    filters.add(dem);

    // FITS - fits/FitsForm
    FileFilter fits = new ExtensionFileFilter("fits",
                        "Flexible Image Transport System");
    filters.add(fits);

    // FlashPix - jai/JAIForm
    if (jai) {
      FileFilter flashpix = new ExtensionFileFilter("flashpix", "FlashPix");
      filters.add(flashpix);
    }

    // HDF-5 - hdf5/HDF5Form
    if (canDoHDF5()) {
      FileFilter hdf5 = new ExtensionFileFilter(new String[] {"hdf", "hdf5"},
                          "HDF-5");
      filters.add(hdf5);
    }

    // HDF-EOS - hdfeos/HdfeosForm
    FileFilter hdfeos = new ExtensionFileFilter(new String[] {"hdf",
            "hdfeos"}, "HDF-EOS");
    filters.add(hdfeos);

    // McIDAS area - mcidas/AreaForm
    FormFileFilter mcidasArea = new FormFileFilter(new AreaForm(),
                                  "McIDAS area (AREA*, *area)");
    filters.add(mcidasArea);

    // McIDAS map - mcidas/MapForm
    FormFileFilter mcidasMap = new FormFileFilter(new MapForm(),
                                 "McIDAS map (OUTL*)");
    filters.add(mcidasMap);

    // netCDF - netcdf/Plain
    FileFilter netcdf = new ExtensionFileFilter("nc", "NetCDF");
    filters.add(netcdf);

    // PNM - jai/JAIForm
    if (jai) {
      FileFilter pnm = new ExtensionFileFilter("pnm", "PNM");
      filters.add(pnm);
    }

    // VisAD binary/serialized - visad/VisADForm
    FileFilter visad = new ExtensionFileFilter("vad",
                         "Binary or serialized VisAD");
    filters.add(visad);

    // Vis5D - vis5d/Vis5DForm
    FileFilter vis5d = new ExtensionFileFilter("v5d", "Vis5D");
    filters.add(vis5d);

    // biology-related formats - LociForm
    FileFilter[] lociFilters = new LociForm().getReaderFilters();
    for (int i = 0; i < lociFilters.length; i++)
      filters.add(lociFilters[i]);

    // sort and combine filters alphanumerically
    FileFilter[] ff = ComboFileFilter.sortFilters(filters);

    // combination filter
    FileFilter combo = new ComboFileFilter(ff, "All VisAD file types");

    // add filters to chooser
    dialog.addChoosableFileFilter(combo);
    for (int i = 0; i < ff.length; i++)
      dialog.addChoosableFileFilter(ff[i]);
    dialog.setFileFilter(combo);

    return dialog;
  }

  /**
   * Limit the given text field to one line in height.
   *
   * @param field 
   */
  public static void adjustTextField(JTextField field) {
    Dimension msize = field.getMaximumSize();
    Dimension psize = field.getPreferredSize();
    msize.height = psize.height;
    field.setMaximumSize(msize);
  }

  /**
   * Limit the given combo box to one line in height.
   *
   * @param combo 
   */
  public static void adjustComboBox(JComboBox combo) {
    Dimension msize = combo.getMaximumSize();
    Dimension psize = combo.getPreferredSize();
    msize.height = psize.height;
    combo.setMaximumSize(msize);
  }

  /**
   * Center the given window on the screen.
   *
   * @param window 
   */
  public static void centerWindow(Window window) {
    Dimension s = Toolkit.getDefaultToolkit().getScreenSize();
    Dimension w = window.getSize();
    window.setLocation((s.width - w.width) / 2, (s.height - w.height) / 2);
    int x = (s.width - w.width) / 2;
    int y = (s.height - w.height) / 2;
    if (x < 0) x = 0;
    if (y < 0) y = 0;
    window.setLocation(x, y);
  }

  /**
   * Center the given window within the specified parent window.
   *
   * @param parent 
   * @param window 
   */
  public static void centerWindow(Window parent, Window window) {
    Point loc = parent.getLocation();
    Dimension p = parent.getSize();
    Dimension w = window.getSize();
    int x = loc.x + (p.width - w.width) / 2;
    int y = loc.y + (p.height - w.height) / 2;
    if (x < 0) x = 0;
    if (y < 0) y = 0;
    window.setLocation(x, y);
  }

  /**
   * Test whether HDF-5 native code is present in this JVM.
   * @return true if found, otherwise false
   */
  public static boolean canDoHDF5() {
    boolean success = false;
    try {
      H5.J2C(0); // HDF-5 call initializes HDF-5 native library
      success = true;
    }
    catch (NoClassDefFoundError err) {
    }
    catch (UnsatisfiedLinkError err) {
    }
    catch (Exception exc) {
    }
    return success;
  }

  /**
   * Test whether ImageJ is present in this JVM.
   * @return true if found, otherwise false
   */
  public static boolean canDoImageJ() {
    return canDoClass("ij.IJ") != null;
  }

  /**
   * Test whether javax.imageio can write JPEGs.
   * @return true if found, otherwise false
   */
  public static boolean canDoJPEG() {
    Iterator iter = ImageIO.getImageWritersByFormatName("jpeg");
    return iter.hasNext();
  }

  /**
   * Test whether Java Advanced Imaging is present in this JVM.
   * @return true if found, otherwise false
   */
  public static boolean canDoJAI() {
    return canDoClass("javax.media.jai.JAI") != null;
  }

  /**
   * Test whether Jython is present in this JVM.
   * @return true if found, otherwise false
   */
  public static boolean canDoPython() {
    return canDoClass("org.python.util.PythonInterpreter") != null;
  }

  /**
   * Test whether QuickTime for Java is present in this JVM.
   * @return true if found, otherwise false
   */
  public static boolean canDoQuickTime() {
    return canDoClass("quicktime.QTSession") != null;
  }

  /**
   * Test whether Java3D is present in this JVM.
   * @return true if found, otherwise false
   */
  public static boolean canDoJava3D() {
    return canDoJava3D("1.0");
  }

  /**
   * Check to see if the version of Java3D being used is compatible
   * with the desired specification version.
   * @param version   version to check.  Needs to conform to the dotted format
   *                  of specification version numbers (e.g., 1.2)
   * @return true if the Java3D version being used is greater than or
   *         equal to the desired version number
   */
  public static boolean canDoJava3D(String version) {
    Class testClass = canDoClass("javax.vecmath.Point3d");
    boolean b = (testClass != null)
                ? (canDoClass("javax.media.j3d.SceneGraphObject") != null)
                : false;
    if (b) {
      Package p = testClass.getPackage();
      if (p != null) {
        try {
          b = p.isCompatibleWith(version);
        }
        catch (NumberFormatException nfe) {
          b = false;
        }
      }
    }
    return b;
  }

  /**
   * General classloader tester.
   * @param classname  name of class to test
   * @return  the class or null if class can't be loaded.
   */
  private static Class canDoClass(String classname) {
    Class c = null;
    try {
      c = Class.forName(classname);
    }
    catch (ClassNotFoundException exc) {
    }
    return c;
  }

  /**
   * Capture a DisplayImpl into a JPEG file
   *
   * @param display the DisplayImpl to capture
   * @param filename the name of the file to write into
   *
   */
  public static void captureDisplay(DisplayImpl display, String filename) {
    captureDisplay(display, filename, false);
  }

  /**
   * Capture a DisplayImpl into a JPEG file
   *
   * @param display the DisplayImpl to capture
   * @param filename the name of the file to write into
   * @param sync ensure the display is "done" if true
   *
   */
  public static void captureDisplay(DisplayImpl display, String filename,
                                    boolean sync) {
    final DisplayImpl disp = display;
    final File fn = new File(filename);
    final boolean wait = sync;

    Runnable savedisp = new Runnable() {
      public void run() {
        BufferedImage image = disp.getImage(wait);
        try {
            Iterator iter = ImageIO.getImageWritersByFormatName("jpeg");
            ImageWriter writer = iter.next();
            ImageWriteParam param = writer.getDefaultWriteParam();
            param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            param.setCompressionQuality(1.0f);
            FileOutputStream fout = new FileOutputStream(fn);
            writer.setOutput(fout);
            IIOImage iio = new IIOImage(image, null, null);
            writer.write(null, iio, param);
          fout.close();
        }
        catch (Exception err) {
          System.err.println("Error whilst saving JPEG: " + err);
        }
      }
    };
    Thread t = new Thread(savedisp);
    t.start();
  }

  /**
   * Tests whether two arrays are component-wise equal.
   *
   * @param o1 
   * @param o2 
   *
   * @return true if the arrays are equal
   */
  
  public static boolean arraysEqual(Object[] o1, Object[] o2) {
    // test for null
    if (o1 == null && o2 == null) return true;
    if (o1 == null || o2 == null) return false;

    // test for differing lengths
    if (o1.length != o2.length) return false;

    // test each component
    for (int i = 0; i < o1.length; i++) {
      //if (!o1[i].equals(o2[i])) return false;
      Object a1 = o1[i];
      Object a2 = o2[i];
      if (!(a1 == null
            ? a2 == null
            : a1.equals(a2))) return false;
    }
    return true;
  }


  /**
   * Create a string representation of the given array
   *
   * @param prefix 
   * @param array  array to print
   *
   */
  
  public static void printArray(String prefix, Object[] array) {
    StringBuffer buf = new StringBuffer();
    buf.append(prefix);
    buf.append(": ");
    if (array == null) {
      buf.append(" null ");
    }
    else {
      for (int i = 0; i < array.length; i++) {
        buf.append("[");
        buf.append(i);
        buf.append("]: ");
        buf.append((array[i] == null)
                   ? "null"
                   : array[i]);
        buf.append(" ");
      }
    }
    System.out.println(buf.toString());
  }

  /**
   * Print out the values in a double array.
   *
   * @param prefix  prefix string
   * @param array  array to print
   */
  public static void printArray(String prefix, double[] array) {
    StringBuffer buf = new StringBuffer();
    buf.append(prefix);
    buf.append(": ");
    if (array == null) {
      buf.append(" null ");
    }
    else {
      for (int i = 0; i < array.length; i++) {
        buf.append("[");
        buf.append(i);
        buf.append("]: ");
        buf.append(array[i]);
        buf.append(" ");
      }
    }
    System.out.println(buf.toString());
  }

  /**
   * Executes the given Runnable object with the Swing event handling thread.
   *
   * @param wait true if method should block until Runnable code
   *             finishes execution.
   * @param r Runnable object to execute using the event handling thread.
   */
  public static void invoke(boolean wait, Runnable r) {
    invoke(wait, false, r);
  }

  /**
   * Executes the given Runnable object with the Swing event handling thread.
   *
   * @param wait true if method should block until Runnable code
   *             finishes execution.
   * @param printStackTraces true if the stack trace for
   *                         any exception should be printed.
   * @param r Runnable object to execute using the event handling thread.
   */
  public static void invoke(boolean wait, boolean printStackTraces,
                            Runnable r) {
    if (wait) {
      // use invokeAndWait
      if (Thread.currentThread().getName().startsWith("AWT-EventQueue")) {
        // current thread is the AWT event queue thread; just execute the code
        r.run();
      }
      else {
        // execute the code with the AWT event thread
        try {
          SwingUtilities.invokeAndWait(r);
        }
        catch (InterruptedException exc) {
          if (printStackTraces) exc.printStackTrace();
        }
        catch (InvocationTargetException exc) {
          if (printStackTraces) exc.getTargetException().printStackTrace();
        }
      }
    }
    else {
      // use invokeLater
      SwingUtilities.invokeLater(r);
    }
  }

  /**
   * Create a ConstantMap array of colors for use with
   * {@link Display#addReference(DataReference, ConstantMap[])}
   *
   * @param color color to encode
   *
   * @return an array containing either 3 colors or, if the color
   *         parameter included an alpha component, a 4 element array
   *         with 3 colors and an @{link Display.Alpha alpha} component.
   *
   * @throws VisADException 
   */
  public static ConstantMap[] getColorMaps(Color color)
          throws VisADException {
    final int alpha = color.getAlpha();

    ConstantMap[] maps = new ConstantMap[alpha == 255
                                         ? 3
                                         : 4];

    maps[0] = new ConstantMap((float)color.getRed() / 255.0f, Display.Red);
    maps[1] = new ConstantMap((float)color.getGreen() / 255.0f,
                              Display.Green);
    maps[2] = new ConstantMap((float)color.getBlue() / 255.0f, Display.Blue);
    if (alpha != 255) {
      maps[3] = new ConstantMap((float)color.getAlpha() / 255f,
                                Display.Alpha);
    }

    return maps;
  }

  /**
   * Configure basic logging for the visad package. In a production
   * environment the preferred way to configure logging is using the
   * logging.properties file. This is intended only as a convenience method
   * for configuring console logging for the purposes of testing.
   *
   * @param verbosity 0 is Level.WARNING and progresses to a
   *  maximum of Level.ALL.
   * @param pkg Name of the java package to configure logging for.
   * @return The Level logging was set to.
   */
  public static Level configureLogging(int verbosity, String pkg) {
    Level lvl = Level.WARNING;
    switch (verbosity) {
      case 1:
        lvl = Level.INFO;
        break;
      case 2:
        lvl = Level.FINE;
        break;
      case 3:
        lvl = Level.FINER;
        break;
      case 4:
        lvl = Level.FINEST;
        break;
      case 5:
        lvl = Level.ALL;
        break;
      default:
        lvl = Level.WARNING;
    }
    Logger logger = Logger.getLogger(pkg);
    logger.setLevel(lvl);
    logger.setUseParentHandlers(false);
    Handler console = new ConsoleHandler();
    console.setLevel(lvl);
    console.setFormatter(new Formatter() {
      public String format(LogRecord r) {
        return String.format(
                 "[%s] %s\n", r.getLevel().getName(), r.getMessage());
      }
    });
    logger.addHandler(console);
    return lvl;
  }

  /**
   * @see #configureLogging(int, java.lang.String)
   *
   * @param verbosity 
   *
   * @return logging level
   */
  
  public static Level configureLogging(int verbosity) {
    return configureLogging(verbosity, "visad");
  }


  /**
   * Utility method to return the stack trace
   *
   * @return The stack trace
   */
  public static String getStackTrace() {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    (new IllegalArgumentException("stack trace")).printStackTrace(
      new PrintStream(baos));
    return baos.toString();
  }


  /**
   * do a deep clone 
   *
   * @param input  the array
   *
   * @return  cloned array
   */
  public static float[][] clone(float[][] input) {
    float[][] output = (float[][])input.clone();
    for (int i = 0; i < input.length; i++) {
      output[i] = (float[])input[i].clone();
    }
    return output;
  }


  /**
   * do a deep clone 
   *
   * @param input  the array
   *
   * @return  cloned array
   */
  public static double[][] clone(double[][] input) {
    double[][] output = (double[][])input.clone();
    for (int i = 0; i < input.length; i++) {
      output[i] = (double[])input[i].clone();
    }
    return output;
  }

	/**
	 * Converts an (unsigned) byte to an unsigned int. 
	 * Since Java doesn't have an unsigned
	 * byte type, this requires some foolery.
	 * This solution based on information and code from
	 * http://www.rgagnon.com/javadetails/java-0026.html
	 * @param s The unsigned short to convert
	 * @return the unsigned int equivalent
	 */
	
	public static int unsignedShortToInt(short s) {
		return (int) s & 0xFFFF;
	}

	/**
	 * Converts an (unsigned) byte to an unsigned int. 
	 * Since Java doesn't have an unsigned
	 * byte type, this requires some foolery.
	 * This solution based on information and code from
	 * http://www.rgagnon.com/javadetails/java-0026.html
	 * @param b The unsigned byte to convert
	 * @return the unsigned int equivalent
	 */
	
	public static int unsignedByteToInt(byte b) {
		return (int) b & 0xFF;
	}
	
	/**
	 * Converts an (unsigned) byte to an unsigned long.
	 * Since Java doesn't have an unsigned
	 * byte type, this requires some foolery.
	 * This solution based on information and code from
	 * http://www.rgagnon.com/javadetails/java-0026.html
	 * @param b The unsigned byte to convert
	 * @return the unsigned long equivalent
	 */
	public static long unsignedByteToLong(byte b) {
		return (long) b & 0xFF;
	}

}


 

Ads help maintain this website.