Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Tuesday, January 11, 2022

Blog: What I learned, forgot and learned again about Java Preferences

I'm not an expert in Java, but I have been using and teaching it for a really long time. One thing I've always struggled with is the right way to manage settings and preferences, especially stacking different levels of settings (defaults, setup time and run time). The various systems I worked on in grad school were massively over complicated (to the extent where I think in my masters’ system, configuration was probably a third of the whole system).

For the Game Tracker I set out to keep things really simple, and ideally to do things "the right way". Given that I don't actually work in software development with anyone other than myself, figuring out "the right way" can be a bit of a struggle sometimes. Still I'm trying to start modelling my own technology learning after Julia Even's approach and learn and focus on what I have learned.

For handling settings, I've used Java's Properties quite a bit (they made up a big portion of my PhD system, which was *much* more streamlined than my masters). Having an API to handle getting simple values in and out of a file is really useful. Preferences are an extra layer on top of that, where you hand over keeping track of the storage to the JVM.

While I'm still a little grouchy about Preferences not being transparent to the user or the programmer. They do feel like "the right way" to keep track of settings. I was especially confused when I cam back to the Game Tracker after a long break and so had to relearn a lot about Preferences. Hopefully this will be a good overview and collection of the good resources I found.


Overview

As I mentioned, preferences let you hand off worrying about where your information gets stored. All you have to do is tell Java that you would like this class’s preferences and then either read or store a value. When you end your program, Java will store everything and the next time you run your program your preferences will be there.

You can recompile or even completely delete and replace your program and your preferences will still be there. If you’ve ever deleted and then reinstalled a program and been surprised that it remembered everything from before, that’s preferences in action. (And that bit where stuff just gets magically remembered is why I’m sometimes uncomfortable with the idea of preferences, sometimes you just want a program to go away, but we’ll talk about that later.)

When we’re talking about preferences, we’re talking about the small pieces of information you need to make your program run. For example, with the game tracker the list of games is too big to go in preferences, but keeping the file name where I'm keeping the list of games makes sense. There’s not a strict limit, but keeping too much data in your preferences isn’t a great idea. Additionally, everything is handled as a string by Java so you want to avoid storing anything complex, like an object, and should keep in mind that you’ll have to handle moving numeric data back and forth.


Using Java Preferences

You access Java preferences through a java.util.prefs.Preferences object. We’ll talk about how you *get* a Preferences object shortly, but once you have an object you can treat it as a dictionary. You have a put(String key, String value) method where you can store a value under a key and a get(String key) method which gives you the last value you stored under key.

If you have other types of data, you have to handle them separately. The good news is that you don’t have to do any parsing, there are putBoolean(), putDouble(), putInt() methods and getBoolean(), getDouble(), getInt() methods (and actually there’s a pair of methods for every primitive Java type).

You get a Preferences object by asking the Preferences system for it. Similar to the way you ask System for System.out, there are static methods on the Preferences class for getting your Preferences object. Unlike System.out where there’s only one object to get back, there’s actually many possible objects.

Each package in your program gets its own preferences, so when you ask Preferences for your object, you need to give it your class so it knows which package’s preferences to give back. The static method Preferences.systemNodeForPackage(Class theClass), takes a class object and so you can statically give it a class like CLI.class. *

That there are actually two kinds of preferences. System Preferences (which is what you get if you use the method above) and User Preferences. System preferences are shared by every user on a computer, but the user preferences are unique for each user. Generally user preferences are the right place to store things unless you have a strong reason why everyone needs to share. To get the user preferences, you’ll use the static method Preferences.userNodeForPackage().

From there you’re pretty good to go. The first time someone runs your program you can store their preferences and there after you can access and update them as necessary. I’m not sure it’s the “right way” but I think you should also offer your users a chance to see what’s stored in preferences and a way to remove them (the Preference class has methods to do both). I still need to implement this in the Game Tracker, but I think transparency is good to keep in mind.


My GameTracker Example

The last time I worked on the Game Tracker I reorganized a lot of the components. At this point ** I’m really only keeping two preferences for Game Tracker; the name of the persistence manager and the  file name of the file which has the game and play session data.

