Bean Shell

Hi,
I’m going to be computer-less for a long time since I’m going on holiday (yee-hah! :D) so I made this little program that I can run code on without needing the JDK (only the JRE).

It’s just a simple GUI on top of BeanShell which is a really cool scripting language. I remember Onyx once said it was slow, and it seems that’s true. Still, I tried it with zeroone’s cool 4K game template and the game worked! Just copy his entire source code into the ‘Code’ text pane and then add this line after the last curly bracket:

M.main(null);

Then click ‘Run’ at the bottom. Its really awesome…

Web Start 8): http://www.freewebs.com/commanderkeith/JavaBeanShell.jnlp

Source: http://www.freewebs.com/commanderkeith/BeanShellTest.zip

Keith

PS: If you’re interested in scripting, there’s a new language called F3 which looks as though it will supercede BeanShell. It is made by Sun, based on the Java language (but with script-like aspects) and its meant to be a competitor to Flash… it still deploys using Java though so I don’t think it will fly like Flash has. Details: http://blogs.sun.com/jag/entry/f3%3A_a_whole_new_take

That sounded really exciting, but looking at F3 it seems to lack bsh’s core “features”:

  • directly compilable into java
  • …where that’s not true, you could write some search-replace regexs quickly (assuming your IDE uses regex in its search/replace) that would do the source conversion very quickly because every bsh shortcut is deterministically linked to java (e.g. blah = 3 if there is no public blah variable in scope means setBlah(3))
  • aims always to require no special knowledge for a java developer to read and understand it. Does not embrace-and-extend java syntax.

(I’m being influenced here mainly by the fact that the primary source example is the same one used in bsh, and looks practically identical, but is followed by the authors comment: “Of course, this isn’t the preferred way of creating GUI’s in F3. The following F3 code achieves the same effect:” and the code that follows lacks the easy-to-read javaness of bsh).

So, it looks like its a langauge you CAN program in a bsh-fashion, but you are encouraged not to. From sitting on the bsh mailing list for many years I’d guesstimate that at least 40% of all bsh users use it specifically because it is designed to discourage non-java usage, and that around another 20%-40% consider that a major bonus but would still use it if it didn’t.

Obviously, sun’s backing makes the world of difference.

I remember Onyx once said it was slow[…]

Once? I’m like saying that each and every day ;D

