PageRenderTime 60ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/bluej/utility/Utility.java

https://github.com/thatsmydoing/bluej-colno
Java | 1049 lines | 948 code | 22 blank | 79 comment | 34 complexity | a2021d0ee67df073b0e2c2c8793981a5 MD5 | raw file
  1. /*
  2. This file is part of the BlueJ program.
  3. Copyright (C) 1999-2009 Michael Kolling and John Rosenberg
  4. This program is free software; you can redistribute it and/or
  5. modify it under the terms of the GNU General Public License
  6. as published by the Free Software Foundation; either version 2
  7. of the License, or (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  15. This file is subject to the Classpath exception as provided in the
  16. LICENSE.txt file that accompanied this code.
  17. */
  18. package bluej.utility;
  19. import java.awt.Component;
  20. import java.awt.FontMetrics;
  21. import java.awt.Graphics;
  22. import java.awt.Insets;
  23. import java.awt.Shape;
  24. import java.awt.Window;
  25. import java.awt.event.KeyEvent;
  26. import java.io.BufferedReader;
  27. import java.io.File;
  28. import java.io.FileInputStream;
  29. import java.io.FileNotFoundException;
  30. import java.io.FileOutputStream;
  31. import java.io.IOException;
  32. import java.io.InputStreamReader;
  33. import java.io.OutputStream;
  34. import java.lang.management.ManagementFactory;
  35. import java.net.MalformedURLException;
  36. import java.net.URI;
  37. import java.net.URISyntaxException;
  38. import java.net.URL;
  39. import java.util.ArrayList;
  40. import java.util.HashSet;
  41. import java.util.List;
  42. import java.util.Set;
  43. import java.util.jar.JarEntry;
  44. import java.util.jar.JarInputStream;
  45. import javax.swing.AbstractButton;
  46. import javax.swing.border.Border;
  47. import javax.swing.text.TabExpander;
  48. import bluej.Config;
  49. /**
  50. * Some generally useful utility methods available to all of bluej.
  51. *
  52. * @author Michael Cahill
  53. * @author Michael Kolling
  54. * @version $Id: Utility.java 8510 2010-10-21 04:12:29Z davmac $
  55. */
  56. public class Utility
  57. {
  58. /**
  59. * Used to track which events have occurred for firstTimeThisRun()
  60. */
  61. private static Set<String> occurredEvents = new HashSet<String>();
  62. /**
  63. * Draw a thick rectangle - another of the things missing from the AWT
  64. */
  65. public static void drawThickRect(Graphics g, int x, int y, int width, int height, int thickness)
  66. {
  67. for (int i = 0; i < thickness; i++)
  68. g.drawRect(x + i, y + i, width - 2 * i, height - 2 * i);
  69. }
  70. /**
  71. * Draw a thick rounded rectangle - another of the things missing from the AWT
  72. */
  73. public static void drawThickRoundRect(Graphics g, int x, int y, int width, int height, int arc, int thickness)
  74. {
  75. for (int i = 0; i < thickness; i++)
  76. g.drawRoundRect(x + i, y + i, width - 2 * i, height - 2 * i, arc, arc);
  77. }
  78. /**
  79. * Draw stripes over a rectangle - yet another thing missing from the AWT
  80. */
  81. public static void stripeRect(Graphics g, int x, int y, int width, int height, int separation, int thickness)
  82. {
  83. for (int offset = 0; offset < width + height; offset += separation)
  84. for (int i = 0; i < thickness; i++, offset++) {
  85. int x1, y1, x2, y2;
  86. if (offset < height) {
  87. x1 = x;
  88. y1 = y + offset;
  89. }
  90. else {
  91. x1 = x + offset - height;
  92. y1 = y + height;
  93. }
  94. if (offset < width) {
  95. x2 = x + offset;
  96. y2 = y;
  97. }
  98. else {
  99. x2 = x + width;
  100. y2 = y + offset - width;
  101. }
  102. g.drawLine(x1, y1, x2, y2);
  103. }
  104. }
  105. /**
  106. * Draw a string at a given location on screen centered in a given
  107. * rectangle.<br>
  108. * Left justifies the string if it is too long to fit all of the string
  109. * inside the rectangle.
  110. */
  111. public static void drawCentredText(Graphics g, String str, int x, int y, int width, int height)
  112. {
  113. FontMetrics fm = g.getFontMetrics();
  114. Shape oldClip = g.getClip();
  115. g.clipRect(x, y, width, height);
  116. int xOffset = (width - fm.stringWidth(str)) / 2;
  117. if (xOffset < 0) {
  118. xOffset = 0;
  119. }
  120. // This is the space left around the text, divided by 2 (equal gap above and below)
  121. // to get the top of the text, plus the ascent to get the baseline
  122. int yOffset = fm.getAscent() + ((height - fm.getAscent() - fm.getDescent()) / 2);
  123. g.drawString(str, x + xOffset, y + yOffset);
  124. g.setClip(oldClip);
  125. }
  126. /**
  127. * Draw a string at a given location on screen right-aligned in a given
  128. * rectangle.
  129. */
  130. public static void drawRightText(Graphics g, String str, int x, int y, int width, int height)
  131. {
  132. FontMetrics fm = g.getFontMetrics();
  133. Shape oldClip = g.getClip();
  134. g.clipRect(x, y, width, height);
  135. g.drawString(str, x + width - fm.stringWidth(str), y + (height + fm.getAscent()) / 2);
  136. g.setClip(oldClip);
  137. }
  138. /**
  139. * Splits "string" by "Delimiter"
  140. *
  141. * @param str - the string to be split
  142. * @param delimiter - the field delimiter within str
  143. * @returns an array of Strings
  144. */
  145. public static String[] split(String str, String delimiter)
  146. {
  147. List<String> strings = new ArrayList<String>();
  148. int start = 0;
  149. int len = str.length();
  150. int dlen = delimiter.length();
  151. int offset = str.lastIndexOf(delimiter); // First of all, find the
  152. // Last occurance of the Delimiter
  153. // Stop empty delimiters
  154. if (dlen < 1)
  155. return null;
  156. else if (offset < 0) // one element
  157. {
  158. String[] result = {str};
  159. return result;
  160. }
  161. //
  162. // Append the delimiter onto the end if it doesn't already exit
  163. //
  164. if (len > offset + dlen) {
  165. str += delimiter;
  166. len += dlen;
  167. }
  168. do {
  169. // Get the new Offset
  170. offset = str.indexOf(delimiter, start);
  171. strings.add(str.substring(start, offset));
  172. // Get the new Start position
  173. start = offset + dlen;
  174. } while ((start < len) && (offset != -1));
  175. // Convert the list into an Array of Strings
  176. String result[] = new String[strings.size()];
  177. strings.toArray(result);
  178. return result;
  179. }
  180. /**
  181. * Splits "string" into lines (stripping end-of-line characters)
  182. *
  183. * @param str - the string to be split
  184. * @returns an array of Strings
  185. */
  186. public static String[] splitLines(String str)
  187. {
  188. return (str == null ? null : split(str, "\n"));
  189. }
  190. /**
  191. * Return a string in which all the quotable characters (tab, newline, ' and ",
  192. * etc) are quoted, Java-style.
  193. */
  194. public static String quoteString(String src)
  195. {
  196. StringBuffer buf = new StringBuffer();
  197. for (int i = 0; i < src.length(); i++) {
  198. char c = src.charAt(i);
  199. if (c == '\n')
  200. buf.append("\\n");
  201. else if (c == '\r')
  202. buf.append("\\r");
  203. else if (c == '\t')
  204. buf.append("\\g");
  205. else if (c < 32 || c > 128) {
  206. // Character is outside normal ASCII range, output it as unicode
  207. // escape sequence.
  208. String n = Integer.toHexString(c);
  209. n = "0000".substring(n.length()) + n;
  210. buf.append("\\u");
  211. buf.append(n);
  212. }
  213. else {
  214. if (c == '\\' || c == '"' || c == '\'')
  215. buf.append('\\');
  216. buf.append(src.charAt(i));
  217. }
  218. }
  219. return buf.toString();
  220. }
  221. /**
  222. * Translate a given, qualified class name into a URL where we believe its
  223. * documentation to be, and display that URL in a web browser.
  224. */
  225. public static void showClassDocumentation(String classname, String suffix)
  226. {
  227. classname = classname.replace('.', '/');
  228. String docURL = Config.getPropString("bluej.url.javaStdLib");
  229. if (docURL.endsWith(".html")) {
  230. int lastSlash = docURL.lastIndexOf('/');
  231. if (lastSlash != -1)
  232. docURL = docURL.substring(0, lastSlash + 1);
  233. }
  234. // Debug.message(docURL + classname + ".html" + suffix);
  235. openWebBrowser(docURL + classname + ".html" + suffix);
  236. }
  237. /**
  238. * Let the given URL be shown in a browser window.
  239. *
  240. * @param url the URL or file path to be shown.
  241. * @return true if the web browser could be started, false otherwise.
  242. */
  243. public static boolean openWebBrowser(String url)
  244. {
  245. if (Config.isWinOS()) { // Windows
  246. String cmd;
  247. // catering for stupid differences in Windows shells...
  248. if (Config.osname.startsWith("Windows 9") || Config.osname.equals("Windows Me")) // win95/98/Me
  249. cmd = "command.com";
  250. else
  251. // other
  252. cmd = "cmd.exe";
  253. try {
  254. // more stupid Windows differences...
  255. if (Config.osname.startsWith("Windows 98") || Config.osname.equals("Windows Me")) {
  256. Runtime.getRuntime().exec(new String[]{cmd, "/c", "start", '"' + url + '"'});
  257. }
  258. else {
  259. Runtime.getRuntime().exec(new String[]{cmd, "/c", "start", "\"\"", '"' + url + '"'});
  260. }
  261. }
  262. catch (IOException e) {
  263. Debug.reportError("could not start web browser. exc: " + e);
  264. return false;
  265. }
  266. }
  267. else { // Mac, Unix and other
  268. // The string should be either a URL or a file path
  269. try {
  270. return openWebBrowser(new URL(url));
  271. }
  272. catch (MalformedURLException mfue) {
  273. return openWebBrowser(new File(url));
  274. }
  275. }
  276. return true;
  277. }
  278. /**
  279. * Let the given URL be shown in a browser window.
  280. *
  281. * @param url the URL to be shown.
  282. * @return true if the web browser could be started, false otherwise.
  283. */
  284. public static boolean openWebBrowser(URL url)
  285. {
  286. if (Config.isMacOS()) {
  287. // Mac
  288. try {
  289. com.apple.eio.FileManager.openURL(url.toString());
  290. }
  291. catch (IOException e) {
  292. Debug.reportError("could not start web browser. exc: " + e);
  293. return false;
  294. }
  295. }
  296. else if (Config.isWinOS()) {
  297. // Windows
  298. return openWebBrowser(url.toString());
  299. }
  300. else {
  301. // Unix and other
  302. if (JavaUtils.getJavaUtils().openWebBrowser(url)) {
  303. return true;
  304. }
  305. String cmd = mergeStrings(Config.getPropString("browserCmd1"), url.toString());
  306. String cmd2 = mergeStrings(Config.getPropString("browserCmd2"), url.toString());
  307. Process p = null;
  308. try {
  309. p = Runtime.getRuntime().exec(cmd);
  310. }
  311. catch (IOException e) {
  312. try {
  313. p = Runtime.getRuntime().exec(cmd2);
  314. cmd2 = null;
  315. }
  316. catch (IOException e2) {
  317. Debug.reportError("could not start web browser. exc: " + e);
  318. return false;
  319. }
  320. }
  321. final String command2 = cmd2;
  322. final Process process = p;
  323. new Thread() {
  324. public void run()
  325. {
  326. runUnixWebBrowser(process, command2);
  327. }
  328. }.start();
  329. }
  330. return true;
  331. }
  332. /**
  333. * Wait for the given process to finish, try running the second command if
  334. * it returns false.
  335. *
  336. * @param p
  337. * @param url
  338. * @param cmd2
  339. */
  340. private static void runUnixWebBrowser(Process p, String cmd2)
  341. {
  342. try {
  343. // wait for exit code. 0 indicates success, otherwise
  344. // we try second command
  345. int exitCode = p.waitFor();
  346. if (exitCode != 0 && cmd2 != null && cmd2.length() > 0) {
  347. p = Runtime.getRuntime().exec(cmd2);
  348. }
  349. }
  350. catch (InterruptedException ie) {
  351. Debug.reportError("cannot start web browser:");
  352. Debug.reportError("caught exc " + ie);
  353. }
  354. catch (IOException ioe) {
  355. Debug.reportError("cannot start web browser:");
  356. Debug.reportError("caught exc " + ioe);
  357. }
  358. }
  359. /**
  360. * Let the given file be shown in a browser window.
  361. *
  362. * @param file the file to be shown.
  363. * @return true if the web browser could be started, false otherwise.
  364. */
  365. public static boolean openWebBrowser(File file)
  366. {
  367. if (Config.isWinOS()) { // Windows
  368. return openWebBrowser(file.toString());
  369. }
  370. else { // Mac, Unix and other
  371. try {
  372. return openWebBrowser(file.toURI().toURL());
  373. }
  374. catch (MalformedURLException mfue) {
  375. // This shouldn't happen.
  376. return false;
  377. }
  378. }
  379. }
  380. /**
  381. * Method copied from Boot since we don't always have access to Boot here (if this method is called from the user VM for instance).
  382. *
  383. * Calculate the bluejLibDir value by doing some reasoning on a resource
  384. * we know we have: the .class file for the Utility class.
  385. *
  386. * @return the path of the BlueJ lib directory
  387. */
  388. private static File calculateBluejLibDir()
  389. {
  390. File bluejDir = null;
  391. String bootFullName = Utility.class.getResource("Utility.class").toString();
  392. try {
  393. if (! bootFullName.startsWith("jar:")) {
  394. // Boot.class is not in a jar-file. Find a lib directory somewhere
  395. // above us to use
  396. File startingDir = (new File(new URI(bootFullName)).getParentFile());
  397. while((startingDir != null) &&
  398. !(new File(startingDir.getParentFile(), "lib").isDirectory())) {
  399. startingDir = startingDir.getParentFile();
  400. }
  401. if (startingDir == null) {
  402. bluejDir = null;
  403. }
  404. else {
  405. bluejDir = new File(startingDir.getParentFile(), "lib");
  406. }
  407. }
  408. else {
  409. // The class is in a jar file, '!' separates the jar file name
  410. // from the class name. Cut off the class name and the "jar:" prefix.
  411. int classIndex = bootFullName.indexOf("!");
  412. String bootName = bootFullName.substring(4, classIndex);
  413. File finalFile = new File(new URI(bootName));
  414. bluejDir = finalFile.getParentFile();
  415. }
  416. }
  417. catch (URISyntaxException use) { }
  418. return bluejDir;
  419. }
  420. /**
  421. * Bring the current process to the front in the OS window stacking order.
  422. * The given window will be brought to the front.
  423. *
  424. * <p>This method can be called from the debug VM.
  425. */
  426. public static void bringToFront(final Window window)
  427. {
  428. // If not showing at all we return now.
  429. if (!window.isShowing()) {
  430. return;
  431. }
  432. String pid = getProcessId();
  433. boolean isWindows = Config.isWinOS();
  434. boolean isMacOS = Config.isMacOS();
  435. if (isWindows || isMacOS) {
  436. // Use WSH (Windows Script Host) to execute a javascript that brings
  437. // a window to front.
  438. File libdir = calculateBluejLibDir();
  439. String[] command;
  440. if (isWindows) {
  441. command = new String[] {"cscript","\"" + libdir.getAbsolutePath() + "\\windowtofront.js\"",pid };
  442. }
  443. else {
  444. command = new String[] {"osascript", "-e", "tell application \"System Events\"", "-e",
  445. "set frontmost of first process whose unix id is " + pid + " to true", "-e", "end tell"};
  446. }
  447. final StringBuffer commandAsStr = new StringBuffer();
  448. for (int i = 0; i < command.length; i++) {
  449. commandAsStr.append(command[i] + " ");
  450. }
  451. try {
  452. Process p = Runtime.getRuntime().exec(command);
  453. new ExternalProcessLogger(command[0], commandAsStr.toString(), p).start();
  454. }
  455. catch (IOException e) {
  456. Debug.reportError("While trying to launch \"" + command + "\", got this IOException:", e);
  457. }
  458. }
  459. if (Config.isLinux()) {
  460. // http://ubuntuforums.org/archive/index.php/t-197207.html
  461. // However, usually X doesn't care about process stacking:
  462. window.toFront();
  463. }
  464. // alternative technique: using 'open command. works only for BlueJ.app,
  465. // not for remote VM
  466. // // first, find the path of BlueJ.app
  467. // String path = getClass().getResource("PkgMgrFrame.class").getPath();
  468. // int index = path.indexOf("BlueJ.app");
  469. // if(index != -1) {
  470. // path = path.substring(0, index+9);
  471. // // once we found it, call 'open' on it to bring it to front.
  472. // String[] openCmd = { "open", path };
  473. // Runtime.getRuntime().exec(openCmd);
  474. // }
  475. }
  476. private static class ExternalProcessLogger extends Thread
  477. {
  478. String commandAsStr;
  479. String processName;
  480. Process p;
  481. public ExternalProcessLogger(String processName, String command, Process process)
  482. {
  483. this.processName = processName;
  484. commandAsStr = command;
  485. p = process;
  486. }
  487. public void run()
  488. {
  489. BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
  490. StringBuffer extra = new StringBuffer();
  491. try {
  492. char[] buf = new char[1024];
  493. Thread.sleep(1000);
  494. // discontinue if no data available or stream closed
  495. if (br.ready()) {
  496. int len = br.read(buf);
  497. if (len != -1) {
  498. extra.append(buf, 0, len);
  499. }
  500. }
  501. if (extra.length() != 0) {
  502. Debug.message("When trying to launch " + processName + ":" + commandAsStr);
  503. Debug.message(" This error was recieved: " + extra);
  504. }
  505. }
  506. catch (InterruptedException ie) {}
  507. catch (IOException ioe) {}
  508. finally {
  509. try {
  510. br.close();
  511. }
  512. catch (IOException ioe) {}
  513. }
  514. }
  515. }
  516. /**
  517. * Get the process ID of this process.
  518. */
  519. public static String getProcessId()
  520. {
  521. String pid = ManagementFactory.getRuntimeMXBean().getName();
  522. // Strip the host name from the pid.
  523. int atIndex = pid.indexOf("@");
  524. if (atIndex != -1) {
  525. pid = pid.substring(0, atIndex);
  526. }
  527. return pid;
  528. }
  529. /**
  530. * merge s2 into s1 at position of first '$'
  531. */
  532. public static String mergeStrings(String s1, String s2)
  533. {
  534. int pos = s1.indexOf('$');
  535. if (pos == -1)
  536. return s1;
  537. else
  538. return s1.substring(0, pos) + s2 + s1.substring(pos + 1);
  539. }
  540. /**
  541. * merge strings in s2 into s1 at positions of '$'
  542. */
  543. public static String mergeStrings(String s1, String s2[])
  544. {
  545. for (int current = 0; current < s2.length; current++) {
  546. s1 = mergeStrings(s1, s2[current]);
  547. }
  548. return s1;
  549. }
  550. /**
  551. * Converts tabs in a String into a specified number of spaces. It assumes
  552. * that beginning of String is the starting point of tab offsets.
  553. *
  554. * @param original the String to convert
  555. * @param tabSize number of spaces to be inserted in place of tab
  556. * @return the String with spaces replacing tabs (if tabs present).
  557. */
  558. public static String convertTabsToSpaces(String originalString, int tabSize)
  559. {
  560. // if there are tab(s) in the String
  561. if (originalString.indexOf('\t') != -1) {
  562. StringBuffer buffer = new StringBuffer(originalString);
  563. for (int i = 0; i < buffer.length(); i++) {
  564. if (buffer.charAt(i) == '\t') {
  565. buffer.deleteCharAt(i);
  566. // calculate how many spaces to add
  567. int numberOfSpaces = tabSize - (i % tabSize);
  568. for (int j = 0; j < numberOfSpaces; j++)
  569. buffer.insert(i, ' ');
  570. }
  571. }
  572. return buffer.toString();
  573. }
  574. else
  575. return originalString;
  576. }
  577. /**
  578. * Calculates how many spaces each tab in the given string turns into.
  579. *
  580. * If there is a tab at character index N, the array entry N in the
  581. * returned array will indicate how many spaces the tab converts into.
  582. * The value of all other entries is undefined.
  583. */
  584. public static int[] calculateTabSpaces(String line, int tabSize)
  585. {
  586. // Bigger array than necessary, but we're only doing one line at a time:
  587. int[] tabSpaces = new int[line.length()];
  588. int curPos = 0;
  589. for (int i = 0; i < line.length(); i++) {
  590. if (line.charAt(i) == '\t') {
  591. // calculate how many spaces to add
  592. int numberOfSpaces = tabSize - (curPos % tabSize);
  593. tabSpaces[i] = numberOfSpaces;
  594. curPos += numberOfSpaces;
  595. }
  596. else {
  597. curPos += 1;
  598. }
  599. }
  600. return tabSpaces;
  601. }
  602. /**
  603. * Makes a TabExpander object that will turn tabs into the appropriate
  604. * white-space, based on the original String. This means that the tabs
  605. * will get aligned to the correct tab-stops rather than just being
  606. * converted into a set number of spaces. Thus, the TabExpander will match
  607. * the behaviour of the editor.
  608. */
  609. public static TabExpander makeTabExpander(String line, int tabSize, final FontMetrics fontMetrics)
  610. {
  611. final int[] tabSpaces = Utility.calculateTabSpaces(line, tabSize);
  612. return new TabExpander() {
  613. public float nextTabStop(float x, int tabOffset) {
  614. return x + tabSpaces[tabOffset] * fontMetrics.charWidth(' ');
  615. }
  616. };
  617. }
  618. /**
  619. * Given a String and an index into it, along with the pre-calculated tabSpaces array,
  620. * advances the index by the given number of character widths.
  621. *
  622. * If the String contains to tabs, this effectively adds advanceBy to index.
  623. *
  624. * If the String does contain tabs, their width is taken into account
  625. * as the index is advanced through the array.
  626. *
  627. */
  628. public static int advanceChars(String line, int[] tabSpaces, int index, int advanceBy)
  629. {
  630. while (advanceBy > 0 && index < line.length())
  631. {
  632. int width = (line.charAt(index) == '\t') ? tabSpaces[index] : 1;
  633. advanceBy -= width;
  634. index += 1;
  635. }
  636. return index;
  637. }
  638. /**
  639. * Check if this is the first time a particular event (identified by the
  640. * context string) has occurred during this run of BlueJ.
  641. *
  642. * @param context Identifies the event (suggested:
  643. * fully-qualified-class-name:event-id)
  644. * @return true the first time the method was called with the given context;
  645. * false every subsequent time.
  646. */
  647. public static boolean firstTimeThisRun(String context)
  648. {
  649. if (occurredEvents.contains(context))
  650. return false;
  651. occurredEvents.add(context);
  652. return true;
  653. }
  654. /**
  655. * Check if this is the first time a particular event (identified by the
  656. * context string) has occurred "ever" (in this BlueJ installation).
  657. *
  658. * @param context Identifies the event (a property name)
  659. * @return true the first time the method was called with the given context;
  660. * false every subsequent time.
  661. */
  662. public static boolean firstTimeEver(String context)
  663. {
  664. boolean occurred = Config.getPropBoolean(context);
  665. if (occurred) {
  666. return false;
  667. }
  668. Config.putPropBoolean(context, true);
  669. return true;
  670. }
  671. /**
  672. * This method creates a MacOS button. It will create a "textured" button on
  673. * MacOS 10.5 and newer and a "toolbar" button on older MasOS.
  674. *
  675. * @param button The button that should be changed.
  676. */
  677. public static void changeToMacButton(AbstractButton button)
  678. {
  679. // available button styles, as of MacOS 10.5:
  680. // square, gradient, bevel, textured, roundRect, recessed, help
  681. // segmented styles:
  682. // segmented, segmentedRoundRect, segmentedCapsule, segmentedTextured
  683. // see: http://developer.apple.com/technotes/tn2007/tn2196.html
  684. if (!Config.isMacOS()) {
  685. return;
  686. }
  687. Border oldBorder = button.getBorder();
  688. // the following works since MacOS 10.5
  689. button.putClientProperty("JButton.buttonType", "square");
  690. if (oldBorder == button.getBorder()) {
  691. // if the border didn't change the "square" type probably doesn't
  692. // exist, which means we are running on MacOS < 10.5. This means we
  693. // should use the old pre-10.5 "toolbar" style instead.
  694. button.putClientProperty("JButton.buttonType", "toolbar");
  695. }
  696. else {
  697. // if we get to this point, the square button type is available, and
  698. // we can continue configuring for that one.
  699. button.setMargin(new Insets(3, 1, 3, 1));
  700. }
  701. }
  702. /**
  703. * Determines whether the given key is a dead key.
  704. */
  705. public static boolean isDeadKey(KeyEvent event)
  706. {
  707. switch(event.getKeyCode()) {
  708. case KeyEvent.VK_DEAD_GRAVE:
  709. case KeyEvent.VK_DEAD_ACUTE:
  710. case KeyEvent.VK_DEAD_CIRCUMFLEX:
  711. case KeyEvent.VK_DEAD_TILDE:
  712. case KeyEvent.VK_DEAD_MACRON:
  713. case KeyEvent.VK_DEAD_BREVE:
  714. case KeyEvent.VK_DEAD_ABOVEDOT:
  715. case KeyEvent.VK_DEAD_DIAERESIS:
  716. case KeyEvent.VK_DEAD_ABOVERING:
  717. case KeyEvent.VK_DEAD_DOUBLEACUTE:
  718. case KeyEvent.VK_DEAD_CARON:
  719. case KeyEvent.VK_DEAD_CEDILLA:
  720. case KeyEvent.VK_DEAD_OGONEK:
  721. case KeyEvent.VK_DEAD_IOTA:
  722. case KeyEvent.VK_DEAD_VOICED_SOUND:
  723. case KeyEvent.VK_DEAD_SEMIVOICED_SOUND:
  724. return true;
  725. }
  726. return false;
  727. }
  728. /**
  729. * Takes a list of lines and forms them into a multiline tool-tip.
  730. *
  731. * The way to do this is to use HTML; see http://www.jguru.com/faq/view.jsp?EID=10653
  732. */
  733. public static String multilineTooltip(String... lines)
  734. {
  735. StringBuilder str = new StringBuilder("<html>");
  736. for (int i = 0; i < lines.length; i++) {
  737. str.append(lines[i]);
  738. if (i != lines.length - 1) {
  739. str.append("<br>");
  740. }
  741. }
  742. str.append("</html>");
  743. return str.toString();
  744. }
  745. /**
  746. * Attempt to determine the prefix folder of a zip or jar archive.
  747. * That is, if all files in the archive are stored under a first-level
  748. * folder, return the name of that folder; otherwise return null.
  749. *
  750. * @param arName The archive file
  751. * @return The prefix folder of the archive, or null.
  752. * @throws FileNotFoundException
  753. * @throws IOException
  754. */
  755. public static String getArchivePrefixFolder(File arName)
  756. throws FileNotFoundException, IOException
  757. {
  758. JarInputStream jarInStream = null;
  759. FileInputStream is = null;
  760. String prefixFolder = null;
  761. try {
  762. is = new FileInputStream(arName);
  763. jarInStream = new JarInputStream(is);
  764. // Extract entries in the jar file
  765. JarEntry je = jarInStream.getNextJarEntry();
  766. while (je != null) {
  767. String entryName = je.getName();
  768. int slashIndex = entryName.indexOf('/');
  769. if (slashIndex == -1) {
  770. prefixFolder = null;
  771. break;
  772. }
  773. String prefix = entryName.substring(0, slashIndex);
  774. if (prefixFolder == null)
  775. prefixFolder = prefix;
  776. else if (! prefixFolder.equals(prefix)) {
  777. prefixFolder = null;
  778. break;
  779. }
  780. je = jarInStream.getNextJarEntry();
  781. }
  782. }
  783. catch (FileNotFoundException fnfe) {
  784. throw fnfe; // rethrow after processing finally block
  785. }
  786. catch (IOException ioe) {
  787. throw ioe; // rethrow after processing finally block
  788. }
  789. finally {
  790. if (jarInStream != null)
  791. jarInStream.close();
  792. if (is != null)
  793. is.close();
  794. }
  795. return prefixFolder;
  796. }
  797. public static File maybeExtractArchive(File archive, Component parent)
  798. {
  799. JarInputStream jarInStream = null;
  800. File oPath = archive.getParentFile();
  801. try {
  802. // first need to determine the output path. If the jar file
  803. // contains a root-level (eg bluej.pkg) entry, extract into a directory
  804. // whose name is the basename of the archive file. Otherwise, if
  805. // all entries have a common ancestor, extract to that directory
  806. // (after checking it doesn't exist).
  807. String prefixFolder = getArchivePrefixFolder(archive);
  808. if (prefixFolder == null) {
  809. // Try to extract to directory which has same name as the jar
  810. // file, with the .jar or .bjar extension stripped.
  811. String archiveName = archive.getName();
  812. int dotIndex = archiveName.lastIndexOf('.');
  813. String strippedName = null;
  814. if(dotIndex != -1) {
  815. strippedName = archiveName.substring(0, dotIndex);
  816. } else {
  817. strippedName = archiveName;
  818. }
  819. oPath = new File(oPath, strippedName);
  820. if (oPath.exists()) {
  821. DialogManager.showErrorWithText(parent, "jar-output-dir-exists", oPath.toString());
  822. return null;
  823. }
  824. else if (! oPath.mkdir()) {
  825. DialogManager.showErrorWithText(parent, "jar-output-no-write", archive.toString());
  826. return null;
  827. }
  828. }
  829. else {
  830. File prefixFolderFile = new File(oPath, prefixFolder);
  831. if (prefixFolderFile.exists()) {
  832. DialogManager.showErrorWithText(parent, "jar-output-dir-exists", prefixFolderFile.toString());
  833. return null;
  834. }
  835. if (! prefixFolderFile.mkdir()) {
  836. DialogManager.showErrorWithText(parent, "jar-output-no-write", archive.toString());
  837. return null;
  838. }
  839. }
  840. // Need to extract the project somewhere, then open it
  841. FileInputStream is = new FileInputStream(archive);
  842. jarInStream = new JarInputStream(is);
  843. // Extract entries in the jar file
  844. JarEntry je = jarInStream.getNextJarEntry();
  845. while (je != null) {
  846. File outFile = new File(oPath, je.getName());
  847. // An entry could represent a file or directory
  848. if (je.getName().endsWith("/"))
  849. outFile.mkdirs();
  850. else {
  851. outFile.getParentFile().mkdirs();
  852. OutputStream os = new FileOutputStream(outFile);
  853. // try to read 8k at a time
  854. byte [] buffer = new byte[8192];
  855. int rlength = jarInStream.read(buffer);
  856. while (rlength != -1) {
  857. os.write(buffer, 0, rlength);
  858. rlength = jarInStream.read(buffer);
  859. }
  860. jarInStream.closeEntry();
  861. }
  862. je = jarInStream.getNextJarEntry();
  863. }
  864. // Now, the jar file may contain a bluej project, or it may
  865. // be a regular jar file in which case we should convert it
  866. // to a bluej project first.
  867. if (prefixFolder != null)
  868. oPath = new File(oPath, prefixFolder);
  869. }
  870. catch (Exception e) {
  871. e.printStackTrace();
  872. DialogManager.showError(parent, "jar-extraction-error");
  873. return null;
  874. }
  875. finally {
  876. try {
  877. if (jarInStream != null)
  878. jarInStream.close();
  879. }
  880. catch (IOException ioe) {}
  881. }
  882. return oPath;
  883. }
  884. /**
  885. * Convert an array of files into a classpath string that can be used to start a VM.
  886. * If files is null or files is empty then an empty string is returned.
  887. *
  888. * @param files an array of files.
  889. * @return a non null string, possibly empty.
  890. */
  891. public static final String toClasspathString(File[] files)
  892. {
  893. if ((files == null) || (files.length < 1)) {
  894. return "";
  895. }
  896. boolean addSeparator = false; // Do not add a separator at the beginning
  897. StringBuffer buf = new StringBuffer();
  898. for (int index = 0; index < files.length; index++) {
  899. File file = files[index];
  900. // It may happen that one entry is null, strange, but just skip it.
  901. if (file == null) {
  902. continue;
  903. }
  904. if (addSeparator) {
  905. buf.append(File.pathSeparatorChar);
  906. }
  907. buf.append(file.toString());
  908. // From now on, you have to add a separator.
  909. addSeparator = true;
  910. }
  911. return buf.toString();
  912. }
  913. /**
  914. * Transform an array of URL into an array of File. Any non-file URLs are skipped.
  915. *
  916. * @param urls an array of URL to be converted
  917. * @return a non null (but possibly empty) array of File
  918. */
  919. public static final File[] urlsToFiles(URL[] urls)
  920. {
  921. if ((urls == null) || (urls.length < 1)) {
  922. return new File[0];
  923. }
  924. List<File> rlist = new ArrayList<File>();
  925. for (int index = 0; index < urls.length; index++) {
  926. URL url = urls[index];
  927. // A class path is always without the qualifier file in front of it.
  928. // However some characters (such as space) are encoded.
  929. if ("file".equals(url.getProtocol())) {
  930. URI uri = URI.create(url.toString());
  931. rlist.add(new File(uri));
  932. }
  933. }
  934. return rlist.toArray(new File[rlist.size()]);
  935. }
  936. }