//Warning: This code has been marked up for HTML

/****************************************************************************
  $Workfile: NSIBrowserFrame.java $
  $Revision: 1 $
  $Modtime:: $
  $Copyright:

  Copyright (c) 1997 Novell, Inc.  All Rights Reserved.

  THIS WORK IS  SUBJECT  TO  U.S.  AND  INTERNATIONAL  COPYRIGHT  LAWS  AND
  TREATIES.   NO  PART  OF  THIS  WORK MAY BE  USED,  PRACTICED,  PERFORMED
  COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED,  ABRIDGED, CONDENSED,
  EXPANDED,  COLLECTED,  COMPILED,  LINKED,  RECAST, TRANSFORMED OR ADAPTED
  WITHOUT THE PRIOR WRITTEN CONSENT OF NOVELL, INC. ANY USE OR EXPLOITATION
  OF THIS WORK WITHOUT AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO
  CRIMINAL AND CIVIL LIABILITY.$

 ***************************************************************************/

import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.net.*;
import java.util.*;
import java.io.*;

import javax.naming.*;
import javax.naming.directory.*;

/**
 * This class is responsible for displaying, processing and updating the
 * program's one or more browser window(s) and its components.
 *
 * The components of the main window include the following:
 *   title bar - displays the application's name and version
 *   menu bar - provides structure of main- and sub-menus
 *   current path area - shows the current path
 *   name list area - shows the object available at the current path
 *   initial context area - displays the initial context setting
 */

