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

Last change on this file since 31582 was 31582, checked in by ak19, 7 years ago

In place of the Input- and OutputStreamGobbler classes, am now shifting GLI code to use SafeProcess too, copied across from GS3 src code. Class SafeProcess includes the two streamgobblers as static inner classes and some more functionality with safely running an external process from Java.

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