Personal tools
You are here: Home Information for Students Submitter.java
Document Actions

Submitter.java

by admin last modified 2007-09-18 13:30

Source code for the submit program.

Click here to get the file

Size 26.4 kB - File type text/x-java

File contents

import java.awt.* ; 
import javax.swing.* ; 
import java.util.* ; 
import java.awt.event.* ; 
import java.io.* ; 

// import sun.misc.* ; 
/*
 * 
 */
@SuppressWarnings("serial") class Submitter
extends JFrame 
{
   static final boolean debugging = false ; 
   JScrollPane fileList, messageScrollPane ; 
   JList files ; 
   Vector<String> fileVector ; 
   JFileChooser jfc ; 
   JTextField sname, sclass, sass ; 
   JTextArea messageArea ; 
   JButton doit, quit ; 
   JMenu cmenu, cmenu1 ; 
   String whichClass = null ; 
   String whichAss = null ; 
   Color oopsColor = new Color(255,100, 200) ; 

   static public void main (String[] args)
   {
	  try { 
          new Submitter() ; 
	  } 
	  catch (OutOfMemoryError oe)
	  {
		  System.out.println("Out of memory.   Try again with a larger heap size.") ;
		  System.out.println("For example: java -Xmx512M Submitter.jar") ; 
	  }
   }

   Submitter  () 
   {
      super("Homework Formatter and Submitter - 2") ; 
      fileVector = new Vector<String>() ; 

      GridBagLayout gbl = new GridBagLayout() ; 
      setLayout(gbl) ; 
      GridBagConstraints c = new GridBagConstraints() ; 
      c.fill = GridBagConstraints.BOTH ; 


// name (just to be sure)  on top line 

      c.gridx = 0 ; 
      c.gridy = 5 ; 
      c.gridwidth = 1; 
      c.gridheight = 1 ; 
      c.weightx = 1 ; 
      c.weighty = 1 ; 
      c.fill = GridBagConstraints.HORIZONTAL ; 
      JLabel lab = new JLabel("Your Name : ") ; 
      lab.setHorizontalAlignment(SwingConstants.LEFT) ; 
      this.add(lab) ; 
      gbl.setConstraints(lab, c) ; 

      c.gridx = 1; 
      c.gridy = 5 ; 
      c.gridwidth = 3 ; 
      c.gridheight = 1 ; 
      c.weightx = 10; 
      c.weighty = 1 ; 
      c.fill = GridBagConstraints.HORIZONTAL ; 
      sname = new JTextField(30) ; 
      this.add(sname) ; 
      gbl.setConstraints(sname, c) ; 

// class and homework number 

      JMenuBar jmb = new JMenuBar() ; 

      c.gridx = 0 ; 
      c.gridy = 6 ; 
      c.gridwidth  = 1 ; 
      c.gridheight = 1 ; 
      c.weightx = 10; 
      c.weighty = 1; 
      c.fill = GridBagConstraints.HORIZONTAL ; 
   
      this.add(jmb) ; 
      gbl.setConstraints(jmb, c) ; 

      cmenu = new JMenu("Class ") ; 
      ClassMenuAction class1action = new ClassMenuAction("OS(340)") ; 
      cmenu.add(class1action) ; 
      ClassMenuAction class2action = new ClassMenuAction("Languages(435)") ; 
      cmenu.add(class2action) ; 
      ClassMenuAction class4action = new ClassMenuAction("Other") ; 
      cmenu.add(class4action) ; 
      jmb.add(cmenu) ; 
      
      jmb = new JMenuBar() ; 

      c.gridx = 1 ; 
      c.gridy = 6 ; 
      c.gridwidth  = 1 ; 
      c.gridheight = 1 ; 
      c.weightx = 10; 
      c.weighty = 1; 
      c.fill = GridBagConstraints.HORIZONTAL ; 
      this.add(jmb) ; 
      gbl.setConstraints(jmb, c) ; 
      
      cmenu1 = new JMenu("Assignment ") ; 
      AssMenuAction class21action = new AssMenuAction("1") ; 
      cmenu1.add(class21action) ; 
      AssMenuAction class22action = new AssMenuAction("2") ; 
      cmenu1.add(class22action) ; 
      AssMenuAction class23action = new AssMenuAction("3") ; 
      cmenu1.add(class23action) ; 
      AssMenuAction class24action = new AssMenuAction("4") ; 
      cmenu1.add(class24action) ; 
      AssMenuAction class25action = new AssMenuAction("5") ; 
      cmenu1.add(class25action) ; 
      AssMenuAction class26action = new AssMenuAction("6") ; 
      cmenu1.add(class26action) ; 
      AssMenuAction class27action = new AssMenuAction("7") ; 
      cmenu1.add(class27action) ; 
  
      AssMenuAction class28action = new AssMenuAction("8") ; 
      cmenu1.add(class28action) ;   
      AssMenuAction class29action = new AssMenuAction("9") ; 
      cmenu1.add(class29action) ; 
      AssMenuAction class2Aaction = new AssMenuAction("10") ; 
      cmenu1.add(class2Aaction) ; 
    
      AssMenuAction class210action = new AssMenuAction("PostMortem") ; 
      cmenu1.add(class210action) ; 
      
      jmb.add(cmenu1) ; 

      // message area 
      c.gridx = 0 ; 
      c.gridy = 0 ; 
      c.gridwidth = 4 ; 
      c.gridheight = 4; 
      c.weightx = 10 ; 
      c.weighty = 3 ; 
      c.fill = GridBagConstraints.BOTH ; 
      messageArea = new JTextArea() ; 
      messageArea.setBackground(new Color(255,255,200));
      messageArea . setEditable(false) ; 
      messageScrollPane = new JScrollPane(messageArea) ; 
      // messageArea.setSize(300,300);
      this.add(messageScrollPane) ; 
      gbl.setConstraints(messageScrollPane, c) ;
      messageArea.append("Don't forget to select a Makefile and a README file.\n If this is a group project, please include a TEXT file\"names\" \nwith the names of everyone in the group, one per line") ; 
 
// file chooser - big fucker - all the middle of the left 
      c.gridx = 5 ; 
      c.gridy = 0 ; 
      c.gridwidth = 4 ; 
      c.gridheight = 4 ; 
      c.weightx = 1 ; 
      c.weighty = 1 ; 
      c.fill = GridBagConstraints.BOTH ; 
      jfc = new JFileChooser() ; 
      this.add(jfc);
      gbl.setConstraints(jfc, c) ; 
      jfc.addActionListener(new FileChooseAction() ) ; 

// list of files 
      
      c.gridx = 5 ;
      c.gridy = 5 ;
      c.gridwidth = 4  ; 
      c.gridheight = 4 ; 
      c.weightx = 10 ; 
      c.weighty = 10 ; 
      c.fill = GridBagConstraints.BOTH ; 
      fileVector = new Vector<String>() ; 

      files = new JList() ; 
      files.setListData(fileVector) ; 
      files.setSize(400, 400) ; 
      fileList = new JScrollPane(files) ; 
      fileList.setSize(300,600) ; 
      this.add(fileList) ; 
      gbl.setConstraints(fileList, c) ; 
//    submit button 

      c.gridx = 0 ; 
      c.gridy = 7 ; 
      c.gridwidth = 2 ; 
      c.gridheight = 1 ; 
      c.weightx = 5; 
      c.weighty = 1 ; 
      c.fill = GridBagConstraints.HORIZONTAL ; 
      doit = new JButton("Make Assignment File") ; 
     // doit = new JButton("") ; 
      this.add(doit) ; 
      gbl.setConstraints(doit, c) ; 
      doit.addActionListener(new MakeAction()) ; 

      c.gridx = 2 ; 
      c.gridy = 7 ; 
      c.gridwidth = 1 ; 
      c.gridheight = 1 ; 
      c.weightx = 5 ; 
      c.weighty = 1 ; 
      c.fill = GridBagConstraints.HORIZONTAL ; 
      quit = new JButton("Exit") ; 
      this.add(quit) ; 
      gbl.setConstraints(quit, c) ; 
      quit.addActionListener(new QuitAction()) ; 

      setSize(1000,600) ; 
      setVisible(true) ; 
   } 

   String makeField(String name, String innards)
   {
      return "<" + name + ">" + innards + "</" + name + ">\n" ; 
   } 
   String makeCDATAField(String name, String innards)
   {
      return "<" + name + "><![CDATA[" + innards + "]]></" + name + ">\n" ; 
   } 
   boolean empty(String x)
   {
      return x == null || x == "" || x.length() == 0 ;
   }

   String crunchName(String sname)
   {
	   int start = 0 ; 
	   while (sname.charAt(start) == ' ') start++ ; 
	   int sp = 1 + sname.indexOf(" ", start+1) ; 
	   if (sp <= 0) sp = 2 ; 
	   
	   String res = sname.substring(start, start+2) + sname.substring(sp, sp+2) ;
	   return res ; 
   }
   void logError(String mess)
   {
      messageArea.append("\n");
      messageArea.append(mess);
   }
   void buildSubmission()
   {
      boolean hasMakefile ;
      boolean hasReadme ; 
      boolean needMore = false ; 
      // there aint no sanity clause 
      if (fileVector.isEmpty())
      {
    	  logError("No Files!");
    	  needMore = true ; 
      }
      if (empty(whichClass)) 
      {
         logError("You need to specify a class.");
         needMore = true ; 
      }
      if (empty(whichAss))  
      {
          logError("You need to specify an assignment.");
          needMore = true ; 
      }
      if (empty(sname.getText())) 
      {
          logError("You need to specify your name.");
          needMore = true ; 
      }
      if (needMore) return ; 
      String assFileName = "ass-" + whichClass + "-" + whichAss + "-" + crunchName(sname.getText()) + ".submit" ; 
      String outstr = "\n\nBuilt assignment submission file in \"" + assFileName + "\"" ; 
      outstr += "\nYou should submit that file to me through blackboard." ; 
      messageArea . replaceRange("", 0, messageArea.getText().length()) ; 
      StringBuffer submit = new StringBuffer(20000) ; 

      submit.append(makeField("class", whichClass)) ; 
      submit.append(makeField("assignment", whichAss)) ; 
      submit.append(makeCDATAField("student-name", sname.getText())) ; 

      String filestring = "" ;  
      for (Object o:fileVector) 
      {
         String filename = (String) o ; 
         String fn = makeCDATAField("file-name",filename);
         String fileContents = makeField("file-contents", b64(filename)) ; 
         filestring += makeField("file", fn + fileContents) ; 
         messageArea.append("\n" + filename) ; 
         if (fn.endsWith("Makefile"))
         {
            hasMakefile = true ; 
            messageArea.append("*") ; 
            outstr = outstr + "\nMakefile: ok" ; 
         }
         if (fn.endsWith("readme"))
         {
            messageArea.append("*") ; 
            hasReadme = true ; 
            outstr = outstr + "\nReadme: ok" ; 
         }
      }
      submit.append(makeField("files", filestring)) ; 
      String submission = makeField("submission", new String(submit)) ; 
      try 
      {
         FileOutputStream fos = new FileOutputStream(assFileName) ;
         fos.write(submission.getBytes()) ; 
      }
      catch (Exception e)
      {
    	 messageArea.setBackground(oopsColor) ; 
         messageArea.replaceRange("Failed to write output.", 0, messageArea.getText().length()) ; 
         try 
         {
            Thread.sleep(30) ; 
         } 
         catch (Exception e1) 
         {
            e1.printStackTrace() ; 
         } 
//         System.exit(1) ; 
      }

      messageArea.append(outstr) ; 
      
      try
      {
         Thread.sleep(30) ; 
      }
      catch (Exception e1)
      {
         e1.printStackTrace() ; 
//         System.exit(0) ; 
      } 
      System.out.println("wrote assignment submission file in \"" + assFileName + "\"") ;  ; 
   } 

   String b64(String filename)
   {
      BASE64Encoder encer = new BASE64Encoder() ; 
      try 
      {
         FileInputStream fis = new FileInputStream(filename) ; 
         int available = fis.available() ; 
         byte[] dataArray = new byte[available]; 
         int i = fis.read(dataArray) ; 
         return encer.encode(dataArray);
      }
      catch (Exception e)
      {
    	 messageArea.setBackground(oopsColor) ; 
    	 messageArea.append("Failed to read " + filename) ; 
         System.out.println("Failure in reading " + filename ) ; 
         e.printStackTrace() ; 
      }
      return "" ; 
   }

   class MakeAction
   implements ActionListener
   {
      public void actionPerformed(ActionEvent ev) 
      {
         if (debugging)
         {
            System.out.println("make button pushed") ; 
         }
         try 
         {
        	 buildSubmission() ; 
         }
         catch (OutOfMemoryError e)
         {
        	 messageArea.setBackground(oopsColor) ; 
        	 messageArea.append("---------------!!!!!!!!!!!!!!!!-----------------\n"); 
        	 messageArea.append("Out of memory.   Try again with a larger heap size.\n") ;
   		     messageArea.append("For example: java -Xmx512M Submitter.jar\n") ;   
         }
      }
   }    

   class QuitAction
   implements ActionListener
   {
      public void actionPerformed(ActionEvent ev) 
      {
         System.exit(0) ; 
      }
   }    

   class FileChooseAction
   implements ActionListener
   {
      public void actionPerformed(ActionEvent ev) 
      {
         if (debugging)
         {
            System.out.println("got file choose action ") ;
            System.out.println("command was " + ev.getActionCommand()) ;
            System.out.println(  "file was "
                               + jfc.getSelectedFile().getName()) ; 
         }
         if (ev.getActionCommand().equals("CancelSelection")) return ; 
         if (fileVector.contains(jfc.getSelectedFile().getPath())) return ;  
         fileVector.add(jfc.getSelectedFile().getPath());
         files.setListData(fileVector) ; 
      }
   }    
   class ClassMenuAction
   extends AbstractAction
   {
      String who ; 
      public ClassMenuAction(String text)
      {
         super(text) ; 
         who = text ; 
      }
      public void actionPerformed(ActionEvent e)
      {
    	 if (debugging)
    	 {
    		 System.out.println("action performed " + e) ; 
    		 System.out.println("source is " + e.getSource()) ; 
    		 System.out.println("cmenu is " + cmenu) ; 
    		 System.out.println("this is " + this) ; 
    	 }
         whichClass = who ; 
         messageArea.append("\nclass is : " + whichClass) ; 
         cmenu.setText("Class: " + who) ; 
         // cmenu.setBackground(new Color(200,255,200));
      }
   } 

   class AssMenuAction
   extends AbstractAction
   {
      String who ; 
      public AssMenuAction(String text)
      {
         super(text) ; 
         who = text ; 
      }
      public void actionPerformed(ActionEvent e)
      {
    	 if (debugging)
    	 {
            System.out.println("action performed " + e) ; 
            System.out.println("source is " + e.getSource()) ; 
            System.out.println("cmenu is " + cmenu) ; 
            System.out.println("this is " + this) ; 
    	 }
         whichAss = who ; 
         messageArea.append("\nAssignment is : " + whichAss) ; 
         cmenu1.setText("Assignment: " + who);
      }
   } 
} 

