poornerd

my thoughts on programming and other nerdy stuff

August 22, 2014
by poornerd
0 comments

Programming a simple slot machine game using Java

Screen Shot 2014-08-22 at 17.48.55No mat­ter how sim­ple or com­plex the game is, Java can do the job!

On this post, let’s take a look at how begin­ners of Java pro­gram­ming can make a sim­ple, yet fully func­tional slot machine. Slot machines have been around for a long time, but its enter­tain­ment value doesn’t seem to fade one bit. Inter­Casino, the first web­site to offer online casino gam­ing to the world in 1996, is still around and its slot games seem to get updated often. In addi­tion, accord­ing to the Amer­i­can Gam­ing Asso­ci­a­tion, slots gen­er­ate around 62% — 90% of gam­ing money, mak­ing the machines the cash cows of casi­nos. With these facts in mind, don’t you ever want to cre­ate your very own slot machine that mil­lions of casino gam­ing fans might like in the future? If you’re inter­ested in cre­at­ing Java-based slot games, the code below might prove use­ful for you.

package slotMachineGUI;

import java.awt.*;
import javax.swing.*;
import javax.swing.LayoutStyle.ComponentPlacement;
import java.text.DecimalFormat;
import java.util.Random;
import java.util.ArrayList;
import javax.swing.border.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class SlotMachineGUI {
    
    private JButton btnCash, btnSpin;
    private JCheckBox cbAlwaysWin, cbSuperJackpot, cbTrollface;
    private JFrame frmFrame;
    private JLabel lblCredits, lblLost, lblMatchThree, lblMatchTwo, lblMoney, lblReel1, lblReel2, lblReel3, lblStatus, lblWon;
    private JPanel pnlReels, pnlReel1, pnlReel2, pnlReel3;
    private JProgressBar prgbarCheatUnlocker;
    private JSeparator sepCheats, sepStats, sepStats2, sepStatus;
    private JToggleButton tgglSound;
    private int credits = 100, boughtCredits = 100, bet = 15, matchThree, matchTwo, win, lost;
    private double payout = 25.0, creditBuyout = 10.0, funds;
    private int reel1 = 7, reel2 = 7, reel3 = 7; // starting values of the reels.
    private ArrayList<ImageIcon> images = new ArrayList<ImageIcon>();
    private DecimalFormat df = new DecimalFormat("0.00");
    
    public SlotMachineGUI(int credits, int boughtCredits, int bet, double payout, double creditBuyout, int reel1, int reel2, int reel3) {
        this.credits=credits;
        this.boughtCredits=boughtCredits;
        this.bet=bet;
        this.payout=payout;
        this.creditBuyout=creditBuyout;
        this.reel1=reel1;
        this.reel2=reel2;
        this.reel3=reel3;
        createForm();
        loadImages();
        addFields();
        addButtons();
        layoutFrame();
        layoutReels();
        layoutOther();
    }
    
    public SlotMachineGUI() {
        createForm();
        loadImages();
        addFields();
        addButtons();
        layoutFrame();
        layoutReels();
        layoutOther();
    }
    
    /** Creates the JFrame and Panels. */
    private void createForm() {
        
        frmFrame = new JFrame();
        frmFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frmFrame.setTitle("Warner Slots");
        frmFrame.setResizable(false);
        frmFrame.setVisible(true);
        
        pnlReels = new JPanel();
        pnlReels.setBorder(BorderFactory.createEtchedBorder());
        
        pnlReel1 = new JPanel();
        pnlReel1.setBackground(new Color(255, 215, 0));
        pnlReel1.setBorder(new SoftBevelBorder(BevelBorder.LOWERED));
        pnlReel2 = new JPanel();
        pnlReel2.setBackground(new Color(255, 216, 0));
        pnlReel2.setBorder(new SoftBevelBorder(BevelBorder.LOWERED));
        pnlReel3 = new JPanel();
        pnlReel3.setBackground(new java.awt.Color(255, 215, 0));
        pnlReel3.setBorder(new SoftBevelBorder(BevelBorder.LOWERED));
        
    }
    
    /** Adds labels to the form. */
    private void addFields() {
        
        lblReel1 = new JLabel();
        lblReel2 = new JLabel();
        lblReel3 = new JLabel();
        
        sepStats = new JSeparator();
        lblMatchTwo = new JLabel();
        lblMatchTwo.setText("Matched Two: ");
        lblMatchThree = new JLabel();
        lblMatchThree.setText("Matched Three: ");
        lblWon = new JLabel();
        lblWon.setText("Won: ");
        
        sepStats2 = new JSeparator();
        sepStats2.setOrientation(SwingConstants.VERTICAL);
        lblCredits = new JLabel();
        lblCredits.setText("Credits: "+credits);
        lblMoney = new JLabel();
        lblMoney.setText("Money: £"+df.format(funds));
        lblLost = new JLabel();
        lblLost.setText("Lost: ");
        
        sepStatus = new JSeparator();
        lblStatus = new JLabel();
        lblStatus.setBackground(new Color(255, 255, 255));
        lblStatus.setFont(new Font("Arial", 1, 14));
        lblStatus.setHorizontalAlignment(SwingConstants.CENTER);
        lblStatus.setText("Welcome to WARNER SLOTS!!! ©2012");
        
        sepCheats = new JSeparator();
        prgbarCheatUnlocker = new JProgressBar();
        prgbarCheatUnlocker.setToolTipText("Fill the bar to unlock the cheat menu.");
        
        lblReel1.setIcon(images.get(reel1));
        lblReel2.setIcon(images.get(reel2));
        lblReel3.setIcon(images.get(reel3));
        
    }
    
    /** Adds buttons to the form. */
    private void addButtons() {
        
        btnSpin = new JButton();
        btnSpin.setBackground(new Color(50, 255, 50));
        btnSpin.setText("Spin");
        btnSpin.setToolTipText("Click to spin the reels!");
        btnSpin.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
        btnSpin.setInheritsPopupMenu(true);
        btnSpin.setMaximumSize(new Dimension(200, 50));
        btnSpin.setMinimumSize(new Dimension(200, 50));
        btnSpin.addActionListener(new SpinHandler());
        
        btnCash = new JButton();
        btnCash.setBackground(new Color(255, 0, 0));
        btnCash.setText("Buy Credits");
        btnCash.setToolTipText("£"+df.format(bet)+" converts to "+boughtCredits+" credits.");
        btnCash.setHorizontalTextPosition(SwingConstants.CENTER);
        btnCash.addActionListener(new BuyCreditsHandler());
        
        tgglSound = new JToggleButton();
        tgglSound.setSelected(false);
        tgglSound.setText("Sound ON");
        tgglSound.addActionListener(new SoundHandler());
        
        cbAlwaysWin = new JCheckBox();
        cbAlwaysWin.setText("Always Win Mode");
        cbAlwaysWin.setEnabled(false);
        cbAlwaysWin.addActionListener(new AlwaysWinHandler());
        
        cbTrollface = new JCheckBox();
        cbTrollface.setText("Trollface");
        cbTrollface.setEnabled(false);
        cbTrollface.addActionListener(new TrollfaceHandler());
        
        cbSuperJackpot = new JCheckBox();
        cbSuperJackpot.setText("Super Jackpot");
        cbSuperJackpot.setEnabled(false);
        cbSuperJackpot.addActionListener(new SuperPrizeHandler());
        
    }
    
    /** Lays out the frame. */
    private void layoutFrame() {
        
        GroupLayout frameLayout = new GroupLayout(frmFrame.getContentPane());
        frmFrame.getContentPane().setLayout(frameLayout);
        frameLayout.setHorizontalGroup(
        frameLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGap(0, 400, Short.MAX_VALUE)
        );
        frameLayout.setVerticalGroup(
        frameLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGap(0, 300, Short.MAX_VALUE)
        );
    }
    
    /** Lays out the panels and reels. */
    private void layoutReels() {
        
        GroupLayout pnlReelsLayout = new GroupLayout(pnlReels);
        pnlReels.setLayout(pnlReelsLayout);
        pnlReelsLayout.setHorizontalGroup(
        pnlReelsLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGroup(pnlReelsLayout.createSequentialGroup()
        .addContainerGap()
        .addComponent(pnlReel1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
        .addGap(18, 18, 18)
        .addComponent(pnlReel2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
        .addGap(18, 18, 18)
        .addComponent(pnlReel3, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
        .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        pnlReelsLayout.setVerticalGroup(
        pnlReelsLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGroup(pnlReelsLayout.createSequentialGroup()
        .addContainerGap()
        .addGroup(pnlReelsLayout.createParallelGroup(GroupLayout.Alignment.TRAILING, false)
        .addComponent(pnlReel2, GroupLayout.Alignment.LEADING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addComponent(pnlReel1, GroupLayout.Alignment.LEADING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addComponent(pnlReel3, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        
        GroupLayout pnlReel1Layout = new GroupLayout(pnlReel1);
        pnlReel1.setLayout(pnlReel1Layout);
        pnlReel1Layout.setHorizontalGroup(
        pnlReel1Layout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGroup(pnlReel1Layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(lblReel1)
        .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        pnlReel1Layout.setVerticalGroup(
        pnlReel1Layout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGroup(pnlReel1Layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(lblReel1)
        .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        
        GroupLayout pnlReel2Layout = new GroupLayout(pnlReel2);
        pnlReel2.setLayout(pnlReel2Layout);
        pnlReel2Layout.setHorizontalGroup(
        pnlReel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGroup(pnlReel2Layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(lblReel2)
        .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        pnlReel2Layout.setVerticalGroup(
        pnlReel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGroup(pnlReel2Layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(lblReel2)
        .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        
        GroupLayout pnlReel3Layout = new GroupLayout(pnlReel3);
        pnlReel3.setLayout(pnlReel3Layout);
        pnlReel3Layout.setHorizontalGroup(
        pnlReel3Layout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGroup(pnlReel3Layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(lblReel3)
        .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        pnlReel3Layout.setVerticalGroup(
        pnlReel3Layout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGroup(pnlReel3Layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(lblReel3)
        .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        
    }
    
    /** lays out the remaining labels, check boxes, progress bars, etc. */
    private void layoutOther() {
        
        GroupLayout layout = new GroupLayout(frmFrame.getContentPane());
        frmFrame.getContentPane().setLayout(layout);
        
        layout.setHorizontalGroup(
        layout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
        .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING, false)
        .addComponent(sepCheats)
        .addComponent(prgbarCheatUnlocker, GroupLayout.DEFAULT_SIZE, 426, Short.MAX_VALUE))
        .addGap(0, 0, Short.MAX_VALUE))
        .addGroup(layout.createSequentialGroup()
        .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING, false)
        .addGroup(layout.createSequentialGroup()
        .addComponent(cbAlwaysWin)
        .addGap(18, 18, 18)
        .addComponent(cbTrollface)
        .addGap(18, 18, 18)
        .addComponent(cbSuperJackpot)
        .addPreferredGap(ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addComponent(tgglSound))
        .addComponent(btnSpin, GroupLayout.Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addComponent(pnlReels, GroupLayout.Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addComponent(sepStats, GroupLayout.Alignment.TRAILING)
        .addComponent(lblStatus, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addGroup(layout.createSequentialGroup()
        .addGroup(layout.createParallelGroup(GroupLayout.Alignment.TRAILING, false)
        .addComponent(lblMatchTwo, GroupLayout.Alignment.LEADING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addComponent(lblWon, GroupLayout.Alignment.LEADING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addComponent(lblMatchThree, GroupLayout.DEFAULT_SIZE, 149, Short.MAX_VALUE))
        .addPreferredGap(ComponentPlacement.UNRELATED)
        .addComponent(sepStats2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(ComponentPlacement.UNRELATED)
        .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING, false)
        .addComponent(lblLost, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addComponent(lblCredits, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addComponent(lblMoney, GroupLayout.DEFAULT_SIZE, 154, Short.MAX_VALUE))
        .addGap(0, 0, Short.MAX_VALUE)))
        .addGroup(layout.createParallelGroup(GroupLayout.Alignment.TRAILING)
        .addComponent(btnCash)
        .addComponent(sepStatus, GroupLayout.PREFERRED_SIZE, 426, GroupLayout.PREFERRED_SIZE)))
        .addContainerGap())))
        );
        
        layout.setVerticalGroup(
        layout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(pnlReels, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(ComponentPlacement.RELATED)
        .addComponent(btnSpin, GroupLayout.PREFERRED_SIZE, 56, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(ComponentPlacement.UNRELATED)
        .addComponent(sepStats, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(ComponentPlacement.UNRELATED)
        .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
        .addComponent(lblWon, GroupLayout.PREFERRED_SIZE, 19, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(ComponentPlacement.RELATED)
        .addComponent(lblMatchTwo, GroupLayout.PREFERRED_SIZE, 19, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(ComponentPlacement.RELATED)
        .addComponent(lblMatchThree, GroupLayout.DEFAULT_SIZE, 25, Short.MAX_VALUE))
        .addComponent(sepStats2)
        .addGroup(layout.createSequentialGroup()
        .addComponent(lblLost, GroupLayout.PREFERRED_SIZE, 19, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(ComponentPlacement.RELATED)
        .addComponent(lblCredits, GroupLayout.PREFERRED_SIZE, 19, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(ComponentPlacement.RELATED)
        .addComponent(lblMoney, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        .addComponent(btnCash, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        .addPreferredGap(ComponentPlacement.UNRELATED)
        .addComponent(sepStatus, GroupLayout.PREFERRED_SIZE, 2, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(ComponentPlacement.UNRELATED)
        .addComponent(lblStatus, GroupLayout.PREFERRED_SIZE, 30, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(ComponentPlacement.UNRELATED)
        .addComponent(sepCheats, GroupLayout.PREFERRED_SIZE, 5, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(ComponentPlacement.RELATED)
        .addComponent(prgbarCheatUnlocker, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(ComponentPlacement.UNRELATED)
        .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
        .addComponent(cbAlwaysWin)
        .addComponent(cbTrollface)
        .addComponent(cbSuperJackpot)
        .addComponent(tgglSound))
        .addContainerGap())
        );
        
        frmFrame.pack();
        
    }
    
    /** Performs action when Buy Credits button is clicked. */
    class BuyCreditsHandler implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            buyCredits();
        }
    }
    
    /** if the player has enough funds credits are added. */
    public void buyCredits() {
        if (funds >= creditBuyout) {
            funds -= creditBuyout;
            lblMoney.setText("Money: £"+df.format(funds));
            credits += boughtCredits;
            lblCredits.setText("Credits: "+credits);
            lblStatus.setText("+"+boughtCredits+" credits purchased! -£"+df.format(creditBuyout));
            } else {
            lblStatus.setText("Insufficient £ to purchase credits!");
        }
        buyCreditsCheck();
    }
    
    /** if user has enough funds to buy credits changes buttons colour to alert user. */
    public void buyCreditsCheck() {
        if (funds < bet) {
            btnCash.setBackground(new java.awt.Color(255, 0, 0));
            } else {
            btnCash.setBackground(new java.awt.Color(50, 255, 50));
        }
    }
    
    /** Performs action when Spin button is clicked. */
    class SpinHandler implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            if (funds < creditBuyout && credits < bet) {
                lblStatus.setText("&lt;html&gt;<a href='http://www.gambleaware.co.uk/'>www.gambleaware.co.uk</a>&lt;/html&gt;");
                } else if ((credits - bet) &gt;= 0) {
                pnlReel1.setBackground(new java.awt.Color(255, 215, 0));
                pnlReel2.setBackground(new java.awt.Color(255, 215, 0));
                pnlReel3.setBackground(new java.awt.Color(255, 215, 0));
                genReelNumbers();
                matchCheck();
                } else {
                lblStatus.setText("Bet is "+bet+" credits, purchase more with £!");
            }
            buyCreditsCheck();
        }
    }
    
    /** Generates the 3 reel numbers. */
    public void genReelNumbers() {
        Random rand = new Random();
        if (cbAlwaysWin.isSelected() == true) { // If the Always win cheat mode is enabled.
            int winType = rand.nextInt(4); // generates number between 0-3 to determine the type of win
            reel1 = rand.nextInt(images.size());
            if (winType == 0) { // winType = 0 - Reels 1, 2 and 3 will all match.
                reel2 = reel1;
                reel3 = reel1;
                } else if (winType == 1) { // winType = 1 - Reels 1 and 2 will match.
                reel2 = reel1;
                } else if (winType == 2) { // winType = 2 - Reels 1 and 3 will match.
                reel3 = reel1;
                } else {    // winType = 3 - Reels 2 and 3 will match.
                if (reel1 &gt;= 0 ) {
                    reel2 = reel1 + 1;
                    reel3 = reel1 + 1;
                    } if (reel1 == images.size()-1) {
                    reel2 = reel1 - 1;
                    reel3 = reel1 - 1;
                }
            }
            } else { // If the Always win cheat mode is disabled play a normal game.
            reel1 = rand.nextInt(images.size());
            reel2 = rand.nextInt(images.size());
            reel3 = rand.nextInt(images.size());
        }
        setReelIcon(reel1, reel2, reel3); // Set the reel image
    }
    
    /** Sets the reels icon based on loaded image in images ArrayList. */
    public void setReelIcon(int ico1, int ico2, int ico3) {
        lblReel1.setIcon(images.get(ico1)); // icon = the ArrayList index = random reel number
        lblReel2.setIcon(images.get(ico2));
        lblReel3.setIcon(images.get(ico3));
    }
    
    /** Checks for number matches and adjusts score depending on result. */
    public void matchCheck() {
        if (reel1 == reel2 && reel2 == reel3) {
            lblStatus.setText("You matched THREE symbols ("+images.get(reel1).getDescription()+")! +£"+df.format(getPrize(payout))+"!");
            lblMatchThree.setText("Matched Three: "+matchThree());
            pnlReel1.setBackground(new java.awt.Color(255, 0, 0)); // Highlights matched icons.
            pnlReel2.setBackground(new java.awt.Color(255, 0, 0));
            pnlReel3.setBackground(new java.awt.Color(255, 0, 0));
            } else if (reel1 == reel2 || reel1 == reel3) {
            lblStatus.setText("You matched TWO symbols ("+images.get(reel1).getDescription()+")! +£"+df.format(getPrize(payout))+"!");
            lblMatchTwo.setText("Matched Two: "+matchTwo());
            if (reel1 == reel2) {
                pnlReel1.setBackground(new java.awt.Color(255, 0, 0)); // Highlights matched icons.
                pnlReel2.setBackground(new java.awt.Color(255, 0, 0));
                } else if (reel1 == reel3){
                pnlReel1.setBackground(new java.awt.Color(255, 0, 0)); // Highlights matched icons.
                pnlReel3.setBackground(new java.awt.Color(255, 0, 0));
            }
            } else if (reel2 == reel3) {
            lblStatus.setText("You matched TWO symbols ("+images.get(reel2).getDescription()+")! +£"+df.format(getPrize(payout))+"!");
            lblMatchTwo.setText("Matched Two: "+matchTwo());
            pnlReel2.setBackground(new java.awt.Color(255, 0, 0)); // Highlights matched icons.
            pnlReel3.setBackground(new java.awt.Color(255, 0, 0));
            } else {
            lblStatus.setText("Sorry, you didn't match any symbols. -"+bet+" credits!");
            lblLost.setText("Lost: "+lose());
        }
        lblCredits.setText("Credits: "+(credits -= bet)); // deduct bet amount from available credits.
        lblMoney.setText("Money: £"+df.format((funds += getPrize(payout)))); // If there is a win add amount to cash pot.
        lblWon.setText("Wins: "+win()); // increment win amount.
    }
    
    /** sets progress bar equal to the current win count. if bar is full it unlocks cheat menu */
    public void prgBarCheck() {
        if (prgbarCheatUnlocker.getValue() &lt;= 99) {
            prgbarCheatUnlocker.setValue(win);
            } else if (prgbarCheatUnlocker.getValue() == 100) { // after 100 wins unlock the cheats.
            prgbarCheatUnlocker.setValue(100);
            lblStatus.setText("100 wins! Congratulations you've unlocked the cheat menu!");
            cbTrollface.setEnabled(true);
            cbSuperJackpot.setEnabled(true);
            cbAlwaysWin.setEnabled(true);
        }
    }
    
    /** calculates prize to be awarded for win based on number of matches and cheats. */
    public double getPrize(double prize) {
        if (reel1 == reel2 && reel2 == reel3) {
            if (cbSuperJackpot.isSelected() == true) {
                prize *= 100; // if cheating and all are matched return the full pay out x100.
                } else {
                prize = payout; // if all are matched return the full pay out.
            }
            } else if (reel1 == reel2 || reel1 == reel3 || reel2 == reel3) {
            if (cbSuperJackpot.isSelected() == true) {
                prize *= 50; // if cheating and two are matched return the pay out x50.
                } else {
                prize = payout / 5; // if two are matched return 1/5th of the pay out.
            }
            } else {
            prize = 0; // If no win return no prize.
        }
        return prize;
    }
    
    /** Performs action when Super Jack pot check box is clicked. */
    class SuperPrizeHandler implements ActionListener{
        public void actionPerformed(ActionEvent e) {
            if (cbSuperJackpot.isSelected() == true) {
                lblStatus.setText("Super Prize mode ENABLED! The £ won is now x100!");
            }
            if (cbSuperJackpot.isSelected() == false) {
                lblStatus.setText("Super Prize mode DISABLED! :'(");
            }
        }
    }
    
    /** Performs action when Troll face check box is clicked. */
    class AlwaysWinHandler implements ActionListener{
        public void actionPerformed(ActionEvent e) {
            if (cbAlwaysWin.isSelected() == true) {
                lblStatus.setText("Always Win mode ENABLED! 7-7-7's here we come!");
            }
            if (cbAlwaysWin.isSelected() == false) {
                lblStatus.setText("Always Win mode DISABLED! :'(");
            }
        }
    }
    
    /** Performs action when Troll face check box is clicked. */
    class TrollfaceHandler implements ActionListener{
        public void actionPerformed(ActionEvent e) {
            if (cbTrollface.isSelected() == true && images.get(images.size()-1) != createImageIcon("images/Trollface.png", "Trollface")) {
                images.add(createImageIcon("images/Trollface.png", "Trollface")); // adds a bonus image to the images ArrayList.
                lblStatus.setText("Trollface mode ENABLED! Trolololololol!");
            }
            if (cbTrollface.isSelected() == false && images.get(images.size()-1) != createImageIcon("images/Trollface.png", "Trollface")) {
                images.remove(images.size()-1); // removes the bonus image (or last one added to the ArrayList) from the images ArrayList.
                lblStatus.setText("Trollface mode DISABLED! :'(");
            }
        }
    }
    
    /** Performs action when sound toggle button is clicked.
    * NOT IMPLEMENTED
    */
    class SoundHandler implements ActionListener{
        public void actionPerformed(ActionEvent e) {
            if (tgglSound.isSelected() == false) {
                tgglSound.setText("Sound ON");
                lblStatus.setText("Sound effects have been ENABLED!");
                // allowed to play sounds
                } else {
                tgglSound.setText("Sound OFF");
                lblStatus.setText("Sound effects have been DISABLED!");
                // disable sounds
            }
        }
    }
    
    /** Loads ImageIcons into the images ArrayList.
    *    The difficulty is determined by the number of images present in the ArrayList:
    *    •    Add images here to make game more difficult.
    *    •    Remove images here to make game easier.
    */
    public void loadImages() {
        images.add(createImageIcon("images/Banana.png", "Banana"));
        images.add(createImageIcon("images/Bar.png", "Bar"));
        images.add(createImageIcon("images/Bell.png", "Bell"));
        images.add(createImageIcon("images/Cherry.png", "Cherry"));
        images.add(createImageIcon("images/Clover.png", "Clover"));
        images.add(createImageIcon("images/Diamond.png", "Diamond"));
        images.add(createImageIcon("images/Plum.png", "Plum"));
        images.add(createImageIcon("images/Seven.png", "Seven"));
        images.add(createImageIcon("images/Watermelon.png", "Watermelon"));
    }
    
    /** Create a new ImageIcon, unless the URL is not found. */
    public ImageIcon createImageIcon(String path, String description) {
        java.net.URL imgURL = getClass().getResource(path);
        if (imgURL != null) {
            return new ImageIcon(imgURL, description);
            } else {
            System.err.println("Couldn't find file: " + path);
            return null;
        }
    }
    
    /** Increments matchThree by 1 and returns value. */
    public int matchThree() {
        matchThree++;
        return matchThree;
    }
    
    /** Increments matchTwo by 1 and returns value. */
    public int matchTwo() {
        matchTwo++;
        return matchTwo;
    }
    
    /** Increments lost by 1 and returns value. */
    public int lose() {
        lost++;
        return lost;
    }
    
    /** Increments win by 1, increases progress bar and returns value. */
    public int win() {
        win = matchThree + matchTwo;
        prgBarCheck(); // Increments the progress bar to unlock cheat menu.
        return win;
    }
    
    public static void main(String args[]) {
        
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
            } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(SlotMachineGUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
            } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(SlotMachineGUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
            } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(SlotMachineGUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
            } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(SlotMachineGUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        
        java.awt.EventQueue.invokeLater(new Runnable() {
            
            public void run() {
                new SlotMachineGUI();
            }
        });
        
    }
    
}

Kudos to M ajes­tic, a YouTube user, for the code above. Here are the images that he used in the cre­ation of the game.

If you have read this far, you may as well fol­low me on Twit­ter:

April 1, 2014
by poornerd
9 Comments

How to implement a Session Timeout in Play Framework 2

If you fol­low the Play Frame­work 2 guide for imple­ment­ing authen­ti­ca­tion: http://​www​.playframe​work​.com/​d​o​c​u​m​e​n​t​a​t​i​o​n​/​2​.​2​.​2​/​J​a​v​a​G​u​i​de4 — you will notice that there is no ses­sion time­out in Play Frame­work 2. It was there in Play Frame­work 1, but Play Frame­work 2 fol­lows a dif­fer­ent approach.

I you want to imple­ment your own ses­sion time­out, then fol­low the guide for set­ting up authen­ti­ca­tion, by extend­ing the Security.Authenticator, and store a time­stamp in the ses­sion and keep extend­ing it every time a request is made.

Here is how I did it:

public class Secured extends Security.Authenticator {

    public static final String UNAUTHENTICATED = "unauthenticated";

    public static User getLoggedInUser() {
        if (session("userId") == null)
            return null;
        return User.findById(Long.parseLong(session("userId")));
    }

    public static String getLoggedInUsername() {
        if (session("userId") == null)
            return null;
        return User.findById(Long.parseLong(session("userId"))).getUsername();
    }


    @Override
    public String getUsername(Http.Context ctx) {

        // see if user is logged in
        if (session("userId") == null)
            return null;

        // see if the session is expired
        String previousTick = session("userTime");
        if (previousTick != null && !previousTick.equals("")) {
            long previousT = Long.valueOf(previousTick);
            long currentT = new Date().getTime();
            long timeout = Long.valueOf(Play.application().configuration().getString("sessionTimeout")) * 1000 * 60;
            if ((currentT - previousT) > timeout) {
                // session expired
                session().clear();
                return null;
            } 
        }

        // update time in session
        String tickString = Long.toString(new Date().getTime());
        session("userTime", tickString);

        return User.findById(Long.parseLong(session("userId"))).getUsername();
    }
}

Then just add a sessionTimeout=15 (in Min­utes) to your conf file.

If you have read this far, you may as well fol­low me on Twit­ter:

March 18, 2014
by poornerd
0 comments

Examples for working with JSON in Play Framework 2 with Java

  • String to JSON
    JsonNode json = mapper.readTree(notification.getSharedData());
  • Json to Object: 
    Asset asset = mapper.readValue(a.toString(), Asset.class)
  • Json to extist­ing Object: 
    asset = mapper.readerForUpdating(asset).readValue(assetJson.toString())
  • Object to JsonString:
    mapper.writeValueAsString(new ResultDocument(document))
  • Con­vert Object ot JsonNode:
    JsonNode documentNode = mapper.convertValue(document, JsonNode.class);

    This causes an error — Caused by: com.fasterxml.jackson.databind.JsonMappingException: Direct self-reference leading to cycle so do this workaround:

    String messageJson = null;
    
    String documentAsString = BaseApiController.mapper.writeValueAsString(document);
    
    JsonNode documentNode = BaseApiController.mapper.readTree(documentAsString);
If you have read this far, you may as well fol­low me on Twit­ter:

March 13, 2014
by poornerd
0 comments

Configuring Play2War with Play 2.2 and build.sbt

Screen Shot 2014-03-13 at 21.07.45I had pre­vi­ously used Play2War with Play 2 projects that con­tained a Build.scala file fol­low­ing the con­fig­u­ra­tion direc­tions from Play2War. How­ever, when attempt­ing to con­fig­ure a newer project which now uses a build.sbt instead of the Build.scala, It wasn’t imme­di­ately appar­ent how to do it.

  1. Add this to your plugins.sbt: addSbtPlugin(“com.github.play2war” % “play2-war-plugin” % “1.2-beta4”)
  2. Add this to the top of your build.sbt:
     import com.github.play2war.plugin._
  3. Add this at the end of your build.sbt: (yes with the line between them, and the servlet Ver­sion you need). 
    Play2WarPlugin.play2WarSettings
    Play2WarKeys.servletVersion := "2.5"
  4. Run “play war” and when it is done, look in the tar­get directory
If you have read this far, you may as well fol­low me on Twit­ter:

January 4, 2014
by poornerd
0 comments

How to configure an SSL Certificate with Play Framework for https

sslI spent hours try­ing to get this to work, and in the end, then prob­lem was that I did not gen­er­ate the CSR (Cer­tifi­cate Request) myself with the keytool.

I kept get­ting this error when I tried access­ing Play with https:
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated

The prob­lem ended up being that the key­store I cre­ated and imported the SSL cer­tifi­cate into did not have the pub­lic key that was used for the CSR (cer­tifi­cate request).

So here it the quick ver­sion of gen­er­at­ing an SSL Cer­tifi­cate with godaddy​.com and installing it with Play Frame­work 2.1+.

1. Fol­low these instruc­tions from godaddy​.com to gen­er­ate the CSR like this:
First gen­er­ate the key pair like this:

keytool -keysize 2048 -genkey -alias tomcat -keyalg RSA -keystore tomcat.keystore

Then gen­er­ate the CSR:

keytool -certreq -alias tomcat -file csr.txt -keystore tomcat.keystore

2. Use the CSR to apply for certificate

3. Add the Inter­me­di­ate Cer­tifi­cate Bun­dle and the Cer­tifi­cate that were gen­er­ated to your key­store.
NOTE: make sure this is the same key­store that you gen­er­ated the pri­vate key in, in step 1!

keytool -import -alias intermed -keystore tomcat.keystore -trustcacerts -file gd_bundle.crt
keytool -import -alias tomcat -keystore tomcat.keystore -trustcacerts -file mycert.crt

(Replace mycert.crt with the file name and loca­tion of the new GoDaddy certificate)

4. Finally, fol­low­ing the instruc­tions for con­fig­ur­ing https with Play 2.1+ ( http://​www​.playframe​work​.com/​d​o​c​u​m​e​n​t​a​t​i​o​n​/​2​.​2​.​1​/​C​o​n​f​i​g​u​r​i​n​g​H​t​tps ) which had already worked great with the self gen­er­ated key, I cre­ated a shell script for start­ing Play with the cor­rect parameters:

# script for starting play in production with SSL and the keystore
target/start -Dhttps.port=443 -Dhttps.keyStore=/Users/bp/mypath/tomcat.keystore -Dhttps.keyStorePassword=itl80809

Note: you need to do a “play dist” before­hand, so that the cur­rent Soft­ware is com­piled into a dis­tri­b­u­tion in the tar­get subdirectory.

If you have read this far, you may as well fol­low me on Twit­ter:

December 6, 2013
by poornerd
1 Comment

How to run a Play Framework Application in Tomcat or JBoss

So you have writ­ten an appli­ca­tion using Play! Frame­work 2.x and your Sys­tem Admin­is­tra­tor tells you that he won’t install Play! and that your appli­ca­tion needs to run in a sup­ported Java Servlet Con­tainer like JBoss, Tom­cat or Glassfish?

No wor­ries, you can use the play2-war-plugin to cre­ate a WAR file which can be deployed in a Java Servlet 2.5 or 2.3 spec Servlet Container!

Here is the Quick Guide for 2.1.x (mainly from stack­over­flow):

  1. Within plugins.sbt add:
    addSbtPlugin("com.github.play2war" % "play2-war-plugin" % "1.1.1")

    Accord­ing to the project web page, you will need to pick the cor­rect Ver­sion for you Play! Frame­work version:

        Play 2.0 -> 2.0.4 : 0.8.1
        Play 2.0.5+       : 0.8.2
        Play 2.1 -> 2.1.3 : 1.0.1
        Play 2.1.4+       : 1.1.1
        Play 2.2.x        : 1.2-beta1 (unstable)
    
  2. Then within the Build.scala file add:
    import com.github.play2war.plugin._
    ...
    .settings(Play2WarPlugin.play2WarSettings: _*)
    .settings(Play2WarKeys.servletVersion := "X.X")
    

    The X.X needs to be changed to the required Java Servlet ver­sion (usu­ally 3.0 or 2.5 depend­ing on your con­tainer)

If you are using Play! Frame­work 2.2 then you can also try this method with­out the Plu­gin, just using sbt http://​play2war​.blogspot​.in.

If you have read this far, you may as well fol­low me on Twit­ter:

October 22, 2013
by poornerd
0 comments

Crowd Dialog: Crowdfunding Conference 2013

Screen Shot 2013-10-22 at 15.10.12
Some of you have noticed that I have not pub­lished much recently.  In addi­tion to my day-job, I have been orga­niz­ing a Crowd Fund­ing con­fer­ence for Star­tups and Com­pa­nies that want to use Crowd­fund­ing to finance new prod­ucts.  The Crowd Dia­log Con­fer­ence is on Nov. 21 in Munich, Ger­many if you are inter­ested in coming.

If you have read this far, you may as well fol­low me on Twit­ter:

July 25, 2013
by poornerd
2 Comments

How to set the Java Version for Compiling a Play Framework Module

@jaliss and I were recently won­der­ing how to make sure that a mod­ule was com­piled with JSDK 1.6 after we had upgraded the Java Ver­sion on our Macbooks.

Stack­over­flow user mgre­gori just pro­vided this solu­tion (Thanks!):

In the file project/Build.scala, add the set­ting below if you want to com­pile with java 1.6 :

val main = play.Project(appName, appVersion, appDependencies).settings(
  // Force compilation in java 1.6
  javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6")
)
If you have read this far, you may as well fol­low me on Twit­ter:

July 5, 2013
by poornerd
4 Comments

How to integrate Concordion in Play Framework and write acceptance tests in natural language

Screen Shot 2013-07-05 at 10.24.25After start­ing to read “Spec­i­fi­ca­tion by Exam­ple”, I wanted to have a go a writ­ing accep­tance tests for my Play Frame­work projects.  I stum­bled onto Con­cor­dion (http://​www​.con​cor​dion​.org/), because it is sim­i­lar to Cucum­ber but writ­ten in and for Java.

Con­cor­dion is sim­i­lar to Cucum­ber but focuses on readability.

Con­cor­dion inte­grates as Unit tests (which is the pri­mary focus of this post), and lets you write the Tests in Nat­ural Lan­guage in HTML Files so that it also becomes self documenting.

I got most of how to inte­grate Con­cor­dion from Craig Aspinall in Aus­tralia (@aspinall) who had writ­ten a Blog post about it, but which was no long “online”.   (he sent me a link to the mark­down so I could try it out — thank Craig!)

Inte­grat­ing Concordion

You first need to add the depen­dency to your Build.scala, and then add a line so that the SBT copies the Con­cor­dion HTML files:

import sbt._
import Keys._
import play.Project._

object ApplicationBuild extends Build {

    val appName         = "computer-database"
    val appVersion      = "1.0"

    val appDependencies = Seq(
       "org.concordion" % "concordion" % "1.4.2" % "test"  ,
      javaCore,
      javaJdbc,
      javaEbean
    )

    val main = play.Project(appName, appVersion, appDependencies).settings(
      // Next, you need to instruct SBT to copy the Concordion HTML files to the target folder
        unmanagedClasspath in Test <+= (baseDirectory) map { bd => Attributed.blank(bd / "test") }
    )
}

Telling Play where to out­put the Test results to

This turned out to be a chal­lenge. If you do not set the concordion.output.dir sys­tem prop­erty, then they are out­puted to your Java temp direc­tory. Craig sug­gested set­ting it in the Build.scala, but as it turns out the most recent Ver­sion of Play forks the Tests into a sep­a­rate process and loses the set­ting. So I ended up adding this to my Global Set­tings class:

import play.Application;
import play.GlobalSettings;
public class Global extends GlobalSettings {

    public void onStart(play.Application arg0) {
        if (arg0.isTest())
            System.setProperty("concordion.output.dir", "target/test-reports/concordion");
    }

}

Note: I think I dis­cov­ered another Bug in Play, because although this works, if I run “test-only” it doesn’t!!!

The Test

The key to get­ting Play to rec­og­nize and run Unit Tests is the @Test anno­ta­tion, so it seems that the eas­i­est way to get Play to run the Con­cor­dion tests is to add a method like this to your Con­cor­dion fix­ture class:

    @Test
    public void runThisTest() {
    }

I ended up imple­ment­ing one of the tuto­r­ial exam­ples from here: http://​www​.con​cor​dion​.org/​T​u​t​o​r​i​a​l​.​h​tml

You write the test case in HTML, and use <span> tags with Con­cor­dion ele­ments to spec­ify the paramters and assertions:

<html xmlns:concordion="http://www.concordion.org/2007/concordion">

    <head>
        <link href="../concordion.css" rel="stylesheet" type="text/css" />
    </head>

    <body>

        <h1>Splitting Names</h1>

        <p>
            To help personalise our mailshots we want to have the first name
            and last name of the customer. Unfortunately the customer data
            that we are supplied only contains full names.
        </p>

        <p>
            The system therefore attempts to break a supplied full name into
            its constituents by splitting around whitespace.
        </p>

        <div class="example">

            <h3>Example</h3>

            <p>
                The full name
                <span concordion:execute="#result = split(#TEXT)">John Smith</span>
                will be broken into first name
                <span concordion:assertEquals="#result.firstName">John</span>
                and last name
                <span concordion:assertEquals="#result.lastName">Smith</span>.
            </p>

        </div>
    </body>
</html>

Then you write a fix­ture class with the Tests (and the @Test “hack” I mentioned).

import org.junit.Test;
import play.mvc.Result;

import java.util.HashMap;
import java.util.Map;

import static org.fest.assertions.Assertions.assertThat;
import static play.test.Helpers.*;

import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;
import org.junit.Test;

@RunWith(ConcordionRunner.class)
public class ConcordianTestFixture {

    public Result split(String fullName) {
        Result result = new Result();
        String[] words = fullName.split(" ");
        result.firstName = words[0];
        result.lastName = words[1];
        return result;
    }

    class Result {
        public String firstName;
        public String lastName;
    }

    @Test
    public void runThisTest() {
    }
}

When you run the tests, then the result­ing HTML page it out­puted (as pre­vi­ously defined) to your target/test-reports directory. Here is an exam­ple of a failed test (for a suc­cess­ful one, see the image at the start of the post):

Screen Shot 2013-07-05 at 10.30.39

I hope you try this out!  Let me know if you have more ideas or tips…

If you have read this far, you may as well fol­low me on Twit­ter:

June 27, 2013
by poornerd
0 comments

How to use Reflection to Document your Data Model based on JPA Annotations

annotation_reflectionSo using JPA, Hiber­nate or EBeans is cool when you can just anno­tate your Java classes, but haven’t you always wished you could “gen­er­ate” doc­u­men­ta­tion of your data model from the code? Pulling infor­ma­tion of the the JPA / Hiber­nate  and other val­i­da­tion annotations?

Assum­ing you have all those nice Anno­ta­tions in your beans:

@Entity
@Table(name = "project_bills")
public class Bill extends Model {
	
	private static final long serialVersionUID = 1L;

	@Id
	@Column(name="PBI_ID")
	public Long id;
	
	@DoubleFormat
	@Column(name="PBI_BILL_AMOUNT",length=22)
	public Double billAmount;
	
	@Column(name="PBI_BILL_DATE")
	@DateTime(pattern="dd.MM.yyyy")
	public Date billDate;
	
	@Column(name="PBI_BILL_NUMBER",length=10)
	public String billNumber;
	
	@Column(name="PBI_CAN_BILL")
	public Boolean canBill;
	
	@Column(name="PBI_COMMENT",length=65535)
	public String comment;
	
	@Column(name="PBI_PAID_DATE")
	@DateTime(pattern="dd.MM.yyyy")
	public Date paidDate;

 

Here is an exam­ple of how to go about accom­plish­ing that task:

    public static String listEntities(String _package) {
        StringBuffer retval = new StringBuffer();
        Reflections reflections = new Reflections(_package, Play.application().classloader());
        Set<Class<?>> classes = reflections.getTypesAnnotatedWith(javax.persistence.Entity.class);
        for (Class c : classes) {
            retval.append(c.getName() + "\n");
            retval.append(listAnnotations(c.getName()) + "\n\n");
            retval.append(listAttributes(c.getName()) + "\n\n");
        }
        return retval.toString();
    }

    public static String listAnnotations(String _class) {
        StringBuffer retval = new StringBuffer();
        try {
            Annotation[] annotations = Class.forName(_class).getAnnotations();
            if (annotations.length != 0) {
                for (int j = 0; j < annotations.length; j++) {
                    retval.append(annotations[j].toString() + ": " + annotations[j].annotationType() + "\n");
                }
                retval.append("\n");
            }
        } catch (ClassNotFoundException e) {
            System.out.println(e.toString());
            return retval.toString();
        }
        return retval.toString();
    }

    public static String listAttributes(String _class) {
        StringBuffer retval2 = new StringBuffer();
        boolean perstistent = false;
        try {
            for (Field field : Class.forName(_class).getDeclaredFields()) {
                Class type = field.getType();
                String name = field.getName();
                perstistent = false;
                StringBuffer retval = new StringBuffer();
                retval.append("\t" + name + " (" + type + ")\n");
                Annotation[] annotations = field.getDeclaredAnnotations();

                if (annotations.length != 0) {
                    for (int j = 0; j < annotations.length; j++) {
                        retval.append(annotations[j].toString() + ": " + annotations[j].annotationType() + "\n");
                        if (annotations[j].toString().startsWith("@javax.persistence")) {
                            perstistent = true;
                        }
                    }
                    retval.append("\n");
                }
                if (perstistent) {
                    retval2.append(retval);
                }
            }
        } catch (ClassNotFoundException e) {
            System.out.println(e.toString());
            return retval2.toString();
        }
        return retval2.toString();
    }

Which will gen­er­ate some­thing like this:

models.controlling.Bill
@javax.persistence.Table(schema=, uniqueConstraints=[], catalog=, name=project_bills): interface javax.persistence.Table
@javax.persistence.Entity(name=): interface javax.persistence.Entity

	id (class java.lang.Long)
@javax.persistence.Id(): interface javax.persistence.Id
@javax.persistence.Column(insertable=true, scale=0, unique=false, precision=0, columnDefinition=, name=PBI_ID, updatable=true, length=255, nullable=true, table=): interface javax.persistence.Column

	billAmount (class java.lang.Double)
@utils.data.formatters.Formats$DoubleFormat(): interface utils.data.formatters.Formats$DoubleFormat
@javax.persistence.Column(insertable=true, scale=0, unique=false, precision=0, columnDefinition=, name=PBI_BILL_AMOUNT, updatable=true, length=22, nullable=true, table=): interface javax.persistence.Column

	billDate (class java.util.Date)
@javax.persistence.Column(insertable=true, scale=0, unique=false, precision=0, columnDefinition=, name=PBI_BILL_DATE, updatable=true, length=255, nullable=true, table=): interface javax.persistence.Column
@play.data.format.Formats$DateTime(pattern=dd.MM.yyyy): interface play.data.format.Formats$DateTime

	billNumber (class java.lang.String)
@javax.persistence.Column(insertable=true, scale=0, unique=false, precision=0, columnDefinition=, name=PBI_BILL_NUMBER, updatable=true, length=10, nullable=true, table=): interface javax.persistence.Column

Of course this is only the tip of the ice­berg, but you get the idea.

Let me know if you have some good ideas of what other types of doc­u­men­ta­tion can be gen­er­ated from the source code.

If you have read this far, you may as well fol­low me on Twit­ter: