9/11 Memories

Today is the 15th anniversary of the 9/11 terrorist attacks in 2001. It is a particularly poignant anniversary for me because I was 15 years old when it happened. It is strange to think that we have reached the point where more of my life has taken place since the attacks than before. For the sake of historical and family record, I wanted to describe the events of that day from my perspective.

I am a member of The Church of Jesus Christ of Latter-day Saints (the Mormons), which encourages young men and women to attend daily religious studies courses (known as “Seminary”) during their high school years in order to deepen their scriptural understanding of the Gospel during that critical, but turbulent, part of their lives. In Utah, Idaho, and a few other places where Mormons make up a significant part of the local population, the Church will often work with the local school district to make these courses available as release-time electives such that LDS students can attend Seminary during the school day. But in most other areas of the world, including where I grew up, Seminary is instead offered in an “early-morning” format. We met from 6:00-6:45am on each school day for our class, so I would have woken up on 9/11 no later than 5:30am.

I was also a proud member of my high school marching band, which means that immediately following Seminary I would have caught a ride over to the high school practice field for early-morning band practice. The marching band class itself was held during 1st period (8-9am) but in order to learn our show in time for the first football games of the season, we were expected to arrive at 7am to practice.

As I grew up in the central time zone, the hijackings and impact of the first planes would have taken place while we were outside practicing. But this was 2001; there was no Facebook or Twitter or even Google News. Cell phones existed, but not as we think of them today. They were still simple but expensive devices with no Internet connection, and most did not even support text messaging. Few teachers and even fewer students would have owned one. And so innocently we continued our rehearsal, unaware of the terror that was now unfolding on the east coast.

In fact, I didn’t receive my first indication that anything was wrong until around 8:45am local time. We had concluded our band practice on the field and had meandered inside to put away our instruments and other equipment. Our band director came into the room holding a memo from the main office. He announced that the World Trade Center had been impacted by two aircraft and was on fire. No additional information was available at the time and we were advised to remain calm.

And calm I was. It didn’t even cross my mind at the time that this was a coordinated attack. I played Microsoft Flight Simulator a lot growing up, which used downtown Chicago as the default location in at least a couple releases. I was a bad enough wannabe pilot that I had crashed dozens of simulated planes into simulated skyscrapers. So the idea that a plane would crash into a skyscraper in real life didn’t seem all that far-fetched to be honest. Granted, I thought the planes involved were small general aviation planes, and it was pretty crazy that not one but two pilots would screw up like that on the same day…but I really didn’t grasp why this was even news. I was more confused as to why the main office felt a need to send out a school-wide memo detailing events in New York City that were clearly of no consequence to us.

The next few minutes would shatter that naive bubble of security though.

As I left the band room to head to my next class, I overheard the band director in his office talking to the main office on the phone, asking questions about the incident. The anxiousness and concern in his voice gave me pause, but I brushed it aside and proceeded down the hall to my next class. When I was about halfway there, the office made a school-wide announcement on the PA. The Pentagon has been destroyed, they said.

Suddenly, it all began to click in my head. The plane crashes in New York City weren’t accidents or coincidental. Someone was doing this on purpose. We were under attack!

I don’t remember much of the school day after that. It no longer mattered to me. I genuinely thought that this was the start of World War III. Everything was a blur…

At one point, another announcement was made that another plane was en route to either the Capitol building or the White House. We were never told whether or not it impacted. Initially we all figured that Russia or China or maybe North Korea was behind the attacks, but eventually rumors started going around that it was actually a country in the Middle East. At another point, we were told that the US Air Force had shot down an airliner that was preparing to attack another facility.

I was in the locker room getting ready for PE when the announcement came that the World Trade Center had collapsed – with thousands of people still inside. We all cursed and swore in anger that we were all gonna go enlist and fight these bastards – just like our grandfathers did after Pearl Harbor. We would make ’em proud.