/*
 * all the following was shamelessly stolen from the internet
 * I could have used the sun.misc stuff except that it is 
 * undocumented and not guaranteed to be anywhere.  Seems 
 * odd, but there you have it.
 * 
 * original source at:
 * http://show.docjava.com:8086/book/cgij/doc/net/proxy/BASE64Encoder.java.html
 */ 
/**
 * Utility class to do Base64 encoding, as defined by RFC 2045,
 * section 6.8 (http://www.ietf.org/rfc/rfc2045.txt)
 * Uses the same class and function names as Sun's implementation from
 * sun.misc
 */
class BASE64Encoder {

  /**
   * Convert a byte to an integer.  Needed because in Java bytes
   * are signed, and for Base64 purposes they are not.  If not done
   * this way, when converted to an int, 0xFF will become -127
   * @param b Byte value to be converted
   * @return Value as an integer, as if byte was unsigned
   */
  private int uByteToInt(byte b) {
    if (b >= 0) {
      return (int) b;
    }

    return 256 + b;
  }

  /**
   * Encode an array of bytes using Base64
   * @param data[] The bytes to be encoded
   * @return A valid Base64 representation of the input
   */
  public String encode(byte data[]) {
    // Base64 encoding yields a String that is 33% longer than the byte array
    int charCount = ((data.length * 4) / 3) + 4;

    // New lines will also be needed for every 76 charactesr, so allocate a
    // StringBuffer that is long enough to hold the full result without
    // having to expand later
    StringBuffer result = new StringBuffer(
        (charCount * 77) / 76);

    int byteArrayLength = data.length;
    int byteArrayIndex = 0;
    int byteTriplet = 0;
    while (byteArrayIndex < byteArrayLength - 2) {
      byteArrayIndex = processData(
          data, byteArrayIndex, result);

    }

    byteArrayIndex = checkIfWeHave1ByteLeftOver(
        byteArrayIndex, byteArrayLength, data, result);
    checkIfWeHaveTwoBytesLeftOver(
        byteArrayIndex, byteArrayLength, data, result);

    return result.toString();
  }