(I actually mentioned it today over in #lwjgl)

Hi,
I’ve run into a big snag with BeanShell and WebStart.

From: http://www.beanshell.org/bsh20announce.txt

[quote]Class generation currently requires reflective accessibility security permissions (will probably not work under a tight security policy or in an applet). BeanShell currently installs generated classes directly in the current classloader by accessing the protected defineClass() method.
[/quote]
The trouble is that when you run a beanshell script that generates a class from a Web-Started VM, the script throws a SecurityException since the created class is unsigned which is illegal in WebStart (see the SecurityException throw section of the defineClass method in http://java.sun.com/j2se/1.5.0/docs/api/java/lang/ClassLoader.html).

To see it fail, paste the following code into my BeanShell code editor and click the running man button: http://www.freewebs.com/commanderkeith/JavaBeanShell.jnlp
failing class-generation code:

    count = 5;

    class HelloWorld extends Thread {
        public void run() {
           for(i=0; i<count; i++)
              print("Hello World!");
        }     
    }

    new HelloWorld().start();

Can anyone think of a way to get around this but still use Web Start? Its a Security/ClassLoader problem. The WebStart class loader is special since it wants all new classes to be signed, otherwise it throws that SecurityException. The app works fine if started by just double-clicking the JAR file but if I do it that way I lose WebStart’s benefits…

Thanks heaps,
Keith

Er, yeah, sign your code.

Cas :slight_smile:

Thanks for the reply.

I have signed my classes, but the classes created by the method ClassLoader.defineClass() (http://java.sun.com/j2se/1.5.0/docs/api/java/lang/ClassLoader.html) don’t appear to be signed… read this from the method declaration:

throws SecurityException - If an attempt is made to add this class to a package that contains classes that were signed by a different set of certificates than this class (which is unsigned), or if name begins with “java.”.

???

Keith

Surely signed classes could create new classes and load them with their classloader, and then these classes would operate with the same security credentials?

Cas :slight_smile:

apparently not. Since this class is created dynamically webstart just won’t accept it since its not signed, crashing the thread with a security exception. :frowning:

Any ideas? The only way I can see it working is by starting a new VM from within the original VM of the signing-shackled webstart classloader.

Keith

PS: Maybe someone can help with the code to start a new VM?!
http://www.java-gaming.org/forums/index.php?topic=16231.msg128300#new

This is a little embarrassing for how simple it is, but I found the way to bypass the signing issues (which doesn’t require a VM restart :P)

Just execute this code early on in your main class. It installs a dummy security manager. Then all the webstart security crap is bypassed (but to get to the stage where your Main class executes you’ll still need to sign your jars ;))

	//grant all permissions on the clientside
	Policy.setPolicy( new Policy() {
		public PermissionCollection getPermissions(CodeSource codesource) {
			Permissions perms = new Permissions();
			perms.add(new AllPermission());
			return(perms);
		}
		public void refresh(){
		}
	});

Of course this requires the ‘all-permissions’ security dialog to be accepted.

I found it here: http://forum.java.sun.com/thread.jspa?messageID=3755247 :slight_smile:

Keith

PS: Another interesting webstarty fact I learned today was that 1.4+ doesn’t mean “get the latest JVM which is at least 1.4”, it means 1.4.* is preferred over 1.5 and anything else. So you need to put in loads of tags like this to get the latest JRE, but one which is greater than 1.4. See here: http://mindprod.com/jgloss/javawebstart.html

So what does this get you round if you still have to give permission? Signing the JARs?

Thinking about it, isn’t:


Policy.setPolicy( new Policy() {
         public PermissionCollection getPermissions(CodeSource codesource) {
            Permissions perms = new Permissions();
            perms.add(new AllPermission());
            return(perms);
         }
         public void refresh(){
         }
      });

A prived op, so you’d need to be signed to perform it? (this isn’ t actually changing the security manager just installing an additional policy, which I supposed might not be prived?)

Kev

It lets BeanShell dynamically create classes on the fly. 8)

The trouble was that when you run a beanshell script that generates a class from a Web-Started VM, the script throws a SecurityException since the created class is unsigned which is illegal in WebStart (but is OK with this dummy security manager).

With this change you should be able to paste the below code into my webstarted beanshell front-end app (the only webstartable beanshell editor I’ve ever found), click ‘Run’ and then see a game window. Before it was impossible.

Thanks for the interest :),
Keith

webstart: http://www.freewebs.com/commanderkeith/SlaveBot/SlaveBot_jvm1.4+.jnlp

code:


