package org.greenstone.gatherer.util; /** *######################################################################### * * A component of the Gatherer application, part of the Greenstone digital * library suite from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * *

* * Author: John Thompson, Greenstone Digital Library, University of Waikato * *

* * Copyright (C) 1999 New Zealand Digital Library Project * *

* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * *

* * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * *

* * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *######################################################################## */ import java.awt.*; import java.io.*; import java.net.*; import java.util.*; import javax.swing.*; import javax.swing.tree.*; import org.apache.xerces.parsers.*; import org.apache.xml.serialize.*; import org.greenstone.gatherer.Gatherer; import org.greenstone.gatherer.util.HTMLStringTokenizer; import org.w3c.dom.*; import org.xml.sax.*; /** To provide a library of common methods, in a static context, for use in the Gatherer. * @author John Thompson, Greenstone Digital Library, University of Waikato * @version 2.3b */ public class Utility { /** The default size of a gatherer progress bar, in either the download view or the build view. */ static final public Dimension PROGRESS_BAR_SIZE = new Dimension(580,65); /** The number of kilobytes to use as a io buffer. */ static final public int FACTOR = 1; /** The size of the io buffer, calculated as FACTOR * 1024. */ static final public int BUFFER_SIZE = FACTOR * 1024; /** Definition of an important directory name, in this case the archive directory for the collection. */ static final public String ARCHIVE_DIR = "archives" + File.separator; /** Definition of an important directory name, in this case the base dir, or the working directory of the Gatherer. */ static final public String BASE_DIR = System.getProperty("user.dir") + File.separator; /** Definition of an important directory name, in this case the building directory for the collection. */ static final public String BUILD_DIR = "building" + File.separator; /** Definition of an important directory name, in this case the public web cache for the Gatherer. */ static final public String CACHE_DIR = BASE_DIR + "cache" + File.separator; static final public String CFG_COLLECTIONMETA_COLLECTIONNAME = "collectionmeta collectionname"; static final public String CFG_COLLECTIONMETA_COLLECTIONEXTRA = "collectionmeta collectionextra"; static final public String CFG_COLLECTIONMETA_ICONCOLLECTION = "collectionmeta iconcollection"; static final public String CFG_CLASSIFY = "classify"; static final public String CFG_CLASSIFY_BUTTONNAME = "-buttonname"; static final public String CFG_CLASSIFY_HFILE = "-hfile"; static final public String CFG_CLASSIFY_METADATA = "-metadata"; static final public String CFG_CLASSIFY_SORT = "-sort"; static final public String CFG_CREATOR = "creator"; static final public String CFG_FORMAT = "format"; static final public String CFG_MAINTAINER = "maintainer"; /** Definition of an important directory name, in this case the parent directory of all the collections in the gsdl. */ static final public String COL_DIR = "collect" + File.separator; static final public String COLLECTION_DEMO = "greenstone demo"; static final public String COLLECTION_DEMO_DIRECTORY = "demo" + File.separator; static final public String COLLECTION_DLS = "Development Library Subset"; static final public String COLLECTION_DLS_DIRECTORY = "dls" + File.separator; static final public String COLLECTION_TREE = "Collection"; /** Definition of an important directory name, in this case the file the collection configuration is expect to be in. */ static final public String CONFIG_DIR = "etc" + File.separator + "collect.cfg"; /** The default file name for the urls missing any file. */ static final public String DEFAULT_FILE = "index.html"; static final public String DEFAULT_NAMESPACE = "gsp"; /** The default protocol header for those urls missing any protocol. */ static final public String DEFAULT_PROTOCOL = "http://"; /** The default dictionary to load. */ static final public String DICTIONARY = "dictionary"; static final public String DLS_MDS = "dls.mds"; static final public String ENGLISH_VALUE = "en"; /** Definition of an important directory name, in this case the etc (or extra information) directory for the collection. */ static final public String ETC_DIR = "etc" + File.separator; static final public String EXTRACTED_METADATA_NAMESPACE = "ex"; /** The location of the default greenstone metadata file. */ static final public String GREENSTONEDIRECTORYMETADATA_TEMPLATE = "xml/metadata.xml"; /** Definition of an important directory name, in this case the private web cache directory for the collection. */ static final public String GCACHE_DIR = "gcache" + File.separator; static final public String GLI_ARCHIVE = "GLI.jar"; /** Definition of an important directory name, in this case the location of help documentation. */ static final public String HELP_DIR = BASE_DIR + "help" + File.separator; /** Definition of an important directory name, in this case the import directory for the collection. */ static final public String IMPORT_DIR = "gimport" + File.separator; /** Definition of an important directory name, in this case the index directory for the collection. */ static final public String INDEX_DIR = "index" + File.separator; static final public String LANGUAGE_ATTRIBUTE = "language"; /** Definition of an important directory name, in this case the log directory for the collection. */ static final public String LOG_DIR = "log" + File.separator; /** Definition of an important directory name, in this case the location of the expected collection metadata sets.. */ static final public String META_DIR = "metadata" + File.separator; // Col. Copy /** Definition of an important directory name, in this case the location of the default metadata sets. */ static final public String METADATA_DIR = BASE_DIR + "metadata" + File.separator; static final public String METADATA_EXTRACTED = "extracted.mds"; /** The location the gatherer expects to find metadata set information. */ static final public String METADATA_SET_TEMPLATE = "xml/template.mds"; static final public String METADATA_VALUE_TEMPLATE = "xml/template.mdv"; static final public String METADATA_XML = "metadata.xml"; static final public String NAME_ELEMENT = "Name"; /** Definition of an important directory name, in this case the import directory for the collection. */ static final public String OLD_IMPORT_DIR = "import" + File.separator; /** The default name of the perl executable under unix. */ static final public String PERL_EXECUTABLE_UNIX = "perl"; /** The default name of the perl executable under windows. */ static final public String PERL_EXECUTABLE_WINDOWS = "Perl.exe"; /** The default profile file */ static final public String PROFILE_TEMPLATE = "xml/protemp.xml"; /** The name of the Gatherer. */ static final public String PROGRAM_NAME = "Greenstone Librarian Interface"; /** The current version of the Gatherer. */ static final public String PROGRAM_VERSION = "ver 2.0"; /** Definition of an important directory name, in this case the location of the recycled files location. */ static final public String RECYCLE = BASE_DIR + "recycle" + File.separator; /** Definition of an important directory name, in this case the location of image and other resources. */ static final public String RES_DIR = BASE_DIR + "resource" + File.separator; static final public String SERVER_EXE = "server.exe"; /** Definition of an important directory name, in this case the location of opening (or welcome) screen html. */ static final public String WELCOME_DIR = BASE_DIR + "welcome" + File.separator; static final public String WORKSPACE_TREE = "Workspace"; static final public String XML_DIRECTORY = "xml" + File.separator; // These are out of alphabetic order to avoid forward reference error. /** The default icon to produce a 'help-icon' sized blank space before a menu entry. */ static final public ImageIcon BLANK_ICON = new ImageIcon(ClassLoader.getSystemResource("images/blank.gif")); /** The default error icon image. */ static final public ImageIcon ERROR_ICON = new ImageIcon(ClassLoader.getSystemResource("images/error.gif")); static final public ImageIcon HELP_ICON = new ImageIcon(ClassLoader.getSystemResource("images/help.gif")); /** The image for a toggle button whose state is 'on'. */ static final public ImageIcon ON_ICON = new ImageIcon(ClassLoader.getSystemResource("images/check.gif")); /** The image for a toggle button whose state is 'off'. */ static final public ImageIcon OFF_ICON = new ImageIcon(ClassLoader.getSystemResource("images/cross.gif")); /** Method to turn a file from with the system file tree into a tree path for insertion into a tree. * @param file The File whose tree path you are attempting to discover. * @param in_col A boolean indicating whether we are looking for a file within a collection of not. If true then the tree paths head in the collection name, and no element in the path refers to the import directory. Otherwise the paths head will be one of the system roots and all traversed file locations will exist in the path. * @return A TreePath which traverses the file system tree to the specified file. */ public static TreePath createTreePath(File file, boolean in_col) { TreePath path = null; // Get the absolute path of the file. String abs_path = file.getAbsolutePath(); while(file != null) { // If we are looking for a node within our collection, we expect // its path from root to be /... without any higher // details and without gimport. So if we encounter a gimport we // skip to its parent, add that, then return. if(in_col && file.getName().equals("gimport")) { file = file.getParentFile(); if(path == null) { path = new TreePath(file.getName()); } else { path = path.pathByAddingChild(file.getName()); } file = null; } else { if(path == null) { path = new TreePath(file.getName()); } else { path = path.pathByAddingChild(file.getName()); } file = file.getParentFile(); } } // Unfortunately we've created the path in reverse order so we have to // reverse it. Object temp[] = new Object[path.getPathCount()]; for(int i = 0; i < temp.length; i++) { temp[(temp.length - 1) - i] = path.getPathComponent(i); } return new TreePath(temp); } /** Decodes a string of text so its safe to use in a Greenstone configuration file. Esentially replaces "\n" with a newline. * @param raw The String before decoding, read from the configuration file.. * @return A String ready to be placed in a component. */ static public String decodeGreenstone(String raw) { raw = raw.replaceAll("'", "\'"); raw = raw.replaceAll(">", ">"); raw = raw.replaceAll("<", "<"); raw = raw.replaceAll(""", "\""); raw = raw.replaceAll("'", "\'"); raw = raw.replaceAll("\\\\n", "\n"); return raw; } /** Takes a rfc2616 'safe' String and translates it back into its 'unsafe' form. Basically the native c wget decode_string() function, but without pointer stuff. If searches through the String looking for the pattern %xy where x and y are hexidecimal digits and where xy maps to a character.
If x or y are not hexidecimal or % is followed by a \0 then the pattern is left as is. * @param encoded The url-safe String to be decoded. * @return The decoded String. */ public static String decodeString(String encoded) { String decoded = ""; for(int i = 0; i < encoded.length(); i++) { if(encoded.charAt(i) == '%') { if(hexidecimal(encoded.charAt(i+1)) != -1 && hexidecimal(encoded.charAt(i+2)) != -1) { char unsafe_chr = (char) ((hexidecimal(encoded.charAt(i+1)) * 16) + hexidecimal(encoded.charAt(i+2))); decoded = decoded + unsafe_chr; i = i + 2; } } else { decoded = decoded + encoded.charAt(i); } } return decoded; } /** It turns out that in Java you have to make sure a directory is empty before you delete it (much like unix I suppose), and so just like unix I'll have to set up a recursive delete function. * @param file The File you want to delete. * @return A boolean which is true if the file specified was successfully deleted, false otherwise. */ static public boolean delete(File file) { boolean result = true; // If files a directory, delete files children. if(file.isDirectory()) { File files[] = file.listFiles(); for(int i = 0; files != null && result && i < files.length; i++) { result = delete(files[i]); } } if(result) { // Delete file. return file.delete(); } return result; } /** Generate a depth first enumeration of a tree. */ static public EnumeratedVector depthFirstEnumeration(TreeNode node, EnumeratedVector result) { result.add(node); for(int i = 0; i < node.getChildCount(); i++) { depthFirstEnumeration(node.getChildAt(i), result); } return result; } /** Encodes a string of text so its safe to use in a Greenstone configuration file. Esentially replaces newlines with their escaped form. * @param raw The String before encoding. * @return A String which is safe to write to the configuration file. */ static public String encodeGreenstone(String raw) { raw = raw.replaceAll("<", "<"); raw = raw.replaceAll(">", ">"); return raw.replaceAll("\n", "\\\\n"); } /** Using this method we can request that a certain document be written, as valid XML, to a certain output stream. This makes use of the Xerces Serialization suite, which should in no way be confused with the usual method of Serialization used by Java. */ static public boolean export(Document document, String filename) { return export(document, new File(filename)); } static public boolean export(Document document, File file) { try { OutputStream os = new FileOutputStream(file); // Create an output format for our document. OutputFormat f = new OutputFormat(document); f.setIndenting(true); f.setLineWidth(0); f.setPreserveSpace(false); // Create the necessary writer stream for serialization. OutputStreamWriter osw = new OutputStreamWriter(os); Writer w = new BufferedWriter(osw); // Generate a new serializer from the above. XMLSerializer s = new XMLSerializer(w, f); s.asDOMSerializer(); // Finally serialize the document to file. s.serialize(document); // And close. os.close(); return true; } // A file not found exception is most likely thrown because the directory the metadata.xml file is attempting to be written to no longer has any files in it. I'll add a test in GDMDocument to test for this, but if it still happens ignore it (a non-existant directory can't really have metadata added to it any way. catch (FileNotFoundException fnf_exception) { if(!file.getName().endsWith(METADATA_XML)) { fnf_exception.printStackTrace(); return false; } return true; } catch (IOException ioe) { ioe.printStackTrace(); return false; } } /** Given a starting directory, searches for the collect.cfg file and returns it if found. * @return The collect.cfg File or null if not found. */ static final public File findConfigFile(File start) { if(start == null) { return null; } // See if the collect.cfg files here. File collect_cfg = new File(start, "collect.cfg"); if(collect_cfg.exists()) { return collect_cfg; } // Search for the existance of collect.cfg in a etc directory. File etc_dir = new File(start, "etc" + File.separator + "collect.cfg"); if(etc_dir.exists()) { return etc_dir; } // Otherwise search this directories parent if its not null. return findConfigFile(start.getParentFile()); } /** Convert a long, detailing the length of a file in bytes, into a nice human readable string using b, kb, Mb and Gb. */ static final public String BYTE_SUFFIX = " b"; static final public long GIGABYTE = 1024000000l; static final public String GIGABYTE_SUFFIX = " Gb"; static final public long KILOBYTE = 1024l; static final public String KILOBYTE_SUFFIX = " kb"; static final public long MEGABYTE = 1024000l; static final public String MEGABYTE_SUFFIX = " mb"; static final public String formatFileLength(long length) { StringBuffer result = new StringBuffer(""); float number = 0f; String suffix = null; // Determine the floating point number and the suffix (radix) used. if(length >= GIGABYTE) { number = (float) length / (float) GIGABYTE; suffix = GIGABYTE_SUFFIX; } else if(length >= MEGABYTE) { number = (float) length / (float) MEGABYTE; suffix = MEGABYTE_SUFFIX; } else if(length >= KILOBYTE) { number = (float) length / (float) KILOBYTE; suffix = KILOBYTE_SUFFIX; } else { number = (float) length; suffix = BYTE_SUFFIX; } // Create the formatted string remembering to round the number to 2.d.p. To do this copy everything in the number string from the start to the first occurance of '.' then copy two more digits. Finally search for and print anything that appears after (and including) the optional 'E' delimter. String number_str = Float.toString(number); char number_char[] = number_str.toCharArray(); int pos = 0; // Print the characters up to the '.' while(number_char != null && pos < number_char.length && number_char[pos] != '.') { result.append(number_char[pos]); pos++; } if(pos < number_char.length) { // Print the '.' and at most two characters after it result.append(number_char[pos]); pos++; for(int i = 0; i < 2 && pos < number_char.length; i++, pos++) { result.append(number_char[pos]); } // Search through the remaining string for 'E' while(pos < number_char.length && number_char[pos] != 'E') { pos++; } // If we still have string then we found an E. Copy the remaining string. while(pos < number_char.length) { result.append(number_char[pos]); pos++; } } // Add suffix result.append(suffix); // Done return result.toString(); } /** This method formats a given string, using HTML markup, so its width does not exceed the given width and its appearance if justified. * @param text The String requiring formatting. * @param width The maximum width per line as an int. * @return A String formatted so as to have no line longer than the specified width. * TODO Currently HTML formatting tags are simply removed from the text, as the effects of spreading HTML tags over a break are undetermined. To solve this we need to associate tags with a certain text token so if it gets broken on to the next line the tags go with it, or if the tags cover a sequence of words that are broken we need to close then reopen the tags. However all this is a major task and well beyond anything I have time to 'muck-round' on. */ static public String formatHTMLWidth(String text, int width) { HTMLStringTokenizer html = new HTMLStringTokenizer(text); int current_width = 0; int threshold = width / 2; Stack lines = new Stack(); String line = ""; while(html.hasMoreTokens()) { String token = html.nextToken(); while(token != null) { if(html.isTag()) { // Insert smart HTML tag code here. token = null; } else { // If the token is bigger than two thirds width, before we've even started break it down. if(current_width + 1 + token.length() > width && token.length() > threshold) { String prefix = token.substring(0, width - 1 - current_width); token = token.substring(prefix.length()); if(current_width == 0) { line = line + prefix; } else { line = line + " " + prefix; } lines.push(line); line = ""; current_width = 0; } // If adding the next token would push us over the maximum line width. else if(current_width + 1 + token.length() > width) { line = space(line, width, current_width); lines.push(line); line = token; current_width = token.length(); token = null; } // Otherwise we should be able to just add the token, give or take. else { if(current_width == 0) { line = line + token; current_width = token.length(); } else { // Special case for standard punctuation which may exist after a tag like so: // My name is Slim Shady. <-- Annoying punctuation. if(token.equals(".") || token.equals(",") || token.equals("!") || token.equals("?")) { line = line + token; current_width = current_width + 1; } else { line = line + " " + token; current_width = current_width + 1 + token.length(); } } token = null; } } } } String result = line; while(!lines.empty()) { result = (String)lines.pop() + "
" + result; } // Replace ' ' with " " boolean tag = false; int pos = 0; while(pos < result.length()) { if(result.charAt(pos) == '<') { tag = true; } else if(result.charAt(pos) == '>') { tag = false; } else if(result.charAt(pos) == ' ' && !tag) { String prefix = result.substring(0, pos); String suffix = result.substring(pos + 1); result = prefix + " " + suffix; } pos++; } result = "" + result + ""; return result; } /** Format the given filename path string so that it is no longer than the given width. If it is wider replace starting directories with ... * @param key The key String used to retrieve a phrase from the dictionary for this item. * @param raw The raw filename path String. * @param width The maximum width as an int. * @return A path String no longer than width. */ static public String formatPath(String key, String raw, int width) { JLabel label = new JLabel(Gatherer.dictionary.get(key, raw)); int position = -1; while(label.getPreferredSize().width > width && (position = raw.indexOf(File.separator)) != -1) { raw = "..." + raw.substring(position + 1); label.setText(Gatherer.dictionary.get(key, raw)); } if(raw.indexOf(File.separator) == -1 && raw.startsWith("...")) { raw = raw.substring(3); } return raw; } /** Method which constructs the archive directory given a certain collection. * @param col_dir The location of the collection directory as a String. * @return The location of the given collections archive directory, also as a String. */ static public String getArchiveDir(String gsdl_path, String col_name) { return gsdl_path + File.separator + COL_DIR + col_name + File.separator + ARCHIVE_DIR; } /** Method which constructs the build directory given a certain collection. * @param col_dir The location of the collection directory as a String. * @return The location of the given collections build directory, also as a String. */ static public String getBuildDir(String col_dir) { if(col_dir == null) { return BASE_DIR + BUILD_DIR; } return col_dir + BUILD_DIR; } /** Builds the private cache dir by appending col_dir and 'cache'. * @param col_dir A String representing the directory path of the current collection. * @return A String representing the path to the private file cache within the current collection. */ public static String getCacheDir(String col_dir) { return col_dir + GCACHE_DIR; } /** Method which constructs the collection directory for Greenstone. * @param gsdl_path The location of the gsdl installation directory as a String. * @return The location of the collection directory, also as a String. */ public static String getCollectionDir(String gsdl_path) { return gsdl_path + COL_DIR; } /** Method which constructs the configuration file given a certain collection. * @param col_dir The location of the collection directory as a String. * @return The location of the given collections configuration file, also as a String. */ static public String getConfigDir(String col_dir) { return col_dir + CONFIG_DIR; } static public String getDateString() { Calendar current = Calendar.getInstance(); String day_name = null; switch(current.get(Calendar.DAY_OF_WEEK)) { case Calendar.MONDAY: day_name = "Mon"; break; case Calendar.TUESDAY: day_name = "Tue"; break; case Calendar.WEDNESDAY: day_name = "Wed"; break; case Calendar.THURSDAY: day_name = "Thu"; break; case Calendar.FRIDAY: day_name = "Fri"; break; case Calendar.SATURDAY: day_name = "Sat"; break; case Calendar.SUNDAY: day_name = "Sun"; break; default: day_name = ""; } String month_name = null; switch(current.get(Calendar.MONTH)) { case Calendar.JANUARY: month_name = "Jan"; break; case Calendar.FEBRUARY: month_name = "Feb"; break; case Calendar.MARCH: month_name = "Mar"; break; case Calendar.APRIL: month_name = "Apr"; break; case Calendar.MAY: month_name = "May"; break; case Calendar.JUNE: month_name = "Jun"; break; case Calendar.JULY: month_name = "Jul"; break; case Calendar.AUGUST: month_name = "Aug"; break; case Calendar.SEPTEMBER: month_name = "Sep"; break; case Calendar.OCTOBER: month_name = "Oct"; break; case Calendar.NOVEMBER: month_name = "Nov"; break; case Calendar.DECEMBER: month_name = "Dec"; break; default: month_name = ""; } int day = current.get(Calendar.DAY_OF_MONTH); int hour = current.get(Calendar.HOUR_OF_DAY); int minute = current.get(Calendar.MINUTE); int second = current.get(Calendar.SECOND); int year = current.get(Calendar.YEAR); return day_name + " " + month_name + " " + day + " " + year + " " + Utility.pad(String.valueOf(hour), 2, '0', true) + ":" + Utility.pad(String.valueOf(minute), 2, '0', true) + ":" + Utility.pad(String.valueOf(second), 2, '0', true); } /** Retrieves and formats the depth field of the config file to four characters. * @param length The length of the desired string as an int. * @return A String representation of the mirroring depth padded to length characters. */ public static String getDepthString(int length) { return pad("" + Gatherer.self.config.getInt("mirroring.depth", false), length); } /** Method which constructs the etc directory given a certain collection. * @param col_dir The location of the collection directory as a String. * @return The location of the given collections etc directory, also as a String. */ public static String getEtcDir(String col_dir) { return col_dir + ETC_DIR; } /** Method to retrieve an image icon with the given filename found in classpath or the resouces directory. * @return The specified ImageIcon, or an error image replacement if no such images exists. */ static public ImageIcon getImage(String filename) { ImageIcon image = null; try { image = new ImageIcon(ClassLoader.getSystemResource("images/" + Gatherer.dictionary.get("Version") + "/" + filename)); } catch(NullPointerException exception) { image = new ImageIcon(ClassLoader.getSystemResource("images/" + filename)); } if(image == null) { image = ERROR_ICON; } return image; } /** Method which constructs the import directory given a certain collection. * @param col_dir The location of the collection directory as a String. * @return The location of the given collections import directory, also as a String. */ public static String getImportDir(String col_dir) { return col_dir + IMPORT_DIR; } /** Method which constructs the index directory given a certain collection. * @param col_dir The location of the collection directory as a String. * @return The location of the given collections index directory, also as a String. */ static public String getIndexDir(String col_dir) { return col_dir + INDEX_DIR; } /** Method which constructs the log directory given a certain collection. * @param col_dir The location of the collection directory as a String. * @return The location of the given collections log directory, also as a String. */ public static String getLogDir(String col_dir) { return col_dir + LOG_DIR; } /** Determine this machines name. * @return The name as a String. */ static public String getMachineName() { try { return InetAddress.getLocalHost().getHostName(); } catch(UnknownHostException ex) { } return "Unknown Machine"; } /** Method which constructs the metadata directory given a certain collection. * @param col_dir The location of the collection directory as a String. * @return The location of the given collections metadata directory, also as a String. */ static public String getMetadataDir(String col_dir) { return col_dir + META_DIR; } static public File getRecycleDirectory() { return new File(RECYCLE); } /** Determine whether a character is a hexidecimal one. * @param chr The char in question. * @return An int representing the value of the hexidecimal character or -1 if not a hexidecimal. */ public static int hexidecimal(char chr) { switch(chr) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'A': return 10; case 'B': return 11; case 'C': return 12; case 'D': return 13; case 'E': return 14; case 'F': return 15; default: return -1; } } /** A string is a valid hierarchy index if it matches '[0-9](\.[0-9])*' */ static public boolean isIndex(String raw) { boolean result = true; for(int i = 0; result && i < raw.length(); i++) { char c = raw.charAt(i); if(Character.isDigit(c) || (c == '.' && (i != 0 || i != raw.length() - 1))) { // Valid index } else { result = false; } } return result; } /** Method to determine if the host system is Microsoft Windows based. * @return A boolean which is true if the platform is Windows, false otherwise. */ public static boolean isWindows() { Properties props = System.getProperties(); String os_name = props.getProperty("os.name",""); if(os_name.startsWith("Windows")) { return true; } return false; } /** Takes a string and a desired length and pads out the string to the length by adding spaces to the left. * @param str The target String that needs to be padded. * @param length The desired length of the string as an int. * @return A String made from appending space characters with the string until it has a length equal to length. */ public static String pad(String str, int length) { return pad(str, length, ' ', true); } public static String pad(String str_raw, int length, char fill, boolean end) { StringBuffer str = new StringBuffer(str_raw); while(str.length() < length) { if(end) { str.insert(0, fill); } else { str.append(fill); } } return str.toString(); } /** Parse in a xml document from a given filename. Note that this filename may need to be resolved by the class loader, especially for template files within a jar. */ static public Document parse(String filename, boolean use_classloader) { File file = null; if(use_classloader) { try { URL url = ClassLoader.getSystemResource(filename); file = new File(URLDecoder.decode(url.getFile(), "UTF-8")); url = null; } catch (Exception error) { // Most likely file name. file = new File("classes" + File.separator + filename); //Gatherer.printStackTrace(error); } } if(file == null) { file = new File(filename); } return parse(file, true); } /** Parse in a xml document from a given file. */ static public Document parse(File file) { return parse(file, true); } /** Parse in a xml document from a given file. */ static public Document parse(File file, boolean noisey) { Document document = null; try { FileInputStream fis = new FileInputStream(file); InputStreamReader isr = new InputStreamReader(fis); Reader r = new BufferedReader(isr); InputSource isc = new InputSource(r); DOMParser parser = new DOMParser(); parser.setFeature("http://xml.org/sax/features/validation", false); parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); // May or may not be ignored, the documentation for Xerces is contradictory. If it works then parsing -should- be faster. parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", true); parser.setFeature("http://apache.org/xml/features/dom/include-ignorable-whitespace", false); parser.parse(isc); document = parser.getDocument(); isr.close(); fis.close(); parser = null; isc = null; r = null; isr = null; fis = null; file = null; } catch (Exception error) { if(noisey) { error.printStackTrace(); Gatherer.printStackTrace(error); } } return document; } /** Method to spread out a line of text so that is is justified to the given width, by attempting to widen white-spacing in a balanced way. * @param original The String to justify. * @param width The desired width as an int. * @param current_width An int representing the current width of the string, which takes into account special characters. * @return The newly justified String. */ static public String space(String original, int width, int current_width) { // Strip trailing whitespace. while(original.charAt(original.length() - 1) == ' ') { original = original.substring(0, original.length() - 2); } int diff = width - current_width; // Now add diff spaces, one at each existing space. int pos = 0; while(diff > 0) { if(pos == original.length()) { pos = 0; } if(original.charAt(pos) == ' ') { // Insert a space. String prefix = original.substring(0, pos); String suffix = original.substring(pos); original = prefix + " " + suffix; pos = pos + 2; diff--; } pos++; } return original; } /** Method to strip new lines and extra spaces from a string. Used to restore text that has been mangled into width formatted blocks by the DOM parser. * @param raw The Strong containing the mangled text. * @return A String with new lines and extra spaces removed. */ static public String stripNL(String raw_str) { byte raw[] = raw_str.getBytes(); byte formatted[] = new byte[raw.length]; byte previous = '\0'; int j = 0; for(int i = 0; i < raw.length; i++) { if(raw[i] == '\n') { // Skip new lines. } else if(raw[i] == '\t') { // Skip tabs. } else if(raw[i] == ' ' && raw[i] == previous) { // Skip erroneous whitespace. } else { formatted[j] = raw[i]; j++; } previous = raw[i]; } byte finish[] = new byte[j]; System.arraycopy(formatted, 0, finish, 0, j); return new String(finish); } /** Trims the string text to the length specified removing end characters and adding if necessary. * @param text A String which you wish to ensure is shorter than length. * @param length An int specifying the strings maximum length after which its trimmed. * @return The trimmed String. */ public static String trim(String text, int length) { if(text.length() > length) { text = text.substring(0, length); text = text + "..."; } return text; } static public String trimCenter(String text, int length) { if(text.length() > length) { int half = (length - 3) / 2; StringBuffer temp = new StringBuffer(text.substring(0, half)); temp.append("..."); temp.append(text.substring(text.length() - half)); text = temp.toString(); } return text; } /** This method checks to see what registered file system root directorys are mounted, and returns only accessible ones. The exception is removable media drives (in particular floppy-disk drives) which will throw all sorts of error if we test them here. Instead they are assumed to be always accessible, but a test is conducted at the time you attempt to map them to test for actual accessibility (then at least the errors are thrown after the user tries to initiate the mapping of the drive which has no disk in it). * @param roots A File[] containing all of the file system roots registered on this system. * @return A filtered File[] containing only those drives that are accessible and/or are floppy-disk media drives. */ public static File[] validateDrives(File roots[]) { Vector valid = new Vector(); for(int i = 0; i < roots.length; i++) { String name = roots[i].getAbsolutePath(); name = name.toLowerCase(); if(!name.startsWith("a:") && !name.startsWith("b:")) { valid.add(roots[i]); } } roots = new File[valid.size()]; for(int i = 0; i < roots.length; i++) { roots[i] = (File)valid.get(i); } return roots; } }