  private int processData(
      byte[] data, int index,
      StringBuffer result) {
    int byteTriplet;
    // Build the 24 bit byte triplet from the input data
    byteTriplet = uByteToInt(data[index++]);
    // Each input byte contributes 8 bits to the triplet
    byteTriplet <<= 8;
    byteTriplet |= uByteToInt(data[index++]);
    byteTriplet <<= 8;
    byteTriplet |= uByteToInt(data[index++]);

    // Look at the lowest order six bits and remember them
    byte b4 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
    // Move the byte triplet to get the next 6 bit value
    byteTriplet >>= 6;
    byte b3 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
    byteTriplet >>= 6;
    byte b2 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
    byteTriplet >>= 6;
    byte b1 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);

    // Add the Base64 encoded character to the result String
    result.append(byteToChar(b1));
    result.append(byteToChar(b2));
    result.append(byteToChar(b3));
    result.append(byteToChar(b4));

    // There are 57 bytes for every 76 characters, so wrap the line when needed
    if (index % 57 == 0) {
      result.append("\n");
    }
    return index;
  }

  private void checkIfWeHaveTwoBytesLeftOver(
      int index,
      int length,
      byte[] byteArray,
      StringBuffer sb) {
    int byteTriplet;
    if (index == length - 2) {
      // Convert our two bytes to an int
      byteTriplet = twoBytesToInt(byteArray, index);
      // Right pad the third 6 bit value with zeros
      byteTriplet <<= 2;

      byte b3 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
      byteTriplet >>= 6;
      byte b2 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
      byteTriplet >>= 6;
      byte b1 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);

      sb.append(byteToChar(b1));
      sb.append(byteToChar(b2));
      sb.append(byteToChar(b3));

      // Add "==" to the output to make it a multiple of 4 Base64 characters
      sb.append("=");
    }
  }

  private int checkIfWeHave1ByteLeftOver(
      int index,
      int length,
      byte[] byteArray,
      StringBuffer sb) {
    int byteTriplet;
    if (index == length - 1) {
      // Convert our one byte to an int
      byteTriplet = uByteToInt(byteArray[index++]);
      // Right pad the second 6 bit value with zeros
      byteTriplet <<= 4;

      byte b2 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);
      byteTriplet >>= 6;
      byte b1 = (byte) (Constants.SIX_BIT_MASK & byteTriplet);

      sb.append(byteToChar(b1));
      sb.append(byteToChar(b2));

      // Add "==" to the output to make it a multiple of 4 Base64 characters
      sb.append("==");
    }
    return index;
  }

  private int twoBytesToInt(byte[] data, int byteArrayIndex) {
    int byteTriplet;
    byteTriplet =
        uByteToInt(data[byteArrayIndex++]);
    byteTriplet <<= 8;
    byteTriplet |= uByteToInt(data[byteArrayIndex++]);
    return byteTriplet;
  }

  /**
   * Convert a byte between 0 and 63 to its Base64 character equivalent
   * @param b Byte value to be converted
   * @return Base64 char value
   */
  private char byteToChar(byte b) {
    if (b < Constants.LOWER_CASE_A_VALUE) {
      return (char) ('A' + b);
    }

    if (b < Constants.ZERO_VALUE) {
      return (char) ('a' + (b - Constants.LOWER_CASE_A_VALUE));
    }

    if (b < Constants.PLUS_VALUE) {
      return (char) ('0' + (b - Constants.ZERO_VALUE));
    }

    if (b == Constants.PLUS_VALUE) {
      return '+';
    }

    if (b == Constants.SLASH_VALUE) {
      return '/';
    }

    throw
        new IllegalArgumentException(
            "Byte " +
            new Integer(b) +
            " is not a valid Base64 value");
  }

  /**
   * Simple test method to make sure everything works correctly
   * Creates 100 randomly sized arrays of random bytes, encodes them,
   * decodes them, and checks to make sure the result matches the input
   */
  public static void main(String args[]) throws Exception {
    testCodec();

  }

  private static void testCodec() {
//      sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
//      sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();

    for (int j = 0; j < 100; j++)
      test(new BASE64Encoder(), new BASE64Decoder());

  }

  private static void test(BASE64Encoder encoder, BASE64Decoder decoder) {
    byte test[] = new byte[(int) (100000 * Math.random())];
    for (int i = 0; i < test.length; i++) {
      test[i] = (byte) (256 * Math.random());
    }

    String string = encoder.encode(test);
    byte result[] = decoder.decodeBuffer(string);

    if (!Arrays.equals(test, result) || test.length != result.length) {
      System.out.println("ARRAYS DO NOT MATCH!");
    }
  }
}