Around lunchtime the decision was made that no further announcements would be made, as the events of the day were becoming too distracting to our studies. We were furious. Didn’t the idiot teachers realize that this day was going to be the defining moment of our generation? Who cares about Algebra 1 on a day like that?

So several of the more rebellious kids found ways to eavesdrop on the faculty break room, which had a TV; or to sneak onto the Internet in the school library between classes and get some updates. But what information these heroes were smuggling out to the rest of the student body could only really spread by rumor and word of mouth and ended up pretty scattered and incoherent. What I ended up hearing ranged everywhere from “isolated terrorist attack” to “nuclear war”.

As soon as school was over, I literally ran all the way home and burst into my house on a beeline towards our TV. My mom was already there watching the news. And for the first time, I saw with my own eyes what had unfolded.

I spent the rest of the day and most of the night glued to the TV, watching again and again the replays of the impacts in New York and Washington, the subsequent collapse of the WTC towers, and the apocalyptic scene of bloodied people running through the streets of New York City dodging a wall of dust, smoke, and debris. It was a Tom Clancy movie in real life. It was surreal. But it was also real. The United States of America had been attacked. Thousands of Americans were dead.

At some point I must have fallen asleep, though I don’t remember whether it was in my room or downstairs in front of the TV. The next morning, I woke up at 5:30am to go to Seminary and band practice again.

But the world was a different place.


As an epilogue I share one final memory. A month after 9/11, I sat in General Conference as Gordon B. Hinckley, the President of the Church, announced that he had just learned that a retaliatory US missile attack was underway as a prelude to war. He then delivered a most powerful and sober sermon on the “times in which we live.” With prophetic authority he comforted and counseled the members of the church at a time when we needed it most. Fifteen years later, his counsel is just as relevant now as it was in 2001:

No one knows how long [the conflict] will last. No one knows precisely where it will be fought. No one knows what it may entail before it is over. We have launched an undertaking the size and nature of which we cannot see at this time.

Occasions of this kind pull us up sharply to a realization that life is fragile, peace is fragile, civilization itself is fragile.
[ . . . ]

Through centuries of time, men and women, so very, very many, have lived and died. Some may die in the conflict that lies ahead. To us, and we bear solemn testimony of this, death will not be the end. There is life beyond this as surely as there is life here. Through the great plan which became the very essence of the War in Heaven, men shall go on living.
[ . . . ]

Now, brothers and sisters, we must do our duty, whatever that duty might be. Peace may be denied for a season. Some of our liberties may be curtailed. We may be inconvenienced. We may even be called on to suffer in one way or another. But God our Eternal Father will watch over this nation and all of the civilized world who look to Him. He has declared, “Blessed is the nation whose God is the Lord” (Ps. 33:12). Our safety lies in repentance. Our strength comes of obedience to the commandments of God.

Let us be prayerful. Let us pray for righteousness. Let us pray for the forces of good. Let us reach out to help men and women of goodwill, whatever their religious persuasion and wherever they live. Let us stand firm against evil, both at home and abroad. Let us live worthy of the blessings of heaven, reforming our lives where necessary and looking to Him, the Father of us all. He has said, “Be still, and know that I am God” (Ps. 46:10).

Are these perilous times? They are. But there is no need to fear. We can have peace in our hearts and peace in our homes. We can be an influence for good in this world, every one of us.

May the God of heaven, the Almighty, bless us, help us, as we walk our various ways in the uncertain days that lie ahead. May we look to Him with unfailing faith. May we worthily place our reliance on His Beloved Son who is our great Redeemer, whether it be in life or in death, is my prayer in His holy name, even the name of Jesus Christ, amen.

Gordon B. Hinckley
https://www.lds.org/general-conference/2001/10/the-times-in-which-we-live

Header image: “2004 memorial, New York” by Derek Jensen, Public Domain

Java+YAML (Cascading) Configuration Made Simple