The persistence manager is managed in the CLI class. As the CLI class is starting it figures out which PersistenceManager it needs and then calls a factory to return an instance of that manager.  For every preference, I keep a constant string in the class to keep the key value, and I’ll often also keep a default value as well.

When the CLI starts, it processes the command line arguments (a story for another day) and checks if the option for the persistence manager has been set. If the value has been set, the CLI saves the value in the preferences. Then the CLI consults the preferences for the name of the persistence manager (with the default). This means that the value that’s loaded is either the new value that was just passed in, the preference that was set before or the default value if it’s never been set.


    private static final String PERSISTENCE_MANAGER_PREF = "gametracker.persistence_manager";
    private static final String PERSISTENCE_MANAGER_DEFAULT = "CSV";

if (cl.hasOption("datamanager")) {
  Preferences.userNodeForPackage(CLI.class).
    put(PERSISTENCE_MANAGER_PREF, cl.getOptionValue("datamanager"));
}

persistenceManager = PersistenceManagerFactory.getManager(
  Preferences.userNodeForPackage(CLI.class).get(
    PERSISTENCE_MANAGER_PREF, PERSISTENCE_MANAGER_DEFAULT), args);

Within the CSVPersistenceManager, the only preference kept is the data file name. Like the name of the persistence manager in the CLI, I keep the name of the preference and the default value as constants, although here the preference name actually sits in the super class FilePersistenceManager.


In FilePersistenceManager:
protected static final String DATA_FILE_PERF = "gametracker.datafile";

In CSVPersistenceManager:
private static final String DATA_FILE_DEFAULT = "gametracker.csv";

Persistence managers are passed the arguments off the command line, so as in the CLI, the super class checks the arguments if there’s a value for the datafile and that is taken and loaded into the preferences.

if (cl.hasOption("datafile")){
  Preferences.userNodeForPackage(
    FilePersistenceManager.class).put(
      DATA_FILE_PERF, cl.getOptionValue("datafile"));
}

The CSVPersistenceManager then checks the preferences to find the filename for the data file.

datafile = new File(
  Preferences.userNodeForPackage(FilePersistenceManager.class).get(
    DATA_FILE_PERF, DATA_FILE_DEFAULT));

The data file can be changed from the setFile method in FilePersistenceManager.

public void setFile(String fileName) {
  Preferences.userNodeForPackage(
    FilePersistenceManagerMenu.class).put(DATA_FILE_PERF, fileName);
      datafile = new File(fileName);
}

I’m not getting a ton of use out of preferences right now. I was doing more before I started streamlining, but right at the moment those are the only two things I need to keep track of. Again, I’m not sure I’m “doing it right,” but this setup seems to work for me. 


The Magic Inside

As I said, the great thing about preferences is that the JVM and the operating system really take care of it for you. So if you’re happy to trust the “magic system” to keep stuff up and running, then you’re good to go. On the other hand if you’re trying to figure out what the hell is going on, then let’s talk about it.

I ended up researching this because when I came back to the GameTracker after a long break, I had no idea what was going on or why the software (which I wrote) knew where the files were without me telling it (also sometimes I’m bad at taking notes - but I did have an issue on the topic for myself). This was especially a problem because I was looking for a local configuration file, which just wasn’t there.

I set out to figure out what was going on. Thankfully a number of people have written pretty good explanations of the preferences system. I decided that I could add a little bit by bringing those articles together and the best way to learn anything is always to teach it (or in this case blog about it).

So with that being said, there are two things to talk about if we want to open up the magic inside.


How are the preferences organized?

At the JVM level, all preferences are kept in trees. There’s the system tree and a tree for each user. (So as a programmer you have access to two trees, the system and the current user). 

Each node in the tree represents a package. The root of the tree is the empty package and it’s named “/”.  Then every package below the root is its own node. If we take my GameTracker as an example, the CLI.java main class is in package ca.kendon.gametracker.cli. That means that the root node has a child node, “ca/”, which has a child node “kendon/”, which has a child node “gametracker/”, which has a child node “cli/”.


A tree consisting of a single branch with nodes "/", "ca/", "kendon/", "gametracker/", "cli/"
A tree diagram of the CLI class's preference.



