Source code: com/hartmath/util/Misc.java
1 /*
2 * MiscUtilities.java - Various miscallaneous utility functions
3 * Copyright (C) 1999, 2000, 2001 Slava Pestov
4 * Portions copyright (C) 2000 Richard S. Hall
5 * Portions copyright (C) 2001 Dirk Moebius
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 */
21
22 package com.hartmath.util;
23
24 import javax.swing.JMenuItem;
25 import java.io.*;
26 import java.util.Vector;
27 import java.util.StringTokenizer;
28 import org.gjt.sp.util.Log;
29
30 /**
31 * Class with several useful miscellaneous functions.<p>
32 *
33 * It provides methods for converting file names to class names, for
34 * constructing path names, and for various indentation calculations.
35 * A quicksort implementation is also available.
36 *
37 * @author Slava Pestov
38 * @version $Id: Misc.java,v 1.1 2001/10/06 13:30:21 khartlage Exp $
39 */
40 public class Misc
41 {
42 /**
43 * Converts a file name to a class name. All slash characters are
44 * replaced with periods and the trailing '.class' is removed.
45 * @param name The file name
46 */
47 public static String fileToClass(String name)
48 {
49 char[] clsName = name.toCharArray();
50 for(int i = clsName.length - 6; i >= 0; i--)
51 if(clsName[i] == '/')
52 clsName[i] = '.';
53 return new String(clsName,0,clsName.length - 6);
54 }
55
56 /**
57 * Converts a class name to a file name. All periods are replaced
58 * with slashes and the '.class' extension is added.
59 * @param name The class name
60 */
61 public static String classToFile(String name)
62 {
63 return name.replace('.','/').concat(".class");
64 }
65
66 /**
67 * Constructs an absolute path name from a directory and another
68 * path name.
69 * @param parent The directory
70 * @param path The path name
71 */
72 public static String constructPath(String parent, String path)
73 {
74 if(new File(path).isAbsolute())
75 return canonPath(path);
76
77 if(parent == null)
78 parent = System.getProperty("user.dir");
79
80 // have to handle these cases specially on windows.
81 if(File.separatorChar == '\\')
82 {
83 if(path.length() == 2 && path.charAt(1) == ':')
84 return path;
85 if(path.startsWith("/") || path.startsWith("\\"))
86 parent = parent.substring(0,2);
87 }
88
89 if(parent.endsWith(File.separator) || path.endsWith("/"))
90 return canonPath(parent + path);
91 else
92 return canonPath(parent + File.separator + path);
93 }
94
95 /**
96 * Constructs an absolute path name from three path components.
97 * @param parent The parent directory
98 * @param path1 The first path
99 * @param path2 The second path
100 */
101 public static String constructPath(String parent,
102 String path1, String path2)
103 {
104 return constructPath(constructPath(parent,path1),path2);
105 }
106
107 /**
108 * Like constructPath(), except <code>path</code> will be
109 * appended to <code>parent</code> even if it is absolute.
110 * @param path
111 * @param parent
112 */
113 public static String concatPath(String parent, String path)
114 {
115 // Make all child paths relative.
116 if (path.startsWith(File.separator))
117 path = path.substring(1);
118 else if ((path.length() >= 3) && (path.charAt(1) == ':'))
119 path = path.replace(':', File.separatorChar);
120
121 if (parent == null)
122 parent = System.getProperty("user.dir");
123
124 if (parent.endsWith(File.separator))
125 return parent + path;
126 else
127 return parent + File.separator + path;
128 }
129
130 /**
131 * Returns the extension of the specified filename, or an empty
132 * string if there is none.
133 * @param name The file name
134 */
135 public static String getFileExtension(String name)
136 {
137 int index = name.indexOf('.');
138 if(index == -1)
139 return "";
140 else
141 return name.substring(index);
142 }
143
144 /**
145 * For use with local files only - returns the last component
146 * of the specified path.
147 * @param path The path name
148 */
149 public static String getFileName(String path)
150 {
151 int count = Math.max(0,path.length() - 2);
152 int index1 = path.lastIndexOf(File.separatorChar,count);
153 int index2 = path.lastIndexOf('/',count);
154
155 return path.substring(Math.max(index1,index2) + 1);
156 }
157
158 /**
159 * @deprecated Call getParentOfPath() instead
160 */
161 public static String getFileParent(String path)
162 {
163 return getParentOfPath(path);
164 }
165
166 /**
167 * For use with local files only - returns the parent of the
168 * specified path.
169 * @param path The path name
170 * @since jEdit 2.6pre5
171 */
172 public static String getParentOfPath(String path)
173 {
174 // ignore last character of path to properly handle
175 // paths like /foo/bar/
176 int count = Math.max(0,path.length() - 2);
177 int index = path.lastIndexOf(File.separatorChar,count);
178 if(index == -1)
179 index = path.lastIndexOf('/',count);
180 if(index == -1)
181 {
182 // this ensures that getFileParent("protocol:"), for
183 // example, is "protocol:" and not "".
184 index = path.lastIndexOf(':');
185 }
186
187 return path.substring(0,index + 1);
188 }
189
190 /**
191 * @deprecated Call getProtocolOfURL() instead
192 */
193 public static String getFileProtocol(String url)
194 {
195 return getProtocolOfURL(url);
196 }
197
198 /**
199 * Returns the protocol specified by a URL.
200 * @param url The URL
201 * @since jEdit 2.6pre5
202 */
203 public static String getProtocolOfURL(String url)
204 {
205 return url.substring(0,url.indexOf(':'));
206 }
207
208 /**
209 * Checks if the specified string is a URL.
210 * @param str The string to check
211 * @return True if the string is a URL, false otherwise
212 */
213 public static boolean isURL(String str)
214 {
215 int fsIndex = Math.max(str.indexOf(File.separatorChar),
216 str.indexOf('/'));
217 if(fsIndex == 0) // /etc/passwd
218 return false;
219 else if(fsIndex == 2) // C:\AUTOEXEC.BAT
220 return false;
221
222 int cIndex = str.indexOf(':');
223 if(cIndex <= 1) // D:\WINDOWS
224 return false;
225 else if(fsIndex != -1 && cIndex > fsIndex) // /tmp/RTF::read.pm
226 return false;
227
228 return true;
229 }
230
231 /**
232 * Returns the number of leading white space characters in the
233 * specified string.
234 * @param str The string
235 */
236 public static int getLeadingWhiteSpace(String str)
237 {
238 int whitespace = 0;
239 loop: for(;whitespace < str.length();)
240 {
241 switch(str.charAt(whitespace))
242 {
243 case ' ': case '\t':
244 whitespace++;
245 break;
246 default:
247 break loop;
248 }
249 }
250 return whitespace;
251 }
252
253 /**
254 * Returns the number of trailing whitespace characters in the
255 * specified string.
256 * @param str The string
257 * @since jEdit 2.5pre5
258 */
259 public static int getTrailingWhiteSpace(String str)
260 {
261 int whitespace = 0;
262 loop: for(int i = str.length() - 1; i >= 0; i--)
263 {
264 switch(str.charAt(i))
265 {
266 case ' ': case '\t':
267 whitespace++;
268 break;
269 default:
270 break loop;
271 }
272 }
273 return whitespace;
274 }
275
276 /**
277 * Returns the width of the leading white space in the specified
278 * string.
279 * @param str The string
280 * @param tabSize The tab size
281 */
282 public static int getLeadingWhiteSpaceWidth(String str, int tabSize)
283 {
284 int whitespace = 0;
285 loop: for(int i = 0; i < str.length(); i++)
286 {
287 switch(str.charAt(i))
288 {
289 case ' ':
290 whitespace++;
291 break;
292 case '\t':
293 whitespace += (tabSize - whitespace % tabSize);
294 break;
295 default:
296 break loop;
297 }
298 }
299 return whitespace;
300 }
301
302 /**
303 * Creates a string of white space with the specified length.
304 * @param len The length
305 * @param tabSize The tab size, or 0 if tabs are not to be used
306 */
307 public static String createWhiteSpace(int len, int tabSize)
308 {
309 StringBuffer buf = new StringBuffer();
310 if(tabSize == 0)
311 {
312 while(len-- > 0)
313 buf.append(' ');
314 }
315 else
316 {
317 int count = len / tabSize;
318 while(count-- > 0)
319 buf.append('\t');
320 count = len % tabSize;
321 while(count-- > 0)
322 buf.append(' ');
323 }
324 return buf.toString();
325 }
326
327 /**
328 * Converts a Unix-style glob to a regular expression.
329 * ? becomes ., * becomes .*, {aa,bb} becomes (aa|bb).
330 * @param glob The glob pattern
331 */
332 public static String globToRE(String glob)
333 {
334 StringBuffer buf = new StringBuffer();
335 boolean backslash = false;
336 boolean insideGroup = false;
337
338 for(int i = 0; i < glob.length(); i++)
339 {
340 char c = glob.charAt(i);
341 if(backslash)
342 {
343 buf.append('\\');
344 buf.append(c);
345 backslash = false;
346 continue;
347 }
348
349 switch(c)
350 {
351 case '\\':
352 backslash = true;
353 break;
354 case '?':
355 buf.append('.');
356 break;
357 case '.':
358 buf.append("\\.");
359 break;
360 case '*':
361 buf.append(".*");
362 break;
363 case '{':
364 buf.append('(');
365 insideGroup = true;
366 break;
367 case ',':
368 if(insideGroup)
369 buf.append('|');
370 else
371 buf.append(',');
372 break;
373 case '}':
374 buf.append(')');
375 insideGroup = false;
376 break;
377 default:
378 buf.append(c);
379 }
380 }
381
382 return buf.toString();
383 }
384
385 /**
386 * Converts "\n" and "\t" escapes in the specified string to
387 * newlines and tabs.
388 * @param str The string
389 * @since jEdit 2.3pre1
390 */
391 public static String escapesToChars(String str)
392 {
393 StringBuffer buf = new StringBuffer();
394 for(int i = 0; i < str.length(); i++)
395 {
396 char c = str.charAt(i);
397 switch(c)
398 {
399 case '\\':
400 if(i == str.length() - 1)
401 {
402 buf.append('\\');
403 break;
404 }
405 c = str.charAt(++i);
406 switch(c)
407 {
408 case 'n':
409 buf.append('\n');
410 break;
411 case 't':
412 buf.append('\t');
413 break;
414 default:
415 buf.append(c);
416 break;
417 }
418 break;
419 default:
420 buf.append(c);
421 }
422 }
423 return buf.toString();
424 }
425
426 /**
427 * Escapes newlines, tabs, backslashes, quotes in the specified
428 * string.
429 * @param str The string
430 * @since jEdit 2.3pre1
431 */
432 public static String charsToEscapes(String str)
433 {
434 return charsToEscapes(str,false);
435 }
436
437 /**
438 * Escapes newlines, tabs, backslashes, quotes in the specified
439 * string.
440 * @param str The string
441 * @param history jEdit history files require additional escaping
442 * @since jEdit 2.7pre2
443 */
444 public static String charsToEscapes(String str, boolean history)
445 {
446 StringBuffer buf = new StringBuffer();
447 for(int i = 0; i < str.length(); i++)
448 {
449 char c = str.charAt(i);
450 switch(c)
451 {
452 case '\n':
453 buf.append("\\n");
454 break;
455 case '\t':
456 buf.append("\\t");
457 break;
458 case '[':
459 if(history)
460 buf.append("\\[");
461 else
462 buf.append(c);
463 break;
464 case ']':
465 if(history)
466 buf.append("\\]");
467 else
468 buf.append(c);
469 break;
470 case '"':
471 if(history)
472 buf.append(c);
473 else
474 buf.append("\\\"");
475 break;
476 case '\'':
477 if(history)
478 buf.append(c);
479 else
480 buf.append("\\\'");
481 break;
482 case '\\':
483 buf.append("\\\\");
484 break;
485 default:
486 buf.append(c);
487 break;
488 }
489 }
490 return buf.toString();
491 }
492
493 /**
494 * Sorts the specified array.
495 * @param obj The array
496 * @param compare Compares the objects
497 */
498 public static void quicksort(Object[] obj, Compare compare)
499 {
500 if(obj.length == 0)
501 return;
502
503 quicksort(obj,0,obj.length - 1,compare);
504 }
505
506 /**
507 * Sorts the specified vector.
508 * @param vector The vector
509 * @param compare Compares the objects
510 */
511 public static void quicksort(Vector vector, Compare compare)
512 {
513 if(vector.size() == 0)
514 return;
515
516 quicksort(vector,0,vector.size() - 1,compare);
517 }
518
519 /**
520 * An interface for comparing objects.
521 */
522 public interface Compare
523 {
524 int compare(Object obj1, Object obj2);
525 }
526
527 /**
528 * Compares strings.
529 */
530 public static class StringCompare implements Compare
531 {
532 public int compare(Object obj1, Object obj2)
533 {
534 return obj1.toString().compareTo(obj2.toString());
535 }
536 }
537
538 /**
539 * Compares strings ignoring case.
540 */
541 public static class StringICaseCompare implements Compare
542 {
543 public int compare(Object obj1, Object obj2)
544 {
545 return obj1.toString().toLowerCase()
546 .compareTo(obj2.toString()
547 .toLowerCase());
548 }
549 }
550
551 public static class MenuItemCompare implements Compare
552 {
553 public int compare(Object obj1, Object obj2)
554 {
555 return ((JMenuItem)obj1).getText().compareTo(
556 ((JMenuItem)obj2).getText());
557 }
558 }
559
560 /**
561 * Compares two version strings formatted like 'xxx.xx.xxx'.
562 * The versions string are tokenized at '.' and the tokens
563 * are compared with each other one by one.
564 * For each substring they are compared as Integers first
565 * and if that fails, as Strings. The comparison ends with
566 * the first difference.
567 * Note, that "1.2.0" < "1.2.0pre1", because "0" < "0pre1".
568 * Therefore you should avoid mixing numbers and text.
569 * All string comparisons are case sensitive.
570 */
571 public static class VersionCompare implements Compare
572 {
573 /**
574 * compare two version strings
575 * @param obj1 first version. Should be a String.
576 * @param obj2 secons version. Should be a String.
577 * @return a negative value, if <code>obj1 < obj2</code>,
578 * a positive value, if <code>obj1 > obj2</code>,
579 * 0, if <code>obj1.equals(obj2)</code>.
580 */
581 public int compare(Object obj1, Object obj2)
582 {
583 String v1 = obj1.toString();
584 String v2 = obj2.toString();
585 StringTokenizer vt1 = new StringTokenizer(v1,".");
586 StringTokenizer vt2 = new StringTokenizer(v2,".");
587 int comp = 0;
588
589 while(vt1.hasMoreTokens() && vt2.hasMoreTokens()) {
590 String vt1tok = vt1.nextToken();
591 String vt2tok = vt2.nextToken();
592 try
593 {
594 int i1 = Integer.parseInt(vt1tok);
595 int i2 = Integer.parseInt(vt2tok);
596 comp = i1 < i2 ? -1 : i1 > i2 ? 1 : 0;
597 }
598 catch(NumberFormatException e)
599 {
600 comp = vt1tok.compareTo(vt2tok);
601 }
602 if(comp != 0)
603 return comp;
604 }
605
606 return vt1.hasMoreTokens() ? 1
607 : vt2.hasMoreTokens() ? -1 : 0;
608 }
609 }
610
611 /**
612 * Helper function to compare two version strings, using the
613 * VersionCompare class.
614 * @param version1 the first version string
615 * @param version2 the second version string
616 * @return a negative value, if <code>version1 < version2</code>,
617 * a positive value, if <code>version1 > version2</code>,
618 * 0, if <code>version1.equals(version2)</code>.
619 */
620 public static int compareVersions(String version1, String version2)
621 {
622 VersionCompare comparator = new VersionCompare();
623 return comparator.compare(version1,version2);
624 }
625
626 /**
627 * Converts an internal version number (build) into a
628 * `human-readable' form.
629 * @param build The build
630 */
631 public static String buildToVersion(String build)
632 {
633 if(build.length() != 11)
634 return "<unknown version: " + build + ">";
635 // First 2 chars are the major version number
636 int major = Integer.parseInt(build.substring(0,2));
637 // Second 2 are the minor number
638 int minor = Integer.parseInt(build.substring(3,5));
639 // Then the pre-release status
640 int beta = Integer.parseInt(build.substring(6,8));
641 // Finally the bug fix release
642 int bugfix = Integer.parseInt(build.substring(9,11));
643
644 return "" + major + "." + minor
645 + (beta != 99 ? "pre" + beta :
646 (bugfix != 0 ? "." + bugfix : "final"));
647 }
648
649 // private members
650 private Misc() {}
651
652 private static String canonPath(String path)
653 {
654 if(File.separatorChar == '\\')
655 {
656 // get rid of mixed paths on Windows
657 path = path.replace('/','\\');
658 }
659
660 try
661 {
662 return new File(path).getCanonicalPath();
663 }
664 catch(Exception e)
665 {
666 return path;
667 }
668 }
669
670 private static void quicksort(Object[] obj, int _start, int _end,
671 Compare compare)
672 {
673 int start = _start;
674 int end = _end;
675
676 Object mid = obj[(_start + _end) / 2];
677
678 if(_start > _end)
679 return;
680
681 while(start <= end)
682 {
683 while((start < _end) && (compare.compare(obj[start],mid) < 0))
684 start++;
685
686 while((end > _start) && (compare.compare(obj[end],mid) > 0))
687 end--;
688
689 if(start <= end)
690 {
691 Object o = obj[start];
692 obj[start] = obj[end];
693 obj[end] = o;
694
695 start++;
696 end--;
697 }
698 }
699
700 if(_start < end)
701 quicksort(obj,_start,end,compare);
702
703 if(start < _end)
704 quicksort(obj,start,_end,compare);
705 }
706
707 private static void quicksort(Vector obj, int _start, int _end,
708 Compare compare)
709 {
710 int start = _start;
711 int end = _end;
712
713 Object mid = obj.elementAt((_start + _end) / 2);
714
715 if(_start > _end)
716 return;
717
718 while(start <= end)
719 {
720 while((start < _end) && (compare.compare(obj.elementAt(start),mid) < 0))
721 start++;
722
723 while((end > _start) && (compare.compare(obj.elementAt(end),mid) > 0))
724 end--;
725
726 if(start <= end)
727 {
728 Object o = obj.elementAt(start);
729 obj.setElementAt(obj.elementAt(end),start);
730 obj.setElementAt(o,end);
731
732 start++;
733 end--;
734 }
735 }
736
737 if(_start < end)
738 quicksort(obj,_start,end,compare);
739
740 if(start < _end)
741 quicksort(obj,start,_end,compare);
742 }
743 }