/**
 * Utility class to do Base64 decoding, as defined by RFC 2045,
 * section 6.8 (http://www.ietf.org/rfc/rfc2045.txt)
 * Uses the same class and function names as Sun's implementation from
 * sun.misc
 */
class BASE64Decoder 
{

  /**
   * Bit mask for one byte worth of bits in Base64 encoding.
   * Equivalent to binary value 11111111b.
   */
  private static final int EIGHT_BIT_MASK = 0xFF;

  /**
   * Decode an input String using Base64
   * @param data The String to be decoded
   * @return The appropriate byte array
   */
  public byte[] decodeBuffer(String data) {
    // Create a wrapper around the input to screen out non-Base64 characters
    StringWrapper wrapper = new StringWrapper(data);
    // A Base64 byte array is 75% the size of its String representation
    int length = wrapper.getUsefulLength() * 3 / 4;

    byte byteArray[] = new byte[length];

    int byteTriplet = 0;
    int index = 0;

    // Continue until we have less than 4 full characters left to
    // decode in the input.
    while (index + 2 < length) {

      index = processByteArray(
          wrapper,
          byteArray,
          index);

    }

    checkIfWeHave1ByteLeft(
        index,
        length,
        wrapper, byteArray);

    checkIfWeHave2BytesLeft(index,
                            length,
                            wrapper,
                            byteArray);

    return byteArray;
  }