Since we have multiple packages with preferences (.cli and .core)  both of those packages's preferences would be held under their common parent.  so "gametracker/" would be the parent to both both "cli/" and "core/"


A tree diagram consisting a branch with nodes "/", "ca/", "kendon/", "gametracker/", which has two child nodes "cli/" and "core/"
A tree diagram showing the common ancestors for both the "cli/" and "core/" packages.



Notice that the names of the preference node have those slashes at the end. If you ask for the full name of the node the CLI preferences are in it will be "/ca/kendon/gametracker/cli”. If that seems like a file name... well that might be important later (in some contexts).


Where are the preferences stored?

The JVM has its nice abstraction of preferences in trees, but that doesn’t help when your question is “where is that value *actually* stored?” Now we actually have to take a look under the JVM and see what the operating system is up to. Preferences exist in all operating systems, not just for Java but for any software that needs to remember things. Of course, since all operating systems have their own ideas about where stuff is, there’s a different answer for every operating system. For a quick overview we’ll look at Windows, MacOS and Linux.

Victor Kropp has a really good post on the details.

Windows 10

As windows is wont to do with all of its configuration information, things are stored in the registry. 

User preferences are found at HKEY_CURRENT_USER\Software\JavaSoft\Prefs

System preferences are found HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs

Remember that you can look at the registry using the Registry Editor (regedit) utility, which is installed by default in Windows.

MacOS

On MacOS, preferences are stored in the Library. Since we have two types of preferences, system and user, MacOS actually has two types of libraries (system and user) for preferences.

System preferences are found in /Library/Preferences/ in a file called com.apple.java.util.prefs.plist

Each user’s preferences are found in their home directory in ~/Library/Preferences/ in a file *also* called com.apple.java.util.prefs.plist

plists are Apple’s favourite way to store things in a way that slightly frustrates everyone … well it frustrates me certainly. They’re XML files (often compressed) which can be a pain to actually look at. On the command line you can run the command:


defaults read ~/Library/Preferences/com.apple.java.util.prefs.plist


Which will show you the preferences for your own user.  For example mine includes:


        "gametracker/" =         {
            "cli/" =             {
                "gametracker.persistence_manager" = "CSV";
            };
            "core/" =             {
                "data_file" = "data/games.data";
}; };

If this seems a little overbuilt it's the weird collision of Apple's plists and xml files which always end up having some weird structures.

Linux


On Linux, preferences for each node are stored in its own file (in case you’d wondered why the Java name for preferences looked so much like a file name).

System Preferences are stored starting in the directory /etc/.java/.systemPrefs/

User preferences are stored in each user’s home directory under ~/.java/.systemPrefs/

Under those directories, the JVM builds a directory structure that matches the preference nodes, with the leaf nodes being files. In my Game Tracker Example, that means that the "gametracker.persistence_manager" = "CSV" preference is stored in the file ~/.java/.systemPrefs/ca/kendon/gametracker/cli

This makes a lot of sense to my *nix-centric brain and I think explains a lot about the structure they chose when setting up preferences in *nix environments.

Conclusion

I'm always wary of systems that aren't transparent to the user, but preferences provide a good way to keep track of the details about a program without making a mess or requiring a lot of overhead. I still have a lot to learn, already, I'm wondering about some of the things I'm doing to update the preferences from the program. Hopefully this overview provides a place to get started and answers a few of the questions the preferences seem to draw. 

Footnotes

* I *think* you can use the this identifier to get the class dynamically, but I'm not sure whether or not that's a bad idea. (Thus far I've found I've made mistakes using the literal class - copy and paste errors - so using this may be smarter.)


** This is a snapshot of the GameTracker from early November 2021.


Monday, November 22, 2021

Project 18: Game Tracker Update - 2021

 So it's been a long while since I've sat down and worked on the Game Tracker and it's also been a long while since I've written anything other than vague intentions for working on the project. Fortunately my new focus on creative work has push me back to it.


I was actually pushed by a post I'm writing for the blog that I'll share pretty soon. I've wanted to do a good explanation of Java's Preferences. I managed to thoroughly confuse myself with them and so thought I'd follow the Julia Evan's approach of writing about what confused me. I'd left my project in a thorough mess back in March - the equivalent of the truck sitting in the old bard with the engine out and wires and tubes laid out all over the place - and so in order to show how I was using preferences, I thought it might be a good idea to put the bits back together.


