As I was working on a small application, I started implementing a custom JToolTip to be used throughout the app, in stead of Swing’s default JToolTip.
Testing the application I was surprised by the behavior of the tooltips when they would exceed the borders of a JFrame (exceeding for a pixel was enough).
In the image below, you can notice that a background (default Swing Color) is present for the tooltip component. This background was not present for the exact same tooltip if it appeared inside the frame. So, the goal was to remove this background.
First thing that came in mind was, ‘I should change the background to a transparent one’. And so I did, by calling this line on the JToolTip (mind the last parameter, the alpha value of the color):
setBackground(new Color(255, 255, 255, 0));<br />
Obviously that didn’t do the trick, but it was one step closer to the solution. In fact, someone pointed out that when the component surpasses the frame’s borders, it is actually added to a JWindow prior to being displayed.
SwingUtilities provides a method that returns the Window of a specified component:
SwingUtilities.windowForComponent(...);
It sufficed to update the background color of the window, in which the tooltip was housed, with a transparent one (alpha value of 0).
As an additional measure I made sure all the parent components of the tooltip were set to be non-opaque.
The final solution could be implemented in the overridden addNotify() method:
In the above code, you may notice the catch block handling potentially raised exception: IllegalComponentStateException. It was vital to add that catch statement in. A tooltip inside a frame, not exceeding the borders, is not added to a JWindow before being displayed. This means no window background should be changed, as there would be no window.
Find the question on StackOverflow for further reference.
With Jetbrains‘ recent plugin, ‘JVM Debugger Memory View’ you can go the extra mile while debugging your applications in IntelliJ IDEA and Android Studio. If you want to keep an eye on the heap, the plugin is a valid tool to use during your debug sessions!
Memory View basically lists a ton of Objects that are placed on the heap at the time present. It gives you the possibility to get a better feel of the current situation (at a breakpoint) of your application, memory-wise. The tool has packed its features inside a new tab in the IDE. You can find it by default in a panel on the right side.
In the overview you can see how many Objects have been added, removed when stepping over code. If you would place a few breakpoints you can easily view how the just executed code has affected the heap.
It is also possible to track new Instances of a particular Object. To do so you will need to turn on this feature for every class name listed.
Opening the class instances dialog enables filtering. Apply a filter with an evaluated expression.
Once you have a desired overview of instances you can unfold instances, just like you already could in the variables section in the regular debug mode. This is great for inspection of the objects. Objects where exceptions were raised properly stand out as well.
Furthermore, there is a possibility to view the call stack of the instance. When an object is selected you can view this in the stack frame on the right.
Another nice feature is the ‘Referring Objects For Object X’ one. To open up this frame, right click on an object and click ‘Show Referring Objects…’. You will now get a thorough overview of every referrer of the selected Object.
If you’ve got IntelliJ IDEA 2016.1 or higher, you can simply install the plugin from the Jetbrains repository.
As a side note I’d like to warn Android Studio users. The IDE may freeze due to a bug still present at the time of writing. Also, getting large amounts of instances could cause problems due to memory restrictions in Android. It seems the development is still ongoing. If you’ve found a bug, you can always log it in their issue tracker.
So you wrote this game, but would now like to step up? Unlockable achievements is an aspect that enhances your game and it’s fun and interesting to program.
The Concept
Let’s elaborate on the actual purpose and the concept of unlockable achievements. Basically you could consider an Achievement to be a milestone. It is a goal your end user achieves, literally. It is up to you to determine these milestones.
Let’s take Minecraft‘s ‘Taking Inventory’ achievement as an example. The achievement is unlocked at the moment the inventory is opened for the first time. This example is a single Achievement, meaning it has no predecessors or successors. Possibly you’d want to create an achievement that could only be unlocked when this one was unlocked first. You may even want to have a set of achievements unlocked before achieving a parent achievement.
The previous example was obviously very simplistic. There was one task, one goal that lead to the unlocking of the achievement. Different achievements might consist of multiple milestones to reach before unlock. Or maybe you’d want specific actions to take place in order to achieve the achievement.
Achievements Everywhere
Gaming companies, publishers and console builders all make use of achievements. Think about the trophies you can earn on PlayStation and XBox consoles. Think about achievements that you can collect on the Steam platform. And don’t forget the achievements on mobile devices in Google Play Games (Android) and Game Center (iOS). There are so many possibilities, so many things you can do with your in-game achievements. You can go beyond an implementation in your game itself.
Implementation
Let’s put things in practice by covering a possible implementation approach in Java.
You want to get things started by defining an interface:
public interface Achievable {
void achieve();
}
Now we’re ready to define an Achievement class. You can make it Observable as we did in the following example. That way registered observers can get notified at the moment the Achievement is unlocked.
public class Achievement extends Observable implements Achievable {
private String name, description;
private List<AchievementProperty> propertyList;
private boolean unlocked;
/**
* Class constructor specifying name, description and properties to be activated.
*
* @param name the untranslated name of the achievement
* @param description the untranslated description of the achievement
* @param properties the properties related to the achievement
*/
public Achievement(String name, String description, List<AchievementProperty> properties) {
this.name = name;
this.description = description;
this.propertyList = properties;
this.unlocked = false;
}
/**
* Unlocks this achievement and notifies its' observers.
*/
@Override
public void achieve() {
if (!unlocked) {
boolean allActive = true;
for (AchievementProperty property : propertyList) {
if (!property.isActive()) {
allActive = false;
break;
}
}
if (allActive) {
this.unlocked = true;
setChanged();
notifyObservers();
}
}
//Getters, setters and toString omitted...
}
As you can see in the above code, the achievement contains a list of AchievementProperty. These are properties that need to be active when the achievement is unlocked.
Here’s how the AchievementProperty class may look like:
public class AchievementProperty {
private String name;
private int value;
private String activation;
private int activationValue;
private int initialValue;
private String tag;
/**
* Class constructor specifying name, activation, activation value and initial value.
*
* @param name the untranslated name of the property
* @param activation the activation method of the property
* @param activationValue the activation value of the property
* @param initialValue the initial value of the property
*/
public AchievementProperty(String name, String activation, int activationValue, int initialValue) {
this.name = name;
this.activation = activation;
this.activationValue = activationValue;
this.initialValue = initialValue;
}
/**
* Class constructor specifying name, activation, activation value, initial value and tag.
*
* @param name the untranslated name of the property
* @param activation the activation method of the property
* @param activationValue the activation value of the property
* @param initialValue the initial value of the property
* @param tag the tag of the property
*/
public AchievementProperty(String name, String activation, int activationValue, int initialValue, String tag) {
this.name = name;
this.activation = activation;
this.activationValue = activationValue;
this.initialValue = initialValue;
this.tag = tag;
}
/**
* Returns true when this property is active, false if otherwise.
*
* @return true when this property is active, false if otherwise
*/
public boolean isActive() {
boolean flag = false;
switch (activation) {
case Achiever.ACTIVE_IF_GREATER_THAN:
flag = value > activationValue;
break;
case Achiever.ACTIVE_IF_LESS_THAN:
flag = value < activationValue;
break;
case Achiever.ACTIVE_IF_EQUALS_TO:
flag = value == activationValue;
}
return flag;
}
/**
* Resets this property's current value.
*/
public void reset() {
this.value = initialValue;
}
Now it’s up to you to get all that linked up by creating the ‘Achiever’ class. I’ll leave that as an exercise to the reader. Here’s a bit more context to get you going:
Make use of a HashMap to keep AchievementProperty instances;
Make use of a HashMap to keep Achievement instances;
Provide a ‘defineProperty’ method that creates and returns a new AchievementProperty based on specified parameters. If the property is not yet listed in the HashMap, put it in there;
Provide a ‘defineAchievement’ method that creates and returns a new Achievement based on specified parameters (do not forget its’ related Achievementproperty/AchievementProperties!). If the achievement is not yet listed in the HashMap, put in in there;
Add a method that will achieve all eligible achievements (don’t forget checks on the related AchievementProperty/AchievementProperties;
Add methods to add/set a value to properties, or even to reset property values.
At this point you can create your new achievements and their respective related property/properties.
On the moment a property needs to get an update, you call the ‘addValue’ method from step 6 and you call the ‘checkAchievements’ method from step 5. This way your achievements can get unlocked at the proper time.
Since my experience with Selenium is growing I become to understand it better every day. At first I was amazed about its possibilities. You can do a lot with Selenium WebDriver, but soon enough I came to realize it’s missing a few aspects. That’s how Bromine evolved from idea to project.
Filling in the Gaps
The Bromine project’s aim is to fill in the gaps in the default Selenium WebDriver Livery for Java. In stead of using Selenium, use Bromine that will on its turn use Selenium for you, while still leaving you enough means of customizing the WebDriver functionality.
Screenshots
A good example of a feature I was really missing in Selenium was a descent way of taking screenshots. The method getScreenshotAs(OutputType.FILE); just simply didn’t quit fulfill my needs (and those were from an automatic testing’s perspective). What I specifically wanted to be able to take a screenshot of the current browser instance any time I’d desire, but more importantly, every time a unit test fails.
In Bromine, a simple JUnit rule can take care of just that. Add it to your test class as follows:
@Rule
public ScreenShotOnFailure failure = new ScreenShotOnFailure("./screenshots/");
Statistics
When I use Selenium in a test framework, at some point, I might become interested in several statistics. Of course I do not intend to re-invent the wheel. I respect the current web analytics standard of today, Google Analytics, and I believe it to cover most needs one can have regarding statistics and analysis of a website.
What Google Analytics doesn’t provide you is statistics of events on your web page right in your test framework.
Why would that even be useful? Well, if you’re creating an automated test framework you are trying to ensure a qualitative final product for your users to interact with. Wouldn’t you want to know how many clicks one has to perform before reaching a particular page? Aren’t you interested in how many times form fields have to be filled in before a desired action occurs? Those are examples of what Bromine would like to cover.
As of version 0.2-alpha, a basic implementation of this is in place, ready for you to extend at will. I made sure I provided at least a very minimum, being tracking of left mouse button clicks, double clicks and the amount of times keys are entered.
Since I can image one might find the need to create their own tracking means, I ensured the system works with plugins (StatsPlugin) that can be registered and enabled any time.
Bringing SUT Structure
Selenium merely provides the ability to browse through websites, while what many might want to do would be structuring their System Under Test (SUT) inside their framework. It is a good practice to do so, since you might want to reuse pages and features that are used across the site multiple times.
Bromine provides a base to register pages (Page) and their respective sections (section) to a globally accessible collection (Pages). The easiest way to implement your application structure would be to assign a specific package in your project to contain classes extending the Page class. If you make use of the default no-parameter constructor, you won’t even have to create new instances of your pages. Just make sure to call registerAllPagesFromPackage(String pack) from the Pages class to register all your predefined pages to the collection.
Work in Progress
Bromine is currently in a very early stage. Additional extensions to Selenium are more than likely to appear, as well as probable breaking changes in the future. Needless to say that Bromine has to evolve as does Selenium.
Personally, I attempt to use the framework in multiple different projects. That way I can evaluate how useful it is and how well it works. Most useful features at the moment (for me personnally) are the screenshots and the structuring of pages from the SUT.
I’m happy to share the source on GitHub. I made it to be open source since I will always be open to feedback and improvement suggestions.