PageRenderTime 203ms CodeModel.GetById 30ms app.highlight 132ms RepoModel.GetById 20ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/MiscUtilities.java

#
Java | 1289 lines | 804 code | 103 blank | 382 comment | 281 complexity | 2a1a20ffb2f0a396fc0d170bb75c30bd MD5 | raw file
   1/*
   2 * MiscUtilities.java - Various miscallaneous utility functions
   3 * :tabSize=8:indentSize=8:noTabs=false:
   4 * :folding=explicit:collapseFolds=1:
   5 *
   6 * Copyright (C) 1999, 2005 Slava Pestov
   7 * Portions copyright (C) 2000 Richard S. Hall
   8 * Portions copyright (C) 2001 Dirk Moebius
   9 *
  10 * This program is free software; you can redistribute it and/or
  11 * modify it under the terms of the GNU General Public License
  12 * as published by the Free Software Foundation; either version 2
  13 * of the License, or any later version.
  14 *
  15 * This program is distributed in the hope that it will be useful,
  16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18 * GNU General Public License for more details.
  19 *
  20 * You should have received a copy of the GNU General Public License
  21 * along with this program; if not, write to the Free Software
  22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  23 */
  24
  25package org.gjt.sp.jedit;
  26
  27//{{{ Imports
  28import java.io.*;
  29import java.net.MalformedURLException;
  30import java.net.URL;
  31import java.nio.charset.MalformedInputException;
  32import java.util.*;
  33import java.util.regex.Matcher;
  34import java.util.regex.Pattern;
  35
  36import org.gjt.sp.jedit.io.*;
  37import org.gjt.sp.util.Log;
  38import org.gjt.sp.util.IOUtilities;
  39
  40import org.gjt.sp.jedit.buffer.JEditBuffer;
  41//}}}
  42
  43/**
  44 * Path name manipulation, string manipulation, and more.<p>
  45 *
  46 * The most frequently used members of this class are:<p>
  47 *
  48 * <b>Some path name methods:</b><p>
  49 * <ul>
  50 * <li>{@link #getFileName(String)}</li>
  51 * <li>{@link #getParentOfPath(String)}</li>
  52 * <li>{@link #constructPath(String,String)}</li>
  53 * </ul>
  54 *
  55 * @author Slava Pestov
  56 * @author John Gellene (API documentation)
  57 * @version $Id: MiscUtilities.java 19871 2011-08-29 21:31:58Z ezust $
  58 */
  59public class MiscUtilities
  60{
  61	//{{{ Path name methods
  62
  63	//{{{ canonPath() method
  64	/**
  65	 * @return the canonical form of the specified path name. Currently
  66	 * only expands a leading <code>~</code>. <b>For local path names
  67	 * only.</b>
  68	 * @param path The path name
  69	 * @since jEdit 4.0pre2
  70	 */
  71	public static String canonPath(String path)
  72	{
  73		if(path.length() == 0)
  74			return path;
  75
  76		if(path.startsWith("file://"))
  77			path = path.substring("file://".length());
  78		else if(path.startsWith("file:"))
  79			path = path.substring("file:".length());
  80		else if(isURL(path))
  81			return path;
  82
  83		if(File.separatorChar == '\\')
  84		{
  85				// get rid of mixed paths on Windows
  86				path = path.replace('/','\\');
  87				// also get rid of trailing spaces on Windows
  88				int trim = path.length();
  89				while(path.charAt(trim - 1) == ' ')
  90					trim--;
  91
  92				if (path.charAt(trim - 1) == '\\')
  93					while (trim > 1 && path.charAt(trim - 2) == '\\')
  94					{
  95						trim--;
  96					}
  97				path = path.substring(0,trim);
  98		}
  99
 100		if(path.startsWith('~' + File.separator))
 101		{
 102			path = path.substring(2);
 103			String home = System.getProperty("user.home");
 104
 105			if(home.endsWith(File.separator))
 106				return home + path;
 107			else
 108				return home + File.separator + path;
 109		}
 110		else if("~".equals(path))
 111			return System.getProperty("user.home");
 112		else if ("-".equals(path))
 113			return getParentOfPath(jEdit.getActiveView().getBuffer().getPath());
 114		else
 115			return path;
 116	} //}}}
 117
 118	//{{{ expandVariables() method
 119	static final String varPatternString = "(\\$([a-zA-Z0-9_]+))";
 120	static final String varPatternString2 = "(\\$\\{([^}]+)\\})";
 121	static final String winPatternString  = "(%([^%]+)%)";
 122	static final Pattern varPattern = Pattern.compile(varPatternString);
 123	static final Pattern varPattern2 = Pattern.compile(varPatternString2);
 124	static final Pattern winPattern = Pattern.compile(winPatternString);
 125	
 126	/** Accepts a string from the user which may contain variables of various syntaxes.
 127	 *  The function supports the following expansion syntaxes:
 128	 *     $varname
 129	 *     ${varname} (on non-windows)
 130	 *     %varname% (on Windows)
 131	 *     And expand each of these by looking at the system environment variables for possible
 132	 *     expansions.
 133	 *     @return a string which is either the unchanged input string, or one with expanded variables.
 134	 *     @since 4.3pre7
 135	 *     @see #abbreviate
 136	 *     @author ezust
 137	 */
 138	public static String expandVariables(String arg)
 139	{
 140		Pattern p = varPattern;
 141		Matcher m = p.matcher(arg);
 142		if (!m.find())
 143		{
 144			if (OperatingSystem.isWindows()) 
 145				p = winPattern;
 146			else p = varPattern2;
 147			m = p.matcher(arg);
 148			if (!m.find()) // no variables to substitute
 149				return arg;
 150		}
 151		String varName = m.group(2);
 152		String expansion = System.getenv(varName);
 153		if (expansion == null)
 154		{ // try everything uppercase?
 155			varName = varName.toUpperCase();
 156			String uparg = arg.toUpperCase();
 157			m = p.matcher(uparg);
 158			expansion = System.getenv(varName);
 159		}
 160		if (expansion != null)
 161		{
 162			expansion = expansion.replace("\\", "\\\\");
 163			return m.replaceFirst(expansion);
 164		}
 165		return arg;
 166	} //}}}
 167
 168	//{{{ abbreviate() method
 169	/** @return an abbreviated path, replacing values with variables, if a prefix exists.
 170	 *  @see #expandVariables
 171	 *  @since jEdit 4.3pre16
 172	 */
 173	public static String abbreviate(String path)
 174	{
 175		if (svc == null)
 176			svc = new VarCompressor();
 177		return svc.compress(path);
 178	} //}}}
 179
 180	//{{{ resolveSymlinks() method
 181	/**
 182	 * Resolves any symbolic links in the path name specified
 183	 * using <code>File.getCanonicalPath()</code>. <b>For local path
 184	 * names only.</b>
 185	 * @since jEdit 4.2pre1
 186	 */
 187	public static String resolveSymlinks(String path)
 188	{
 189		if(isURL(path))
 190			return path;
 191
 192		// 2 aug 2003: OS/2 Java has a broken getCanonicalPath()
 193		if(OperatingSystem.isOS2())
 194			return path;
 195		// 18 nov 2003: calling this on a drive letter on Windows causes
 196		// drive access
 197		if(OperatingSystem.isDOSDerived())
 198		{
 199			if(path.length() == 2 || path.length() == 3)
 200			{
 201				if(path.charAt(1) == ':')
 202					return path;
 203			}
 204		}
 205		try
 206		{
 207			return new File(path).getCanonicalPath();
 208		}
 209		catch(IOException io)
 210		{
 211			return path;
 212		}
 213	} //}}}
 214
 215	//{{{ isAbsolutePath() method
 216	/**
 217	 * Returns if the specified path name is an absolute path or URL.
 218	 * @since jEdit 4.1pre11
 219	 */
 220	public static boolean isAbsolutePath(String path)
 221	{
 222		if(isURL(path))
 223			return true;
 224		else if(path.startsWith("~/") || path.startsWith('~' + File.separator) || "~".equals(path))
 225			return true;
 226		else if ("-".equals(path))
 227			return true;
 228		else if(OperatingSystem.isDOSDerived())
 229		{
 230			if(path.length() == 2 && path.charAt(1) == ':')
 231				return true;
 232			if(path.length() > 2 && path.charAt(1) == ':'
 233				&& (path.charAt(2) == '\\'
 234					|| path.charAt(2) == '/'))
 235				return true;
 236			if(path.startsWith("\\\\")
 237				|| path.startsWith("//"))
 238				return true;
 239		}
 240		// not sure if this is correct for OpenVMS.
 241		else if(OperatingSystem.isUnix()
 242				|| OperatingSystem.isVMS())
 243		{
 244			// nice and simple
 245			if(path.length() > 0 && path.charAt(0) == '/')
 246				return true;
 247		}
 248
 249		return false;
 250	} //}}}
 251
 252	//{{{ constructPath() methods
 253	/**
 254	 * Constructs an absolute path name from a directory and another
 255	 * path name. This method is VFS-aware.
 256	 * @param parent The directory
 257	 * @param path The path name
 258	 */
 259	public static String constructPath(String parent, String path)
 260	{
 261		if(isAbsolutePath(path))
 262			return canonPath(path);
 263
 264		if (parent == null)
 265			parent = System.getProperty("user.dir");
 266
 267		if (path == null || path.length() == 0)
 268			return parent;
 269
 270		// have to handle this case specially on windows.
 271		// insert \ between, eg A: and myfile.txt.
 272		if(OperatingSystem.isDOSDerived())
 273		{
 274			if(path.length() == 2 && path.charAt(1) == ':')
 275				return path;
 276			else if(path.length() > 2 && path.charAt(1) == ':'
 277					&& path.charAt(2) != '\\')
 278			{
 279				path = path.substring(0,2) + '\\'
 280					+ path.substring(2);
 281				return canonPath(path);
 282			}
 283		}
 284
 285		String dd = ".." + File.separator;
 286		String d = '.' + File.separator;
 287
 288		for(;;)
 289		{
 290			if(".".equals(path))
 291				return parent;
 292			else if("..".equals(path))
 293				return getParentOfPath(parent);
 294			else if(path.startsWith(dd) || path.startsWith("../"))
 295			{
 296				parent = getParentOfPath(parent);
 297				path = path.substring(3);
 298			}
 299			else if(path.startsWith(d) || path.startsWith("./"))
 300				path = path.substring(2);
 301			else
 302				break;
 303		}
 304		if(path.length() == 0)
 305			return parent;
 306
 307		if(OperatingSystem.isDOSDerived()
 308			&& !isURL(parent)
 309		&& path.charAt(0) == '\\')
 310			parent = parent.substring(0,2);
 311
 312		VFS vfs = VFSManager.getVFSForPath(parent);
 313
 314		return canonPath(vfs.constructPath(parent,path));
 315	}
 316
 317	/**
 318	 * Constructs an absolute path name from three path components.
 319	 * This method is VFS-aware.
 320	 * @param parent The parent directory
 321	 * @param path1 The first path
 322	 * @param path2 The second path
 323	 */
 324	public static String constructPath(String parent,
 325				    String path1, String path2)
 326	{
 327		return constructPath(constructPath(parent,path1),path2);
 328	} //}}}
 329
 330	//{{{ concatPath() method
 331	/**
 332	 * Like {@link #constructPath}, except <code>path</code> will be
 333	 * appended to <code>parent</code> even if it is absolute.
 334	 * <b>For local path names only.</b>.
 335	 *
 336	 * @param parent the parent path
 337	 * @param path the path to append to the parent
 338	 */
 339	public static String concatPath(String parent, String path)
 340	{
 341		parent = canonPath(parent);
 342		path = canonPath(path);
 343
 344		// Make all child paths relative.
 345		if (path.startsWith(File.separator))
 346			path = path.substring(1);
 347		else if (path.length() >= 3 && path.charAt(1) == ':')
 348			path = path.replace(':', File.separatorChar);
 349
 350		if (parent == null)
 351			parent = System.getProperty("user.dir");
 352
 353		if (parent.endsWith(File.separator))
 354			return parent + path;
 355		else
 356			return parent + File.separator + path;
 357	} //}}}
 358
 359	//{{{ getFirstSeparatorIndex() method
 360	/**
 361	 * Return the first index of either / or the OS-specific file
 362	 * separator.
 363	 * @param path The path
 364	 * @since jEdit 4.3pre3
 365	 */
 366	public static int getFirstSeparatorIndex(String path)
 367	{
 368		int start = getPathStart(path);
 369		int index = path.indexOf('/',start);
 370		if(index == -1)
 371			index = path.indexOf(File.separatorChar,start);
 372		return index;
 373	} //}}}
 374
 375	//{{{ getLastSeparatorIndex() method
 376	/**
 377	 * Return the last index of either / or the OS-specific file
 378	 * separator.
 379	 * @param path The path
 380	 * @since jEdit 4.3pre3
 381	 */
 382	public static int getLastSeparatorIndex(String path)
 383	{
 384		int start = getPathStart(path);
 385		if(start != 0)
 386			path = path.substring(start);
 387		int index = Math.max(
 388			path.lastIndexOf('/'), path.lastIndexOf(File.separatorChar));
 389		if(index == -1)
 390			return index;
 391		else
 392			return index + start;
 393	} //}}}
 394
 395
 396	//{{{ getFileExtension() method
 397	/**
 398	 * Returns the extension of the specified filename, or an empty
 399	 * string if there is none.
 400	 * @param path The path
 401	 */
 402	public static String getFileExtension(String path)
 403	{
 404		int fsIndex = getLastSeparatorIndex(path);
 405		int index = path.lastIndexOf('.');
 406		// there could be a dot in the path and no file extension
 407		if(index == -1 || index < fsIndex )
 408			return "";
 409		else
 410			return path.substring(index);
 411	} //}}}
 412
 413	//{{{ getFileName() method
 414	/**
 415	 * Returns the last component of the specified path.
 416	 * This method is VFS-aware.
 417	 * @param path The path name
 418	 */
 419	public static String getFileName(String path)
 420	{
 421		return VFSManager.getVFSForPath(path).getFileName(path);
 422	} //}}}
 423
 424	//{{{ getFileNameNoExtension() method
 425	/**
 426	 * Returns the last component of the specified path name without the
 427	 * trailing extension (if there is one).
 428	 * @param path The path name
 429	 * @since jEdit 4.0pre8
 430	 */
 431	public static String getFileNameNoExtension(String path)
 432	{
 433		String name = getFileName(path);
 434		int index = name.indexOf('.');
 435		if(index == -1)
 436			return name;
 437		else
 438			return name.substring(0,index);
 439	} //}}}
 440
 441	//{{{ getParentOfPath() method
 442	/**
 443	 * Returns the parent of the specified path. This method is VFS-aware.
 444	 * @param path The path name
 445	 * @since jEdit 2.6pre5
 446	 */
 447	public static String getParentOfPath(String path)
 448	{
 449		return VFSManager.getVFSForPath(path).getParentOfPath(path);
 450	} //}}}
 451
 452	//{{{ getProtocolOfURL() method
 453	/**
 454	 * Returns the protocol specified by a URL.
 455	 * @param url The URL
 456	 * @since jEdit 2.6pre5
 457	 */
 458	public static String getProtocolOfURL(String url)
 459	{
 460		return url.substring(0,url.indexOf(':'));
 461	} //}}}
 462
 463	//{{{ isURL() method
 464	/**
 465	 * Checks if the specified string is a URL.
 466	 * @param str The string to check
 467	 * @return True if the string is a URL, false otherwise
 468	 */
 469	public static boolean isURL(String str)
 470	{
 471		int fsIndex = getLastSeparatorIndex(str);
 472		if(fsIndex == 0) // /etc/passwd
 473			return false;
 474		else if(fsIndex == 2) // C:\AUTOEXEC.BAT
 475			return false;
 476
 477		int cIndex = str.indexOf(':');
 478		if(cIndex <= 1) // D:\WINDOWS, or doesn't contain : at all
 479			return false;
 480
 481		String protocol = str.substring(0,cIndex);
 482		VFS vfs = VFSManager.getVFSForProtocol(protocol);
 483		if(vfs != null && !(vfs instanceof UrlVFS))
 484			return true;
 485
 486		try
 487		{
 488			new URL(str);
 489			return true;
 490		}
 491		catch(MalformedURLException mf)
 492		{
 493			return false;
 494		}
 495	} //}}}
 496
 497	//{{{ saveBackup() methods
 498	/**
 499	 * Saves a backup (optionally numbered) of a file.
 500	 * @param file A local file
 501	 * @param backups The number of backups. Must be >= 1. If > 1, backup
 502	 * files will be numbered.
 503	 * @param backupPrefix The backup file name prefix
 504	 * @param backupSuffix The backup file name suffix
 505	 * @param backupDirectory The directory where to save backups; if null,
 506	 * they will be saved in the same directory as the file itself.
 507	 * @since jEdit 4.0pre1
 508	 */
 509	 public static void saveBackup(File file, int backups,
 510		 String backupPrefix, String backupSuffix,
 511		 String backupDirectory)
 512	{
 513		saveBackup(file,backups,backupPrefix,backupSuffix,backupDirectory,0);
 514	}
 515
 516	/**
 517	 * Saves a backup (optionally numbered) of a file.
 518	 * @param file A local file
 519	 * @param backups The number of backups. Must be >= 1. If > 1, backup
 520	 * files will be numbered.
 521	 * @param backupPrefix The backup file name prefix
 522	 * @param backupSuffix The backup file name suffix
 523	 * @param backupDirectory The directory where to save backups; if null,
 524	 * they will be saved in the same directory as the file itself.
 525	 * @param backupTimeDistance The minimum time in minutes when a backup
 526	 * version 1 shall be moved into version 2; if 0, backups are always
 527	 * moved.
 528	 * @since jEdit 4.2pre5
 529	 */
 530	public static void saveBackup(File file, int backups,
 531			       String backupPrefix, String backupSuffix,
 532			       String backupDirectory, int backupTimeDistance)
 533	{
 534		if(backupPrefix == null)
 535			backupPrefix = "";
 536		if(backupSuffix == null)
 537			backupSuffix = "";
 538
 539		String name = file.getName();
 540
 541		// If backups is 1, create ~ file
 542		if(backups == 1)
 543		{
 544			File backupFile = new File(backupDirectory,
 545				backupPrefix + name + backupSuffix);
 546			long modTime = backupFile.lastModified();
 547			/* if backup file was created less than
 548			 * 'backupTimeDistance' ago, we do not
 549			 * create the backup */
 550			if(System.currentTimeMillis() - modTime
 551			   >= backupTimeDistance)
 552			{
 553				Log.log(Log.DEBUG,MiscUtilities.class,
 554					"Saving backup of file \"" +
 555					file.getAbsolutePath() + "\" to \"" +
 556					backupFile.getAbsolutePath() + '"');
 557				backupFile.delete();
 558				if (!file.renameTo(backupFile))
 559					IOUtilities.moveFile(file, backupFile);
 560			}
 561		}
 562		// If backups > 1, move old ~n~ files, create ~1~ file
 563		else
 564		{
 565			/* delete a backup created using above method */
 566			new File(backupDirectory,
 567				backupPrefix + name + backupSuffix
 568				+ backups + backupSuffix).delete();
 569
 570			File firstBackup = new File(backupDirectory,
 571				backupPrefix + name + backupSuffix
 572				+ '1' + backupSuffix);
 573			long modTime = firstBackup.lastModified();
 574			/* if backup file was created less than
 575			 * 'backupTimeDistance' ago, we do not
 576			 * create the backup */
 577			if(System.currentTimeMillis() - modTime
 578			   >= backupTimeDistance)
 579			{
 580				for(int i = backups - 1; i > 0; i--)
 581				{
 582					File backup = new File(backupDirectory,
 583						backupPrefix + name
 584						+ backupSuffix + i
 585						+ backupSuffix);
 586
 587					backup.renameTo(new File(backupDirectory,
 588						backupPrefix + name
 589						+ backupSuffix + (i + 1)
 590						+ backupSuffix));
 591				}
 592
 593				File backupFile = new File(backupDirectory,
 594					backupPrefix + name + backupSuffix
 595					+ '1' + backupSuffix);
 596				Log.log(Log.DEBUG,MiscUtilities.class,
 597					"Saving backup of file \"" +
 598					file.getAbsolutePath() + "\" to \"" +
 599					backupFile.getAbsolutePath() + '"');
 600				if (!file.renameTo(backupFile))
 601					IOUtilities.moveFile(file, backupFile);
 602			}
 603		}
 604	} //}}}
 605
 606	//{{{ isBinary() methods
 607	/**
 608	 * Check if an InputStream is binary.
 609	 * First this tries encoding auto detection. If an encoding is
 610	 * detected, the stream should be a text stream. Otherwise, this
 611	 * will check the first characters 100
 612	 * (jEdit property vfs.binaryCheck.length) in the system default
 613	 * encoding. If more than 1 (jEdit property vfs.binaryCheck.count)
 614	 * NUL(\u0000) was found, the stream is declared binary.
 615	 *
 616	 * This is not 100% because sometimes the autodetection could fail.
 617	 *
 618	 * This method will not close the stream. You have to do it yourself
 619	 *
 620	 * @param in the stream
 621	 * @return <code>true</code> if the stream was detected as binary
 622	 * @throws IOException IOException If an I/O error occurs
 623	 * @since jEdit 4.3pre10
 624	 */
 625	public static boolean isBinary(InputStream in) throws IOException
 626	{
 627		AutoDetection.Result detection = new AutoDetection.Result(in);
 628		// If an encoding is detected, this is a text stream
 629		if (detection.getDetectedEncoding() != null)
 630		{
 631			return false;
 632		}
 633		// Read the stream in system default encoding. The encoding
 634		// might be wrong. But enough for binary detection.
 635		try
 636		{
 637			return containsNullCharacter(
 638				new InputStreamReader(detection.getRewindedStream()));
 639		}
 640		catch (MalformedInputException mie)
 641		{
 642			// This error probably means the input is binary.
 643			return true;
 644		}
 645	} //}}}
 646
 647	//{{{ isBackup() method
 648	/**
 649	 * Check if the filename is a backup file.
 650	 * @param filename the filename to check
 651	 * @return true if this is a backup file.
 652	 * @since jEdit 4.3pre5
 653	 */
 654	public static boolean isBackup(String filename)
 655	{
 656		if (filename.startsWith("#")) return true;
 657		if (filename.endsWith("~")) return true;
 658		if (filename.endsWith(".bak")) return true;
 659		return false;
 660	} //}}}
 661
 662
 663	//{{{ autodetect() method
 664	/**
 665	 * Tries to detect if the stream is gzipped, and if it has an encoding
 666	 * specified with an XML PI.
 667	 *
 668	 * @param in the input stream reader that must be autodetected
 669	 * @param buffer a buffer. It can be null if you only want to autodetect the encoding of a file
 670	 * @return a Reader using the detected encoding
 671	 * @throws IOException io exception during read
 672	 * @since jEdit 4.3pre5
 673	 */
 674	public static Reader autodetect(InputStream in, Buffer buffer) throws IOException
 675	{
 676		String encoding;
 677		if (buffer == null)
 678			encoding = System.getProperty("file.encoding");
 679		else
 680			encoding = buffer.getStringProperty(JEditBuffer.ENCODING);
 681		boolean gzipped = false;
 682
 683		if (buffer == null || buffer.getBooleanProperty(Buffer.ENCODING_AUTODETECT))
 684		{
 685			AutoDetection.Result detection = new AutoDetection.Result(in);
 686			gzipped = detection.streamIsGzipped();
 687			if (gzipped)
 688			{
 689				Log.log(Log.DEBUG, MiscUtilities.class
 690					, "Stream is Gzipped");
 691			}
 692			String detected = detection.getDetectedEncoding();
 693			if (detected != null)
 694			{
 695				encoding = detected;
 696				Log.log(Log.DEBUG, MiscUtilities.class
 697					, "Stream encoding detected is " + detected);
 698			}
 699			in = detection.getRewindedStream();
 700		}
 701		else
 702		{
 703			// Make the stream buffered in the same way.
 704			in = AutoDetection.getMarkedStream(in);
 705		}
 706
 707		Reader result = EncodingServer.getTextReader(in, encoding);
 708		if (buffer != null)
 709		{
 710			// Store the successful properties.
 711			if (gzipped)
 712			{
 713				buffer.setBooleanProperty(Buffer.GZIPPED,true);
 714			}
 715			buffer.setProperty(JEditBuffer.ENCODING, encoding);
 716		}
 717		return result;
 718	} //}}}
 719
 720	//{{{ fileToClass() method
 721	/**
 722	 * Converts a file name to a class name. All slash characters are
 723	 * replaced with periods and the trailing '.class' is removed.
 724	 * @param name The file name
 725	 */
 726	public static String fileToClass(String name)
 727	{
 728		char[] clsName = name.toCharArray();
 729		for(int i = clsName.length - 6; i >= 0; i--)
 730			if(clsName[i] == '/')
 731				clsName[i] = '.';
 732		return new String(clsName,0,clsName.length - 6);
 733	} //}}}
 734
 735	//{{{ classToFile() method
 736	/**
 737	 * Converts a class name to a file name. All periods are replaced
 738	 * with slashes and the '.class' extension is added.
 739	 * @param name The class name
 740	 */
 741	public static String classToFile(String name)
 742	{
 743		return name.replace('.','/').concat(".class");
 744	} //}}}
 745
 746	//{{{ pathsEqual() method
 747	/**
 748	 * @param p1 A path name
 749	 * @param p2 A path name
 750	 * @return True if both paths are equal, ignoring trailing slashes, as
 751	 * well as case insensitivity on Windows.
 752	 * @since jEdit 4.3pre2
 753	 */
 754	public static boolean pathsEqual(String p1, String p2)
 755	{
 756		VFS v1 = VFSManager.getVFSForPath(p1);
 757		VFS v2 = VFSManager.getVFSForPath(p2);
 758
 759		if(v1 != v2)
 760			return false;
 761
 762		if(p1.endsWith("/") || p1.endsWith(File.separator))
 763			p1 = p1.substring(0,p1.length() - 1);
 764
 765		if(p2.endsWith("/") || p2.endsWith(File.separator))
 766			p2 = p2.substring(0,p2.length() - 1);
 767
 768		if((v1.getCapabilities() & VFS.CASE_INSENSITIVE_CAP) != 0)
 769			return p1.equalsIgnoreCase(p2);
 770		else
 771			return p1.equals(p2);
 772	} //}}}
 773
 774	//}}}
 775
 776	//{{{ Text methods
 777
 778	//{{{ escapesToChars() method
 779	/**
 780	 * Converts "\n" and "\t" escapes in the specified string to
 781	 * newlines and tabs.
 782	 * @param str The string
 783	 * @since jEdit 2.3pre1
 784	 */
 785	public static String escapesToChars(String str)
 786	{
 787		StringBuilder buf = new StringBuilder();
 788		for(int i = 0; i < str.length(); i++)
 789		{
 790			char c = str.charAt(i);
 791			switch(c)
 792			{
 793			case '\\':
 794				if(i == str.length() - 1)
 795				{
 796					buf.append('\\');
 797					break;
 798				}
 799				c = str.charAt(++i);
 800				switch(c)
 801				{
 802				case 'n':
 803					buf.append('\n');
 804					break;
 805				case 't':
 806					buf.append('\t');
 807					break;
 808				default:
 809					buf.append(c);
 810					break;
 811				}
 812				break;
 813			default:
 814				buf.append(c);
 815			}
 816		}
 817		return buf.toString();
 818	} //}}}
 819
 820	//{{{ getLongestPrefix() methods
 821	/**
 822	 * Returns the longest common prefix in the given set of strings.
 823	 * @param str The strings
 824	 * @param ignoreCase If true, case insensitive
 825	 * @since jEdit 4.2pre2
 826	 */
 827	public static String getLongestPrefix(List<String> str, boolean ignoreCase)
 828	{
 829		if(str.isEmpty())
 830			return "";
 831
 832		int prefixLength = 0;
 833
 834loop:		for(;;)
 835		{
 836			String s = str.get(0);
 837			if(prefixLength >= s.length())
 838				break loop;
 839			char ch = s.charAt(prefixLength);
 840			for(int i = 1; i < str.size(); i++)
 841			{
 842				s = str.get(i);
 843				if(prefixLength >= s.length())
 844					break loop;
 845				if(!compareChars(s.charAt(prefixLength),ch,ignoreCase))
 846					break loop;
 847			}
 848			prefixLength++;
 849		}
 850
 851		return str.get(0).substring(0,prefixLength);
 852	}
 853
 854	/**
 855	 * Returns the longest common prefix in the given set of strings.
 856	 * @param str The strings
 857	 * @param ignoreCase If true, case insensitive
 858	 * @since jEdit 4.2pre2
 859	 */
 860	public static String getLongestPrefix(String[] str, boolean ignoreCase)
 861	{
 862		return getLongestPrefix((Object[])str,ignoreCase);
 863	}
 864
 865	/**
 866	 * Returns the longest common prefix in the given set of strings.
 867	 * @param str The strings (calls <code>toString()</code> on each object)
 868	 * @param ignoreCase If true, case insensitive
 869	 * @since jEdit 4.2pre6
 870	 */
 871	public static String getLongestPrefix(Object[] str, boolean ignoreCase)
 872	{
 873		if(str.length == 0)
 874			return "";
 875
 876		int prefixLength = 0;
 877
 878		String first = str[0].toString();
 879
 880loop:		for(;;)
 881		{
 882			if(prefixLength >= first.length())
 883				break loop;
 884			char ch = first.charAt(prefixLength);
 885			for(int i = 1; i < str.length; i++)
 886			{
 887				String s = str[i].toString();
 888				if(prefixLength >= s.length())
 889					break loop;
 890				if(!compareChars(s.charAt(prefixLength),ch,ignoreCase))
 891					break loop;
 892			}
 893			prefixLength++;
 894		}
 895
 896		return first.substring(0,prefixLength);
 897	} //}}}
 898
 899	//}}}
 900
 901	//{{{ buildToVersion() method
 902	/**
 903	 * Converts an internal version number (build) into a
 904	 * `human-readable' form.
 905	 * @param build The build
 906	 */
 907	public static String buildToVersion(String build)
 908	{
 909		if(build.length() != 11)
 910			return "<unknown version: " + build + '>';
 911		// First 2 chars are the major version number
 912		int major = Integer.parseInt(build.substring(0,2));
 913		// Second 2 are the minor number
 914		int minor = Integer.parseInt(build.substring(3,5));
 915		// Then the pre-release status
 916		int beta = Integer.parseInt(build.substring(6,8));
 917		// Finally the bug fix release
 918		int bugfix = Integer.parseInt(build.substring(9,11));
 919
 920		return major + "." + minor
 921			+ (beta != 99 ? "pre" + beta :
 922			(bugfix != 0 ? "." + bugfix : ""));
 923	} //}}}
 924
 925	//{{{ isToolsJarAvailable() method
 926	/**
 927	 * If on JDK 1.2 or higher, make sure that tools.jar is available.
 928	 * This method should be called by plugins requiring the classes
 929	 * in this library.
 930	 * <p>
 931	 * tools.jar is searched for in the following places:
 932	 * <ol>
 933	 *   <li>the classpath that was used when jEdit was started,
 934	 *   <li>jEdit's jars folder in the user's home,
 935	 *   <li>jEdit's system jars folder,
 936	 *   <li><i>java.home</i>/lib/. In this case, tools.jar is added to
 937	 *       jEdit's list of known jars using jEdit.addPluginJAR(),
 938	 *       so that it gets loaded through JARClassLoader.
 939	 * </ol><p>
 940	 *
 941	 * On older JDK's this method does not perform any checks, and returns
 942	 * <code>true</code> (even though there is no tools.jar).
 943	 *
 944	 * @return <code>false</code> if and only if on JDK 1.2 and tools.jar
 945	 *    could not be found. In this case it prints some warnings on Log,
 946	 *    too, about the places where it was searched for.
 947	 * @since jEdit 3.2.2
 948	 */
 949	public static boolean isToolsJarAvailable()
 950	{
 951		Log.log(Log.DEBUG, MiscUtilities.class,"Searching for tools.jar...");
 952
 953		Collection<String> paths = new LinkedList<String>();
 954
 955		//{{{ 1. Check whether tools.jar is in the system classpath:
 956		paths.add("System classpath: "
 957			+ System.getProperty("java.class.path"));
 958
 959		try
 960		{
 961			// Either class sun.tools.javac.Main or
 962			// com.sun.tools.javac.Main must be there:
 963			try
 964			{
 965				Class.forName("sun.tools.javac.Main");
 966			}
 967			catch(ClassNotFoundException e1)
 968			{
 969				Class.forName("com.sun.tools.javac.Main");
 970			}
 971			Log.log(Log.DEBUG, MiscUtilities.class,
 972				"- is in classpath. Fine.");
 973			return true;
 974		}
 975		catch(ClassNotFoundException e)
 976		{
 977			//Log.log(Log.DEBUG, MiscUtilities.class,
 978			//	"- is not in system classpath.");
 979		} //}}}
 980
 981		//{{{ 2. Check whether it is in the jEdit user settings jars folder:
 982		String settingsDir = jEdit.getSettingsDirectory();
 983		if(settingsDir != null)
 984		{
 985			String toolsPath = constructPath(settingsDir, "jars",
 986				"tools.jar");
 987			paths.add(toolsPath);
 988			if(new File(toolsPath).exists())
 989			{
 990				Log.log(Log.DEBUG, MiscUtilities.class,
 991					"- is in the user's jars folder. Fine.");
 992				// jEdit will load it automatically
 993				return true;
 994			}
 995		} //}}}
 996
 997		//{{{ 3. Check whether it is in jEdit's system jars folder:
 998		String jEditDir = jEdit.getJEditHome();
 999		if(jEditDir != null)
1000		{
1001			String toolsPath = constructPath(jEditDir, "jars", "tools.jar");
1002			paths.add(toolsPath);
1003			if(new File(toolsPath).exists())
1004			{
1005				Log.log(Log.DEBUG, MiscUtilities.class,
1006					"- is in jEdit's system jars folder. Fine.");
1007				// jEdit will load it automatically
1008				return true;
1009			}
1010		} //}}}
1011
1012		//{{{ 4. Check whether it is in <java.home>/lib:
1013		String toolsPath = System.getProperty("java.home");
1014		if(toolsPath.toLowerCase().endsWith(File.separator + "jre"))
1015			toolsPath = toolsPath.substring(0, toolsPath.length() - 4);
1016		toolsPath = constructPath(toolsPath, "lib", "tools.jar");
1017		paths.add(toolsPath);
1018
1019		if(!new File(toolsPath).exists())
1020		{
1021			Log.log(Log.WARNING, MiscUtilities.class,
1022				"Could not find tools.jar.\n"
1023				+ "I checked the following locations:\n"
1024				+ paths.toString());
1025			return false;
1026		} //}}}
1027
1028		//{{{ Load it, if not yet done:
1029		PluginJAR jar = jEdit.getPluginJAR(toolsPath);
1030		if(jar == null)
1031		{
1032			Log.log(Log.DEBUG, MiscUtilities.class,
1033				"- adding " + toolsPath + " to jEdit plugins.");
1034			jEdit.addPluginJAR(toolsPath);
1035		}
1036		else
1037			Log.log(Log.DEBUG, MiscUtilities.class,
1038				"- has been loaded before.");
1039		//}}}
1040
1041		return true;
1042	} //}}}
1043
1044	//{{{ parsePermissions() method
1045	/**
1046	 * Parse a Unix-style permission string (rwxrwxrwx).
1047	 * @param s The string (must be 9 characters long).
1048	 * @since jEdit 4.1pre8
1049	 */
1050	public static int parsePermissions(String s)
1051	{
1052		int permissions = 0;
1053
1054		if(s.length() == 9)
1055		{
1056			if(s.charAt(0) == 'r')
1057				permissions += 0400;
1058			if(s.charAt(1) == 'w')
1059				permissions += 0200;
1060			if(s.charAt(2) == 'x')
1061				permissions += 0100;
1062			else if(s.charAt(2) == 's')
1063				permissions += 04100;
1064			else if(s.charAt(2) == 'S')
1065				permissions += 04000;
1066			if(s.charAt(3) == 'r')
1067				permissions += 040;
1068			if(s.charAt(4) == 'w')
1069				permissions += 020;
1070			if(s.charAt(5) == 'x')
1071				permissions += 010;
1072			else if(s.charAt(5) == 's')
1073				permissions += 02010;
1074			else if(s.charAt(5) == 'S')
1075				permissions += 02000;
1076			if(s.charAt(6) == 'r')
1077				permissions += 04;
1078			if(s.charAt(7) == 'w')
1079				permissions += 02;
1080			if(s.charAt(8) == 'x')
1081				permissions += 01;
1082			else if(s.charAt(8) == 't')
1083				permissions += 01001;
1084			else if(s.charAt(8) == 'T')
1085				permissions += 01000;
1086		}
1087
1088		return permissions;
1089	} //}}}
1090
1091	//{{{ getEncodings() methods
1092	/**
1093	 * Returns a list of supported character encodings.
1094	 * @since jEdit 4.3pre5
1095	 * @param getSelected Whether to return just the selected encodings or all.
1096	 */
1097	public static String[] getEncodings(boolean getSelected)
1098	{
1099		Set<String> set;
1100		if (getSelected)
1101		{
1102			set = EncodingServer.getSelectedNames();
1103		}
1104		else
1105		{
1106			set = EncodingServer.getAvailableNames();
1107		}
1108		return set.toArray(new String[set.size()]);
1109	} //}}}
1110
1111	//{{{ throwableToString() method
1112	/**
1113	 * Returns a string containing the stack trace of the given throwable.
1114	 * @since jEdit 4.2pre6
1115	 */
1116	public static String throwableToString(Throwable t)
1117	{
1118		StringWriter s = new StringWriter();
1119		t.printStackTrace(new PrintWriter(s));
1120		return s.toString();
1121	} //}}}
1122
1123	//{{{ Private members
1124	private MiscUtilities() {}
1125
1126	//{{{ compareChars() method
1127	/**
1128	 * Compares two chars.
1129	 * should this be public?
1130	 * @param ch1 the first char
1131	 * @param ch2 the second char
1132	 * @param ignoreCase true if you want to ignore case
1133	 */
1134	private static boolean compareChars(char ch1, char ch2, boolean ignoreCase)
1135	{
1136		if(ignoreCase)
1137			return Character.toUpperCase(ch1) == Character.toUpperCase(ch2);
1138		else
1139			return ch1 == ch2;
1140	} //}}}
1141
1142	//{{{ getPathStart() method
1143	private static int getPathStart(String path)
1144	{
1145		if(path.startsWith("/"))
1146			return 0;
1147		else if(OperatingSystem.isDOSDerived()
1148			&& path.length() >= 3
1149			&& path.charAt(1) == ':'
1150			&& (path.charAt(2) == '/'
1151			|| path.charAt(2) == '\\'))
1152			return 3;
1153		else
1154			return 0;
1155	} //}}}
1156
1157	//{{{ containsNullCharacter() method
1158	private static boolean containsNullCharacter(Reader reader)
1159		throws IOException
1160	{
1161		int nbChars = jEdit.getIntegerProperty("vfs.binaryCheck.length",100);
1162		int authorized = jEdit.getIntegerProperty("vfs.binaryCheck.count",1);
1163		for (long i = 0L;i < nbChars;i++)
1164		{
1165			int c = reader.read();
1166			if (c == -1)
1167				return false;
1168			if (c == 0)
1169			{
1170				authorized--;
1171				if (authorized == 0)
1172					return true;
1173			}
1174		}
1175		return false;
1176	} //}}}
1177
1178	//}}}
1179
1180	static VarCompressor svc = null;
1181
1182	//{{{ VarCompressor class
1183	/**
1184	 * Singleton class for quickly "compressing" paths into variable-prefixed values.
1185	 * @author alan ezust
1186	 */
1187	static class VarCompressor
1188	{
1189		/** a reverse mapping of values to environment variable names */
1190		final Map<String, String> prefixMap = new HashMap<String, String>();
1191		/** previously compressed strings saved for quick access later */
1192		final Map<String, String> previous = new HashMap<String, String>();
1193
1194		//{{{ VarCompressor constructor
1195		VarCompressor()
1196		{
1197			ProcessBuilder pb = new ProcessBuilder();
1198			Map<String, String> env = pb.environment();
1199			if (OperatingSystem.isUnix())
1200				prefixMap.put(System.getProperty("user.home"), "~");
1201			for (Map.Entry<String, String> entry: env.entrySet())
1202			{
1203				String k = entry.getKey();
1204				if (k.equalsIgnoreCase("pwd") || k.equalsIgnoreCase("oldpwd")) continue;
1205				if (!Character.isLetter(k.charAt(0))) continue;
1206				String v = entry.getValue();
1207				// only add possible candidates to the prefix map
1208				if (!canBePathPrefix(v)) continue;
1209				// no need for trailing file separator
1210				if (v.endsWith(File.separator))
1211					v = v.substring(0, v.length()-1);
1212				// check if it is actually shorter
1213				if (OperatingSystem.isWindows())
1214					if (k.length()+2 > v.length()) continue; // gets replaced by %FOO%
1215				else
1216					if (k.length()+1 > v.length()) continue; // gets replaced by $FOO
1217				if (OperatingSystem.isWindows())
1218				{
1219					// no case sensitivity, might as well convert to lower case
1220					v = v.toLowerCase();
1221					k = k.toLowerCase();
1222				}
1223				if (prefixMap.containsKey(v))
1224				{
1225					String otherKey = prefixMap.get(v);
1226					if (otherKey.length() < k.length()) continue;
1227				}
1228				prefixMap.put(v, k);
1229			}
1230		} //}}}
1231
1232		//{{{ compress() method
1233		String compress(String path)
1234		{
1235			String original = path;
1236			if (previous.containsKey(path))
1237			{
1238				return previous.get(path);
1239			}
1240			String bestPrefix = "/";
1241			String verifiedPrefix = bestPrefix;
1242			for (String tryPrefix : prefixMap.keySet())
1243			{
1244				if (tryPrefix.length() < bestPrefix.length()) continue;
1245				if (OperatingSystem.isWindows() &&
1246				    path.toLowerCase().startsWith(tryPrefix))
1247					bestPrefix = tryPrefix;
1248				else if (path.startsWith(tryPrefix))
1249				{
1250					bestPrefix = tryPrefix;
1251				}
1252				// Only use prefix if it is a directory-prefix of the path
1253				if (!bestPrefix.equals(verifiedPrefix))
1254				{
1255					String remainder = original.substring(bestPrefix.length());
1256					if (remainder.length() < 1 || remainder.startsWith(File.separator))
1257						verifiedPrefix = bestPrefix;
1258					else bestPrefix = verifiedPrefix;
1259				}
1260			}
1261			if (bestPrefix.length() > 1)
1262			{
1263				String remainder = original.substring(bestPrefix.length());
1264				String envvar = prefixMap.get(bestPrefix);
1265				if (envvar.equals("~"))
1266					path = envvar + remainder;
1267				else if (OperatingSystem.isWindows())
1268					path = '%' + envvar.toUpperCase() + '%' + remainder;
1269				else
1270					path = '$' + envvar + remainder;
1271			}
1272			previous.put(original, path);
1273			return path;
1274		} //}}}
1275
1276		//{{{ canBePathPrefix() method
1277		// Returns true if the argument may absolutely point a directory.
1278		// For speed, no access to file system or network should happen.
1279		private boolean canBePathPrefix(String s)
1280		{
1281			// Do not use File#isDirectory() since it causes
1282			// access to file system or network to check if
1283			// the directory is actually exists.
1284			return !s.contains(File.pathSeparator)
1285				&& new File(s).isAbsolute();
1286		} //}}}
1287	} //}}}
1288
1289}