When I abandoned the project, I was in the midst of trying to convert the save system (persistence) to use JSON. That was part of a grander idea to connect to some kind of web interface - Google Sheets and Firebase are the two I've been eying. I ended up struggling to get Java and JSON to play nicely together and after a little bit of cobbling, I discovered that Joda-Time -  which I've used for managing time in Java for *years* - was also making everything complicated. 


I gave up and decided that since I mostly wanted the system up and running so I could finish the blog post - which I've been working on for months. I set up a project - well... took over the one from March - called 2021 refresh and tried to get a minimal set of the Game Tracker up and running. That was more or less fun, I built a CSV version of the data manager and then did all the cleanup I had to do to get up and running.


A screen shot of the Game Tracker showing the main menu.
Game Tracker - Pretty much the same after all these years.



I also realized that I've been keeping the GitHub repository for the project private. I think that's partly based on not feeling like the project was ready for people to see and partly fear about showing off my own programming work. Given what I've been thinking about with creative work and how I want to use my time going forward, hiding isn't going to help me, so I opened the repository to the public and put and MIT Licence on it - not that I think it will be terribly useful for anyone. I found Choose a Licence, which was helpful for comparing licences.

 

I also got to write a readme, which at the end of the day turned out to help me feel better about the whole project and make sure that anyone who stumbles across it will know what on earth is happening. Nothing about the whole project feels at all glossy or cool - like I would like it to - but it's happening and I've moved it a little bit forward. I found Make a README which was a really good resource.


I like that I can return to the idea of small tasks for the project now. For example, for reasons that are beyond me - maybe as a demo for students - I set the game systems to be a Java Enum. This is a terrible idea since for some reason new video game systems have been released in the last few years and I've even purchased some of them. It'll be nice to be able to jump in and work on that for a little bit and then be done.


I also have a lot of other things I'd like to do. I'd like to expand the interfaces to include a desktop GUI, something for my phone and something for the web. I also still want to set up remote data management for the system. I'll get to those at some point, but again my new approaching to getting things done means they'll hopefully trickle out over time.

Friday, May 18, 2018

Project 18 - Video Game Play Tracker - Update 1 (v0.1.0)

I’ve really had a lot of fun with this project so far.


For version 0.1.0 I’ve added in the basics, including:
  • A game representation, including the name, the year and the platform.
  • A set of games and get back games that match particular criteria
  • A play session representation, including the game, the date and how long the session lasted.
  • A list of play sessions
  • Managers to load and save game and play  data from and from csv files.

I’ve also built a simple text-based UI, which allows you to save, load and add games and play sessions.

The Basic Text Based UI Menu


I also think I did an ok job using the git flow workflow, although I think I ended up adding too many features in my release 0.1.0 branch. I also added a ton of tests, even if I didn’t quite get myself to a test first mentality.

So I’m happy enough with v0.1.0. RIght now I’m keeping it in a private GitHub Repository, I should probably just open it up, but right at the moment I’m not sure I want to. At least just yet.

The basic tracker running in the terminal.
In terms of usability it’s not great, but it’s a good starting point. My next goal from here is to introduce filtering and aggregation so I can see play sessions aggregated over a list and filter for specific games or time periods. I’ll post an update around July 1, 2018.

Sunday, August 07, 2011

Project 2 : The Ogre Manager

One of my fondest memories is receiving Ogre Battle 64 for my birthday (a long, long time ago). It's gone on to be both one of my favorite games of all time, and also one of the games I have the strongest associations with. Every year when the days get short and the weather gets cold and snowy, my first thought is to put Ogre Battle in the N64* and spend the afternoon pitting my army against the morally ambiguous other guys.

From GameFAQs - user Count Dackula

One of the things that's a little unusual about Ogre Battle, is that while it may look like your usual 90s RPG with swords and magic, you don't actually get to control anything. You take the role of the organizing general, setting up units out of your soldiers and then deploying them to the battle field with orders to take over a strong point or defend a town. Once the game starts they march where you tell them and fight the enemies according to their own internal mechanics and you just get to sit back and watch.**