  private void checkIfWeHave2BytesLeft(int index, int length, StringWrapper wrapper, byte[] byteArray) {
    int byteTriplet;
    if (index == length - 2) {
      // Take out the last three characters from the String
      byteTriplet = charToInt(wrapper.getNextUsefulChar());
      byteTriplet <<= 6;
      byteTriplet |= charToInt(wrapper.getNextUsefulChar());
      byteTriplet <<= 6;
      byteTriplet |= charToInt(wrapper.getNextUsefulChar());

      // Remove the padded zeros
      byteTriplet >>= 2;
      byteArray[index + 1] = (byte) (byteTriplet & EIGHT_BIT_MASK);
      byteTriplet >>= 8;
      byteArray[index] = (byte) (byteTriplet & EIGHT_BIT_MASK);
    }
  }

  private void checkIfWeHave1ByteLeft(int byteIndex, int byteArrayLength, StringWrapper wrapper, byte[] result) {
    int byteTriplet;
    if (byteIndex == byteArrayLength - 1) {
      // Take out the last two characters from the String
      byteTriplet = charToInt(wrapper.getNextUsefulChar());
      byteTriplet <<= 6;
      byteTriplet |= charToInt(wrapper.getNextUsefulChar());

      // Remove the padded zeros
      byteTriplet >>= 4;
      result[byteIndex] = (byte) (byteTriplet & EIGHT_BIT_MASK);
    }
  }

