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

Last change on this file since 32270 was 32270, checked in by ak19, 6 years ago

Adding in a handy utility function to see the call stack by printing out the stacktrace even when there's no exception object

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