001/* Utilities used to manipulate strings. 002 003 Copyright (c) 2002-2018 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.util; 029 030// Note that classes in ptolemy.util do not depend on any 031// other ptolemy packages. 032import java.io.BufferedReader; 033import java.io.File; 034import java.io.IOException; 035import java.io.StreamTokenizer; 036import java.io.StringReader; 037import java.lang.reflect.Field; 038import java.net.MalformedURLException; 039import java.net.URI; 040import java.net.URL; 041import java.util.ArrayList; 042import java.util.Arrays; 043import java.util.LinkedList; 044import java.util.List; 045import java.util.Properties; 046import java.util.StringTokenizer; 047 048import sun.misc.Unsafe; 049 050/////////////////////////////////////////////////////////////////// 051//// StringUtilities 052 053/** 054 A collection of utilities for manipulating strings. 055 These utilities do not depend on any other ptolemy packages. 056 057 @author Christopher Brooks, Contributors: Teale Fristoe 058 @version $Id$ 059 @since Ptolemy II 2.1 060 @Pt.ProposedRating Green (eal) 061 @Pt.AcceptedRating Green (cxh) 062 */ 063public class StringUtilities { 064 /** Instances of this class cannot be created. 065 */ 066 private StringUtilities() { 067 } 068 069 /////////////////////////////////////////////////////////////////// 070 //// public methods //// 071 072 /** Abbreviate a string. 073 * If the string is longer than 80 characters, truncate it by 074 * displaying the first 37 chars, then ". . .", then the last 38 075 * characters. 076 * If the <i>longName</i> argument is null, then the string 077 * ">Unnamed<" is returned. 078 * @param longName The string to be abbreviated. 079 * @return The possibly abbreviated name. 080 * @see #split(String) 081 */ 082 public static String abbreviate(String longName) { 083 // This method is used to abbreviate window titles so that long 084 // file names may appear in the window title bar. It is not 085 // parameterized so that we can force a unified look and feel. 086 // FIXME: it would be nice to split on a nearby space. 087 if (longName == null) { 088 return "<Unnamed>"; 089 } 090 091 if (longName.length() <= 80) { 092 return longName; 093 } 094 095 return longName.substring(0, 37) + ". . ." 096 + longName.substring(longName.length() - 38); 097 } 098 099 /** Add a directory to the java.library.path directory.. 100 * The java.library.path directory determines where the JVM 101 * looks for native shared libraries. It is typically read once 102 * when the JVM is started and no longer read after that. 103 * <p>This code may only work on certain JVMs</p> 104 * 105 * <p>Based on code from http://forums.sun.com/thread.jspa?threadID=707176 106 * and http://stackoverflow.com/questions/5419039/is-djava-library-path-equivalent-to-system-setpropertyjava-library-path</p> 107 * 108 * @param directoryName The directory to be added. 109 * @exception IOException If there are insufficient permissions to 110 * get set the java.library.path environment variable or there 111 * is no such field as usr_paths in the ClassLoader. 112 */ 113 public static void addDirectoryToJavaLibraryPath(String directoryName) 114 throws IOException { 115 try { 116 // Java 1.9 throws a warning unless we use this: 117 // https://stackoverflow.com/a/46458447 118 Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); 119 theUnsafe.setAccessible(true); 120 Unsafe unsafe = (Unsafe) theUnsafe.get(null); 121 122 Field usrPathsField = ClassLoader.class 123 .getDeclaredField("usr_paths"); 124 String[] libraryPathsArray = (String[]) unsafe.getObjectVolatile( 125 ClassLoader.class, unsafe.staticFieldOffset(usrPathsField)); 126 ArrayList<String> libraryPaths = new ArrayList<String>( 127 Arrays.asList(libraryPathsArray)); 128 if (libraryPaths.contains(directoryName)) { 129 return; 130 } 131 libraryPaths.add(directoryName); 132 unsafe.putObjectVolatile(ClassLoader.class, 133 unsafe.staticFieldOffset(usrPathsField), 134 libraryPaths.toArray(new String[libraryPaths.size()])); 135 System.setProperty("java.library.path", 136 System.getProperty("java.library.path") + File.pathSeparator 137 + directoryName); 138 } catch (IllegalAccessException ex) { 139 IOException ioException = new IOException( 140 "Failed to get permissions to set library path"); 141 ioException.initCause(ex); 142 throw ioException; 143 } catch (NoSuchFieldException ex2) { 144 IOException ioException = new IOException( 145 "Failed to get field handle to set library path"); 146 ioException.initCause(ex2); 147 throw ioException; 148 } 149 } 150 151 /** Add the $PTII/lib directory to the java.library.path directory. 152 * The java.library.path directory determines where the JVM 153 * looks for native shared libraries. It is typically read once 154 * when the JVM is started and no longer read after that. 155 * <p>This code may only work on certain JVMs</p> 156 * 157 * @exception IOException If there are insufficient permissions to 158 * get set the java.library.path environment variable or there 159 * is no such field as usr_paths in the ClassLoader. 160 */ 161 public static void addPtolemyLibraryDirectoryToJavaLibraryPath() 162 throws IOException { 163 String ptIIProperty = "ptolemy.ptII.dir"; 164 String ptII = StringUtilities.getProperty(ptIIProperty); 165 if (ptII.length() > 0) { 166 StringUtilities.addDirectoryToJavaLibraryPath( 167 ptII + File.separator + "lib"); 168 } else { 169 System.err.println( 170 "Warning: StringUtilities.addPtolemyLibraryDirectory() " 171 + "could not get the value of the " + ptIIProperty 172 + ". This means that loading shared libraries like the Serial I/O " 173 + "interface could fail. "); 174 } 175 } 176 177 /** Return a string with a maximum line length of <i>length</i> 178 * characters, limited to the given number of characters. 179 * If there are more than 10 newlines, then the string is truncated 180 * after 10 lines. 181 * If the string is truncated, an ellipsis (three periods in a 182 * row: "...") will be appended to the end of the string. 183 * Lines that are longer than 160 characters are split into lines 184 * that are shorter than 160 characters. 185 * @param string The string to truncate. 186 * @param length The number of characters to which to truncate the string. 187 * @return The possibly truncated string with ellipsis possibly added. 188 */ 189 public static String ellipsis(String string, int length) { 190 // If necessary, insert newlines into long strings. 191 // If we don't do split long lines and we throw an exception 192 // with a very long line, then the window close button and 193 // possible the dismiss button will be off the right side of 194 // the screen. 195 // The number 160 was generated by trying different sizes and 196 // seeing what fits on a 1024 wide screen. 197 string = StringUtilities.split(string, 160); 198 199 // Third argument being true means return the delimiters as tokens. 200 StringTokenizer tokenizer = new StringTokenizer(string, LINE_SEPARATOR, 201 true); 202 203 // If there are more than 42 lines and 42 newlines, return 204 // truncate after the first 42 lines and newlines. 205 // This is necessary so that we can deal with very long lines 206 // of text without spaces. 207 if (tokenizer.countTokens() > 42) { 208 StringBuffer results = new StringBuffer(); 209 210 for (int i = 0; i < 42 && tokenizer.hasMoreTokens(); i++) { 211 results.append(tokenizer.nextToken()); 212 } 213 214 results.append("..."); 215 string = results.toString(); 216 } 217 218 if (string.length() > length) { 219 return string.substring(0, length - 3) + "..."; 220 } 221 222 return string; 223 } 224 225 /** Given a string, replace all the instances of XML special characters 226 * with their corresponding XML entities. This is necessary to 227 * allow arbitrary strings to be encoded within XML. 228 * 229 * <p>In this method, we make the following translations: 230 * <pre> 231 * & becomes &amp; 232 * " becomes &quot; 233 * < becomes &lt; 234 * > becomes &gt; 235 * newline becomes &#10; 236 * carriage return becomes $amp;#13; 237 * </pre> 238 * @see #unescapeForXML(String) 239 * 240 * @param string The string to escape. 241 * @return A new string with special characters replaced. 242 */ 243 public static String escapeForXML(String string) { 244 return escapeForXML(string, true); 245 } 246 247 /** Given a string, replace all the instances of XML special characters 248 * with their corresponding XML entities. This is necessary to 249 * allow arbitrary strings to be encoded within XML. 250 * 251 * <p>In this method, we make the following translations: 252 * <pre> 253 * & becomes &amp; 254 * " becomes &quot; 255 * < becomes &lt; 256 * > becomes &gt; 257 * newline becomes &#10;, if requested. 258 * carriage return becomes $amp;#13;, if requested. 259 * </pre> 260 * @see #unescapeForXML(String) 261 * 262 * @param string The string to escape. 263 * @param string Whether or not to escape line break characters. 264 * @return A new string with special characters replaced. 265 */ 266 public static String escapeForXML(String string, boolean escapeLinebreaks) { 267 if (string != null) { 268 StringBuffer buffer = new StringBuffer(); 269 buffer.ensureCapacity(string.length()); 270 for (int i = 0, n = string.length(); i < n; ++i) { 271 char c = string.charAt(i); 272 switch (c) { 273 case '\n': 274 if (escapeLinebreaks) { 275 buffer.append(" "); 276 } else { 277 buffer.append(c); 278 } 279 break; 280 case '\r': 281 if (escapeLinebreaks) { 282 buffer.append(" "); 283 } else { 284 buffer.append(c); 285 } 286 break; 287 case '"': 288 buffer.append("""); 289 break; 290 case '&': 291 buffer.append("&"); 292 break; 293 case '<': 294 buffer.append("<"); 295 break; 296 case '>': 297 buffer.append(">"); 298 break; 299 default: 300 buffer.append(c); 301 break; 302 } 303 } 304 string = buffer.toString(); 305 } 306 return string; 307 } 308 309 /** Given a string, return a string that when fed to the 310 * Ptolemy expression parser, will turn into the argument 311 * string. That is, replace all the instances of backslashes 312 * with double backslashes, all quotation marks with \", 313 * etc. 314 * For example 315 * <pre> 316 * x"y becomes x\"y; 317 * x\"y becomes x\\\"y; 318 * x\y"z becomes x\\y\"z; 319 * x\\y\"z becomes x\\\\y\\\" 320 * </pre> 321 * Similarly, this method replaces the following characters 322 * exactly as defined in Java strings: \n, \t, \b, \r, and \f. 323 * @param string The string to escape. 324 * @return A new string with that can be put between quotation marks. 325 */ 326 public static String escapeString(String string) { 327 // Since the first string is a regular expression, it needs extra escaping. 328 // I have no idea why the extra escaping is needed on the second argument. 329 string = string.replaceAll("\\\\", "\\\\\\\\"); 330 string = string.replaceAll("\"", "\\\\\""); 331 string = string.replaceAll("\n", "\\\\n"); 332 string = string.replaceAll("\t", "\\\\t"); 333 string = string.replaceAll("\b", "\\\\b"); 334 string = string.replaceAll("\r", "\\\\r"); 335 // Not needed. 336 // string = string.replaceAll("\'", "\\\\'"); 337 return string; 338 } 339 340 /** If the ptolemy.ptII.exitAfterWrapup or the 341 * ptolemy.ptII.doNotExit properties are not set, then call 342 * System.exit(). 343 * Ptolemy code should call this method instead of directly calling 344 * System.exit() so that we can test code that would usually exit. 345 * @param returnValue The return value of this process, where 346 * non-zero values indicate an error. 347 */ 348 public static void exit(int returnValue) { 349 try { 350 if (StringUtilities.getProperty("ptolemy.ptII.doNotExit") 351 .length() > 0) { 352 return; 353 } 354 } catch (SecurityException ex) { 355 System.out.println("Warning: failed to get property \"" 356 + "ptolemy.ptII.doNotExit\". " 357 + "(-sandbox always causes this)"); 358 } 359 360 try { 361 if (StringUtilities.getProperty("ptolemy.ptII.exitAfterWrapup") 362 .length() > 0) { 363 throw new RuntimeException("StringUtilities.exit() was called. " 364 + "Normally, we would " 365 + "exit here because Manager.exitAfterWrapup() " 366 + "was called. However, because the " 367 + "ptolemy.ptII.exitAfterWrapup property " 368 + "is set, we throw this exception instead."); 369 } 370 } catch (SecurityException ex) { 371 System.out.println("Warning: failed to get property \"" 372 + "ptolemy.ptII.exitAfterWrapup\". " 373 + "(-sandbox always causes this)"); 374 375 } 376 377 if (!inApplet()) { 378 // Only call System.exit if we are not in an applet. 379 // Non-zero indicates a problem. 380 System.exit(returnValue); 381 } 382 } 383 384 /** Return a number of spaces that is proportional to the argument. 385 * If the argument is negative or zero, return an empty string. 386 * @param level The level of indenting represented by the spaces. 387 * @return A string with zero or more spaces. 388 */ 389 public static String getIndentPrefix(int level) { 390 if (level <= 0) { 391 return ""; 392 } 393 394 StringBuffer result = new StringBuffer(level * 4); 395 396 for (int i = 0; i < level; i++) { 397 result.append(" "); 398 } 399 400 return result.toString(); 401 } 402 403 /** Get the specified property from the environment. An empty 404 * string is returned if the property named by the "propertyName" 405 * argument environment variable does not exist, though if 406 * certain properties are not defined, then we make various 407 * attempts to determine them and then set them. See the javadoc 408 * page for java.util.System.getProperties() for a list of system 409 * properties. 410 411 * <p>The following properties are handled specially 412 * <dl> 413 * <dt> "ptolemy.ptII.dir" 414 * <dd> vergil usually sets the ptolemy.ptII.dir property to the 415 * value of $PTII. However, if we are running under Web Start, 416 * then this property might not be set, in which case we look 417 * for "ptolemy/util/StringUtilities.class" and set the 418 * property accordingly. 419 * <dt> "ptolemy.ptII.dirAsURL" 420 * <dd> Return $PTII as a URL. For example, if $PTII was c:\ptII, 421 * then return file:/c:/ptII/. 422 * <dt> "user.dir" 423 * <dd> Return the canonical path name to the current working 424 * directory. This is necessary because under Windows with 425 * JDK1.4.1, the System.getProperty() call returns 426 * <code><b>c</b>:/<i>foo</i></code> whereas most of the other 427 * methods that operate on path names return 428 * <code><b>C</b>:/<i>foo</i></code>. 429 * </dl> 430 * @param propertyName The name of property. 431 * @return A String containing the string value of the property. 432 * If the property is not found, then we return the empty string. 433 */ 434 public static String getProperty(String propertyName) { 435 // NOTE: getProperty() will probably fail in applets, which 436 // is why this is in a try block. 437 String property = null; 438 439 try { 440 property = System.getProperty(propertyName); 441 // if (propertyName.equals("ptolemy.ptII.dir")) { 442 // System.out.println("StringUtilities.getProperty(" + propertyName + "): " + property); 443 // } 444 } catch (SecurityException ex) { 445 if (!propertyName.equals("ptolemy.ptII.dir")) { 446 // Constants.java depends on this when running with 447 // -sandbox. 448 SecurityException security = new SecurityException( 449 "Could not find '" + propertyName 450 + "' System property"); 451 security.initCause(ex); 452 throw security; 453 } 454 } 455 456 if (propertyName.equals("user.dir")) { 457 try { 458 if (property == null) { 459 return property; 460 } 461 File userDirFile = new File(property); 462 return userDirFile.getCanonicalPath(); 463 } catch (IOException ex) { 464 return property; 465 } 466 } 467 468 // Check for cases where the ptII property starts with 469 // the string "/cygdrive". This can happen if the property 470 // was set by doing "PTII=`pwd`" under Cygwin bash. 471 // 472 // If the property starts with $JAVAROOT, and the 473 // propertyName is ptolemy.ptII.dir, then don't return 474 // the property yet, instead, refine it. 475 if (property != null && (!propertyName.equals("ptolemy.ptII.dir") 476 && !property.startsWith("$JAVAROOT"))) { 477 if (propertyName.equals("ptolemy.ptII.dir") 478 && property.startsWith("/cygdrive") 479 && !_printedCygwinWarning) { 480 // This error only occurs when users build their own, 481 // so it is safe to print to stderr 482 _printedCygwinWarning = true; 483 System.err.println("ptolemy.ptII.dir property = \"" + property 484 + "\", which contains \"cygdrive\". " 485 + "This is almost always an error under Cygwin that " 486 + "is occurs when one does PTII=`pwd`. Instead, do " 487 + "PTII=c:/foo/ptII"); 488 } 489 490 return property; 491 } else { 492 493 if (propertyName.equals("ptolemy.ptII.dirAsURL")) { 494 // Return $PTII as a URL. For example, if $PTII was c:\ptII, 495 // then return file:/c:/ptII/ 496 File ptIIAsFile = new File(getProperty("ptolemy.ptII.dir")); 497 498 try { 499 // Convert first to a URI, then to a URL so that we 500 // properly handle cases where $PTII has spaces in it. 501 URI ptIIAsURI = ptIIAsFile.toURI(); 502 URL ptIIAsURL = ptIIAsURI.toURL(); 503 return ptIIAsURL.toString(); 504 } catch (java.net.MalformedURLException malformed) { 505 throw new RuntimeException("While trying to find '" 506 + propertyName + "', could not convert '" 507 + ptIIAsFile + "' to a URL", malformed); 508 } 509 } 510 511 if (propertyName.equals("ptolemy.ptII.dir")) { 512 if (_ptolemyPtIIDir != null) { 513 // Return the previously calculated value 514 // System.out.println("StringUtilities.getProperty(" + propertyName + "): returning previous " + _ptolemyPtIIDir); 515 return _ptolemyPtIIDir; 516 } else { 517 String stringUtilitiesPath = "ptolemy/util/StringUtilities.class"; 518 519 // PTII variable was not set 520 URL namedObjURL = ClassUtilities 521 .getResource(stringUtilitiesPath); 522 523 if (namedObjURL != null) { 524 // Get the file portion of URL 525 String namedObjFileName = namedObjURL.getFile(); 526 527 // System.out.println("StringUtilities.getProperty(" + propertyName + "): namedObjURL: " + namedObjURL); 528 // FIXME: How do we get from a URL to a pathname? 529 if (namedObjFileName.startsWith("file:")) { 530 if (namedObjFileName.startsWith("file://") 531 || namedObjFileName 532 .startsWith("file:\\\\")) { 533 // We get rid of either file:/ or file:\ 534 namedObjFileName = namedObjFileName 535 .substring(6); 536 } else { 537 // Get rid of file: 538 namedObjFileName = namedObjFileName 539 .substring(5); 540 } 541 } 542 543 String abnormalHome = namedObjFileName.substring(0, 544 namedObjFileName.length() 545 - stringUtilitiesPath.length()); 546 547 // abnormalHome will have values like: "/C:/ptII/" 548 // which cause no end of trouble, so we construct a File 549 // and call toString(). 550 _ptolemyPtIIDir = new File(abnormalHome).toString(); 551 552 // If we are running under Web Start, then strip off 553 // the trailing "!" 554 if (_ptolemyPtIIDir.endsWith("/!") 555 || _ptolemyPtIIDir.endsWith("\\!")) { 556 _ptolemyPtIIDir = _ptolemyPtIIDir.substring(0, 557 _ptolemyPtIIDir.length() - 1); 558 } 559 560 // Web Start, we might have 561 // RMptsupport.jar or 562 // XMptsupport.jar1088483703686 563 String ptsupportJarName = File.separator + "DMptolemy" 564 + File.separator + "RMptsupport.jar"; 565 566 if (_ptolemyPtIIDir.endsWith(ptsupportJarName)) { 567 _ptolemyPtIIDir = _ptolemyPtIIDir.substring(0, 568 _ptolemyPtIIDir.length() 569 - ptsupportJarName.length()); 570 } else { 571 ptsupportJarName = "/DMptolemy/XMptsupport.jar"; 572 573 if (_ptolemyPtIIDir 574 .lastIndexOf(ptsupportJarName) != -1) { 575 _ptolemyPtIIDir = _ptolemyPtIIDir.substring(0, 576 _ptolemyPtIIDir 577 .lastIndexOf(ptsupportJarName)); 578 } else { 579 // Ptolemy II 6.0.1 under Windows: remove 580 // "\ptolemy\ptsupport.jar!" 581 // If we don't do this, then ptolemy.ptII.dir 582 // is set incorrectly and then links to the javadoc 583 // files will not be found if the javadoc only 584 // exists in codeDoc.jar and lib/ptII.properties 585 // is not present. 586 ptsupportJarName = File.separator + "ptolemy" 587 + File.separator + "ptsupport.jar"; 588 589 if (_ptolemyPtIIDir 590 .lastIndexOf(ptsupportJarName) != -1) { 591 _ptolemyPtIIDir = _ptolemyPtIIDir.substring( 592 0, _ptolemyPtIIDir.lastIndexOf( 593 ptsupportJarName)); 594 } 595 } 596 } 597 } 598 599 // Convert %20 to spaces because if a URL has %20 in it, 600 // then we know we have a space, but file names do not 601 // recognize %20 as being a single space, instead file names 602 // see %20 as three characters: '%', '2', '0'. 603 if (_ptolemyPtIIDir != null) { 604 _ptolemyPtIIDir = StringUtilities 605 .substitute(_ptolemyPtIIDir, "%20", " "); 606 } 607 //*.class files are compiled into classes.dex file; therefore, check for StringUtilities.class fails 608 //it's OK to set _ptolemyPtIIDir to an empty string on Android 609 if (_ptolemyPtIIDir == null && System 610 .getProperty("java.vm.name").equals("Dalvik")) { 611 _ptolemyPtIIDir = ""; 612 } 613 if (_ptolemyPtIIDir == null) { 614 throw new RuntimeException("Could not find " 615 + "'ptolemy.ptII.dir'" + " property. " 616 + "Also tried loading '" + stringUtilitiesPath 617 + "' as a resource and working from that. " 618 + "Vergil should be " 619 + "invoked with -Dptolemy.ptII.dir" 620 + "=\"$PTII\", " 621 + "otherwise the following features will not work: " 622 + "PtinyOS, Ptalon, the Python actor, " 623 + "actor document, cg code generation and possibly " 624 + "other features will not work."); 625 } 626 627 try { 628 // Here, we set the property so that future updates 629 // will get the correct value. 630 System.setProperty("ptolemy.ptII.dir", _ptolemyPtIIDir); 631 } catch (SecurityException security) { 632 // Ignore, we are probably running as an applet or -sandbox 633 } 634 635 // System.out.println("StringUtilities.getProperty(" + propertyName + "): returning " + _ptolemyPtIIDir); 636 return _ptolemyPtIIDir; 637 } 638 } 639 640 // If the property is not set then we return the empty string. 641 //if (property == null) { 642 return ""; 643 //} 644 } 645 } 646 647 /** Return true if we are in an applet. 648 * @return True if we are running in an applet. 649 */ 650 public static boolean inApplet() { 651 boolean inApplet = false; 652 try { 653 StringUtilities.getProperty("HOME"); 654 } catch (SecurityException ex) { 655 inApplet = true; 656 } 657 return inApplet; 658 } 659 660 /** Test whether a string is a valid Java identifier. 661 * Section 3.8 of the Java language spec says: 662 * <blockquote> 663 * "An identifier is an unlimited-length sequence of Java letters 664 * and Java digits, the first of which must be a Java letter. An 665 * identifier cannot have the same spelling (Unicode character 666 * sequence) as a keyword (3.9), boolean literal (3.10.3), or 667 * the null literal (3.10.7)." 668 * </blockquote> 669 * Java characters are A-Z, a-z, $ and _. 670 * <p> Characters that are not permitted in a Java identifier are changed 671 * to underscores. 672 * This method does not check whether the string is a keyword or literal. 673 * @param name The name to be checked. 674 * @return True if the given name is a valid Java identifier, or false otherwise. 675 */ 676 public static boolean isValidIdentifier(String name) { 677 char[] nameArray = name.toCharArray(); 678 if (nameArray.length == 0) { 679 return false; 680 } 681 if (!Character.isJavaIdentifierStart(nameArray[0])) { 682 return false; 683 } 684 for (int i = 1; i < nameArray.length; i++) { 685 if (!Character.isJavaIdentifierPart(nameArray[i])) { 686 return false; 687 } 688 } 689 return true; 690 } 691 692 /** Merge the properties in lib/ptII.properties with the current 693 * properties. lib/ptII.properties is searched for in the 694 * classpath. The value of properties listed in 695 * lib/ptII.properties do not override properties with the same 696 * name in the current properties. 697 * @exception IOException If thrown while looking for the 698 * $CLASSPATH/lib/ptII.properties file. 699 */ 700 public static void mergePropertiesFile() throws IOException { 701 Properties systemProperties = System.getProperties(); 702 // Fix for 703 // "ptolemy.util.StringUtilities.mergePropertiesFile() deletes 704 // properties" http://bugzilla.ecoinformatics.org/show_bug.cgi?id=3874 705 // It turns out that the problem seems to be that 706 // newProperties.putAll(systemProperties) does not work in Kepler. 707 Properties newProperties = new Properties(systemProperties); 708 String propertyFileName = "$CLASSPATH/lib/ptII.properties"; 709 710 URL propertyFileURL = FileUtilities 711 .nameToURL("$CLASSPATH/lib/ptII.properties", null, null); 712 713 if (propertyFileURL == null) { 714 throw new IOException("Could not find " + propertyFileName); 715 } 716 717 newProperties.load(propertyFileURL.openStream()); 718 719 System.setProperties(newProperties); 720 } 721 722 /** Return a string representing the name of the file expected to 723 * contain the source code for the specified object. This method 724 * simply replaces "." with "/" and appends ".java" to the class 725 * name. 726 * @param object The object. 727 * @return The expected source file name. 728 */ 729 public static String objectToSourceFileName(Object object) { 730 String sourceFileNameBase = object.getClass().getName().replace('.', 731 '/'); 732 733 // Inner classes: Get rid of everything past the first $ 734 if (sourceFileNameBase.indexOf("$") != -1) { 735 sourceFileNameBase = sourceFileNameBase.substring(0, 736 sourceFileNameBase.indexOf("$")); 737 } 738 739 return sourceFileNameBase + ".java"; 740 } 741 742 /** Return the preferences directory, creating it if necessary. 743 * If the PTOLEMYII_DOT environment variable is set, then 744 * that value is used, otherwise the Java user.home property 745 * is used 746 * 747 * The PTOLEMYII_DOT environment variable is used to support 748 * invoking multiple Kepler processes. 749 * 750 * @return A string naming the preferences directory. The last 751 * character of the string will have the file.separator character 752 * appended. 753 * @exception IOException If the directory could not be created. 754 * @see #PREFERENCES_DIRECTORY 755 */ 756 public static String preferencesDirectory() throws IOException { 757 String baseDirectory = System.getProperty("user.home"); 758 try { 759 if (System.getenv("PTOLEMYII_DOT") != null) { 760 baseDirectory = System.getenv("PTOLEMYII_DOT"); 761 if (baseDirectory.endsWith(File.separator)) { 762 baseDirectory = baseDirectory.substring(0, 763 baseDirectory.length() - 1); 764 } 765 } 766 } catch (SecurityException ex) { 767 // Ignore, we are probably in an applet. 768 } 769 String preferencesDirectoryName = baseDirectory + File.separator 770 + StringUtilities.PREFERENCES_DIRECTORY + File.separator; 771 772 File preferencesDirectory = new File(preferencesDirectoryName); 773 774 if (!preferencesDirectory.isDirectory()) { 775 if (preferencesDirectory.mkdirs() == false) { 776 throw new IOException("Could not create user preferences " 777 + "directory '" + preferencesDirectoryName + "'"); 778 } 779 } 780 781 return preferencesDirectoryName; 782 } 783 784 /** Return the name of the properties file. 785 * The properties file is a file of a format suitable for 786 * java.util.properties.load(InputStream). 787 * The file is named "ptII.properties" and is found in the 788 * {@link #PREFERENCES_DIRECTORY} directory that is returned 789 * by {@link #preferencesDirectory()}. Typically, this value 790 * is "$HOME/.ptolemyII/ptII.properties". 791 * @see #preferencesDirectory() 792 * @see #PREFERENCES_DIRECTORY 793 * @return The name of the properties file. 794 * @exception IOException If {@link #preferencesDirectory()} throws it. 795 */ 796 public static String propertiesFileName() throws IOException { 797 return preferencesDirectory() + "ptII.properties"; 798 } 799 800 /** Return a LinkedList of the lines in a string that aren't comments. 801 * @param lines A String containing the lines to be separated. 802 * @return A LinkedList of the lines that aren't comments. 803 * @exception IOException If thrown when reading from the input String. 804 */ 805 public static LinkedList<String> readLines(String lines) 806 throws IOException { 807 BufferedReader bufferedReader = null; 808 LinkedList<String> returnList = new LinkedList<String>(); 809 String line; 810 bufferedReader = new BufferedReader(new StringReader(lines)); 811 try { 812 // Read line by line, skipping comments. 813 while ((line = bufferedReader.readLine()) != null) { 814 line = line.trim(); 815 if (!(line.length() == 0 || line.startsWith("/*") 816 || line.startsWith("//"))) { 817 returnList.add(line); 818 } 819 } 820 } finally { 821 if (bufferedReader != null) { 822 try { 823 bufferedReader.close(); 824 } catch (IOException ex) { 825 // Ignore 826 ex.printStackTrace(); 827 } 828 } 829 } 830 return returnList; 831 } 832 833 /** Sanitize a String so that it can be used as a Java identifier. 834 * Section 3.8 of the Java language spec says: 835 * <blockquote> 836 * "An identifier is an unlimited-length sequence of Java letters 837 * and Java digits, the first of which must be a Java letter. An 838 * identifier cannot have the same spelling (Unicode character 839 * sequence) as a keyword (3.9), boolean literal (3.10.3), or 840 * the null literal (3.10.7)." 841 * </blockquote> 842 * Java characters are A-Z, a-z, $ and _. 843 * <p> Characters that are not permitted in a Java identifier are changed 844 * to underscores. 845 * This method does not check that the returned string is a 846 * keyword or literal. 847 * Note that two different strings can sanitize to the same 848 * string. 849 * This method is commonly used during code generation to map the 850 * name of a ptolemy object to a valid identifier name. 851 * @param name A string with spaces and other characters that 852 * cannot be in a Java name. 853 * @return A String that follows the Java identifier rules. 854 */ 855 public static String sanitizeName(String name) { 856 char[] nameArray = name.toCharArray(); 857 858 for (int i = 0; i < nameArray.length; i++) { 859 if (!Character.isJavaIdentifierPart(nameArray[i])) { 860 nameArray[i] = '_'; 861 } 862 } 863 864 if (nameArray.length == 0) { 865 return ""; 866 } else { 867 if (!Character.isJavaIdentifierStart(nameArray[0])) { 868 return "_" + new String(nameArray); 869 } else { 870 return new String(nameArray); 871 } 872 } 873 } 874 875 /** If the string is longer than 79 characters, split it up by 876 * adding newlines in all newline delimited substrings 877 * that are longer than 79 characters. 878 * If the <i>longName</i> argument is null, then the string 879 * ">Unnamed<" is returned. 880 * @see #abbreviate(String) 881 * @see #split(String, int) 882 * @param longName The string to optionally split up 883 * @return Either the original string, or the string with newlines 884 * inserted. 885 */ 886 public static String split(String longName) { 887 return split(longName, 79); 888 } 889 890 /** If the string is longer than <i>length</i> characters, 891 * split the string up by adding newlines in all 892 * newline delimited substrings that are longer than <i>length</i> 893 * characters. 894 * If the <i>longName</i> argument is null, then the string 895 * ">Unnamed<" is returned. 896 * @see #abbreviate(String) 897 * @see #split(String) 898 * @param longName The string to optionally split. 899 * @param length The maximum length of the sequence of characters 900 * before a newline is inserted. 901 * @return Either the original string, or the string with newlines 902 * inserted. 903 */ 904 public static String split(String longName, int length) { 905 if (longName == null) { 906 return "<Unnamed>"; 907 } 908 909 if (longName.length() <= length) { 910 return longName; 911 } 912 913 StringBuffer results = new StringBuffer(); 914 915 // The third argument is true, which means return the delimiters 916 // as part of the tokens. 917 StringTokenizer tokenizer = new StringTokenizer(longName, 918 LINE_SEPARATOR, true); 919 920 while (tokenizer.hasMoreTokens()) { 921 String token = tokenizer.nextToken(); 922 int mark = 0; 923 924 while (mark < token.length() - length) { 925 // We look for the space from the end of the first length 926 // characters. If we find one, then we use that 927 // as the place to insert a newline. 928 int lastSpaceIndex = token.substring(mark, mark + length) 929 .lastIndexOf(" "); 930 931 if (lastSpaceIndex < 0) { 932 // No space found, just insert a new line after length 933 results.append(token.substring(mark, mark + length) 934 + LINE_SEPARATOR); 935 mark += length; 936 } else { 937 results.append(token.substring(mark, mark + lastSpaceIndex) 938 + LINE_SEPARATOR); 939 mark += lastSpaceIndex + 1; 940 } 941 } 942 943 results.append(token.substring(mark)); 944 } 945 946 return results.toString(); 947 } 948 949 /** Given a file or URL name, return as a URL. If the file name 950 * is relative, then it is interpreted as being relative to the 951 * specified base directory. If the name begins with 952 * "xxxxxxCLASSPATHxxxxxx" or "$CLASSPATH" 953 * then search for the file relative to the classpath. 954 * Note that this is the value of the globally defined constant 955 * $CLASSPATH available in the expression language. 956 * If no file is found, then throw an exception. 957 * @param name The name of a file or URL. 958 * @param baseDirectory The base directory for relative file names, 959 * or null to specify none. 960 * @param classLoader The class loader to use to locate system 961 * resources, or null to use the system class loader. 962 * @return A URL, or null if no file name or URL has been specified. 963 * @exception IOException If the file cannot be read, or 964 * if the file cannot be represented as a URL (e.g. System.in), or 965 * the name specification cannot be parsed. 966 * @exception MalformedURLException If the URL is malformed. 967 * @deprecated Use FileUtilities.nameToURL instead. 968 */ 969 @Deprecated 970 public static URL stringToURL(String name, URI baseDirectory, 971 ClassLoader classLoader) throws IOException { 972 return FileUtilities.nameToURL(name, baseDirectory, classLoader); 973 } 974 975 /** Replace all occurrences of <i>pattern</i> in the specified 976 * string with <i>replacement</i>. Note that the pattern is NOT 977 * a regular expression, and that relative to the 978 * String.replaceAll() method in jdk1.4, this method is extremely 979 * slow. This method does not work well with back slashes. 980 * @param string The string to edit. 981 * @param pattern The string to replace. 982 * @param replacement The string to replace it with. 983 * @return A new string with the specified replacements. 984 */ 985 public static String substitute(String string, String pattern, 986 String replacement) { 987 if (string == null) { 988 return null; 989 } 990 int start = string.indexOf(pattern); 991 992 while (start != -1) { 993 StringBuffer buffer = new StringBuffer(string); 994 buffer.delete(start, start + pattern.length()); 995 buffer.insert(start, replacement); 996 string = new String(buffer); 997 start = string.indexOf(pattern, start + replacement.length()); 998 } 999 1000 return string; 1001 } 1002 1003 /** Perform file prefix substitution. 1004 * If <i>string</i> starts with <i>prefix</i>, then we return a 1005 * new string that consists of the value or <i>replacement</i> 1006 * followed by the value of <i>string</i> with the value of 1007 * <i>prefix</i> removed. For example, 1008 * substituteFilePrefix("c:/ptII", "c:/ptII/ptolemy, "$PTII") 1009 * will return "$PTII/ptolemy" 1010 * 1011 * <p>If <i>prefix</i> is not a simple prefix of <i>string</i>, then 1012 * we use the file system to find the canonical names of the files. 1013 * For this to work, <i>prefix</i> and <i>string</i> should name 1014 * files that exist, see java.io.File.getCanonicalFile() for details. 1015 * 1016 * <p>If <i>prefix</i> is not a prefix of <i>string</i>, then 1017 * we return <i>string</i> 1018 * 1019 * @param prefix The prefix string, for example, "c:/ptII". 1020 * @param string The string to be substituted, for example, 1021 * "c:/ptII/ptolemy". 1022 * @param replacement The replacement to be substituted in, for example, 1023 * "$PTII" 1024 * @return The possibly substituted string. 1025 */ 1026 public static String substituteFilePrefix(String prefix, String string, 1027 String replacement) { 1028 // This method is currently used by $PTII/util/testsuite/auto.tcl 1029 if (string.startsWith(prefix)) { 1030 // Hmm, what about file separators? 1031 return replacement + string.substring(prefix.length()); 1032 } else { 1033 try { 1034 String prefixCanonicalPath = new File(prefix) 1035 .getCanonicalPath(); 1036 1037 String stringCanonicalPath = new File(string) 1038 .getCanonicalPath(); 1039 1040 if (stringCanonicalPath.startsWith(prefixCanonicalPath)) { 1041 return replacement + stringCanonicalPath 1042 .substring(prefixCanonicalPath.length()); 1043 } 1044 } catch (Throwable throwable) { 1045 // ignore. 1046 } 1047 } 1048 1049 return string; 1050 } 1051 1052 /** Tokenize a String to an array of Strings for use with 1053 * Runtime.exec(String []). 1054 * 1055 * <p>Lines that begin with an octothorpe '#' are ignored. 1056 * Substrings that start and end with a double quote are considered 1057 * to be a single token and are returned as a single array element. 1058 * 1059 * @param inputString The String to tokenize 1060 * @return An array of substrings. 1061 * @exception IOException If StreamTokenizer.nextToken() throws it. 1062 */ 1063 public static String[] tokenizeForExec(String inputString) 1064 throws IOException { 1065 // The java.lang.Runtime.exec(String command) call uses 1066 // java.util.StringTokenizer() to parse the command string. 1067 // Unfortunately, this means that double quotes are not handled 1068 // in the same way that the shell handles them in that 'ls "foo 1069 // 'bar"' will interpreted as three tokens 'ls', '"foo' and 1070 // 'bar"'. In the shell, the string would be two tokens 'ls' and 1071 // '"foo bar"'. What is worse is that the exec() behaviour is 1072 // slightly different under Windows and Unix. To solve this 1073 // problem, we preprocess the command argument using 1074 // java.io.StreamTokenizer, which converts quoted substrings into 1075 // single tokens. We then call java.lang.Runtime.exec(String [] 1076 // commands); 1077 // Parse the command into tokens 1078 List<String> commandList = new LinkedList<String>(); 1079 1080 StreamTokenizer streamTokenizer = new StreamTokenizer( 1081 new StringReader(inputString)); 1082 1083 // We reset the syntax so that we don't convert to numbers, 1084 // otherwise, if PTII is "d:\\tmp\\ptII\ 2.0", then 1085 // we have no end of problems. 1086 streamTokenizer.resetSyntax(); 1087 streamTokenizer.whitespaceChars(0, 32); 1088 streamTokenizer.wordChars(33, 127); 1089 1090 // We can't use quoteChar here because it does backslash 1091 // substitution, so "c:\ptII" ends up as "c:ptII" 1092 // Substituting forward slashes for backward slashes seems like 1093 // overkill. 1094 // streamTokenizer.quoteChar('"'); 1095 streamTokenizer.ordinaryChar('"'); 1096 1097 streamTokenizer.eolIsSignificant(true); 1098 1099 streamTokenizer.commentChar('#'); 1100 1101 // Current token 1102 String token = ""; 1103 1104 // Single character token, usually a - 1105 String singleToken = ""; 1106 1107 // Set to true if we are inside a double quoted String. 1108 boolean inDoubleQuotedString = false; 1109 1110 while (streamTokenizer.nextToken() != StreamTokenizer.TT_EOF) { 1111 switch (streamTokenizer.ttype) { 1112 case StreamTokenizer.TT_WORD: 1113 1114 if (inDoubleQuotedString) { 1115 if (token.length() > 0) { 1116 // FIXME: multiple spaces will get compacted here 1117 token += " "; 1118 } 1119 1120 token += singleToken + streamTokenizer.sval; 1121 } else { 1122 token = singleToken + streamTokenizer.sval; 1123 commandList.add(token); 1124 } 1125 1126 singleToken = ""; 1127 break; 1128 1129 case StreamTokenizer.TT_NUMBER: 1130 throw new RuntimeException("Internal error: Found TT_NUMBER: '" 1131 + streamTokenizer.nval + "'. We should not be " 1132 + "tokenizing numbers"); 1133 1134 //break; 1135 case StreamTokenizer.TT_EOL: 1136 case StreamTokenizer.TT_EOF: 1137 break; 1138 1139 default: 1140 singleToken = Character.toString((char) streamTokenizer.ttype); 1141 1142 if (singleToken.equals("\"")) { 1143 if (inDoubleQuotedString) { 1144 commandList.add(token); 1145 } 1146 1147 inDoubleQuotedString = !inDoubleQuotedString; 1148 singleToken = ""; 1149 token = ""; 1150 } 1151 1152 break; 1153 } 1154 } 1155 1156 return commandList.toArray(new String[commandList.size()]); 1157 } 1158 1159 /** Return a string with a maximum line length of <i>lineLength</i> 1160 * and a maximum number of lines <i>numberOfLines</i>. 1161 * Each line that exceeds the line length is replaced with a line that 1162 * ends with "...". If the number of lines exceeds <i>numberOfLines</i>, 1163 * then the returned string will have exactly <i>numberOfLines</i> lines 1164 * where the last line is "...". 1165 * @param string The string to truncate. 1166 * @param lineLength The number of characters to which to truncate each line. 1167 * @param numberOfLines The maximum number of lines. 1168 * @return The possibly truncated string with ellipsis possibly added. 1169 */ 1170 public static String truncateString(String string, int lineLength, 1171 int numberOfLines) { 1172 1173 // Third argument being true means the delimiters (LINE_SEPARATOR) are 1174 // included in as tokens in the parsed results. 1175 StringTokenizer tokenizer = new StringTokenizer(string, LINE_SEPARATOR, 1176 true); 1177 1178 StringBuffer results = new StringBuffer(); 1179 // Count the lines + newlines. 1180 int lineCount = 0; 1181 while (tokenizer.hasMoreTokens()) { 1182 if (lineCount >= numberOfLines * 2) { 1183 // Presumably, the last line is a line separator. 1184 // We append an additional line to indicate that there 1185 // are more lines. 1186 results.append("..."); 1187 break; 1188 } 1189 lineCount++; 1190 String line = tokenizer.nextToken(); 1191 if (line.length() > lineLength) { 1192 line = line.substring(0, lineLength - 3) + "..."; 1193 } 1194 results.append(line); 1195 } 1196 return results.toString(); 1197 } 1198 1199 /** Given a string, replace all the instances of XML entities 1200 * with their corresponding XML special characters. This is necessary to 1201 * allow arbitrary strings to be encoded within XML. 1202 * 1203 * <p>In this method, we make the following translations: 1204 * <pre> 1205 * &amp; becomes & 1206 * &quot; becomes " 1207 * &lt; becomes < 1208 * &gt; becomes > 1209 * &#10; becomes newline 1210 * &#13; becomes carriage return 1211 * </pre> 1212 * @see #escapeForXML(String) 1213 * 1214 * @param string The string to escape. 1215 * @return A new string with special characters replaced. 1216 */ 1217 public static String unescapeForXML(String string) { 1218 if (string.indexOf("&") != -1) { 1219 string = substitute(string, "&", "&"); 1220 string = substitute(string, """, "\""); 1221 string = substitute(string, "<", "<"); 1222 string = substitute(string, ">", ">"); 1223 string = substitute(string, " ", "\n"); 1224 string = substitute(string, " ", "\r"); 1225 } 1226 return string; 1227 } 1228 1229 /** Return a string that contains a description of how to use a 1230 * class that calls this method. For example, this method is 1231 * called by "$PTII/bin/vergil -help". 1232 * @param commandTemplate A string naming the command and the 1233 * format of the arguments, for example 1234 * "moml [options] [file . . .]" 1235 * @param commandOptions A 2xN array of Strings that list command-line 1236 * options that take arguments where the first 1237 * element is a String naming the command line option, and the 1238 * second element is the argument, for example 1239 * <code>{"-class", "<classname>")</code> 1240 * @param commandFlags An array of Strings that list command-line 1241 * options that are either present or not. 1242 * @return A string that describes the command. 1243 */ 1244 public static String usageString(String commandTemplate, 1245 String[][] commandOptions, String[] commandFlags) { 1246 String[][] commandFlagsWithDescriptions = new String[commandFlags.length][2]; 1247 for (int i = 0; i < commandFlags.length; i++) { 1248 commandFlagsWithDescriptions[i][0] = commandFlags[i]; 1249 commandFlagsWithDescriptions[i][1] = ""; 1250 } 1251 return usageString(commandTemplate, commandOptions, 1252 commandFlagsWithDescriptions); 1253 } 1254 1255 /** Return a string that contains a description of how to use a 1256 * class that calls this method. For example, this method is 1257 * called by "$PTII/bin/vergil -help". 1258 * @param commandTemplate A string naming the command and the 1259 * format of the arguments, for example 1260 * "moml [options] [file . . .]" 1261 * @param commandOptions A 2xN array of Strings that list command-line 1262 * options that take arguments where the first 1263 * element is a String naming the command line option, and the 1264 * second element is the argument, for example 1265 * <code>{"-class", "<classname>")</code> 1266 * @param commandFlagsWithDescriptions A 2xM array of Strings that list 1267 * command-line options that are either present or not and a description 1268 * of what the command line option does. 1269 * @return A string that describes the command. 1270 */ 1271 public static String usageString(String commandTemplate, 1272 String[][] commandOptions, 1273 String[][] commandFlagsWithDescriptions) { 1274 // This method is static so that we can reuse it in places 1275 // like copernicus/kernel/Copernicus and actor/gui/MoMLApplication 1276 StringBuffer result = new StringBuffer("Usage: " + commandTemplate 1277 + "\n\n" + "Options that take values:\n"); 1278 1279 int i; 1280 1281 for (i = 0; i < commandOptions.length; i++) { 1282 result.append(" " + commandOptions[i][0]); 1283 if (commandOptions[i][1].length() > 0) { 1284 result.append(" " + commandOptions[i][1]); 1285 } 1286 result.append("\n"); 1287 } 1288 1289 result.append("\nBoolean flags:\n"); 1290 1291 for (i = 0; i < commandFlagsWithDescriptions.length; i++) { 1292 result.append(" " + commandFlagsWithDescriptions[i][0]); 1293 if (commandFlagsWithDescriptions[i][1].length() > 0) { 1294 result.append("\t" + commandFlagsWithDescriptions[i][1]); 1295 } 1296 result.append("\n"); 1297 } 1298 1299 return result.toString(); 1300 } 1301 1302 /////////////////////////////////////////////////////////////////// 1303 //// public variables //// 1304 // If you change these, be sure to try running vergil on 1305 // a HSIF moml file 1306 // vergil ../hsif/demo/SwimmingPool/SwimmingPool.xml 1307 1308 /** Maximum length in characters of a long string before 1309 * {@link #ellipsis(String, int)} truncates and add a 1310 * trailing ". . .". This variable is used by callers 1311 * of ellipsis(String, int). 1312 */ 1313 public static final int ELLIPSIS_LENGTH_LONG = 2000; 1314 1315 /** Maximum length in characters of a short string before 1316 * {@link #ellipsis(String, int)} truncates and add a 1317 * trailing ". . .". This variable is used by callers 1318 * of ellipsis(String, int). 1319 */ 1320 public static final int ELLIPSIS_LENGTH_SHORT = 400; 1321 1322 /** The line separator string. Under Windows, this would 1323 * be "\r\n"; under Unix, "\n"; Under Macintosh, "\r". 1324 */ 1325 public static final String LINE_SEPARATOR = System 1326 .getProperty("line.separator"); 1327 1328 /** Location of Application preferences such as the user library. 1329 * This field is not final in case other applications want to 1330 * set it to a different directory. 1331 * @see #preferencesDirectory() 1332 */ 1333 public static final String PREFERENCES_DIRECTORY = ".ptolemyII"; 1334 1335 /////////////////////////////////////////////////////////////////// 1336 //// private variables //// 1337 1338 /** Set to true if we print the cygwin warning in getProperty(). */ 1339 private static boolean _printedCygwinWarning = false; 1340 1341 /** Cached value of ptolemy.ptII.dir property. */ 1342 private static String _ptolemyPtIIDir = null; 1343}