// originally written by zeroone, and then made 1.4 compatible
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class M extends JFrame {
   
  boolean[] K = new boolean[65535]; // pressed keys
  
  public M() {
   
    final int WIDTH = 640;
    final int HEIGHT = 480;
    final int FRAMES_PER_SECOND = 60;
    final long FRAME_PERIOD = 1000000000L / FRAMES_PER_SECOND;
    
    Random random = new Random();
        
    // Define model
    final int BALLS = 5;
    final int BALL_SIZE = WIDTH / 10;
    final int PADDLE_WIDTH = BALL_SIZE * 2;
    final int PADDLE_HEIGHT = HEIGHT / 20;
    final int BALL_VELOCITY = 2;
    final int PADDLE_VELOCITY = 10;
    int[][] balls = new int[BALLS][4]; // [ball index][{x, y, Vx, Vy}]
    for(int i = 0; i < BALLS; i++) {
      balls[i] = new int[] { 
        random.nextInt(WIDTH - BALL_SIZE),                     // x
        random.nextInt(HEIGHT - BALL_SIZE - PADDLE_HEIGHT),    // y
        random.nextBoolean() ? -BALL_VELOCITY : BALL_VELOCITY, // Vx
        random.nextBoolean() ? -BALL_VELOCITY : BALL_VELOCITY, // Vy
      };
    }
    int paddleX = (WIDTH - PADDLE_WIDTH) / 2;
    
    // Setup frame
    JPanel panel = (JPanel)getContentPane();
    panel.setPreferredSize(new Dimension(WIDTH, HEIGHT));    
    setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    pack();
    setLocationRelativeTo(null);
    show();    
    
    // Create off-screen image for rendering
    Image image = createImage(WIDTH, HEIGHT);
    Graphics imageGraphics = image.getGraphics();
    
    long nextFrameStart = System.currentTimeMillis()*1000000;	//System.nanoTime();
    while(true) {
      do {
// -- UPDATE MODEL BEGIN -------------------------------------------------------
      
      // Move the balls  
      for(int i = 0; i < BALLS; i++) {
        int[] ball = balls[i];
        if (ball[0] >= WIDTH - BALL_SIZE) {
          ball[2] = -BALL_VELOCITY;
        } else if (ball[0] <= 0) {
          ball[2] = BALL_VELOCITY;
        }
        if (ball[1] >= HEIGHT - BALL_SIZE - PADDLE_HEIGHT) {
          ball[3] = -BALL_VELOCITY;
        } else if (ball[1] <= 0) {
          ball[3] = BALL_VELOCITY;
        }     
        ball[0] += ball[2];
        ball[1] += ball[3];
      }
      
      // Move the paddle
      if (K[KeyEvent.VK_LEFT] && paddleX > 0) {
        paddleX -= PADDLE_VELOCITY;
      }
      if (K[KeyEvent.VK_RIGHT] && paddleX < WIDTH - PADDLE_WIDTH) {
        paddleX += PADDLE_VELOCITY;
      }
      
// -- UPDATE MODEL END ---------------------------------------------------------
        nextFrameStart += FRAME_PERIOD;
      } while(nextFrameStart < System.currentTimeMillis()*1000000);     
// -- RENDER FRAME BEGIN -------------------------------------------------------
      imageGraphics.setColor(Color.BLACK);
      imageGraphics.fillRect(0, 0, WIDTH, HEIGHT);
      
      // Render balls
      imageGraphics.setColor(Color.RED);
      for(int i = 0; i < BALLS; i++) {
        int[] ball = balls[i];
        imageGraphics.fillOval(ball[0], ball[1], BALL_SIZE, BALL_SIZE);
      }
      
      // Render paddle
      imageGraphics.setColor(Color.YELLOW);
      imageGraphics.fillRect(
          paddleX, HEIGHT - PADDLE_HEIGHT, PADDLE_WIDTH, PADDLE_HEIGHT);
      
      // Draw to screen
      Graphics panelGraphics = panel.getGraphics();
      if (panelGraphics != null) {
        panelGraphics.drawImage(image, 0, 0, null);
        panelGraphics.dispose();
      }
// -- RENDER FRAME END ---------------------------------------------------------
      long remaining = nextFrameStart - System.currentTimeMillis()*1000000;//System.nanoTime();
      if (remaining > 0) {
        try {
          Thread.sleep(remaining / 1000000);
        } catch(Throwable t) {          
        }
      }
    }
  }
  
  public void processKeyEvent(KeyEvent e) {
    K[e.getKeyCode()] = e.getID() == 401;
  }
  
  public static void main(String[] args) {
    new M();
  }
}

M.main(null);

Ah ha! Should have read it in context shouldn’t I :slight_smile: Sorry. So essentially as long as you sign the original JARs you distribute and ask for all permissions, you can then build any java classes you like dynamically. Sounds reasonable.

Kev

Hm I dunno… it sounds like a bit of a useability bug in the webstart classloader to me. I’d expect any class created by another class to have the security credentials associated with that class. Or something. Just seems a bit hacky the way it is.

Cas :slight_smile:

I agree, but i think the bug is that if you already request all-permissions in the webstart jnlp file, you shouldn’t have to set a dummy policy…