Assignment 14

Task

This assignment has two parts. You can choose to implement only one of the two parts, or you can do both for extra credit.

In Part 1, you'll write a class that can represent and convert filenames. Then you'll use this class to convert filenames entered by the user. This will give you practice with String methods and OOP.

In Part 2, you'll print a sorted list of filenames entered by the user. (If you do both parts, this sorted list will instead contain converted filenames.) This will give you practice with arrays.

Concepts: Arrays for dynamic storage, String methods
Textbook: 7.4 - 7.6; 4.5

Steps

I will describe Part 1 and Part 2 separately. Then I'll talk about how your main method will be different if you decide to do both parts together. I'll just describe the requirements here; suggestions on how these requirements might be met will be in the FAQ section.

Part 1: A Filename Converter

Step 1: Filename

First, we need a class that can represent a (hypothetical) filename and its component parts (name and extension) as well as translate that filename to an 8.3 filename. (Note that the filenames here are just Strings; they don't correspond to real files on your computer in any way.)

The required class name, method signatures, and method behaviors are as follows:

/**
 * Stores the name of a file and provides methods to access parts
 * of that name.  Can also convert the filename to a short 8.3
 * filename form.
 */
public class Filename {

  /**
   * Constructs a new Filename object with the given filename.
   * Any whitespace characters on either end of filename are
   * ignored (removed).
   */
  public Filename(String filename) {
  }

  /**
   * Returns only the name portion of this filename.
   * <p>
   * The name portion is the part of the filename up to the
   * last dot ('.') character, but not including that dot.
   * If there is no dot in the filename, the entire filename
   * is treated as the name.  If the last dot is the first 
   * character, an empty String is returned.
   * <p>
   * Examples (returned Strings do not include the "quotes"):
   * <ul>
   * <li>hello.bat => "hello"
   * <li>My Budget.xls.backup => "My Budget.xls"
   * <li>NOTICE => "NOTICE"
   * <li>.prefs => ""
   * <li>.prefs.backup => ".prefs"
   * </ul>
   */
  public String getName() {
  }

  /**
   * Returns only the extension portion of this filename.
   * <p>
   * The extension portion is the part of the file after the
   * last dot ('.') character, but not including the dot.
   * If there is no dot in the filename, the extension is equal
   * to an empty String ("").
   * <p>
   * Examples (returned Strings do not include the "quotes"):
   * <ul>
   * <li>hello.bat => "bat"
   * <li>My Budget.xls.backup => "backup"
   * <li>NOTICE => ""
   * <li>.prefs => "prefs"
   * <li>.prefs.backup => "backup"
   * </ul>
   */
  public String getExtension() {
  }

  /**
   * Returns this filename converted to 8.3 format.
   * <p>
   * This conversion includes the following changes:
   * <ul>
   * <li>All spaces (' ') are replaced with underscores ('_')
   * <li>All the dots ('.') in the name (except for the one between
   *     the name and extension) are replaced with underscores.
   * <li>The name part is shortened to a max of 8 characters
   *     (if necessary).
   * <li>The extension part is shortened to a max of 3 characters
   *     (if necessary).
   * <li>The entire 8.3 filename is uppercased.
   * </ul>
   * <p>
   * Examples (returned Strings do not include the "quotes"):
   * <ul>
   * <li>hello.c => "HELLO.C"
   * <li>Filename.java => "FILENAME.JAV"
   * <li>sourcecode.html => "SOURCECO.HTM"
   * <li>.prefs => ".PRE"
   * <li>NOTICE => "NOTICE" or "NOTICE." (depending on implementation)
   * <li>a.out.backup => "A_OUT.BAC"
   * <li>I Like Unicorns.text => "I_LIKE_U.TEX"
   * </ul>
   */
  public String getShortFilename() {
  }
  
  /**
   * Returns the complete filename.
   * This corresponds to the String originally given to the constructor
   * except with any initial or trailing whitespace removed.
   */
  public String toString() {
  }
}

Tamarin will be trying test (reuse) your class, so it must be named Filename and have the above constructor and method signatures.

Extra credit (+0.5): Ideally, a 8.3 filename should also be stripped of any illegal characters. In getShortFilename(), replace all characters other than letters, digits, dashes ('-'), underscores ('_'), and the single/last dot ('.') with underscores. Do this only to the 8.3 filename, not to the regular name and extension output. (Hint: You'll need to loop over each character in the String. Also, check out the Character class in the API, particularly the isLetterOrDigit method.)

If you do this, a filename such as ~myfile#.lock would become _MYFILE_.LOC.

Step 2: Test Filename

You may want to test your Filename class at this point. Your main method is going to be so simple, however, that you can just use that to test your program. You don't really need an extra testing class. However, you must still think about the different categories of filenames that will need to be tested: no name, no extension, names shorter than 8 character, names longer 8 character, extensions longer than 3 characters, extensions shorter than 3, names with spaces, names with extra dots, etc.

Step 3: UsernameA14

Write a program that asks the user to enter a filename. (Again, this is just a String; it doesn't need to correspond to a real file.) Then print out the original full name, the name-only portion, the extension, and the 8.3 converted filename. Ask for only one file each time the program is run (that is, do not loop).

Sample Output

Enter a filename to convert: HelloWorld.java

Filename: HelloWorld.java
Name only: HelloWorld
Extension only: java
8.3 file name: HELLOWOR.JAV

Part 2: Printing a Sorted File List

Write a program that asks the user to enter a series of filenames. (Again, this is just a String; it doesn't need to correspond to a real file.)

Store each entered filename into a String[] array. (You must use an array, not an ArrayList, for this assignment.) The array you use must originally be of size 5. Each time you would exceed the current capacity of the array, instead replace the array with a new array of double the size with the same contents.

The user should indicate they are done entering filenames by simply hitting enter. (That is, stop looping if they enter an empty string.)

At this point, print out a sorted list of the filenames they entered.

Sample Output

This program will sort a series of filenames.
Enter a filename (or nothing to stop): HelloWorld.java
File: HelloWorld.java
Enter a filename (or nothing to stop): header.h
File: header.h
Enter a filename (or nothing to stop): hello.out.exe
File: hello.out.exe
Enter a filename (or nothing to stop): stds.html
File: stds.html
Enter a filename (or nothing to stop): .project
File: .project
Enter a filename (or nothing to stop): NOTICE
File. NOTICE
Enter a filename (or nothing to stop):

Files after sorting:
.project
HelloWorld.java
NOTICE
header.h
hello.out.exe
stds.html

Note that Strings in Java are case-sensitive, so all capital letters come before all lowercase letters when using the compareTo method.


Extra Credit: Part 1 and Part 2 Combined

If you choose this option, you will write the Filename class required for Part 1. You'll also ask the user to enter a series of filenames, store them in an array, and print them out in sorted order at the end, like in Part 2. The difference is that, when you print the names at the end, you'll print the 8.3 converted forms of the files in sorted order.

The array you use must still start at size 5, and this size should still be doubled each time the array gets full. However, this array may be either a String[] or a Filename[]. So you can either store the originally entered input String, the Filename object constructed from that input String, or only the 8.3 filename (a String) to be sorted at the end.

Sample Output

This program will convert a series of filenames.
Enter a filename to convert: HelloWorld.java
Converted: HELLOWOR.JAV
Enter a filename to convert: header.h
Converted: HEADER.H
Enter a filename to convert: hello.out.exe
Converted: HELLO_OU.EXE
Enter a filename to convert: stds.html
Converted: STDS.HTM
Enter a filename to convert: .project
Converted: .PRO
Enter a filename to convert: NOTICE
Converted: NOTICE
Enter a filename to convert:

Files converted:
.PRO
HEADER.H
HELLOWOR.JAV
HELLO_OU.EXE
NOTICE
STDS.HTM

Note that you're welcome to include more information after each file is processed, if you want to. So instead of:

Enter a filename to convert: HelloWorld.java
Converted: HELLOWOR.JAV

you could use this longer form (or something in between):

Enter a filename to convert: HelloWorld.java
Filename: HelloWorld.java
Name only: HelloWorld
Extension only: java
8.3 file name: HELLOWOR.JAV

What to Submit

Upload your UsernameA14.java file to Tamarin. If you did Part 1, don't forget to include the Filename class in your file.

Grading [6+ points]

All parts

1 - Compiles + Coding Standards
Your program compiles successfully (no errors). Your code follows Java coding standards.

Part 1

If you submit a Filename class and a UsernameA14 main method that asks for a single input and then ends, it will be graded as Part 1.

4.5 - Filename
You have the requested methods that behave as documented in the /** javadocs */ above. Includes: a Filename(String) constructor (0.5, required for additional points), getName() (0.5), getExtension() (0.5), toString() (0.5), getShortFilename() (2.5).
0.5 - UsernameA14
Your ask the user to enter a single filename and then print the entered filename, the name portion, the extension portion, and the 8.3 converted form, one per labeled line.
+0.5 - Extra Credit
Your getShortFilename() successfully replaces with underscores ('_') all characters in the name portion that are not digits, English letters, or hyphens (-).

Part 2

If your submission does not include a Filename class, it will be graded as Part 2.

1.5 - Array Use
Your program uses a String[] array to store the filenames read in from the user (0.5). You initially declare this array to be of size 5 (0.5). You double its size (and copy over all elements) each time the array capacity is exceeded (0.5).
1.5 - Input/Output
Your program prompts the user to enter filenames as Strings, stopping if the user enters nothing (empty string) (0.5). Your program then prints out a list of all the filenames the user entered (1).
2 - Correctly sorted.
When the program prints out the entered filenames, they are in sorted order.

Combined

If your submission includes a Filename class and uses a loop to ask for multiple filenames, it will be graded as a combination of Part 1 and Part 2.

4.5 - Filename
As above under Part 1.
1.5 - Array Use
Your program uses either a String[] or Filename[] array to store the filenames read in from the user (0.5). You initially declare this array to be of size 5 (0.5). You double its size (and copy over all elements) each time the array capacity is exceeded (0.5).
1 - Input/Output
Your program prompts the user to enter filenames as Strings, stopping if the user enters nothing (empty string) (0.5). Your program then prints out a list of all the 8.3 converted filenames the user entered (0.5).
1 - Correctly sorted.
When the program prints out the converted filenames, they are in sorted order.

FAQs

Demo code, warnings, and things to consider

Demo code
Section 001: We did this code in lab on Wednesday: StoredNumbers.java.

It demonstrates how to use an array for dynamic storage, as well as many common array operations such as printing the contents of a partially filled array, adding an element to the array, finding a particular value in the array, and deleting an element and shifting the remaining elements down to fill the gap.

Warning: Don't use String's isEmpty() method.
Tamarin is currently running Java 1.5; most of you are probably running Java 1.6. (If you want to check your version, type java -version on the command line.) There is one String method that some students find--isEmpty()--that was added in Java 1.6. If you use this method, your code will probably compile on your machine, but it won't compile when you upload it to Tamarin.

Therefore, I'm asking that you don't use this method in your code. Two alternatives to str.isEmpty() are str.length() == 0 and str.equals("")

Warning: Couldn't I just use Arrays.copyOf to increase the size of the array?
Theoretically, yes. But please don't do it. Tamarin is using Java 1.5, but the copyOf method was added in Java 1.6. (You can find this out by clicking on the method name in the API and going to the full description. It will then list "Since", which is the version of Java at which the method was added. Remember that Java 1.6 == Java 6.) So, if you use this method, your code will not compile when you submit it. Besides, you could probably use more practice with for loops anyway!

Still, if you really want to use a method to help you, check out System.arraycopy

Something to consider: Why arrays are valuable.
Imagine trying to do Part 2 of this assignment without arrays, but only with a number of String or Filename variables. First, sorting would be a nightmare, especially since you wouldn't be able to use a loop to traverse a series of separate variables. Secondly, you would be limited by the number of variables you defined. To handle a list of 10 filenames, you'd have to declare 10 variables, and (unlike arrays) there'd be no way to change this limit while the program is running.

Part 1: Strings and the Filename class

Recommendation: How to implement Filename
Note that Filename is immutable. (It has accessor methods, but no mutators.) This gives you a lot of freedom in how you implement the private internals of the class. This is just one way you could do it:

You really only need one String instance variable to save the original filename. All your other variables can be local.

Your constructor needs to save the filename parameter into your instance variable. You should save a trimmed version of the String so you don't have to worry about any extra whitespace in any of your other methods.

toString() - just return your instance variable, which contains the complete filename.

getName() - Grab everything up to the last dot in the String saved in your instance variable and return it. (You'll need to check if there is a dot first, though, so that your method doesn't crash if there isn't one.)

getExtension() - Grab everything after the last dot in the String saved in your instance variable and return it. (You'll need to check if there is a dot first, though, so that your method doesn't crash if there isn't one.)

getShortFilename() - Call your getName() and getExtension() methods to get the two parts of a filename. Store these in local variables. Manipulate these two Strings as necessary, shortening them if they are too long, etc. Then combine them into a single String. Further manipulations, such as uppercasing, may be required. Then return the 8.3 filename you've created. (Note that some manipulations--such as replacing extra dots--are easier to perform on just the name or extension. Other manipulations--such as uppercasing or replacing spaces--are better performed on the combined short filename so that you don't have to repeat the operation on multiple Strings.)

Some of these String methods listed in the API are really confusing!
Indeed, some of them use other classes (such as CharSequence) and complicated features (such as regular expressions) that we won't even cover in this class. But you don't need to use all the methods; you only need to learn some of them. In particular: length, charAt, indexOf, lastIndexOf, substring, trim, replace, toUpperCase, toLowercase. And you won't even need all of those to complete this particular assignment. If you do find some other String methods you'd like to use that are not in this list, though, feel free to do so (except isEmpty()).
I can't create a new Filename object! I get some error about the new keyword...
Your error probably looks something like this:
C:\Downloads\UsernameA14.java:11: cannot find symbol
symbol  : constructor Filename()
location: class Filename
Filename file = new Filename();
                ^

Although the little ^ is pointing at the new, line 2 of this error tells us that the actual symbol that can't be found is Filename(). This is because you (correctly) don't have a Filename constructor that takes no parameter. You only have a Filename(String) constructor.

So, if you want to create a Filename object, you always have to pass it a String value when you call the constructor. This makes sense, since you want to take the String the user gave you and use that to build your Filename object. It's this String value that you pass to your constructor that your constructor should then trim and save in an instance variable.

But instead, as shown in the error message above, you're trying to do this in main:

   Filename file = new Filename();

You're not passing a String value, which the Filename constructor needs.

How do I get the name and extension parts of the filename?
You need to split the filename on the last '.' character. So this will require use of either the indexOf or the lastIndexOf method. (Which one do you think you should use? What will it return if it can't find a '.' at all? What should you do if that happens?) Once you have the location of the last (or only) dot, you can use the substring method to split the string.
My program crashes if there is no dot in the filename.
You probably get something like this:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String 
index out of range: -1
        at java.lang.String.substring(String.java:1937)
        at Filename.getName(ZtomaszeA14.java:92)
        at Filename.getShortFilename(ZtomaszeA14.java:149)
        at ZtomaszeA14.main(ZtomaszeA14.java:33)
Note how the error is complaining that you're trying to access index -1 of a String. -1 is never a valid index. Specifically, from this error message, we can see the problem comes from line 92 of my code when I make a call to substring from my getName method.

This error usually happens when you forget to check whether an index you got is really valid. You need something like this:

  int lastDot = filename.lastIndexOf('.');
  if (lastDot < 0) {
    //no dot was found
    //so this means there is no extension
    //and the name part goes to the end of the string
  }else {
    //everything's fine
    //grab the name or extension using lastDot and substring
  }
My program crashes if the name (or extension) is too short.
Then you probably need to check the length of the name before you try manipulating it. Use the length method.
So, wait: for the extra credit, we leave all the dots in place?
No. You are to replace everything except the list of characters given. Assuming you did the regular part of the assignment correctly, you already replaced all but one of the dots with underscores (if there are any dots at all). This single remaining dot is a legal character, so leave it there.
I'm having trouble using the replaceAll and/or replaceFirst methods.
These methods actually use regular expressions, which is a sort of pattern-matching language. In a regular expression, the '.' matches any character, so you'll probably run into difficulties trying to use these methods.

Instead, just use the replace method. This will replace all matches found in a String. You can replace either one char with another; or you can replace one String (the API calls this a CharSequence, but a String is such a thing) with another.

Part 2: Arrays

Recommendation: How to implement UsernameA14
Have a look at the demo code. You are going to do something very similar, only with Strings. You will need an array and a count variable (named whatever you want) to track how many elements you have stored in that array.

Take it one step at a time. Get a program that just collects 5 or fewer Strings in an array and prints them out at the end. Then worry about growing the array when it gets full. Then worry about sorting.

How do I sort the filenames?

Remember that you only need to print the numbers in sorted order, not necessarily get the array itself into that sorted order. Also, if you haven't filled your array to capacity, it will still contain 0s in the unused elements. Be careful not to sort these in among your real data.

Here are some different ideas on how you might achieve sorted output:

Remember that if you are sorting Strings, you will need to use the compareTo method to compare them. If you are doing the Combined part, be aware that Filename objects do not have a compareTomethod. This means that java.util.Arrays.sort will not be able to sort a Filename[]. If you want to use this method, I recommend that you create a String[] containing all the converted filenames and sort that instead.

How do I use the compareTo method?
  String a = ...
  String b = ...
  if (a.compareTo(b) < 0) {
    //a comes before (is "smaller" than) b
  }else if (a.compareTo(b) > 0) {
    //a comes after (is "greater" than) b
  }else {
    //must be: a.compareTo(b) == 0
    //which means the two Strings are equal
  }
When I print the files list at the end, my files are all null (or maybe with only the last filename entered).
Be sure you declare your array before the input loop, or else it'll get recreated each time through the loop. Make sure you actually copy the user's input into the array at some point. Also, if this behavior only happens when you enter more than 5 Strings, make sure you copied everything over correctly when doubling the size of the array.
My filenames always include extra nulls at the end, unless I enter exactly 5 Strings.
Sounds like you're printing out the whole array (using .length or similar) rather than only those Strings you entered (as recorded in your count variable). In your printing loop, loop only up to count, not to .length.

If the nulls only show up after you sort (but not if you print out the array before sorting it), then see the next FAQ.

When I sort my array, my program crashes with a NullPointerException!
Remember your array is not always full. The remaining elements will be null. For example, suppose I have an array of length 10 with 7 valid elements in it:
[A C G D E F B|_ _ _ ]

The _s here represent nulls. However, note that once any sort algorithm passes B, it'll try to either call B.compareTo(_) or _.compareTo(B). In either case, you can't do this with null objects, and so the program crashes.

The solution to this is to stop at the |, which corresponds to your count of how many variables are actually in the array. This is true whether you're using a loop to do the sort or calling a method to do the sort. If you wrote your own method to do the sorting, you'll have to pass an extra parameter with this information. If you're trying to use java.util.Arrays.sort, find a version that lets you specify which portion of the array to sort.