  private int processByteArray(StringWrapper wrapper, byte[] result, int byteIndex) {
    int byteTriplet;
    // Package a set of four characters into a byte triplet
    // Each character contributes 6 bits of useful information
    byteTriplet = charToInt(wrapper.getNextUsefulChar());
    byteTriplet <<= 6;
    byteTriplet |= charToInt(wrapper.getNextUsefulChar());
    byteTriplet <<= 6;
    byteTriplet |= charToInt(wrapper.getNextUsefulChar());
    byteTriplet <<= 6;
    byteTriplet |= charToInt(wrapper.getNextUsefulChar());

    // Grab a normal byte (eight bits) out of the byte triplet
    // and put it in the byte array
    result[byteIndex + 2] = (byte) (byteTriplet & EIGHT_BIT_MASK);
    byteTriplet >>= 8;
    result[byteIndex + 1] = (byte) (byteTriplet & EIGHT_BIT_MASK);
    byteTriplet >>= 8;
    result[byteIndex] = (byte) (byteTriplet & EIGHT_BIT_MASK);
    byteIndex += 3;
    return byteIndex;
  }

  /**
   * Convert a Base64 character to its 6 bit value as defined by the mapping.
   * @param c Base64 character to decode
   * @return int representation of 6 bit value
   */
  private int charToInt(char c) {
    if (c >= 'A' && c <= 'Z') {
      return c - 'A';
    }

    if (c >= 'a' && c <= 'z') {
      return (c - 'a') + Constants.LOWER_CASE_A_VALUE;
    }

    if (c >= '0' && c <= '9') {
      return (c - '0') + Constants.ZERO_VALUE;
    }

    if (c == '+') {
      return Constants.PLUS_VALUE;
    }

    if (c == '/') {
      return Constants.SLASH_VALUE;
    }

    throw new IllegalArgumentException(c + " is not a valid Base64 character.");
  }

