source: main/trunk/gli/src/org/greenstone/gatherer/util/Utility.java@ 29017

Last change on this file since 29017 was 29017, checked in by ak19, 10 years ago

Moved the StreamGobbler classes and their closeResource help method out of FormatConversionDialog.java as these are more generally useful

  • Property svn:keywords set to Author Date Id Revision
File size: 20.5 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Gatherer application, part of the Greenstone digital
5 * library suite from the New Zealand Digital Library Project at the
6 * University of Waikato, New Zealand.
7 *
8 * <BR><BR>
9 *
10 * Author: John Thompson, Greenstone Digital Library, University of Waikato
11 *
12 * <BR><BR>
13 *
14 * Copyright (C) 1999 New Zealand Digital Library Project
15 *
16 * <BR><BR>
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
22 *
23 * <BR><BR>
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * <BR><BR>
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with this program; if not, write to the Free Software
34 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
35 *########################################################################
36 */
37
38package org.greenstone.gatherer.util;
39
40
41// Don't even think about adding import java.awt.* here!
42// The functions in this class should not use any graphical classes. Put your function somewhere else buster!
43import java.io.Closeable;
44import java.io.*;
45import java.net.*;
46import java.util.*;
47// Don't even think about adding import javax.swing.* here!
48// The functions in this class should not use any graphical classes. Put your function somewhere else buster!
49import org.greenstone.gatherer.Dictionary;
50// Don't even think about adding import org.greenstone.gatherer.Gatherer in here!
51// The functions in this class should be independent of the Gatherer class. Put your function somewhere else buster!
52
53
54/** To provide a library of common methods, in a static context, for use in the Gatherer.
55 * @author John Thompson, Greenstone Digital Library, University of Waikato
56 * @version 2.3b
57 */
58public class Utility
59{
60 /** Definition of an important directory name, in this case the file the collection configuration is expect to be in. */
61 static final public String CONFIG_FILE = "etc" + File.separator + "collect.cfg";
62 static final public String CONFIG_GS3_FILE = "etc" + File.separator + "collectionConfig.xml";
63 static final public String COLLECT_CFG = "collect.cfg";
64 static final public String COLLECT_BAK = "collect.bak";
65 static final public String COLLECTION_CONFIG_XML = "collectionConfig.xml";
66 static final public String COLLECTION_CONFIG_BAK = "collectionConfig.bak";
67 static final public String GS3MODE_ARGUMENT = "-gs3mode";
68 static final public String BUILD_CFG = "build.cfg";
69 static final public String BUILD_CONFIG_XML = "buildConfig.xml";
70
71 /** The default name of the perl executable under unix. */
72 static final public String PERL_EXECUTABLE_UNIX = "perl";
73 /** The default name of the perl executable under windows. */
74 static final public String PERL_EXECUTABLE_WINDOWS = "Perl.exe";
75
76
77 /**
78 * Delete a file or directory
79 * @param file The <strong>File</strong> you want to delete.
80 * @return A <i>boolean</i> which is <i>true</i> if the file specified was successfully deleted, <i>false</i> otherwise.
81 */
82 static public boolean delete(File file)
83 {
84 // Nothing to do if it doesn't exist
85 if (!file.exists()) {
86 return true;
87 }
88
89 return deleteInternal(file);
90 }
91
92
93 /** Convenience function. */
94 static public boolean delete(String filename)
95 {
96 return delete(new File(filename));
97 }
98
99
100 /** In Java you have to make sure a directory is empty before you delete it, so recursively delete. */
101 static private boolean deleteInternal(File file)
102 {
103 // If file is a directory, we have to recursively delete its contents first
104 if (file.isDirectory()) {
105 File files[] = file.listFiles();
106 for (int i = 0; i < files.length; i++) {
107 if (deleteInternal(files[i]) == false) {
108 System.err.println("Error: Could not delete folder " + file);
109 return false;
110 }
111 }
112 }
113
114 // Delete file
115 if (file.delete() == false) {
116 System.err.println("Error: Could not delete file " + file);
117 return false;
118 }
119
120 return true;
121 }
122
123
124 /** Convert a long, detailing the length of a file in bytes, into a nice human readable string using b, kb, Mb and Gb. */
125 static final public String BYTE_SUFFIX = " b";
126 static final public long GIGABYTE = 1024000000l;
127 static final public String GIGABYTE_SUFFIX = " Gb";
128 static final public long KILOBYTE = 1024l;
129 static final public String KILOBYTE_SUFFIX = " kb";
130 static final public long MEGABYTE = 1024000l;
131 static final public String MEGABYTE_SUFFIX = " Mb";
132 static final public String formatFileLength(long length) {
133 StringBuffer result = new StringBuffer("");
134 float number = 0f;
135 String suffix = null;
136 // Determine the floating point number and the suffix (radix) used.
137 if(length >= GIGABYTE) {
138 number = (float) length / (float) GIGABYTE;
139 suffix = GIGABYTE_SUFFIX;
140 }
141 else if(length >= MEGABYTE) {
142 number = (float) length / (float) MEGABYTE;
143 suffix = MEGABYTE_SUFFIX;
144 }
145 else if(length >= KILOBYTE) {
146 number = (float) length / (float) KILOBYTE;
147 suffix = KILOBYTE_SUFFIX;
148 }
149 else {
150 // Don't need to do anything fancy if the file is smaller than a kilobyte
151 return length + BYTE_SUFFIX;
152 }
153 // 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.
154 String number_str = Float.toString(number);
155 char number_char[] = number_str.toCharArray();
156 int pos = 0;
157 // Print the characters up to the '.'
158 while(number_char != null && pos < number_char.length && number_char[pos] != '.') {
159 result.append(number_char[pos]);
160 pos++;
161 }
162 if(pos < number_char.length) {
163 // Print the '.' and at most two characters after it
164 result.append(number_char[pos]);
165 pos++;
166 for(int i = 0; i < 2 && pos < number_char.length; i++, pos++) {
167 result.append(number_char[pos]);
168 }
169 // Search through the remaining string for 'E'
170 while(pos < number_char.length && number_char[pos] != 'E') {
171 pos++;
172 }
173 // If we still have string then we found an E. Copy the remaining string.
174 while(pos < number_char.length) {
175 result.append(number_char[pos]);
176 pos++;
177 }
178 }
179 // Add suffix
180 result.append(suffix);
181 // Done
182 return result.toString();
183 }
184
185 /** This method formats a given string, using HTML markup, so its width does not exceed the given width and its appearance if justified.
186 * @param text The <strong>String</strong> requiring formatting.
187 * @param width The maximum width per line as an <i>int</i>.
188 * @return A <strong>String</strong> formatted so as to have no line longer than the specified width.
189 * 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.
190 */
191 static public String formatHTMLWidth(String text, int width) {
192 if(text == null) {
193 return "Error";
194 }
195 HTMLStringTokenizer html = new HTMLStringTokenizer(text);
196 int current_width = 0;
197 int threshold = width / 2;
198 Stack lines = new Stack();
199 String line = "";
200 while(html.hasMoreTokens()) {
201 String token = html.nextToken();
202 while(token != null) {
203 if(html.isTag()) {
204 // Insert smart HTML tag code here.
205 token = null;
206 }
207 else {
208 // If the token is bigger than two thirds width, before we've even started break it down.
209 if(current_width + 1 + token.length() > width && token.length() > threshold) {
210 if(width == current_width) {
211 lines.push(line);
212 line = token;
213 current_width = token.length();
214 }
215 else {
216 String prefix = token.substring(0, width - 1 - current_width);
217 token = token.substring(prefix.length());
218 if(current_width == 0) {
219 line = line + prefix;
220 }
221 else {
222 line = line + " " + prefix;
223 }
224 lines.push(line);
225 line = "";
226 current_width = 0;
227 }
228 }
229 // If adding the next token would push us over the maximum line width.
230 else if(current_width + 1 + token.length() > width) {
231 lines.push(line);
232 line = token;
233 current_width = token.length();
234 token = null;
235 }
236 // Otherwise we should be able to just add the token, give or take.
237 else {
238 if(current_width == 0) {
239 line = line + token;
240 current_width = token.length();
241 }
242 else {
243 // Special case for standard punctuation which may exist after a tag like so:
244 // My name is <scratchy>Slim Shady</scratchy>. <-- Annoying punctuation.
245 if(token.equals(".") || token.equals(",") || token.equals("!") || token.equals("?")) {
246 line = line + token;
247 current_width = current_width + 1;
248 }
249 else {
250 line = line + " " + token;
251 current_width = current_width + 1 + token.length();
252 }
253 }
254 token = null;
255 }
256 }
257 }
258 }
259 String result = line;
260 while(!lines.empty()) {
261 result = (String)lines.pop() + "<BR>" + result;
262 }
263 // Replace ' ' with "&nbsp;"
264 boolean tag = false;
265 int pos = 0;
266 while(pos < result.length()) {
267 if(result.charAt(pos) == '<') {
268 tag = true;
269 }
270 else if(result.charAt(pos) == '>') {
271 tag = false;
272 }
273 else if(result.charAt(pos) == ' ' && !tag) {
274 String prefix = result.substring(0, pos);
275 String suffix = result.substring(pos + 1);
276 result = prefix + "&nbsp;" + suffix;
277 }
278 pos++;
279 }
280 result = "<HTML>" + result + "</HTML>";
281 return result;
282 }
283
284
285 static public String getDateString() {
286 Calendar current = Calendar.getInstance();
287 String day_name = null;
288 switch(current.get(Calendar.DAY_OF_WEEK)) {
289 case Calendar.MONDAY: day_name = "Dates.Mon"; break;
290 case Calendar.TUESDAY: day_name = "Dates.Tue"; break;
291 case Calendar.WEDNESDAY: day_name = "Dates.Wed"; break;
292 case Calendar.THURSDAY: day_name = "Dates.Thu"; break;
293 case Calendar.FRIDAY: day_name = "Dates.Fri"; break;
294 case Calendar.SATURDAY: day_name = "Dates.Sat"; break;
295 case Calendar.SUNDAY: day_name = "Dates.Sun"; break;
296 default: day_name = "";
297 }
298 String month_name = null;
299 switch(current.get(Calendar.MONTH)) {
300 case Calendar.JANUARY: month_name = "Dates.Jan"; break;
301 case Calendar.FEBRUARY: month_name = "Dates.Feb"; break;
302 case Calendar.MARCH: month_name = "Dates.Mar"; break;
303 case Calendar.APRIL: month_name = "Dates.Apr"; break;
304 case Calendar.MAY: month_name = "Dates.May"; break;
305 case Calendar.JUNE: month_name = "Dates.Jun"; break;
306 case Calendar.JULY: month_name = "Dates.Jul"; break;
307 case Calendar.AUGUST: month_name = "Dates.Aug"; break;
308 case Calendar.SEPTEMBER: month_name = "Dates.Sep"; break;
309 case Calendar.OCTOBER: month_name = "Dates.Oct"; break;
310 case Calendar.NOVEMBER: month_name = "Dates.Nov"; break;
311 case Calendar.DECEMBER: month_name = "Dates.Dec"; break;
312 default: month_name = "";
313 }
314 int day = current.get(Calendar.DAY_OF_MONTH);
315 int hour = current.get(Calendar.HOUR_OF_DAY);
316 int minute = current.get(Calendar.MINUTE);
317 int second = current.get(Calendar.SECOND);
318 int year = current.get(Calendar.YEAR);
319
320 return Dictionary.get(day_name) + " " + Dictionary.get(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);
321 }
322
323
324 /** Determine this machines name.
325 * @return The name as a <strong>String</strong>.
326 */
327 static public String getMachineName() {
328 try {
329 return InetAddress.getLocalHost().getHostName();
330 }
331 catch(UnknownHostException ex) {
332 }
333 return "Unknown Machine";
334 }
335
336
337 static public String getSitesDir(String gsdl3_path) {
338 return gsdl3_path + "sites" + File.separator;
339
340 }
341
342
343 /** Method to determine if the host system is MacOS based.
344 * @return a boolean which is true if the platform is MacOS, false otherwise
345 */
346 public static boolean isMac() {
347 Properties props = System.getProperties();
348 String os_name = props.getProperty("os.name","");
349 if(os_name.startsWith("Mac OS")) {
350 return true;
351 }
352 return false;
353 }
354
355
356 /** Method to determine if the host system is Microsoft Windows based.
357 * @return A <i>boolean</i> which is <i>true</i> if the platform is Windows, <i>false</i> otherwise.
358 */
359 public static boolean isWindows() {
360 Properties props = System.getProperties();
361 String os_name = props.getProperty("os.name","");
362 if(os_name.startsWith("Windows")) {
363 return true;
364 }
365 return false;
366 }
367
368 public static boolean isWindows9x() {
369 Properties props = System.getProperties();
370 String os_name = props.getProperty("os.name","");
371 if(os_name.startsWith("Windows") && os_name.indexOf("9") != -1) {
372 return true;
373 }
374 return false;
375 }
376 /** Takes a string and a desired length and pads out the string to the length by adding spaces to the left.
377 * @param str The target <strong>String</strong> that needs to be padded.
378 * @param length The desired length of the string as an <i>int</i>.
379 * @return A <strong>String</strong> made from appending space characters with the string until it has a length equal to length.
380 */
381 static private String pad(String str_raw, int length, char fill, boolean end) {
382 StringBuffer str = new StringBuffer(str_raw);
383 while(str.length() < length) {
384 if(end) {
385 str.insert(0, fill);
386 }
387 else {
388 str.append(fill);
389 }
390 }
391 return str.toString();
392 }
393
394
395 /** Builds the cache dir by appending the user path and 'cache'.
396 * @return a File representing the path to the private file cache within the current collection.
397 */
398 public static File getCacheDir() {
399 return new File(getGLIUserFolder(), StaticStrings.CACHE_FOLDER);
400 }
401
402 /** Method which constructs the log directory given a certain collection.
403 * @param col_dir The location of the collection directory as a <strong>String</strong>.
404 * @return The location of the given collections log directory, also as a <strong>String</strong>.
405 */
406 public static String getLogDir(String col_dir) {
407 if(col_dir != null) {
408 return col_dir + LOG_DIR;
409 }
410 else {
411 return getGLIUserFolder().getAbsolutePath() + File.separator + LOG_DIR;
412 }
413 }
414
415 static final private String APPLICATION_DATA_FOLDER = "Application Data";
416 static final private String UNIX_GLI_CONFIG_FOLDER = ".gli";
417 static final private String USER_HOME_PROPERTY = "user.home";
418 static final private String WIN_GLI_CONFIG_FOLDER = "Greenstone" + File.separator + "GLI";
419 /** Definition of an important directory name, in this case the log directory for the collection. */
420 static final public String LOG_DIR = "log" + File.separator;
421
422 static public File getGLIUserFolder()
423 {
424 if (Utility.isWindows()) {
425 return new File(System.getProperty(USER_HOME_PROPERTY) + File.separator + APPLICATION_DATA_FOLDER + File.separator + WIN_GLI_CONFIG_FOLDER + File.separator);
426 }
427 else {
428 return new File(System.getProperty(USER_HOME_PROPERTY) + File.separator + UNIX_GLI_CONFIG_FOLDER + File.separator);
429 }
430 }
431
432 static private HashMap plugin_map = null;
433
434 static private void setUpPluginNameMap() {
435 plugin_map = new HashMap();
436 plugin_map.put("GAPlug", "GreenstoneXMLPlugin");
437 plugin_map.put("RecPlug", "DirectoryPlugin");
438 plugin_map.put("ArcPlug","ArchivesInfPlugin");
439 plugin_map.put("TEXTPlug","TextPlugin");
440 plugin_map.put("XMLPlug","ReadXMLFile");
441 plugin_map.put("EMAILPlug","EmailPlugin");
442 plugin_map.put("SRCPlug","SourceCodePlugin");
443 plugin_map.put("NULPlug","NulPlugin");
444 plugin_map.put("W3ImgPlug","HTMLImagePlugin");
445 plugin_map.put("PagedImgPlug","PagedImagePlugin");
446 plugin_map.put("METSPlug", "GreenstoneMETSPlugin");
447 plugin_map.put("DBPlug", "DatabasePlugin");
448 plugin_map.put("PPTPlug", "PowerPointPlugin");
449 plugin_map.put("PSPlug", "PostScriptPlugin");
450 }
451
452 static public String ensureNewPluginName(String plugin) {
453 if (plugin.endsWith("Plugin")) return plugin;
454 if (plugin_map == null) {
455 setUpPluginNameMap();
456 }
457 String new_name = (String)plugin_map.get(plugin);
458 if (new_name != null) return new_name;
459 new_name = plugin.replaceAll("Plug", "Plugin");
460 return new_name;
461 }
462
463
464 /** Write out a property line--a (property, value) pair--to the gsdl(3)site.cfg file.
465 * If the file already contains the line as-is, it is not re-written.
466 * If the file doesn't contain the line, it is appended.
467 * If the file contained a different value for the property, the line is corrected
468 * and the file is written out.
469 * If the propertyValue parameter is null, the property line is removed from the file.
470 * Not using the Properties class, as we want to keep the file's contents in the
471 * same order and preserve all the comments in as they're meant to help the user.
472 * Return the old value for the property, if it existed, else "".
473 */
474 public static String updatePropertyConfigFile(
475 String filename, String propertyName, String propertyValue)
476 {
477 File propFile = new File(filename);
478 String oldValue = "";
479 if(!propFile.exists()) {
480 System.err.println("*** Unable to update property " + propertyName + " in file "
481 + filename + " to\n" + propertyValue + ". File does not (yet) exist.\n");
482 return oldValue;
483 }
484 BufferedReader fin = null;
485 BufferedWriter fout = null;
486 StringBuffer contents = new StringBuffer();
487 String insertLine = null;
488 if(propertyValue != null) {
489 insertLine = propertyName+"\t"+propertyValue+"\n"; // new line after every propertyLine
490 }
491 boolean found = false;
492 try {
493 fin = new BufferedReader(new FileReader(filename));
494 String line = "";
495 while((line = fin.readLine()) != null) {
496 line = line.trim(); // remove any preceding (surrounding) whitespace
497 if(line.startsWith(propertyName)) { // won't match comment
498 found = true;
499 // store the previous value for the property
500 oldValue = line;
501 oldValue = oldValue.substring(propertyName.length());
502 oldValue = oldValue.trim();
503
504 if(propertyValue != null) { // line should be removed if propertyValue == null
505 if(line.equals(insertLine)) { // file is already correct, nothing to do
506 fin.close();
507 fin = null;
508 break;
509 } else {
510 contents.append(insertLine);
511 }
512 }
513 } else { // any other line
514 contents.append(line);
515 contents.append("\n"); // ensures the required new line at end of file
516 }
517 }
518
519 if(fin != null) { // need to write something out to the file
520 fin.close();
521 fin = null;
522
523 // if collecthome/property wasn't already specified in the file, append it
524 // but only if we have a value to write out to the file
525 if(!found && propertyValue != null) {
526 fout = new BufferedWriter(new FileWriter(filename, true)); // append mode
527 fout.write(insertLine, 0, insertLine.length());
528 } else {
529 fout = new BufferedWriter(new FileWriter(filename)); // hopefully this will overwrite
530 fout.write(contents.toString(), 0, contents.length());
531 }
532
533 fout.close();
534 fout = null;
535
536 } // else the file is fine
537 } catch(IOException e) {
538 System.err.println("*** Could not update file: " + filename);
539 System.err.println("with the " + propertyName + " property set to " + propertyValue);
540 System.err.println("Exception occurred: " + e.getMessage());
541 } finally {
542 try {
543 if(fin != null) {
544 fin.close();
545 fin = null;
546 }
547 if(fout != null) {
548 fout.close();
549 fout = null;
550 }
551 } catch(IOException ioe) {
552 fin = null;
553 fout = null;
554 }
555
556 }
557 return oldValue;
558 }
559
560 // For safely closing streams/handles/resources.
561 // For examples of use look in the Input- and OutputStreamGobbler classes.
562 // http://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html
563 // http://stackoverflow.com/questions/481446/throws-exception-in-finally-blocks
564 public static void closeResource(Closeable resourceHandle) {
565 try {
566 if(resourceHandle != null) {
567 resourceHandle.close();
568 resourceHandle = null;
569 }
570 } catch(Exception e) {
571 System.err.println("Exception closing resource: " + e.getMessage());
572 e.printStackTrace();
573 }
574 }
575}
Note: See TracBrowser for help on using the repository browser.