I love YAML. I use it in almost all my applications to manage configuration. It is easy to read and write and allows for complex objects as well as arrays/lists of data. In fact, YAML 1.2 is technically a complete superset of JSON; so anything you can do with JSON you can do with YAML. YAML also comes with broad cross-language support. For example, there are some powerful libraries out there for working with YAML in Java, such as SnakeYAML and YamlBeans.

The Old

Several years back, my colleague Charles Draper wrote a library to simplify the process of parsing and working with YAML configurations. Initially this library was part of an internal utility package, but about a year ago we went through and split it out into its own repository. During that process, we debated and nailed down some of the expected behavior of this config library and established a few informal guidelines that governed development:

  1. It had to be stupidly convenient to pull discrete pieces of data out of the Java representation of the data.
  2. Since we have a number of student developers who come and go every few semesters, the learning curve needed to be minimal if we wanted to have any hope of widespread usage.
  3. It had to be able to read from multiple files and merge them in a fashion similar to how CSS works.

I want to talk briefly about that 3rd requirement, since that really stands apart from the general case of “I just want to parse a YAML file.” In several situations, we have found it beneficial to have a cascading configuration loader that is able to parse up a baseline configuration, then read subsequent files that tweak the behavior to work for a specific server or environment.

For example, imagine that an application relies on an external database. In production, we of course want to read/write to the production database, put perhaps on our staging and development servers, we want to work with a test database instead. Nothing too crazy.

So we might have a baseline application.yml file that contains the bulk of our configuration along with some placeholders for database connection values. And on our production and staging servers respectively we create a production.yml and stage.yml file containing the specific connection values for that environment, as follows:

---
database :
host : localhost
username : myuser
password : mypassword
# other configuration as appropriate...
view raw application.yml hosted with ❤ by GitHub
---
database :
host : proddb.example.com
password : prodpassword
view raw production.yml hosted with ❤ by GitHub
---
database :
host : stgdb.example.com
username : stguser
port : 3306
view raw stage.yml hosted with ❤ by GitHub

At application startup, then, what we really want to do is to load the baseline application.yml and then substitute the appropriate fields from the system-specific YAML files. The way we have been handling this is by having a master Config object which we loaded one or more YAML files into as data. The Config object provided a long list of access methods to allow data to be pulled out using XPath-type references.

It worked, and we were quite proud of what we built. But we have found something even better!

The New

I recently discovered that the venerable Jackson library has a YAML plugin. This plugin uses SnakeYAML under the hood to parse the YAML data using an ObjectMapper, which means that the output of the parsing operation can be a standard JsonNode or even a targeted POJO bean.

Armed with this plugin, we gutted our Config library this week and rebuilt it to use Jackson as the data parser. We are thrilled with how easy it was to set up and how simple it is to use. Our master Config object is gone altogether; applications now interact directly with the loaded data using the JsonNode or POJO outputs mentioned above. Check out how this easy this makes loading and interacting with configuration data:

---
a : alpha
b : bravo
c :
d : delta
view raw example.yml hosted with ❤ by GitHub
YamlLoader loader = new YamlLoader();
Path sourcePath = Paths.get("example.yml");
//The config library can load data into a generic JsonNode
JsonNode node = loader.load(sourcePath);
System.out.println(node.path("a").asText()); //Outputs "alpha"
System.out.println(node.path("b").asText()); //Outputs "bravo"
System.out.println(node.path("c").path("d").asText()); //Outputs "delta"
view raw example1.java hosted with ❤ by GitHub
YamlLoader loader = new YamlLoader();
Path sourcePath = Paths.get("example.yml");
//The config library can also load data into a Jackson-annotated POJO
ExamplePOJO pojo = loader.load(ExamplePOJO.class, sourcePath);
System.out.println(pojo.a); //Outputs "alpha"
System.out.println(pojo.b); //Outputs "bravo"
System.out.println(pojo.c.d); //Outputs "delta"
view raw example2.java hosted with ❤ by GitHub
public static class ExamplePOJO {
@JsonProperty String a;
@JsonProperty String b;
@JsonProperty Charlie c;
public static class Charlie {
@JsonProperty String d;
}
}

