// The class to read a playlist from a text file import java.io.BufferedReader; import java.io.FileReader; import java.util.ArrayList; import java.util.StringTokenizer; import java.io.File; public class PlayListReader{ /* STUDENTS: You only need to read the comments which follow in the next few lines. There is one class of interest, called PlayListReader, which has only one method of interest, called readPlayList. The rest of the file can be ignored. *Be sure to copy this .java file into the directory with your other files *and compile it. */ /** Read the playlist from the file and return an ArrayList of strings, * one string for each field of the input file. * If the file is invalid in any way, null is returned. * The required format of the file is: * Exactly one "playlist" line, with 3 fields, * followed by any number of "track" lines, each with 4 fields. // "playlist"; name; description // "track"; title; artist; length in seconds // "track"; title; artist; length * ... etc. etc. */ public ArrayList readPlayList(String filename){ BufferedReader brd; // Open the file, and return null if there is an error brd = FileUtilsHW4.URLToBufferedReader(filename); if (brd == null) { //this filename is not a URL. //Try to locate it as a file on the local system. try { brd = FileUtilsHW4.fnameToBufferedReader(filename); } catch (java.io.IOException e) { System.out.println("Unable to locate a file or URL named " + filename + FileUtilsHW4.fs + e); return null; //give up } } //At this point we should have a valid, open BufferedReader. // The lines in the file ArrayList lines = new ArrayList(); try { // Read lines until there are more lines String inputline; while((inputline = brd.readLine()) != null){ inputline = inputline.trim(); if(lines.size() == 0){ //this is the first line of the file if(!inputline.toLowerCase().startsWith("playlist")){ // The first line does not start with "playlist" System.out.println("The first line \"" + inputline + "\"\n\tshould start with \"playlist\" but does not."); return null; } } else { //not the first line -- should start with "track" if(!inputline.toLowerCase().startsWith("track")){ System.out.println("Line does not start with \"track\""); return null; } } // Add the line to the collection of lines lines.add(inputline); } } catch (Exception e) { // Error while reading the file System.out.println("File input error while reading " + filename + ": "+ e.toString()); return null; } // Parse each line and return; ArrayList tokens = new ArrayList(); // Parse the playlist tokens (1st line of the file) String currLine = (String) lines.get(0); StringTokenizer st = new StringTokenizer(currLine, ";"); try { // Add the 4 playlist fields for(int t =0; t < 3; t++){ tokens.add(st.nextToken().trim()); } } catch (Throwable e) { System.out.println( "Line parsing error on file " + filename + "\n\twhile reading what was expected to be a 'playlist' line with 3 fields: " + ":\n\t" + currLine + "\n\t" + e.toString()); return null; } try { for(int ln = 1; ln < lines.size(); ln++){ currLine = (String) lines.get(ln); // Parse the track tokens st = new StringTokenizer(currLine, ";"); // Add the 4 track fields for(int t = 0; t < 4; t++){ tokens.add(st.nextToken().trim()); } } // Return the arraylist of tokens return tokens; } catch (Throwable e) { System.out.println( "Line parsing error on file " + filename + "\n\twhile reading what was expected to be a 'track' line with 4 fields: " + ":\n\t" + currLine + "\n\t" + e.toString()); return null; } } /** just for testing */ public static void main(String[] args) { PlayListReader plr = new PlayListReader(); ArrayList list = plr.readPlayList("sample.txt"); System.out.println("ArrayList result is " + list); ArrayList list2 = plr.readPlayList("http://www.cs.washington.edu/education/courses/cse142/CurrentQtr/homework/hw4-draft/play2.txt"); System.out.println("ArrayList result is " + list2); } //end class PlayListReader } ////////////////////////////////////////////////////////////////////////////////////////////////////////// //This is an "unpackaged" version of MDUtils.FileUtils. Unneeded methods have //been removed /** * FileUtils.java * Static utility methods on files and pathnames, URLs and URIs */ class FileUtilsHW4 { public static final String fileSeparator = System.getProperty("file.separator"); public static final char fs = fileSeparator.charAt(0); /** After searching pretty hard if necessary, try to open the given filename * as a file on the local system, as a BufferedReader. * @param fname the name or partial path of the file to be opened. * @throws FileNotFoundException if the file is not located. */ public static BufferedReader fnameToBufferedReader(String fname) throws java.io.FileNotFoundException { String absPath; absPath = pathToAbsolutePathLookEverywhere(fname); if (absPath == null) { throw new java.io.FileNotFoundException(); } return pathToBufferedReader(absPath); } /** Without looking hard for the file, try to open the given filename * as a file on the local system, as a BufferedReader. * @param fname the path of the file to be opened. * @throws FileNotFoundException if the file is not located. */ public static BufferedReader pathToBufferedReader(String absPath) throws java.io.FileNotFoundException { File fFile = new File(absPath); if (fFile.exists()) { java.io.FileInputStream fis = new java.io.FileInputStream(fFile); java.io.InputStreamReader isr = new java.io.InputStreamReader(fis); BufferedReader bReader = new BufferedReader(isr); return bReader; } else { throw new java.io.FileNotFoundException("fnameToBufferedReader " + " path: " + absPath + " not found."); } //end fnameToBufferedReader } /** Given a URL, open a connection to it and return its stream as a Buffered * Reader. * @param url A string which should be a valid URL. * @return A open BufferedReader, from which no data has yet been read; or * null if the URL could not be opened. */ public static BufferedReader URLToBufferedReader(String urlString) { try { java.net.URL url = new java.net.URL(urlString); java.net.URLConnection connection = url.openConnection(); java.io.InputStream uStream = connection.getInputStream(); java.io.BufferedReader bReader = new java.io.BufferedReader( new java.io.InputStreamReader(uStream)); if (bReader != null) { return bReader; } } catch (Throwable e) { //System.out.println("Unable to open " + urlString + // " as a URL stream:n" + e); } return null; //end URLToBufferedReader } /** Given a file name or path, look VERY HARD for an absolute path * which matches. * Look first to see if the given string is a valid path for an * existing file. * Look then in the "user directory". * Then look in the Classpath directories. * Then, look in the directory which this very class is in. * Then, look in C: * Then, look in C:\ * Then, look up and down from the previous directories. * @param fileID A filename or path name * @return the absolute path of an existing file whose path ends in * the given string; return null if no such file is found, or if the * intput is null. */ public static String pathToAbsolutePathLookEverywhere(String fileID) { if (fileID == null) { return null; } String partialPath = fileID.trim(); if (partialPath.length() == 0) { return null; } //See if the given string is an OK path as-is File fileOfPartialPath = new File(partialPath); if (fileOfPartialPath != null && fileOfPartialPath.exists()) { if (fileOfPartialPath.isFile()) { return fileOfPartialPath.getAbsolutePath(); } else { if (fileOfPartialPath.isDirectory()) { return null; } } } //No luck so far. //Look in the most obvious places. //Along the way, build up a list of directories for further searching // should that be necessary later. java.util.Collection searchables = new java.util.ArrayList(); String userDir = System.getProperty("user.dir"); if (userDir != null) { String currDirPath = lookInDir(userDir, partialPath); if (currDirPath != null) { return currDirPath; } else { searchables.add(userDir); } } String CDrivePath = lookInDir("C:", partialPath); if (CDrivePath != null) { return CDrivePath; } else { searchables.add("C:"); } String CDriveRootPath = lookInDir("C:\\", partialPath); if (CDriveRootPath != null) { return CDriveRootPath; } else { searchables.add("C:\\"); } //String myOwnClass = "FileUtils.class"; String myOwnClass = "PlayListReader.class"; String pathOfMyOwnClass = findPathOfClass(myOwnClass); if (pathOfMyOwnClass != null) { int finalFS = pathOfMyOwnClass.lastIndexOf(fs); if (finalFS > 0) { String dirOfMyOwnClass = pathOfMyOwnClass.substring(0, finalFS); String pathInClassDir = lookInDir(dirOfMyOwnClass, partialPath); if (pathInClassDir != null) { return pathInClassDir; } else { searchables.add(dirOfMyOwnClass); } } } //Now let the Loader look in all paths on the JVM classpath. java.lang.ClassLoader loader = java.lang.ClassLoader.getSystemClassLoader(); try { java.net.URL urlOfPath = loader.getResource(partialPath); File fileFromURL = new File(urlOfPath.getFile()); if (fileFromURL != null && fileFromURL.exists() && fileFromURL.isFile()) { return fileFromURL.getAbsolutePath(); } } catch (Exception e) { } //We've run out of obvious places to look. //Look a little harder in places already searched. //The list of those places was built as we went along. System.out.println("Searching for " + partialPath + " in the obvious places was unsuccessful."); for (int searchDepth = 1; searchDepth <= 2; searchDepth++) { System.out.println("Searching harder for " + partialPath + " (" + searchDepth + ")"); java.util.Iterator pathIter = searchables.iterator(); while (pathIter.hasNext()) { String aPathToTry = (String) pathIter.next(); String ancestor = ancestorOfPath(aPathToTry, searchDepth); if (ancestor != null) { String fullPath = lookDownFromDir(ancestor, partialPath, 2*searchDepth); if (fullPath != null) { return fullPath; } } } } //admit utter defeat System.out.println("Admitting defeat. Unable to find " + partialPath + " ANYWHERE at all."); return null; } /** Given a package-qualified class name, find the path of its .class file, * if it is under a directory on the Class Path. * @param origRelClassPath A string with the classname, possibly in package form. * All of the following should work: * MDUtils.FileUtils.class * MDUtils.FileUtils.java * MDUtils.FileUtils * MDUtils/FileUtils.class * MDUtils\FileUtils.java * @return an absolute path, or null if the path cannot be found. */ public static String findPathOfClass(String origRelClassPath) { String relClassPath = origRelClassPath.trim(); if (relClassPath.endsWith(".class")) { relClassPath = relClassPath.substring(0, relClassPath.length() -6); } else { if (relClassPath.endsWith(".java")) { relClassPath = relClassPath.substring(0, relClassPath.length() -5); } } relClassPath = relClassPath.replace('.', fs); relClassPath += ".class"; java.net.URL url = java.lang.ClassLoader.getSystemResource(relClassPath); if (url == null) { //if we have the class, we must have its URL?? return null; } else { String uPath = URLToPath(url); //will be in local form File f = new File(uPath); if (f.exists()) { assert f.isFile(); assert !f.isDirectory(); String cPath = f.getAbsolutePath(); return cPath; } else { return null; } } // end findPathOfClass } /** See if the file named is in the directory. * @param currDir the directory in which to look. * @fname The file name, ideally with no leading or trailing punctuation * (such as PathSeparators). * @return the fully qualified pathname if the file is found, * null otherwise; null is returned also if either argument is null. */ public static String lookInDir(String currDir, String fname) { if (currDir == null || fname == null) { return null; } String trialPath = concatPaths(currDir, fname); File f = new File(trialPath); if (f.exists()) { return f.getAbsolutePath(); } else { return null; } //end lookInDir; } /** Look for a file in the immediate subdirectories of a directory. * @fname The file name, ideally with no leading or trailing punctuation * (such as PathSeparators). * @return the fully qualified pathname if the file is found, * null otherwise. */ public static String lookDownFromDir(String currDir, String fname) { return lookDownFromDir(currDir, fname, 1); } /** Look for a file in the subdirectories of a directory. * @param fname The file name, ideally with no leading or trailing punctuation * (such as PathSeparators). * @param depth The number of levels deep to go. 1 means look in the * immediate subdirectories only. * @return the fully qualified pathname if the file is found, * null otherwise. */ public static String lookDownFromDir(String currDirName, String fname, int depth) { currDirName = currDirName.trim(); fname = fname.trim(); fname = fname.replace('/', fs); //we'll be comparing to paths from the local system if (depth < 0) { return null; //recursion ends } File dir = new File(currDirName); if (dir == null || !dir.exists() || !dir.isDirectory() || !dir.canRead()) { return null; //recursion ends } if (depth == 0) { return lookInDir(currDirName, fname); //recursion ends } File[] dirList = dir.listFiles(); if (dirList == null || dirList.length == 0) { //There are some dirs, like System Volume Information, that pass //the tests above, yet return null for the dirList. return null; } for (int f = 0; f < dirList.length; f++) { String dname = dirList[f].getAbsolutePath(); //The entry might be a file or a directory if (dirList[f].isFile()) { if (dname.endsWith(fname)) { //we found what we want in the current directory! return dname; } } else { //Not a file. Recurse on this (directory) item. String lookResult = lookDownFromDir(dname, fname, depth-1); if (lookResult != null){ return lookResult; } } } //System.out.println("No luck depth " + depth + " " + currDirName); return null; //end lookDownFromDir } /** Append one path to another. This is just like concatenation, except * there is care to have exactly one file separator ("slash") connecting * the two. The default fs character is the system-local fs. There is no check to see if the result path represents a * valid file. */ public static String concatPaths(String prefixPath, String suffixPath) { return concatPaths(prefixPath, suffixPath, fileSeparator); } /** Append one path to another. This is just like concatenation, except * there is care to have exactly one file separator ("slash") connecting * the two. Exception: if the prefix is a drive letter, then no slash * is inserted. This allows a distinction between "C:" and "C:\". There is no check to see if the result path represents a * valid file. * @param fs The file separator character to use. */ public static String concatPaths(String prefixPath, String suffixPath, String fs) { prefixPath = prefixPath.trim(); suffixPath = suffixPath.trim(); if (prefixPath.length() == 2 && prefixPath.charAt(1) == ':') { //must be a drive letter like c: //no fix-up done } else { //Make sure one and only one has a slash at the joining end. if (prefixPath.endsWith(fs) && suffixPath.startsWith(fs)) { suffixPath = suffixPath.substring(1, suffixPath.length()); } if (!prefixPath.endsWith(fs) && !suffixPath.startsWith(fs)) { prefixPath += fs; } assert !(prefixPath.endsWith(fs) && suffixPath.startsWith(fs)) && (prefixPath.endsWith(fs) || suffixPath.startsWith(fs)); } return prefixPath + suffixPath; //end concatPaths } /** Give a URL, return its path, normalized for the local system. * There is no check to see whether the file exists on the local system. */ public static String URLToPath(java.net.URL url) { assert url != null; String urlPath = url.getPath(); //Get rid of initial '/' before drive letter if (urlPath.charAt(0) == '/' && urlPath.charAt(2) == ':') { urlPath = urlPath.substring(1, urlPath.length()); } String path = urlPath.replace('/', fs); //String unpath = MDUtils.MDStringUtils.unencode(path); String unpath = unencode(path);/*** PACKAGE MODIFICATION ***/ return unpath; } /** Considering the string as a local path, move up to containing directory. * There is no check to see if the path is valid. * @return the path of the containing directory, or null if the path * doesn't have one. * * */ public static String ancestorOfPath(String origPath) { String currDir = /* currDirOfPath*/(origPath); return ancestorOfPath(currDir, 1); //end ancestorOfPath } /** Considering the string as a local path, move up to an ancestor directory. * There is no check to see if the path is valid. A '.' following the final * fs character is considered to be an extension, and the path is taken * to represent a file. Without such a '.', it is taken to be a directory, * unless it can be verified that this is a valid file of the local system. * @param level number of levels to move up. level 0 means: if this is * a directory, then that directory; if this is a file, then the directory * the file is in. level 1 means the directory above the current one, etc. * * */ public static String ancestorOfPath(String origPath, int level) { String currDir = origPath.trim(); currDir = currDirOfPath(origPath); if (level == 0) { return currDir; } //back up the required number of levels, if possible. //Should not back up beyond the drive root. int backedUpCount = 0; //have backed up this many levels so far int slashIndex = currDir.length(); while (backedUpCount < level && slashIndex >= 0) { slashIndex = currDir.lastIndexOf('/'); if (slashIndex < 0) { slashIndex = currDir.lastIndexOf(fs); } if (slashIndex >= 0) { if (currDir.length() == 3 && currDir.charAt(1) == ':') { //it's a drive letter -- don't back up over the root } else { currDir = currDir.substring(0, slashIndex); } backedUpCount++; } else { return null; } } assert origPath.indexOf(currDir) >= 0; return currDir; //end ancestorOfPath } /** Find the current directory of the path. * @param origPath a String which is taken to be a path; it is not * validated. It may either use the system local file separator, or '/'. * @return origPath if it seems to be a directory; otherwise, if this * seems to be a file, return the directory it is in. */ public static String currDirOfPath(String origPath) { String currDir = origPath.trim(); if (probablyFileNotDirectory(currDir)) { //Strip off back to the last slash int slashIndex = currDir.lastIndexOf('/'); if (slashIndex < 0) { slashIndex = currDir.lastIndexOf(fs); } if (slashIndex >0) { //get up to but not including the slash currDir = currDir.substring(0, slashIndex); } else { currDir = ""; //we must have just had an isolated file name } } assert currDir.length() == 0 || origPath.indexOf(currDir) >= 0; return currDir; //end currDirOfPath } public static boolean probablyFileNotDirectory(String origPath) { String fname = origPath.trim(); File f = new File(fname); if (f.exists()) { //In luck -- this is a valid File -- we can query it. if (f.isFile()) { return true; } else { return false; } } //not a valid file, so guess by the path. int dotIndex = fname.lastIndexOf('.'); int fsIndex = fname.lastIndexOf(fs); int slashIndex = fname.lastIndexOf('/'); if (slashIndex > fsIndex) { fsIndex = slashIndex; } assert (dotIndex < 0) || (dotIndex != slashIndex); if (dotIndex >= fsIndex) { return true; } else { return false; } //end isProbablyFileNotDirectory } /** Replace sequences like %20 with their plain equivalent. * Currently ONLY handles %20. * No other characters are changed. * File separator characters (/, \) are not changed. */ public static String unencode(String original) { String sp = "%20"; String retString = original; int position = retString.indexOf(sp); while (position >= 0) { assert position <= retString.length() - 2; retString = retString.substring(0, position) + " " + retString.substring(position+3, retString.length()); position = retString.indexOf(sp); } return retString; } //end class FileUtils }