  /**
   * Simple class to wrap around the String input to ignore all of the
   * non-Base64 characters in the input.  Note that although '=' is
   * a valid character, it does not contribute to the total number
   * of output bytes, and is therefore ignored
   */
  private class StringWrapper {

    /**
     * The input String to be decoded
     */
    private String mString;

    /**
     * Current position in the String
     */
    private int mIndex = 0;

    /**
     * Total number of Base64 characters in the input
     */
    private int mUsefulLength;

    /**
     * @param c Character to be examined
     * @return Whether or not the character is a Base64 character
     */
    private boolean isUsefulChar(char c) {
      return (c >= 'A' && c <= 'Z') ||
          (c >= 'a' && c <= 'z') ||
          (c >= '0' && c <= '9') ||
          (c == '+') ||
          (c == '/');
    }

    /**
     * Create the wrapper and determine the number of Base64 characters in
     * the input
     * @param s Input String to be decoded
     */
    public StringWrapper(String s) {
      mString = s;
      mUsefulLength = 0;
      int length = mString.length();
      for (int i = 0; i < length; i++) {
        if (isUsefulChar(mString.charAt(i))) {
          mUsefulLength++;
        }
      }
    }

    /**
     * @return Total number of Base64 characters in the input.  Does
     * not include '='
     */
    public int getUsefulLength() {
      return mUsefulLength;
    }

    /**
     * Traverse the String until hitting the next Base64 character.
     * Assumes that there is still another valid Base64 character
     * left in the String.
     */
    public char getNextUsefulChar() {
      char result = '_';  // Start with a non-Base64 character
      while (!isUsefulChar(result)) {
        result = mString.charAt(mIndex++);
      }

      return result;
    }
  }
}

class Constants {
  /**
   * Byte value that maps to 'a' in Base64 encoding
   */
  final static int LOWER_CASE_A_VALUE = 26;
  /**
   * Byte value that maps to '0' in Base64 encoding
   */
  final static int ZERO_VALUE = 52;
  /**
   * Byte value that maps to '+' in Base64 encoding
   */
  final static int PLUS_VALUE = 62;
  /**
   * Byte value that maps to '/' in Base64 encoding
   */
  final static int SLASH_VALUE = 63;
  /**
   * Bit mask for one character worth of bits in Base64 encoding.
   * Equivalent to binary value 111111b.
   */
  final static int SIX_BIT_MASK = 63;
}


Eastern Washington University   Copyright © 2007 Jeffrey B Putnam   Computer Science Department