Stupidly convenient? Check.

Minimal learning curve? Check.

But what about cascaded loading?

Since at the end of the day, we’re dealing with native Jackson objects, we did a little hunting to see if Jackson supported deep merge operations out of the box. As far as we can tell, it does not at the time of this writing. However, there is an open feature request for exactly that, and a number of people have rolled their own merge methods.

We took one of those methods and broke it down to understand exactly how it was working. We then added our own version of this merge logic into the YamlLoader class. In a nutshell, it merges two JsonNode objects A and B using the following logic:

  • If A is a missing node, then simply add B.
  • If either A or B is a simple field, then replace A with B
  • If either A or B is an array, then replace A with B
  • If both A and B are complex objects, then recursively call merge on each child element.

We have now theoretically satisfied our 3 basic requirements, so let’s see how this would work in our original example. We will instruct the YamlLoader class to load our baseline application.yml and then overwrite some data from the production.yml and stage.yml files:

YamlLoader loader = new YamlLoader();
// Cascade load application.yml and production.yml. Values in production.yml should
// trump values in application.yml.
ExamplePOJO pojo = loader.load(ExamplePOJO.class,
Paths.get("application.yml"),
Paths.get("production.yml"));
System.out.println(pojo.database.host); //Outputs "proddb.example.com" (production.yml)
System.out.println(pojo.database.username); //Outputs "myuser" (application.yml)
System.out.println(pojo.database.password); //Outputs "prodpassword" (production.yml)
System.out.println(pojo.database.port); //Outputs "null" (referenced by neither file)
// This final example doesn't really jive with our hypothetical situation, but it's
// still interesting from a demo perspective. In this case, stage.yml will trump
// values from the original application.yml, but will itself be trumped by values
// in production.yml.
pojo = loader.load(ExamplePOJO.class,
Paths.get("application.yml"),
Paths.get("stage.yml")
Paths.get("production.yml"));
System.out.println(pojo.database.host); //Outputs "proddb.example.com" (production.yml)
System.out.println(pojo.database.username); //Outputs "stguser" (stage.yml)
System.out.println(pojo.database.password); //Outputs "prodpassword" (production.yml)
System.out.println(pojo.database.port); //Outputs "3306" (stage.yml)
view raw example.java hosted with ❤ by GitHub
public static class ExamplePOJO {
@JsonProperty DatabasePOJO database;
//Other fields as defined in application.yml (outside the scope of this demo)
public static class DatabasePOJO {
@JsonProperty String host;
@JsonProperty String username;
@JsonProprety String password;
@JsonProprety Integer port;
}
}

Conclusion

We think this is a pretty slick way of managing configurations and will start rolling it out across our applications. We have open sourced it, so if you are interested in using it, please check out the repository and let us know what you think of it in the comments below!

 

Author’s note: The statements and views expressed in this article are the author’s own and do not represent the view of Brigham Young University or its sponsors.

Parsing Call Numbers Using BYU’s New Call Number Library

Let’s talk about call numbers, officially known as Library Classifications. The idea is good, allowing librarians to group material together by subject. But in practice they are nightmarish to work with programmatically.

Consider the Library of Congress (LC) Classification, used by many academic libraries around the United States. Like any other classification, it is necessarily complex. Not only must it properly handle the billions of creative works already in existence, but it must also allow for the future insertion of an infinitely large number of not-yet-created works. How do you insert an infinite number of works between “A” and “B”? By adding additional sequences of characters: “A1″, A2”, “A345 .B678”, etc. And over the last 120 years since the LC Classification was invented, librarians have gotten very creative in this endeavor.