public class NSIBrowserFrame extends Frame
                             implements WindowListener,
                                        ActionListener,
                                        ItemListener
{
   public static final int width = 420;
   public static final int height = 400;

   NSIBrowserFrame parent;      // used for sub-browser frames

   Properties props;

   Context initCtx;
   CompositeName currName;   // constructed in resetInitCtx

   Context currCtx;             // current context (relative to initCtx)

   Vector currCtxVector = new Vector ();

   Label initCtxLabel;          // component for showing init context impl

   Label currentPathLabel;      // component for showing current path


   List nameList;               // component for showing name list

   Vector nameListVector;       // parallel vector of names only


  // components for checkbox menu items

   CheckboxMenuItem listClassesItem = new CheckboxMenuItem ("List Classes");
   CheckboxMenuItem listBindingsItem = new CheckboxMenuItem ("List Bindings");
   public boolean doListClasses = false;
   public boolean doListBindings = false;

   Button attrButton;
   Button lookupButton;
   Button specialButton;

   static final String jndiInitCtxPropShort = "Init Ctx:";

   /**
    * Constructs the browser's main window (frame) and its components.
    *
    * @param     title           (in) Specifies the title for the main window
    */
   NSIBrowserFrame (
      String title,
      NSIBrowserFrame parent,
      Properties props)
   {
      super (title);
      this.parent = parent;
      this.props = props;

      if (null != parent)
      { // We're going to use our parent's current context to start

         currCtx = parent.currCtx;
         initCtx = currCtx;
         currName = new CompositeName ();
      }

     // Construct the main menu bar

      MenuBar mainMenu = new MenuBar ();

     // Add this frame into the listener list for the CheckBoxMenuItems


      listClassesItem.addItemListener(this);
      listBindingsItem.addItemListener(this);

     // Construct and add the file menu

      Menu file = new Menu ("File");
      file.add ("Exit");
      file.addActionListener(this);
      mainMenu.add (file);

     // Construct and add the search menu

      Menu find = new Menu ("Find");
      find.add ("Lookup...");
      find.addActionListener(this);
//      find.add ("Search");

      mainMenu.add (find);

     // Construct and add the options menu

      Menu options = new Menu ("Options");
      options.add (listClassesItem);
      options.add (listBindingsItem);
      options.add ("-");
      options.add ("Initial Context...");
      if (null == parent)    // only for the root frame

         options.add ("Save Settings");
      options.addActionListener(this);
      mainMenu.add (options);

     // Construct and add the help menu

      Menu help = new Menu ("Help");
      help.add ("About...");
      help.addActionListener(this);
      mainMenu.add (help);

     // Set the window's menu bar to the one we just constructed

      setMenuBar (mainMenu);

      attrButton = new Button ("Attributes");
      attrButton.addActionListener(this);
      lookupButton = new Button ("Lookup");
      lookupButton.addActionListener(this);
      specialButton = new Button ("Special");
      specialButton.addActionListener(this);

     // Construct and add the name list display area

      Panel listPanel = new Panel ();
      listPanel.setLayout (new BorderLayout ());
      nameList = new List ();
      nameList.addActionListener(this);
      nameListVector = new Vector ();
      nameList.setMultipleMode (false);
      listPanel.add ("Center", nameList);
      Panel buttonPanel = new Panel ();
      buttonPanel.setLayout (new GridLayout (1, 0));
      buttonPanel.add (attrButton);
      buttonPanel.add (lookupButton);
      buttonPanel.add (specialButton);
      listPanel.add ("South", buttonPanel);
      add ("Center", listPanel);

     // Add the current path line

      currentPathLabel = new Label ();
      add ("North", currentPathLabel);

     // Add the status line

      initCtxLabel = new Label ();
      add ("South", initCtxLabel);

     // Make sure we get notified of window events

      addWindowListener(this);

     // Get rid of wasted space before the frame gets displayed

      pack ();

   }// NSIBrowserFrame


   /**
    * This is the action handler for the main window (frame).
    *
    * @param     evt             (in) The event
    */
   public void actionPerformed (
      ActionEvent evt)
   {
      Frame f;
      Object what = evt.getSource ();

     // Handle any menu item selections

      if ("Exit".equals (evt.getActionCommand ()))
      {
        // Close the window and terminate the application

         dispose ();
         if (null == parent)    // only the main window can exit

            System.exit (0);
      }
      else if ("Lookup...".equals (evt.getActionCommand ()))
      {
         f = new NSILookupFrame (this, initCtx, currCtx);
         f.setSize (NSILookupFrame.width, NSILookupFrame.height);
         f.show ();
      }
      else if ("Initial Context...".equals (evt.getActionCommand ()))
      {
         f = new NSIInitialContextFrame (this);
         f.setSize (
            NSIInitialContextFrame.width,
            NSIInitialContextFrame.height);
         f.show ();
      }
      else if ("Save Settings".equals (evt.getActionCommand ()))
      {
         NSIBrowser.saveConfigFile ();
      }
      else if ("About...".equals (evt.getActionCommand ()))
      {
         new NSIMessageBox ("About", "NSIBrowser by Novell Inc.");
      }

     // Handle Buttons

      else if (what == attrButton)
      {
         showAttributes ();
      }
      else if (what == lookupButton)
      {
         lookupContext ();
      }
      else if (what == specialButton)
      {
         callSpecialHandler ();
      }

     // Handle events in the listbox

      else if (what == nameList)
      {
         int itemIndex = nameList.getSelectedIndex ();
         if (-1 != itemIndex)
         {
            SortableString sortStr;
            String atomicName;

            sortStr = (SortableString) nameListVector.elementAt (itemIndex);
            atomicName = sortStr.getKeyStr ();
            Context tempCtx;

            if (atomicName.equals (".."))
            { // We need to back up one context on the 'stack'

               try
               {
                  currName.remove (
                        currName.size () - 1);
               }
               catch (InvalidNameException e)
               {
                  System.out.println ("Unexpected naming exception " + e);
                  e.printStackTrace ();
               }

              // POP the currCtx off the 'stack'

               int stackSize = currCtxVector.size ();
               currCtx = (Context) currCtxVector.elementAt (stackSize - 1);
               currCtxVector.removeElementAt (stackSize - 1);
            }
            else
            { // We can walk this down one with a simple lookup

               try
               {
                  currName.add (atomicName);
                 // PUSH the currCtx onto the 'stack'

                  currCtxVector.addElement (currCtx);

                  tempCtx = (Context) currCtx.lookup (atomicName);
                  currCtx = tempCtx;  // If things went ok, keep it

               }
               catch (NameNotFoundException e)
               { // This could be a next naming system problem

                  try
                  { // Try again with a leading slash for federation

                     tempCtx = (Context) currCtx.lookup ("/" + atomicName);
                     currCtx = tempCtx;
                  }
                  catch (Exception e2)
                  {
                     System.out.println ("Unable to update current context");
                     System.out.println ("Unexpected exception " + e2);
                     e2.printStackTrace ();
                  }
               }
               catch (NamingException e)
               {
                  System.out.println ("Unexpected naming exception " + e);
                  e.printStackTrace ();
               }
            }
            updateList ();
         }
      }
   }// action ()



   public void itemStateChanged(
      ItemEvent evt)
   {
      Object what = evt.getItem ();

      if ("List Classes".equals (what))
      {
         doListClasses = ! doListClasses;
         doListBindings = false;
         listClassesItem.setState (doListClasses);
         listBindingsItem.setState (doListBindings);
         updateList ();
      }
      else if ("List Bindings".equals (what))
      {
         doListBindings = ! doListBindings;
         doListClasses = false;
         listClassesItem.setState (doListClasses);
         listBindingsItem.setState (doListBindings);
         updateList ();
      }
   }

   /**
    * Implement WindowListener functions.  We really only want the
    * windowClosing one.
    */

   public void windowClosed (
      WindowEvent event)
   {
   }

   public void windowDeiconified (
      WindowEvent event)
   {
   }

   public void windowIconified (
      WindowEvent event)
   {
   }

   public void windowActivated (
      WindowEvent event)
   {
   }

   public void windowDeactivated (
      WindowEvent event)
   {
   }

   public void windowOpened (
      WindowEvent event)
   {
   }

   public void windowClosing (
      WindowEvent event)
   {
      dispose ();
      if (null == parent) // only the main window can exit

         System.exit (0);
   }

   /**
    * Set the properties for the browser frame.
    *
    * @param     props             (in) Properties to be set.
    */
   public void setProperties (
      Properties props)
   {
      this.props = props;
   }

   /**
    * Updates the name list component using the initial context and the
    * current name using the initCtx and currName variables.
    */
   public synchronized void updateList ()
   {
      int sortStart = 0;
      int lastElement;
      SortableString sortStr;

      if (null == currCtx)
         throw new NullPointerException ("currCtx");

      this.setCursor (Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));   // We're probably going to be a while


     // Clear out any remaining entries in our internal list

     //  leave the visual one until later

      nameListVector.removeAllElements ();

     // Update the current path area

      if (0 != currName.size ())
         currentPathLabel.setText (currName.toString ());
      else
         currentPathLabel.setText ("[root]");

     // Add a '..' entry for everything but the root context

      if (0 != currName.size ())
      {
         sortStart = 1;// Make sure we skip sorting the .. out of place

         nameListVector.addElement (new SortableString (".."));
      }

      try
      {
         if (true == doListBindings)
         {
           // Enumerate the subordinates and add them into the name list

            NamingEnumeration bindingEnum;
            Binding binding;

           // Do the default/current name system first

            bindingEnum = currCtx.listBindings ("");
            while (true == bindingEnum.hasMoreElements ())
            {
               binding = (Binding) bindingEnum.next ();
               sortStr = new SortableString (
                                 binding.getName (),
                                 binding.getClassName ());
               nameListVector.addElement (sortStr);
            }
           // List the next name system (if there is one) next

            try
            { // Not everyone has a next naming system

               bindingEnum = currCtx.listBindings ("/");
               while (true == bindingEnum.hasMoreElements ())
               {
                  binding = (Binding) bindingEnum.next ();
                  sortStr = new SortableString (
                                    binding.getName (),
                                    binding.getClassName ());
                  nameListVector.addElement (sortStr);
               }
            }
            catch (NameNotFoundException e)
            { // We expect this to happen fairly often so just skip it

System.out.println ("Next naming system not supported (Bindings)");
            }
         }// if (true == doListBindings)

         else
         {
           // Enumerate the subordinates and add them into the name list

            NamingEnumeration ncEnum;
            NameClassPair ncPair;

           // Do the default/current name system first

            ncEnum = currCtx.list ("");
            while (true == ncEnum.hasMoreElements ())
            {
               ncPair = (NameClassPair) ncEnum.next ();
               if (doListClasses)
                  sortStr = new SortableString (
                                    ncPair.getName (),
                                    ncPair.getClassName ());
               else
                  sortStr = new SortableString (ncPair.getName ());
               nameListVector.addElement (sortStr);
            }
           // List the next name system (if there is one) next

            try
            { // Not everyone has a next naming system

               ncEnum = currCtx.list ("/");
               if (null == ncEnum)
                  throw new NameNotFoundException ("/");
               while (true == ncEnum.hasMoreElements ())
               {
                  ncPair = (NameClassPair) ncEnum.next ();
                  if (doListClasses)
                     sortStr = new SortableString (
                                       ncPair.getName (),
                                       ncPair.getClassName ());
                  else
                     sortStr = new SortableString (ncPair.getName ());
                  nameListVector.addElement (sortStr);
               }
            }
            catch (NameNotFoundException e)
            { // We expect this to happen fairly often so just skip it

System.out.println ("Next naming system not supported (NameClassPairs)");
            }
         }
      }// try (the big one)

      catch (NamingException e)
      {
         System.out.println ("Unable to update name list");
         System.out.println ("Unexpected naming exception : " + e);
         e.printStackTrace ();
      }

     // Now we can sort the internal list

      lastElement = nameListVector.size() - 1;
      QuickSort.sort(nameListVector, sortStart, lastElement);

     // Now begin updating the visual list

     nameList.setVisible(false);
      nameList.removeAll ();
     nameList.setVisible(true);
      for (int i = 0; i <= lastElement; i++)
      {
         sortStr = (SortableString) nameListVector.elementAt (i);
         nameList.addItem (sortStr.toString ());  // Display key/extra

      }
      this.setCursor (Cursor.getDefaultCursor());   // Finally, restore the cursor

   }// updateList ()


   /**
    * Updates the initial context variable initCtx using the new initial
    * context as specified in the system properties.
    */
   public void resetInitCtx ()
   {
      String initPropVal;

      initCtx = null;        // Throw away the old one

      currCtx = null;
      currName = null;

      currName = new CompositeName ();

     // Now see if we can allocate an initial context

      initPropVal = props.getProperty (NSIBrowser.jndiInitCtxProp);
      if ((null != initPropVal) && (false == initPropVal.equals ("")))
      {
         try
     {
           // We could have an initial context, but we don't so go get one

            initCtx = new InitialContext ( props );
     }
     catch (NamingException e)
     {
       // Ignore for now

     }
      }
      currCtx = initCtx;

     // Update the initial context area

      if ((null == initPropVal) || (true == initPropVal.equals ("")))
         initCtxLabel.setText (jndiInitCtxPropShort + "[not set]");
      else
         initCtxLabel.setText (jndiInitCtxPropShort + initPropVal);
      updateList ();         // Reflect changes to the list

   }// resetInitCtx ()


   /**
    * Displays the attributes (if any) for the currently selected item.
    */
   public void showAttributes ()
   {
      String atomicName;
      int itemIndex = nameList.getSelectedIndex ();

      if (-1 == itemIndex)
      {
         atomicName = "";
      }
      else
      {
         SortableString sortStr;
         sortStr = (SortableString) nameListVector.elementAt (itemIndex);
         atomicName = sortStr.getKeyStr ();
      }

      if (atomicName.equals (".."))
      {
         new NSIMessageBox ("Error", "No attributes for '..'");
         return;
      }

      try
      {
         Object obj = currCtx.lookup (atomicName);
         if (obj instanceof DirContext)
         {
            Frame f;
            f = new NSIAttributeFrame ((DirContext) obj);
            f.setSize (NSIAttributeFrame.width, NSIAttributeFrame.height);
            f.show ();
         }
         else
            new NSIMessageBox ("Error", atomicName + " is not a DirContext");
      }
      catch (NamingException e)
      {
         System.out.println ("Unable to lookup " + atomicName);
         System.out.println ("Unexpected naming exception : " + e);
         e.printStackTrace ();
      }

   }// showAttributes ()


   /**
    * Looks up a context and begins a new browser frame for that context.
    */
   public void lookupContext ()
   {
      String atomicName;
      int itemIndex = nameList.getSelectedIndex ();

      if (-1 == itemIndex)
      {
         atomicName = "";
      }
      else
      {
         SortableString sortStr;
         sortStr = (SortableString) nameListVector.elementAt (itemIndex);
         atomicName = sortStr.getKeyStr ();
      }

      if (atomicName.equals (".."))
      {
         new NSIMessageBox ("Error", "Cannot perform lookup on '..'");
         return;
      }

      try
      {
         Object obj = currCtx.lookup (atomicName);
         if (obj instanceof Context)
            startNewContextFrame (
                  currName.toString () + atomicName,
                  (Context) obj);
         else
            new NSIMessageBox ("Error", atomicName + " is not a DirContext");
      }
      catch (NamingException e)
      {
         System.out.println ("Unable to lookup " + atomicName);
         System.out.println ("Unexpected naming exception : " + e);
         e.printStackTrace ();
      }

   }// lookupContext ()


   /**
    * Starts a new browser frame for a given context.
    *
    * @param   name              (in) String name for title.
    * @param   ctx               (in) Starting context for new frame.
    */
   public void startNewContextFrame (
         String name,
         Context ctx)
   {
      NSIBrowserFrame f;

     // Temporarily switch the current context so the child starts OK

      Context tempCtx = currCtx;
      currCtx = ctx;

      f = new NSIBrowserFrame (
                  name,
                  this,
                  props);
      f.setSize (width, height);
      f.show ();
      f.updateList ();
      currCtx = tempCtx;     // Restore our current context

   }

   /**
    * Handles 'special' button requests.
    *
    * <p>A 'special' request indicates that the user wants to interact
    * with the 'real' object. Since this can happen in so many different
    * ways, we use a list of property entries to find a special handler
    * for the object selected, based on its class name. This allows
    * anyone who's interested to extend this browser to do just about
    * anything.</p>
    */
   public void callSpecialHandler ()
   {
      String atomicName;
      int itemIndex = nameList.getSelectedIndex ();

      if (-1 == itemIndex)
      {
         atomicName = "";
      }
      else
      {
         SortableString sortStr;
         sortStr = (SortableString) nameListVector.elementAt (itemIndex);
         atomicName = sortStr.getKeyStr ();
      }

      if (atomicName.equals (".."))
      {
         new NSIMessageBox ("Error", "No attributes for '..'");
         return;
      }

      try
      {
         Object obj = currCtx.lookup (atomicName);

        // Loads object handler specified in properties

        // Property name: nsi.browser.handler.<obj_class_name>

        // Property value: <class_name> that implements NSISpecialHandler

Properties props = new Properties ();    // override current props for debug

String fileProp = "jnos.browser.handler.com.novell.service.file.nw.naming.FileDirContext";
props.put (fileProp, "FileSpecialHandler");

         String handlerPropName = "jnos.browser.handler." + obj.getClass ().getName ();
         String handlerClassName = props.getProperty (handlerPropName);

         if (null == handlerClassName || handlerClassName.equals (""))
            new NSIMessageBox ("Error", "No handler registered");
         else try
         {
            Object handlerObj = Class.forName (handlerClassName).newInstance ();

            if (handlerObj instanceof NSISpecialHandler)
            {
               NSISpecialHandler handler = (NSISpecialHandler) handlerObj;

               CompositeName tempName = new CompositeName ();
               tempName.addAll (currName);
               tempName.add (atomicName);

               this.setCursor (Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));   // this may take a while

               if (handler.initialize (tempName, obj))
                  handler.handleObject ();
               else
                  new NSIMessageBox ("Error", "Unable to initialize handler");
               this.setCursor (Cursor.getDefaultCursor());
            }
            else
            {
               new NSIMessageBox ("Error", "Special object handler not " +
                     "registered for this object type (" +
                     obj.getClass ().getName () + ")");
            }
         }
         catch (Exception e)
         {
            System.out.println ("Unable to instantiate handler");
            System.out.println ("Unexpected exception " + e);
            e.printStackTrace ();
         }
      }
      catch (NamingException e)
      {
         System.out.println ("Unable to lookup " + atomicName);
         System.out.println ("Unexpected naming exception : " + e);
         e.printStackTrace ();
      }
   }// callSpecialHandler ()


}// class NSIBrowserFrame


