Search code examples
jruby

Translating a Java internal class into a JRuby counterpart


I am trying to translate a Java class into its JRuby counter part, but I don't know how to translate a Java internal class.

This problem is well known and often comes from visibility/scope difference between Java and Ruby. This can be solved sometimes using blocs instead of inner class.

The Java example is the following, that can be found on Zetcode site. I also give my Ruby attempt below.

The problem is located in the internal ScheduleTask class, that extends TimerClass. While running (in Java), the run() method of ScheduleTask allows to call the repaint() method of the Board object to which it belongs. This is very handy in Java : at regular time interval, the Board panel is repaint. How to translate this in JRuby ? I simply tried to translate this literally, but then the repaint method is not known by the Ruby internal ScheduleTask...

package com.zetcode;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.ImageIcon;
import javax.swing.JPanel;


public class Board extends JPanel  {

    private final int B_WIDTH = 350;
    private final int B_HEIGHT = 350;
    private final int INITIAL_X = -40;
    private final int INITIAL_Y = -40;    
    private final int INITIAL_DELAY = 100;
    private final int PERIOD_INTERVAL = 25;
    
    private Image star;
    private Timer timer;
    private int x, y;
    
    public Board() {
    
        
        initBoard();        
    }
    
    private void loadImage() {
        
        ImageIcon ii = new ImageIcon("star.png");
        star = ii.getImage();        
    }
    
    private void initBoard() {
        
        setBackground(Color.BLACK);
        setPreferredSize(new Dimension(B_WIDTH, B_HEIGHT));
        setDoubleBuffered(true);
        
        loadImage();

        x = INITIAL_X;
        y = INITIAL_Y;
        
        timer = new Timer();
        timer.scheduleAtFixedRate(new ScheduleTask(), 
                INITIAL_DELAY, PERIOD_INTERVAL);        
    }
        

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        drawStar(g);
    }
    
    private void drawStar(Graphics g) {
        
        g.drawImage(star, x, y, this);
        Toolkit.getDefaultToolkit().sync();
    }


    private class ScheduleTask extends TimerTask {

        @Override
        public void run() {
            x += 1;
            y += 1;

            if (y > B_HEIGHT) {
                y = INITIAL_Y;
                x = INITIAL_X;
            }
            
            repaint();
        }
    }

Here is the JRuby attempt, where the ScheduleTask class has been extracted from Board...

include Java

import java.awt.Color
import java.awt.Dimension
import java.awt.Graphics
import java.awt.Image
import java.awt.Toolkit
import java.util.Timer
import java.util.TimerTask
import javax.swing.ImageIcon
import javax.swing.JPanel

B_WIDTH = 650
B_HEIGHT = 450
INITIAL_X = -40
INITIAL_Y = -40    
INITIAL_DELAY = 100
PERIOD_INTERVAL = 25

class Board < JPanel

  def initialize
    super
    initBoard()        
  end
    
  def loadImage
    ii = ImageIcon.new("mario2.png")
    @star = ii.getImage()        
  end
    
  def initBoard
    setBackground(Color.new(255, 255, 255))
    setPreferredSize(Dimension.new(B_WIDTH, B_HEIGHT))
    setDoubleBuffered(true)
    loadImage()
    @x = INITIAL_X
    @y = INITIAL_Y
        
    @timer = Timer.new()
    task=ScheduleTask.new
    task.init(@x,@y)
    @timer.scheduleAtFixedRate(task, INITIAL_DELAY, PERIOD_INTERVAL)        
  end

  def paintComponent(g)
    repaint() # WAS : super.paintComponent(g)
    drawStar(g)
  end
    
  def drawStar(g)
    g.drawImage(@star, @x, @y, self)
    Toolkit.getDefaultToolkit().sync()
  end

  class ScheduleTask < TimerTask	
    def init x,y
      @x,@y=x,y
    end
    def run
      @x += 1
      @y += 1
      if (@y > B_HEIGHT) 
        @y = INITIAL_Y
        @x = INITIAL_X
       end
      repaint() # <===== ERROR in Ruby. Unknown method 
    end
  end #ScheduleTask 

 end #Board

And finally here is the way to start the application :

require_relative 'board'

include Java

import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.SwingUtilities

class UtilityTimerExample < JFrame

    def initialize
      super
      initUI();
      setVisible(true)
    end
    
    def initUI
      add(Board.new());
      setResizable(false);
      pack();
      setTitle("Star");
      setLocationRelativeTo(nil)
      setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE)
    end
end

SwingUtilities.invoke_later do
  UtilityTimerExample.new
end


Solution

  • I finally succeeded to solve my problem, using this blog.

    1. create a timer
    2. add a method "add_action_listener(block)"
    3. pass the intended action to the block