Consider also that there is no central canonical database of call numbers. Historically, each library defined their own. Obviously there were guidelines, and in recent decades there has been more standardization as libraries strive to share more of their metadata with each other. And LC itself of course plays a very central role in the classification process; many libraries simply copy LC’s own call number for any given work. But the reality is that call number creation is really still a process centered more in tradition and general guidelines than on hard and fast rules.

For example, Brigham Young University (BYU) holds the world’s most comprehensive academic collection on Mormonism (represented by the LC subject classes BX 8601-8695), and found themselves in need of more granular/discrete subject guidelines than those represented by the official LC subject schedules. So they defined their own schedule within the subject numbers laid out by LC. Multiply that customization by thousands of libraries over multiple generations of librarians, and the result is a very complicated and only semi-standard way of implementing even the most thought-out and defined library classifications.

And this sort of flexibility is not only allowed but encouraged in the library industry. LC’s own 424 page training manual on the LC classification is filled with a lot of statements similar to “usually but not always”, “cataloger may adjust this at their discretion”, and “but in this situation it’s different.”

Finally, one must remember that library call numbers predate computers and the accompanying character sets that have developed into international standards. There are a number of situations, both by standard and by tradition, that call numbers must be ordered differently than standard UTF-8 or ASCII-based sorting algorithms would normally order them.

It’s a mess. It may be a necessary mess, but it’s still a mess.

BYU’s Call Number Library

The IT group at BYU’s Harold B. Lee Library (HBLL) is trying to tackle that mess. This week, we are open-sourcing a call number library for Java that tries to solve some of the problems we have encountered working with library classifications: https://bitbucket.org/byuhbll/lib-java-callnumber.

As the principal author of this new library, I wanted to explain what it does and why we think it will be useful for the library community. There are two main problems this library tries to solve:

  1. How can we sort call numbers correctly?
  2. How can we programmatically parse call numbers and pull out discrete elements from them?

(Oh, and I should point out that while I’ve mostly focused on LC call numbers so far, we wanted the solution to these problems to work for other library classifications as well.)