class ImageCanvas extends Canvas
{
    int scale = 10;
    Image image;
    Image scaledImage;

    ImageCanvas(Image image)
    {
        this.image = image;
        prepareImage(image, this);
    }

    public void paint(Graphics g)
    {
        update(g);
    }

    public void update(Graphics g)
    {
        int w = image.getWidth(this);
        int h = image.getHeight(this);

        if (w >= 0 || h >= 0)
        {
            if (g.drawImage(image, 0, 0, this) && scaledImage == null)
            {
               // The image has been completely reloaded.


               // Display any comments.

                if (image.getProperty("comment", this) !=
                    Image.UndefinedProperty)
                {
                    System.out.println(
                       image.getProperty("comment", this));
                }

               // Create a pixel grabber and retrieve the pixels.

                int[] pixels = new int[w * h];
                try
                {
                    PixelGrabber pg = new PixelGrabber(
                       image, 0, 0, w, h, pixels, 0, w);
                    pg.grabPixels();

                   // Check for errors

                    if ((pg.status() & ImageObserver.ABORT) != 0)
                    {
                        System.err.println("Error while fetching image");
                        System.exit(1);
                    }
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                    System.exit(1);
                }

               // Now replicate the pixels

                int d = 0, s = 0;
                int[] newpixels = new int[w * h * scale * scale];
                for (int i = 0; i < h; i++)
                {
                    for (int j = 0; j < scale; j++)
                    {
                        for (int k = 0; k < w; k++)
                        {
                            for (int l = 0; l < scale; l++)
                            {
                                newpixels[d++] = pixels[i*w + k];
                            }
                        }
                    }
                }
                scaledImage = getToolkit().createImage(
                   new MemoryImageSource(w*scale, h*scale,
                   ColorModel.getRGBdefault(), newpixels, 0, w*scale));
            }
        }
        if (scaledImage != null)
        {
            g.drawImage(scaledImage, w, 0, this);
        }
    }
}