Your units (in Blue) fight the "Bad" Guys (in Red). You sit and watch.
From GameFAQs - user Storm Shadow

The fun of Ogre Battle comes in putting together all the different character's available to you (some you train up from recruit soldiers, some are given to you by the story and some you pick up from the wild) into units that fight well together, then level them up. Like many strategic rpgs (and as I understand Ogre Battle shares developers with it's cousin series Tactics Ogre which basically spawned the tactical/strategic rpg) the challenge in the game comes from leveling the right units up at the right time. In particular, you have to be careful that a unit has the correct alignment (lawful, neutral or chaotic) and that your units don't become over leveled.

The game tells you what each character's level is, but doesn't give you the total for the unit. As a result I started to keep track for myself, using a sketch book a pencil and my high school Casio not-quite-graphing calculator.

The stats for a character. Sadly I can't find a screen cap of the unit info.
From GameFAQs - user Storm Shadow

After a while I started to realize that the unit I left to defend my base sometimes wasn't strong enough to hold its ground.*** Realizing that I also needed to keep track how much damage each unit was dealing, so that I could always leave the unit most likely to win its fights at home, I started to keep track of that too.

Around this time the Computer Scientist in me started to think that keeping track of this on paper was maybe not the most efficient way to do things. In addition to my mitigating my ability to add calculation errors and screw up putting the numbers down in the right places, I thought it would be nice to be able to graph my progress and maybe keep track of a bit more information. So I started bringing my laptop to game time and keeping track of the statistics in Excel.

One set of unit data for one of my play-throughs.
So, having upgraded myself technologically, I kept on playing. In addition to being able to keep track of each of the characters in a unit, both their level and their attacking strength, I was able to keep track of their alignment (which I could have done before, but now we have a whole spreadsheet to play with right?). However, I wasn't exactly happy with this set up for two different reasons.

The first reason was technical. While the spread sheet kept track of the unit at any point in time (before each mission) it didn't keep the history of the changes. I started to keep a new spread sheet for each mission, but without doing more work than I was really willing to do it meant that I couldn't really graph my progress. Doing this isn't essential to the game or anything very important, but it's still something that would be fun to track.

The other reason was more artistic. As you can see in the screen-shots I've included, there's a very strong esthetic to the game. Everything is very medieval in style and switching from that to the ... polish ... of excel is rather jarring (as you may have noticed) in a way that keeping my information on paper wasn't.

Enter: Ogre Manager, a program I'm creating to solve these two problems. It's not an essential thing, in fact I'd be nearly as happy to go back to tracking all this on paper, but it also struck me as a good chance to practice a whole bunch of things that I've let languish far too much.

The Notes panel from Ogre Manager. It shows who should defend the base, who needs leveling and which units should be grouped together. (But not into in game Legions, those are a terrible idea.)
In short, the vast majority of the programming I do is command-line style stuff and as such I don't get much practice working on GUIs. Ogre Manager seems like a good chance to practice that, in a situation where there's not a huge penalty for not getting everything right. Secondly it would be nice to be able to make a program with a graphical look and feel that matches the game. Whether this is in the form of a look and feel for Java Swing, or Java FX, or custom environment, it will be an adventure trying to put this together. Thirdly this seems like the kind of program that would make an excellent Android app, which is another area in which I'd like to have more experience.

Right now I have development mapped out into 4 phases. The first phase is to finish a Java-standard look and feel program that keeps track of all the information I want to track, which as the screen shots show, has been started. The second phase is to implement graphs and other visualizations of the data. The third phase is to create the Ogre Battlely look and feel and finally the fourth phase is to create the Android app.

Time to go make something.



* In fact, Ogre Battle doesn't really come out of my N64.

** Or clutch the controller in impotent panic as your unit gets wiped off the face of the earth.

*** One of the mechanics of the game is that after a battle is resolved, the losing unit (the one who dealt the least damage) is pushed back on the battle map. The distance it goes is based on how much damage it received. When your base defense unit gets pushed then the enemy can beat you by just walking right into your base.

Reading

I’m not sure that anyone, myself included, really needs this post. On the other hand, I read a thing about re-reading and I want to write ab...