We started by creating a CallNumber interface. For now, we’ve kept this interface simple; it requires only a single method, sortKey which returns a non-pretty representation of the call number meant solely for ordering/sorting (fixing problem #1). It is worth noting that this interface does extend from the Serializable and Comparable interfaces, though we have defined a default implementation of compareTo that is suitable for most implementations. Additionally, there are some contractual requirements outlined in the attached javadocs that provide some behavioral expectations:

  • They should be immutable value objects, similar to String, Integer, and URI. They should override the hashCode and equals methods accordingly.
  • They should implement a constructor that accepts a single non-null, non-empty String argument (more on this later).
  • They should override the toString method to return a human-readable form of the call number.

Once we established the common interface, we created two implementations based on the most common library classifications, LCCallNumber (based on the Library of Congress classification described above) and DeweyCallNumber (based on the popular Dewey library classification used by many public and educational libraries). Both of these classifications go beyond the basic requirements of the CallNumber interface and actually try to parse the provided call number string into semantic elements of a call number. This is done in both cases using regular expressions (I should point out here that the regex used to parse LCCallNumber was originally authored by Bill Dueber of the University of Michigan and released under the licensing terms of Perl. I would encourage any interested readers to check out the work that Bill and a few others have done in a related project focused on parsing and normalizing LC call numbers. We actually looked at contributing our code to that repository but ultimately decided that there was some significant differences in our scope and goals that made it more appropriate to setup a separate project instead).

In addition to the interface-level methods to retrieve both human readable and sortable representations of LC and Dewey call numbers, we provided implementation-specific methods to pull out discrete elements of each call number. Internally, these discrete pieces are used to determine how we need to massage the provided string for it to sort appropriately.

We were very impressed by how well this architecture seemed to solve the problems listed above, and issued an early (internal) release to start working with. Almost immediately, however, we discovered a problem. The HBLL does not use a single classification, and most of our use cases involved working with an arbitrary set of data that could include a mix of LC and Dewey call numbers. We quickly realized that we needed a “best-guess” parser that could iterate through large numbers of call numbers and construct the appropriate CallNumber implementations on the fly. We went back to the proverbial drawing board and wrote the CallNumberParser class. Here’s how it works:

When constructing a CallNumberParser, users list the CallNumber classes that should be considered as valid “targets” for subsequent parsing operations. Order matters, as strings that match the parsing criteria for multiple implementations will be parsed using the class listed earlier in the list. CallNumberParser is immutable and thread-safe, so once created, it can be reused throughout an entire application freely. It’s kind of awesome.

Of course, a string may fail to match any of the provided implementations. We’ve provided a couple different ways to handle this situation based on the needs of the user. We defined a default implementation of CallNumber, UnclassifiedCallNumber, that will always parse any string – even empty and null strings. Users including UnclassifiedCallNumber at the end of their implementation list may rest assured that all values will get parsed. The price of this flexibility is that massaging unclassified data is of course impossible. The toString method will simply return the input string, and sortKey will return a lower-cased form of the same input string. But at an interface level, UnclassifiedCallNumbers behave just like any other CallNumber implementation. They can be checked for equality, compared, converted to human-readable or sortable strings, and so on. Leaving this implementation out of the list will cause unparseable call number candidates to throw an IllegalArgumentException instead.

Usage Examples

Basic Usage

Using the call number parser is pretty straightforward. If I had a list of mixed (LC and Dewey) call number strings that I wanted to parse, here’s how to do it:

// The following call numbers are actually used by BYU to represent
// the first two Harry Potter books in different collections.
String deweyHP1 = "823 R797h";
String lcHP1 = "PZ 4 .R798 H28 1998";
String deweyHP2 = "823 R797hp 2004";
String lcHP2 = "PZ 4 .R798 H23 1999";
//Initialize a CallNumberParser to handle LC and Dewey call numbers.
CallNumberParser parser = new CallNumberParser(LCCallNumber.class, DeweyCallNumber.class);
//Iterate through each raw call number string and parse them into CallNumber value objects.
for(String raw : Arrays.asList(deweyHP1, lcHP1, deweyHP2, lcHP2)) {
CallNumber callNumber = parser.parse(raw);
//Output to show that parsing worked.
System.out.println(callNumber.getClass().getSimpleName());
System.out.println("\tNormalized: " + callNumber.toString());
System.out.println("\tOrderable: " + callNumber.sortKey());
}
view raw example.java hosted with ❤ by GitHub

Which will output the following:

DeweyCallNumber
Normalized: 823 R797h
Orderable: 000823 r797/h
LCCallNumber
Normalized: PZ 4 .R798 H28 1998
Orderable: pz000004 r798/ h28/ 001998
DeweyCallNumber
Normalized: 823 R797hp 2004
Orderable: 000823 r797/hp 002004
LCCallNumber
Normalized: PZ 4 .R798 H23 1999
Orderable: pz000004 r798/ h23/ 001999
view raw example.out hosted with ❤ by GitHub

Even Easier Basic Usage

I’m lazy, which is a somewhat desirable trait for a software developer. I realized that in actual usage, all of my CallNumberParser objects kept getting set up to use the same parsing targets. So I added some prebuilt parser as a static final variables within CallNumberParser.

//Use a prebuilt CallNumberParser that will correctly handle LC call numbers,
//Dewey call numbers, and the default call numbers created in SirsiDynix Symphony
//for new items. It will throw an IllegalArgumentException for anything else.
CallNumber a = CallNumberParser.SYMPHONY_STRICT.parse("PZ 4 .R798 H28 1998");
//This prebuilt CallNumberParser will handle all the targets described above,
//but will parse any other values - including null and empty strings - as
//UnclassifiedCallNumber entities, so it is guaranteed to handle EVERYTHING.
CallNumber b = CallNumberParser.SYMPHONY_NONSTRICT.parse("PZ 4 .R798 H28 1998");
//Note that since CallNumbers are value objects, the following is true, even
//though the two CallNumbers were created separately using different CallNumberParser
//instances.
boolean isEqual = a.equals(b); //TRUE
view raw example.java hosted with ❤ by GitHub

Ordering Call Numbers

Normally, trying to present an ordered list of call numbers is a huge pain. You have to worry about padding numbers, stripping out non-filing characters, etc. But the call number library removes all the pain from the process. For the following example, I will deliberately use 3 call numbers that do not sort correctly as simple String objects. According to our catalogers, the “A88x” should sort before “A888”, but that is a violation of UTF-8 and ASCII-based ordering. The following table shows the difference:

Call Number Order String Order
BF 637 .C6 A88 vol.24 BF 637 .C6 A88 vol.24
BF 637 .C6 A88x vol.24 BF 637 .C6 A888 vol.24
BF 637 .C6 A888 vol.24 BF 637 .C6 A88x vol.24

Let’s make it go using the call number parser:

String first = "BF 637 .C6 A88 vol.24";
String second = "BF 637 .C6 A88x vol.24";
String third = "BF 637 .C6 A888 vol.24";
//Initialize a CallNumberParser. Since we know we're only parsing LCCallNumbers, we'll keep it simple.
CallNumberParser parser = new CallNumberParser(LCCallNumber.class);
//Parse the 3 call numbers and add them to a list (deliberately out of order).
List<CallNumber> list = new ArrayList<>();
list.add(parser.parse(third));
list.add(parser.parse(first));
list.add(parser.parse(second));
//Just to prove that call number sorting works correctly, shuffle the list before sorting it according to its
//natural order (which we can do easily, since the CallNumber interface extends Comparable.
Collections.shuffle(list);
Collections.sort(list);
for(CallNumber callNumber : list) {
//Output to show that sorting worked.
System.out.println(callNumber.getClass().getSimpleName());
System.out.println("\tNormalized: " + callNumber.toString());
System.out.println("\tOrderable: " + callNumber.sortKey());
}
view raw example.java hosted with ❤ by GitHub

Which will output the following:

String first = "BF 637 .C6 A88 vol.24";
String second = "BF 637 .C6 A88x vol.24";
String third = "BF 637 .C6 A888 vol.24";
//Initialize a CallNumberParser. Since we know we're only parsing LCCallNumbers, we'll keep it simple.
CallNumberParser parser = new CallNumberParser(LCCallNumber.class);
//Parse the 3 call numbers and add them to a list (deliberately out of order).
List<CallNumber> list = new ArrayList<>();
list.add(parser.parse(third));
list.add(parser.parse(first));
list.add(parser.parse(second));
//Just to prove that call number sorting works correctly, shuffle the list before sorting it according to its
//natural order (which we can do easily, since the CallNumber interface extends Comparable.
Collections.shuffle(list);
Collections.sort(list);
for(CallNumber callNumber : list) {
//Output to show that sorting worked.
System.out.println(callNumber.getClass().getSimpleName());
System.out.println("\tNormalized: " + callNumber.toString());
System.out.println("\tOrderable: " + callNumber.sortKey());
}
view raw example.java hosted with ❤ by GitHub

Conclusion

We have had great success using this call number library internally. I am thrilled to be able to share it with the larger library community. Included in the repository are over 70 unit tests to verify the correctness of our parsing and sorting algorithms. The first round of unit tests were based on this great tutorial on LC call number sorting by Kent State University’s library. We have since added many more tests based on specific situations we’ve run into here at the HBLL.

Please check out the repository and let me know what you think in the comments below. Also, there are many more library classifications that we have not yet implemented as CallNumber entities. Please feel free to fork the repository and create pull requests expanding the functionality of this library!

 

Author’s note: The statements and views expressed in this article are the author’s own and do not represent the view of Brigham Young University or its sponsors.

Header image: “Carlyle Books on Library Shelf” by ParentingPatch, CC-BY-SA 3.0