/* -*- tab-width: 4 -*- * * Electric(tm) VLSI Design System * * File: TextUtils.java * * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. * * Electric(tm) is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Electric(tm) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Electric(tm); see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, Mass 02111-1307, USA. */ package com.sun.electric.util; import com.sun.electric.database.hierarchy.Cell; import com.sun.electric.database.hierarchy.View; import com.sun.electric.database.network.Network; import com.sun.electric.database.text.Pref; import com.sun.electric.database.topology.Connection; import com.sun.electric.database.variable.TextDescriptor; import com.sun.electric.technology.Technology; import com.sun.electric.tool.Client; import com.sun.electric.tool.Job; import com.sun.electric.tool.lang.EvalJavaBsh; import com.sun.electric.tool.user.User; import java.awt.Color; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.StringTokenizer; import java.util.TimeZone; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * This class is a collection of text utilities. */ public class TextUtils { /** * Determines if the specified character is a ISO-LATIN-1 digit * (<code>'0'</code> through <code>'9'</code>). * <p> * This can be method instead of Character, if we are not ready * to handle Arabi-Indic, Devanagaru and other digits. * * @param ch the character to be tested. * @return <code>true</code> if the character is a ISO-LATIN-1 digit; * <code>false</code> otherwise. * @see java.lang.Character#isDigit(char) */ public static boolean isDigit(char ch) { return '0' <= ch && ch <= '9'; } /** * Determines if the specified character is a letter or digit. * <p> * A character is considered to be a letter or digit if either * <code>Character.isLetter(char ch)</code> or * <code>TextUtils.isDigit(char ch)</code> returns * <code>true</code> for the character. * * @param ch the character to be tested. * @return <code>true</code> if the character is a letter or digit; * <code>false</code> otherwise. * @see TextUtils#isDigit(char) * @see java.lang.Character#isJavaLetterOrDigit(char) * @see java.lang.Character#isLetter(char) */ public static boolean isLetterOrDigit(char ch) { return isDigit(ch) || Character.isLetter(ch); } /** * Returns canonic char for ignore-case comparison . * This is the same as Character.toLowerCase(Character.toUpperCase(ch)). * @param ch given char. * @return canonic char for the given char. */ public static char canonicChar(char ch) { if (ch <= 'Z') { if (ch >= 'A') { ch += 'a' - 'A'; } } else { if (ch >= '\u0080') { ch = Character.toLowerCase(Character.toUpperCase(ch)); } } return ch; } /** * Returns canonic string for ignore-case comparison . * FORALL String s1, s2: s1.equalsIgnoreCase(s2) == canonicString(s1).equals(canonicString(s2) * FORALL String s: canonicString(canonicString(s)).equals(canonicString(s)) * @param s given String * @return canonic String * Simple "toLowerCase" is not sufficient. * For example ("\u0131").equalsIgnoreCase("i") , but Character.toLowerCase('\u0131') == '\u0131' . */ public static String canonicString(String s) { int i = 0; for (; i < s.length(); i++) { char ch = s.charAt(i); if (canonicChar(ch) != ch) { break; } } if (i == s.length()) { return s; } char[] chars = s.toCharArray(); for (; i < s.length(); i++) { chars[i] = canonicChar(chars[i]); } return new String(chars); } // static { // for (int i = Character.MIN_VALUE; i <= Character.MAX_VALUE; i++) { // char ch = (char)i; // char toLower = Character.toLowerCase(ch); // char toUpper = Character.toUpperCase(ch); // char canonic = canonicChar(toUpper); // if (canonic != toLower) { // System.out.println(ch + " " + Integer.toHexString(ch) + // " lower " + toLower + " " + Integer.toHexString(toLower) + // " upper " + toUpper + " " + Integer.toHexString(toUpper) + // " canonic " + canonic + " " + Integer.toHexString(canonic)); // assert Character.toLowerCase(Character.toUpperCase(canonic)) == canonic; // } // } // } /** * Method to determine if one string is a subset of another, but case-insensitive. * @param main the main string. * @param with the substring. * @return true if the main string starts with the substring, ignoring case. */ public static boolean startsWithIgnoreCase(String main, String with) { int mainLen = main.length(); int withLen = with.length(); if (withLen > mainLen) { return false; } for (int i = 0; i < withLen; i++) { char mainChr = canonicChar(main.charAt(i)); char withChr = canonicChar(with.charAt(i)); if (mainChr != withChr) { return false; } } return true; } /** * Method to parse the floating-point number in a string. * There is one reason to use this method instead of Double.parseDouble: * this method does not throw an exception if the number is invalid (or blank). * @param text the string with a number in it. * @return the numeric value. */ public static double atof(String text) { // try // { // return Double.parseDouble(text); // } catch (NumberFormatException e) // { return atof(text, null, null, null); // } } /** * Method to parse the floating-point number in a string, using a default value if no number can be determined. * @param text the string to convert to a double. * @param defaultVal the value to return if the string cannot be converted to a double. * If 'defaultVal' is null and the text cannot be converted to a number, the method returns 0. * @return the numeric value. */ public static double atof(String text, Double defaultVal) { return atof(text, defaultVal, null, null); } /** * Method to parse the floating-point number in a string, assuming that it is a distance value in the current technology. * @param text the string to convert to a double. * @param tech the technology to use for the conversion. * If it is not a layout technology, then use pure numbers. * @return the numeric value in internal database units. */ public static double atofDistance(String text, Technology tech) { if (tech != null && tech.isLayout()) { return atof(text, null, TextDescriptor.Unit.DISTANCE, tech); } return atof(text); } /** * Method to parse the floating-point number in a string, assuming that it is a distance value in the current technology. * @param text the string to convert to a double. * @return the numeric value in internal database units. */ public static double atofDistance(String text) { return atof(text, null, TextDescriptor.Unit.DISTANCE, Technology.getCurrent()); } /** * Method to parse the floating-point number in a string, using a default value if no number can be determined, * and presuming a type of unit. * @param text the string to convert to a double. * @param defaultVal the value to return if the string cannot be converted to a double. * If 'defaultVal' is null and the text cannot be converted to a number, the method returns 0. * @param unitType the type of unit being examined (handles postfix characters). * @return the numeric value. */ public static double atof(String text, Double defaultVal, TextDescriptor.Unit unitType, Technology tech) { if (unitType != null) { // strip off postfix characters if present String pf = unitType.getPostfixChar(); if (text.endsWith(pf)) { text = text.substring(0, text.length() - pf.length()); } } // remove commas that denote 1000's separators text = text.replaceAll(",", ""); double v = 0; try { UnitScale us = null; if (unitType == TextDescriptor.Unit.DISTANCE) { us = User.getDistanceUnits(); } Number n = parsePostFixNumber(text, us); v = n.doubleValue(); } catch (NumberFormatException ex) { int start = 0; while (start < text.length() && text.charAt(start) == ' ') { start++; } int end = start; // allow initial + or - if (end < text.length() && (text.charAt(end) == '-' || text.charAt(end) == '+')) { end++; } // allow digits while (end < text.length() && TextUtils.isDigit(text.charAt(end))) { end++; } // allow decimal point and digits beyond it if (end < text.length() && text.charAt(end) == '.') { end++; while (end < text.length() && TextUtils.isDigit(text.charAt(end))) { end++; } } // allow exponent if (end < text.length() && (text.charAt(end) == 'e' || text.charAt(end) == 'E')) { end++; if (end < text.length() && (text.charAt(end) == '-' || text.charAt(end) == '+')) { end++; } while (end < text.length() && TextUtils.isDigit(text.charAt(end))) { end++; } } if (end <= start) { if (defaultVal != null) { return defaultVal.doubleValue(); } return 0; } try { v = Double.parseDouble(text.substring(start, end - start)); } catch (NumberFormatException e) { v = 0; } } if (unitType == TextDescriptor.Unit.DISTANCE && User.getDistanceUnits() != null) { v = v / Technology.getCurrent().getScale() / UnitScale.NANO.getMultiplier().doubleValue(); } return v; } /** * Method to parse the number in a string. * <P> * There are many reasons to use this method instead of Integer.parseInt... * <UL> * <LI>This method can handle any radix. * If the number begins with "0", presume base 8. * If the number begins with "0b", presume base 2. * If the number begins with "0x", presume base 16. * Otherwise presume base 10. * <LI>This method can handle numbers that affect the sign bit. * If you give 0xFFFFFFFF to Integer.parseInt, you get a numberFormatPostFix exception. * This method properly returns -1. * <LI>This method does not require that the entire string be part of the number. * If there is extra text after the end, Integer.parseInt fails (for example "123xx"). * <LI>This method does not throw an exception if the number is invalid (or blank). * </UL> * @param s the string with a number in it. * @return the numeric value. */ public static int atoi(String s) { return atoi(s, 0, 0); } /** * Method to parse the number in a string. * See the comments for "atoi(String s)" for reasons why this method exists. * @param s the string with a number in it. * @param pos the starting position in the string to find the number. * @return the numeric value. */ public static int atoi(String s, int pos) { return atoi(s, pos, 0); } /** * Method to parse the number in a string. * See the comments for "atoi(String s)" for reasons why this method exists. * @param s the string with a number in it. * @param pos the starting position in the string to find the number. * @param base the forced base of the number (0 to determine it automatically). * @return the numeric value. */ public static int atoi(String s, int pos, int base) { int num = 0; int sign = 1; int len = s.length(); if (pos < len && s.charAt(pos) == '-') { pos++; sign = -1; } if (base == 0) { base = 10; if (pos < len && s.charAt(pos) == '0') { pos++; base = 8; if (pos < len && (s.charAt(pos) == 'x' || s.charAt(pos) == 'X')) { pos++; base = 16; } else if (pos < len && (s.charAt(pos) == 'b' || s.charAt(pos) == 'B')) { pos++; base = 2; } } } for (; pos < len; pos++) { char cat = s.charAt(pos); int digit = Character.digit(cat, base); if (digit < 0) { break; } num = num * base + digit; // if ((cat >= 'a' && cat <= 'f') || (cat >= 'A' && cat <= 'F')) // { // if (base != 16) break; // num = num * 16; // if (cat >= 'a' && cat <= 'f') num += cat - 'a' + 10; else // num += cat - 'A' + 10; // continue; // } // if (!TextUtils.isDigit(cat)) break; // if (cat >= '8' && base == 8) break; // num = num * base + cat - '0'; } return (num * sign); } /** * Method to get the numeric value of a string that may be an expression. * @param expression the string that may be an expression. * @return the numeric value of the expression. * This method uses the Bean Shell to evaluate non-numeric strings. */ public static double getValueOfExpression(String expression) { if (isANumber(expression)) { double res = atof(expression); return res; } Object o = EvalJavaBsh.evalJavaBsh.doEvalLine(expression); if (o == null) { return 0; } if (o instanceof Double) { return ((Double) o).doubleValue(); } if (o instanceof Integer) { return ((Integer) o).intValue(); } return 0; } /** * Method to convert a string with color values into an array of colors. * @param str the string, with colors separated by "/" and the RGB values in * a color separated by ",". For example, "255,0,0/0,0,255" describes two * colors: red and blue. * @return an array of Color values. */ public static Color[] getTransparentColors(String str) { String[] colorNames = str.split("/"); Color[] colors = new Color[colorNames.length]; for (int i = 0; i < colorNames.length; i++) { String colorName = colorNames[i].trim(); String[] rgb = colorName.split(","); if (rgb.length != 3) { return null; } int r = TextUtils.atoi(rgb[0]); int g = TextUtils.atoi(rgb[1]); int b = TextUtils.atoi(rgb[2]); colors[i] = new Color(r, g, b); } return colors; } private static NumberFormat numberFormatPostFix = null; /** * Method to convert a double to a string. * Also scales number and appends appropriate postfix UnitScale string. * @param v the double value to format. * @return the string representation of the number. */ public static String formatDoublePostFix(double v) { if (numberFormatPostFix == null) { numberFormatPostFix = NumberFormat.getInstance(Locale.US); try { DecimalFormat d = (DecimalFormat) numberFormatPostFix; d.setDecimalSeparatorAlwaysShown(false); d.setGroupingSize(300); // make it so comma (1000's separator) is never used } catch (Exception e) { } } numberFormatPostFix.setMaximumFractionDigits(3); int unitScaleIndex = 0; if (v != 0) { while ((Math.abs(v) >= 1000000) && (unitScaleIndex > UnitScale.UNIT_BASE)) { v /= 1000; unitScaleIndex--; } while ((Math.abs(v) < 0.1) && (unitScaleIndex < UnitScale.UNIT_END)) { v *= 1000; unitScaleIndex++; } // if number still out of range, adjust decimal formatting if (Math.abs(v) < 0.1) { int maxDecimals = 3; double v2 = Math.abs(v); while (v2 < 0.1) { maxDecimals++; v2 *= 10; } numberFormatPostFix.setMaximumFractionDigits(maxDecimals); } } UnitScale u = UnitScale.findFromIndex(unitScaleIndex); String result = numberFormatPostFix.format(v); return result + u.getPostFix(); } private static NumberFormat numberFormatSpecific = null; /** * Method to convert a double to a string. * If the double has no precision past the decimal, none will be shown. * @param v the double value to format. * @return the string representation of the number. */ public static String formatDouble(double v) { return formatDouble(v, 3); } /** * Method to convert a distance to a string, using scale from the current technology if necessary. * If the value has no precision past the decimal, none will be shown. * If the units are not scalable, then appropriate values will be shown * @param v the distance value to format. * @return the string representation of the number. */ public static String formatDistance(double v, Technology tech) { if (tech != null && tech.isLayout()) { return displayedUnits(v, TextDescriptor.Unit.DISTANCE, User.getDistanceUnits(), tech); } return formatDouble(v); } /** * Method to convert a distance to a string, using scale from the current technology if necessary. * If the value has no precision past the decimal, none will be shown. * If the units are not scalable, then appropriate values will be shown * @param v the distance value to format. * @return the string representation of the number. */ public static String formatDistance(double v) { return displayedUnits(v, TextDescriptor.Unit.DISTANCE, User.getDistanceUnits(), Technology.getCurrent()); } /** * Method to convert a double to a string. * It will show up to 'numFractions' digits past the decimal point if numFractions is greater * than zero. If numFractions is 0, it will show infinite (as far as doubles go) precision. * If the double has no precision past the decimal, none will be shown. * This method is now thread safe. * @param v the double value to format. * @param numFractions the number of digits to the right of the decimal point. * @return the string representation of the number. */ public static synchronized String formatDouble(double v, int numFractions) { if (numberFormatSpecific == null) { numberFormatSpecific = NumberFormat.getInstance(Locale.US); if (numberFormatSpecific != null) { numberFormatSpecific.setGroupingUsed(false); } try { DecimalFormat d = (DecimalFormat) numberFormatSpecific; // DecimalFormat d = (DecimalFormat)numberFormatPostFix; d.setDecimalSeparatorAlwaysShown(false); } catch (Exception e) { } } if (numFractions == 0) { numberFormatSpecific.setMaximumFractionDigits(340); } else { numberFormatSpecific.setMaximumFractionDigits(numFractions); } return numberFormatSpecific.format(v); } private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE MMM dd, yyyy HH:mm:ss"); /** * Method to convert a Date to a String using local TimeZone. * @param date the date to format. * @return the string representation of the date. */ public static String formatDate(Date date) { return simpleDateFormat.format(date); } // SMR removed this method because the initialization of the "PST" time zone only works in the USA private static SimpleDateFormat simpleDateFormatGMT = new SimpleDateFormat("EEE MMMM dd, yyyy HH:mm:ss zzz"); static { simpleDateFormatGMT.setTimeZone(TimeZone.getTimeZone("GMT")); } /** * Method to convert a Date to a String using GMT TimeZone. * @param date the date to format. * @return the string representation of the date. */ public static String formatDateGMT(Date date) { return simpleDateFormatGMT.format(date); } /** * Method to converts a floating point number into engineering units such as pico, micro, milli, etc. * @param value floating point value to be converted to engineering notation. */ public static String convertToEngineeringNotation(double value) { return convertToEngineeringNotation(value, "", 9999); } /** * Method to converts a floating point number into engineering units such as pico, micro, milli, etc. * @param value floating point value to be converted to engineering notation. * @param unit a unit string to append to the result (null for none). */ public static String convertToEngineeringNotation(double value, String unit) { return convertToEngineeringNotation(value, unit, 9999); } /** * Method to converts a floating point number into engineering units such as pico, micro, milli, etc. * @param time floating point value to be converted to engineering notation. * @param unit a unit string to append to the result (null for none). * @param precpower decimal power of necessary time precision. * Use a very large number to ignore this factor (9999). */ private static class ConversionRange { String postfix; int power; double scale; ConversionRange(String p, int pow, double s) { postfix = p; power = pow; scale = s; } } private static ConversionRange[] allRanges = new ConversionRange[]{ // Although the extremes (yocto and yotta) are defined in the literature, // they aren't common in circuits (at this time) and so they are commented out. // Add them and more as their use in circuitry becomes common. // new ConversionRange("y", -24, 1.0E27), // yocto new ConversionRange("z", -21, 1.0E24), // zepto new ConversionRange("a", -18, 1.0E21), // atto new ConversionRange("f", -15, 1.0E18), // femto new ConversionRange("p", -12, 1.0E15), // pico new ConversionRange("n", -9, 1.0E12), // nano new ConversionRange("u", -6, 1.0E9), // micro new ConversionRange("m", -3, 1.0E6), // milli new ConversionRange("", 0, 1.0E3), // no scale new ConversionRange("k", 3, 1.0E0), // kilo new ConversionRange("M", 6, 1.0E-3), // mega new ConversionRange("G", 9, 1.0E-6), // giga new ConversionRange("T", 12, 1.0E-9), // tera new ConversionRange("P", 15, 1.0E-12), // peta new ConversionRange("E", 18, 1.0E-15), // exa new ConversionRange("Z", 21, 1.0E-18) // zetta // new ConversionRange("Y", 24, 1.0E-21) // yotta }; private static final double LOOKS_LIKE_ZERO = 1.0 / (allRanges[0].scale * 1.0E4); private static final double SMALLEST_JUST_PRINT = 1.0 / (allRanges[0].scale * 1.0); private static final double LARGEST_JUST_PRINT = 1.0 / allRanges[allRanges.length - 1].scale * 1.0E4; public static String convertToEngineeringNotation(double time, String unit, int precpower) { String negative = ""; if (time < 0.0) { negative = "-"; time = -time; } String unitPostfix = unit; if (unitPostfix == null) { unitPostfix = ""; } // if the value is too tiny, just call it zero if (time < LOOKS_LIKE_ZERO) { return "0" + unitPostfix; } // if the value is out of range, use normal formatting for it if (time < SMALLEST_JUST_PRINT || time >= LARGEST_JUST_PRINT) { return negative + TextUtils.formatDouble(time) + unitPostfix; } // get proper time unit to use String secType = ""; int rangePos = -1; long intTime = 0; double scaled = 0; for (int i = 0; i < allRanges.length; i++) { scaled = time * allRanges[i].scale; intTime = Math.round(scaled); if (i == allRanges.length - 1 || (scaled < 2000000.0 && intTime < 100000)) { if (unit == null) { if (allRanges[i].power != 0) { secType = "e" + allRanges[i].power; } } else { secType = allRanges[i].postfix + unitPostfix; } rangePos = i; break; } } if (precpower >= allRanges[rangePos].power) { // if the value is too tiny, just call it zero if (precpower < 1000 && precpower - 4 > allRanges[rangePos].power) { return "0" + unitPostfix; } long timeleft = intTime / 1000; long timeright = intTime % 1000; if (timeright == 0) { return negative + timeleft + secType; } if ((timeright % 100) == 0) { return negative + timeleft + "." + timeright / 100 + secType; } if ((timeright % 10) == 0) { String tensDigit = ""; if (timeright < 100) { tensDigit = "0"; } return negative + timeleft + "." + tensDigit + timeright / 10 + secType; } String tensDigit = ""; if (timeright < 10) { tensDigit = "00"; } else if (timeright < 100) { tensDigit = "0"; } return negative + timeleft + "." + tensDigit + timeright + secType; } // does not fit into 3-digit range easily: drop down a factor of 1000 and use bigger numbers int digits = allRanges[rangePos].power - precpower; if (rangePos > 0) { rangePos--; if (unit == null) { if (allRanges[rangePos].power != 0) { secType = "e" + allRanges[rangePos].power; } } else { secType = allRanges[rangePos].postfix + unitPostfix; } digits += 3; } else { scaled /= 1000; } String numPart = TextUtils.formatDouble(scaled, digits); if (numPart.indexOf('.') >= 0) { while (numPart.endsWith("0")) { numPart = numPart.substring(0, numPart.length() - 1); } if (numPart.endsWith(".")) { numPart = numPart.substring(0, numPart.length() - 1); } } return negative + numPart + secType; } /** * Method to convert an integer to a string that is left-padded with spaces * @param value the integer value. * @param width the minimum field width. * If the result is less than this, extra spaces are added to the beginning. * @return a string describing the integer. */ public static String toBlankPaddedString(int value, int width) { String msg = Integer.toString(value); while (msg.length() < width) { msg = " " + msg; } return msg; } // /** // * Method to convert a double to a string that is left-padded with spaces // * @param value the double value. // * @param width the minimum field width. // * If the result is less than this, extra spaces are added to the beginning. // * @return a string describing the double. // */ // public static String toBlankPaddedString(double value, int width) // { // String msg = Double.toString(value); // while (msg.length() < width) msg = " " + msg; // return msg; // } /** * Method to determine whether or not a string is a number. * This method allows hexadecimal numbers as well as those with exponents. * @param pp the string to test. * @return true if it is a number. */ public static boolean isANumber(String pp) { if (pp == null) { return false; } // ignore the minus sign int i = 0; int len = pp.length(); if (i < len && (pp.charAt(i) == '+' || pp.charAt(i) == '-')) { i++; } // special case for hexadecimal prefix boolean xflag = false; if (i < len - 1 && pp.charAt(i) == '0' && (pp.charAt(i + 1) == 'x' || pp.charAt(i + 1) == 'X')) { i += 2; xflag = true; } boolean founddigits = false; if (xflag) { while (i < len && (TextUtils.isDigit(pp.charAt(i)) || pp.charAt(i) == 'a' || pp.charAt(i) == 'A' || pp.charAt(i) == 'b' || pp.charAt(i) == 'B' || pp.charAt(i) == 'c' || pp.charAt(i) == 'C' || pp.charAt(i) == 'd' || pp.charAt(i) == 'D' || pp.charAt(i) == 'e' || pp.charAt(i) == 'E' || pp.charAt(i) == 'f' || pp.charAt(i) == 'F')) { i++; founddigits = true; } } else { while (i < len && (TextUtils.isDigit(pp.charAt(i)) || pp.charAt(i) == '.')) { if (pp.charAt(i) != '.') { founddigits = true; } i++; } } if (!founddigits) { return false; } if (i == len) { return true; } // handle exponent of floating point numbers if (xflag) { return false; } if (pp.charAt(i) != 'e' && pp.charAt(i) != 'E') { return false; } i++; if (i == len) { return false; } if (pp.charAt(i) == '+' || pp.charAt(i) == '-') { i++; } if (i == len) { return false; } while (i < len && TextUtils.isDigit(pp.charAt(i))) { i++; } if (i == len) { return true; } return false; } /** * Method to determine whether or not a string is a postfix formatted * number, such as 1.02f. * @param pp the string to test. * @return true if it is a postfix number. */ public static boolean isANumberPostFix(String pp) { // ignore the minus sign int i = 0; int len = pp.length(); if (i < len && (pp.charAt(i) == '+' || pp.charAt(i) == '-')) { i++; } boolean founddigits = false; while (i < len && (TextUtils.isDigit(pp.charAt(i)) || pp.charAt(i) == '.')) { if (pp.charAt(i) != '.') { founddigits = true; } i++; } if (!founddigits) { return false; } if (i == len) { return true; } // handle post fix character (spice format) if (i + 1 == len) { char c = Character.toLowerCase(pp.charAt(i)); if (c == 'g' || c == 'k' || c == 'm' || c == 'u' || c == 'n' || c == 'p' || c == 'f') { return true; } } else if (pp.substring(i).toLowerCase().equals("meg")) { return true; } return false; } /** * Method to find a string inside of another string. * @param string the main string being searched. * @param search the string being located in the main string. * @param startingPos the starting position in the main string to look (0 to search the whole string). * @param caseSensitive true to do a case-sensitive search. * @param reverse true to search from the back of the string. * @return the position of the search string. Returns negative if the string is not found. */ public static int findStringInString(String string, String search, int startingPos, boolean caseSensitive, boolean reverse) { if (caseSensitive) { // case-sensitive search int i = 0; if (reverse) { i = string.lastIndexOf(search, startingPos); } else { i = string.indexOf(search, startingPos); } return i; } // case-insensitive search if (startingPos > 0) { string = string.substring(startingPos); } String stringLC = canonicString(string); String searchLC = canonicString(search); int i = 0; if (reverse) { i = stringLC.lastIndexOf(searchLC); } else { i = stringLC.indexOf(searchLC); } if (i >= 0) { i += startingPos; } return i; } /** * Method to break a line into keywords, separated by white space or comma * @param line the string to tokenize. * @param delim the delimiters. * @return an array of Strings for each keyword on the line. */ public static String[] parseString(String line, String delim) { StringTokenizer st = new StringTokenizer(line, delim); int total = st.countTokens(); String[] strings = new String[total]; for (int i = 0; i < total; i++) { strings[i] = st.nextToken().trim(); } return strings; } /** * Unit is a typesafe enum class that describes a unit scale (metric factors of 10). */ public static class UnitScale { private final String name; // private final String description; private final int index; private final String postFix; private final Number multiplier; private UnitScale(String name, String description, int index, String postFix, Number multiplier) { this.name = name; // this.description = description; this.index = index; this.postFix = postFix; this.multiplier = multiplier; } /** * Method to return the name of this UnitScale. * The name can be prepended to a type, for example the name "Milli" can be put in front of "Meter". * @return the name of this UnitScale. */ public String getName() { return name; } /** * Method to convert this UnitScale to an integer. * Used when storing these as preferences. * @return the index of this UnitScale. */ public int getIndex() { return index; } /** * Get the string representing the postfix associated with this unit scale * @return the post fix string */ public String getPostFix() { return postFix; } /** * Get the multiplier value associated with this unit scale. * @return the multiplier. May be an Integer (values >= 1) or a Double (values <= 1) */ public Number getMultiplier() { return multiplier; } /** * Method to convert the index value to a UnitScale. * Used when storing these as preferences. * @param index the index of the UnitScale. * @return the indexed UnitScale. */ public static UnitScale findFromIndex(int index) { int i = index - UNIT_BASE; if (i < 0 || i >= allUnits.length) { return NONE; } return allUnits[i]; } /** * Method to return a list of all scales. * @return an array of all scales. */ public static UnitScale[] getUnitScales() { return allUnits; } /** * Returns a printable version of this Unit. * @return a printable version of this Unit. */ public String toString() { return name; } /** The largest unit value. */ private static final int UNIT_BASE = -3; /** The smallest unit value. */ private static final int UNIT_END = 5; /** Describes giga scale (1 billion). */ public static final UnitScale GIGA = new UnitScale("Giga", "giga: x 1000000000", -3, "G", new Integer(1000000000)); /** Describes mega scale (1 million). */ public static final UnitScale MEGA = new UnitScale("Mega", "mega: x 1000000", -2, "meg", new Integer(1000000)); /** Describes kilo scale (1 thousand). */ public static final UnitScale KILO = new UnitScale("Kilo", "kilo: x 1000", -1, "k", new Integer(1000)); /** Describes unit scale (1). */ public static final UnitScale NONE = new UnitScale("", "-: x 1", 0, "", new Integer(1)); /** Describes milli scale (1 thousandth). */ public static final UnitScale MILLI = new UnitScale("Milli", "milli: x 10 ^ -3", 1, "m", new Double(0.001)); /** Describes micro scale (1 millionth). */ public static final UnitScale MICRO = new UnitScale("Micro", "micro: x 10 ^ -6", 2, "u", new Double(0.000001)); /** Describes nano scale (1 billionth). */ public static final UnitScale NANO = new UnitScale("Nano", "nano: x 10 ^ -9", 3, "n", new Double(0.000000001)); /** Describes pico scale (10 to the -12th). */ public static final UnitScale PICO = new UnitScale("Pico", "pico: x 10 ^ -12", 4, "p", new Double(0.000000000001)); /** Describes femto scale (10 to the -15th). */ public static final UnitScale FEMTO = new UnitScale("Femto", "femto: x 10 ^ -15", 5, "f", new Double(0.000000000000001)); /** Describes atto scale (10 to the -18th). */ public static final UnitScale ATTO = new UnitScale("Atto", "atto: x 10 ^ -18", 6, "a", new Double(0.000000000000000001)); /** Describes zepto scale (10 to the -21st). */ public static final UnitScale ZEPTO = new UnitScale("Zepto", "zepto: x 10 ^ -21", 7, "z", new Double(0.000000000000000000001)); /** Describes yocto scale (10 to the -24th). */ public static final UnitScale YOCTO = new UnitScale("Yocto", "yocto: x 10 ^ -24", 8, "y", new Double(0.000000000000000000000001)); private final static UnitScale[] allUnits = { GIGA, MEGA, KILO, NONE, MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO }; } /** * Method to convert a database coordinate into real spacing. * @param value the database coordinate to convert. * @param tech the technology to use for conversion (provides a real scaling). * @param unitScale the type of unit desired. * @return the database coordinate in the desired units. * For example, if the given technology has a scale of 200 nanometers per unit, * and the value 7 is given, then that is 1.4 microns (1400 nanometers). * If the desired units are UnitScale.MICRO, then the returned value will be 1.4. */ public static double convertDistance(double value, Technology tech, UnitScale unitScale) { double scale = tech.getScale(); double distanceScale = 0.000000001 / unitScale.getMultiplier().doubleValue() * scale; return value * distanceScale; } /** * Method to convert real spacing into a database coordinate. * @param value the real distance to convert. * @param tech the technology to use for conversion (provides a real scaling). * @param unitScale the type of unit desired. * @return the real spacing in the database units. * For example, if the given technology has a scale of 200 nanometers per unit, * and the value 1.6 is given with the scale UnitScale.MICRO, then that is 1.6 microns (1600 nanometers). * Since the technology has 200 nanometers per unit, this converts to 8 units. */ public static double convertFromDistance(double value, Technology tech, UnitScale unitScale) { double scale = tech.getScale(); double distanceScale = 0.000000001 / unitScale.getMultiplier().doubleValue() * scale; return value / distanceScale; } /** * Method to express "value" as a string in "unittype" electrical units. * The scale of the units is in "unitscale". */ private static String displayedUnits(double value, TextDescriptor.Unit unitType, UnitScale unitScale, Technology tech) { if (unitType == TextDescriptor.Unit.DISTANCE && unitScale != null) { value *= UnitScale.NANO.getMultiplier().doubleValue() * tech.getScale(); } String postFix = ""; if (unitScale != null) { value /= unitScale.getMultiplier().doubleValue(); postFix = unitScale.getPostFix() + unitType.getPostfixChar(); } return formatDouble(value) + postFix; } /** * Method to convert a floating point value to a string, given that it is a particular type of unit. * Each unit has a default scale. For example, if Capacitance value 0.0000012 is being converted, and * Capacitance is currently using microFarads, then the result will be "1.2m". * If, however, capacitance is currently using milliFarads, the result will be 0.0012u". * @param value the floating point value. * @param units the type of unit. * @return a string describing that value in the current unit. */ public static String makeUnits(double value, TextDescriptor.Unit units) { if (units == TextDescriptor.Unit.NONE) { return formatDouble(value); } if (units == TextDescriptor.Unit.DISTANCE) { return displayedUnits(value, units, User.getDistanceUnits(), Technology.getCurrent()); } // if (units == TextDescriptor.Unit.RESISTANCE) // return displayedUnits(value, units, User.getResistanceUnits()); // if (units == TextDescriptor.Unit.CAPACITANCE) // return displayedUnits(value, units, User.getCapacitanceUnits()); // if (units == TextDescriptor.Unit.INDUCTANCE) // return displayedUnits(value, units, User.getInductanceUnits()); // if (units == TextDescriptor.Unit.CURRENT) // return displayedUnits(value, units, User.getAmperageUnits()); // if (units == TextDescriptor.Unit.VOLTAGE) // return displayedUnits(value, units, User.getVoltageUnits()); // if (units == TextDescriptor.Unit.TIME) // return displayedUnits(value, units, User.getTimeUnits()); return (formatDoublePostFix(value)); } /** * Try to parse the user input String s into a Number. Conversion into the following formats * is tried in order. If a conversion is successful, that object is returned. * If no conversions are successful, this throws a NumberFormatException. * This method removes any UnitScale postfix, and scales the number accordingly. * No characters in the string are ignored - the string in its entirety (except removed postfix) must be * able to be parsed into the Number by the usual Integer.parseInt(), Double.parseDouble() methods. * <P>Formats: Integer, Long, Double * @param s the string to parse. * @param us the UnitScale to presume if none are given (null for no scaling). * @return a Number that represents the string in its entirety * @throws NumberFormatException if the String is not a parsable Number. */ public static Number parsePostFixNumber(String s, UnitScale us) throws NumberFormatException { // remove character denoting multiplier at end, if any // remove commas that denote 1000's separators s = s.replaceAll(",", ""); Number n = null; // the number Number m = null; // the multiplier if (us != null) { m = us.getMultiplier(); } for (int i = 0; i < UnitScale.allUnits.length; i++) { UnitScale u = UnitScale.allUnits[i]; String postfix = u.getPostFix(); if (postfix.equals("")) { continue; // ignore the NONE suffix case } if (postfix.length() >= s.length()) { continue; // postfix is same length or longer than string } String sSuffix = s.substring(s.length() - postfix.length(), s.length()); if (sSuffix.equalsIgnoreCase(postfix)) { m = u.getMultiplier(); String sub = s.substring(0, s.length() - postfix.length()); // try to convert substring to a number try { n = parseNumber(sub); break; } catch (NumberFormatException e) { m = null; // try again } } } // if no valid postfix found, just parse number if (n == null) { n = parseNumber(s); } if (m != null) { if ((m instanceof Integer) && (m.intValue() == 1)) { return n; } if ((n instanceof Integer) && (m instanceof Integer)) { return new Integer(n.intValue() * m.intValue()); } if ((n instanceof Long) && (m instanceof Integer)) { return new Long(n.longValue() * m.longValue()); } return new Double(n.doubleValue() * m.doubleValue()); } return n; } /** * Try to parse the user input String s into a Number. Conversion into the following formats * is tried in order. If a conversion is successful, that object is returned. * If no conversions are successful, this throws a NumberFormatException. * No characters in the string are ignored - the string in its entirety must be * able to be parsed into the Number by the usual Integer.parseInt(), Double.parseDouble() methods. * <P>Formats: Integer, Long, Double * @param s the string to parse * @return a Number that represents the string in its entirety * @throws NumberFormatException if the String is not a parsable Number. */ private static Number parseNumber(String s) throws NumberFormatException { Number n = null; try { n = new Integer(s); } catch (NumberFormatException e) { // elib format does not know what a Long is //try { // n = new Long(s); //} catch (NumberFormatException ee) { try { n = new Double(s); } catch (NumberFormatException eee) { } //} } if (n == null) { NumberFormatException e = new NumberFormatException(s + "cannot be parsed into a Number"); throw e; } return n; } /** * Method to print a very long string. * The string is broken sensibly. */ public static void printLongString(String str) { String prefix = ""; while (str.length() > 80) { int i = 80; for (; i > 0; i--) { if (str.charAt(i) == ' ' || str.charAt(i) == ',') { break; } } if (i <= 0) { i = 80; } if (str.charAt(i) == ',') { i++; } System.out.println(prefix + str.substring(0, i)); if (str.charAt(i) == ' ') { i++; } str = str.substring(i); prefix = " "; } System.out.println(prefix + str); } // /** // * Method to compare two names and give a sort order. // * The comparison considers numbers in numeric order so that the // * string "in10" comes after the string "in9". // * // * Formal definition of order. // * Lets insert in string's character sequence number at start of digit sequences. // * Consider that numbers in the sequence are less than chars. // * // * Examples below are in increasing order: // * "" { } // * "0" { 0, '0' } // * "9" { 9, '9' } // * "10" { 10, '1', '0' } // * "2147483648" { 2147483648, '2', '1', '4', '7', '4', '8', '3', '6', '4', '8' } // * " " { ' ' } // * "-" { '-' } // * "-1" { '-', 1, '1' } // * "-2" { '-', 2, '2' } // * "a" { 'a' } // * "a0" { 'a', 0, '0' } // * "a0-0" { 'a', 0, '0', '-', 0, '0' } // * "a00" { 'a', 0, '0', '0' } // * "a0a" { 'a', 0, '0', 'a' } // * "a01" { 'a', 1, '0', '1' } // * "a1" { 'a', 1, '1' } // * "in" { 'i', 'n' } // * "in1" { 'i', 'n', 1, '1' } // * "in1a" { 'i', 'n', 1, '1', 'a' } // * "in9" { 'i', 'n', 9, '9' } // * "in10" { 'i', 'n', 10, '1', '0' } // * "in!" { 'i', 'n', '!' } // * "ina" { 'i , 'n', 'a' } // * // * @param name1 the first string. // * @param name2 the second string. // * @return 0 if they are equal, nonzero according to order. // */ // public static int nameSameNumeric(String name1, String name2) { // return STRING_NUMBER_ORDER.compare(name1, name2); // } /** * Method to set the string stored on the system clipboard. * @param text the new text for the clipboard. If text is null, the contents is clean. */ public static void setTextOnClipboard(String text) { // put the text in the clipboard java.awt.datatransfer.Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard(); Transferable transferable = new StringSelection(text); cb.setContents(transferable, null); } /** * Method for obtaining the string on the system clipboard. * @return the string on the system clipboard (returns a null string if nothing is found). */ public static String getTextOnClipboard() { String result = null; try { Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); Transferable contents = clipboard.getContents(null); if (contents != null && contents.isDataFlavorSupported(DataFlavor.stringFlavor)) { result = (String) contents.getTransferData(DataFlavor.stringFlavor); } } catch (UnsupportedFlavorException ex) { ex.printStackTrace(); } catch (IOException ex) { // known problem on MacOSX if (!Client.isOSMac() || !ex.getMessage().toLowerCase().contains("system clipboard data unavailable")) { ex.printStackTrace(); } } return result; } /** * Method to convert a file path to a URL. * @param fileName the path to the file. * @return the URL to that file (null on error). */ public static URL makeURLToFile(String fileName) { if (fileName.startsWith("file://")) { fileName = fileName.substring(6); } if (fileName.startsWith("file:/")) { fileName = fileName.substring(5); } // fix file names with spaces encoded (for example "%20") try { fileName = URLDecoder.decode(fileName, "UTF-8"); } catch (UnsupportedEncodingException e) { } File file = new File(fileName); try { return file.toURI().toURL(); // return file.toURL(); // deprecated in Java 1.6 } catch (java.net.MalformedURLException e) { System.out.println("Cannot find file " + fileName); } return null; } /** * Get the file for the URL. The code * <code> * new File(url.getPath()) * </code> * returns an illegal leading slash on windows, * and has forward slashes instead of back slashes. * This method generates the correct File using * <code> * new File(url.toURI()) * </code> * <P> * use <code>getPath()</code> on the returned File * to get the correct String file path. * <P> * This should only be needed when running an external process under * windows with command line arguments containing file paths. Otherwise, * the Java IO code does the correct conversion. * * @param url the URL to convert to a File. * @return the File. Will return null if * URL does not point to a file. */ public static File getFile(URL url) { try { return new File(url.toURI()); } catch (java.net.URISyntaxException e) { System.out.println("URL -> File conversion error: " + e.getMessage()); return new File(url.getPath()); } catch (java.lang.IllegalArgumentException e) { // Libraries available in the jar fall in this case. Error message only in debug mode if (Job.getDebug()) System.out.println("URL -> File conversion error: " + e.getMessage()); return null; } } /** * Method to convert a URL to a string. * @param url the URL * @return a String that is the path to that URL. */ public static String URLtoString(URL url) { if (url == null) { System.out.println("Null URL in TextUtils.URLtoString"); return ""; } String filePath = url.getFile(); // use proper URI to ensure valid path name try { URI uri = new URI(filePath); filePath = uri.getPath(); } catch (URISyntaxException e) { } // fix encoded file names (for example, with "%20" instead of spaces) try { filePath = URLDecoder.decode(filePath, "UTF-8"); } catch (UnsupportedEncodingException e) { } return filePath; } /** * Method to return the directory path part of a URL (excluding the file name). * For example, the URL "file:/users/strubin/gates.elib" has the directory part "/users/strubin/". * @param url the URL to the file. * @return the directory path part (including the trailing "/", ":", or "\"). * If there is no directory part, returns "". */ public static String getFilePath(URL url) { if (url == null) { return ""; } String filePath = URLtoString(url); // special case of .delib files, which are directories, but we want them to appear as files File file = new File(filePath); if (file.getName().toLowerCase().endsWith(".delib")) { filePath = file.getPath(); } //if (filePath.toLowerCase().endsWith(".delib"+File.separator)) // filePath = filePath.substring(0, filePath.length()-1); // remove trailing '/' int backSlashPos = filePath.lastIndexOf('\\'); int colonPos = filePath.lastIndexOf(':'); int slashPos = filePath.lastIndexOf('/'); int charPos = Math.max(backSlashPos, Math.max(colonPos, slashPos)); if (charPos < 0) { return ""; } return filePath.substring(0, charPos + 1); } /** * Method to return the pure file name of a URL. * The pure file name excludes the directory path and the extension. * It is used to find the library name from a URL. * For example, the URL "file:/users/strubin/gates.elib" has the pure file name "gates". * @param url the URL to the file. * @return the pure file name. */ public static String getFileNameWithoutExtension(URL url) { return getFileNameWithoutExtension(URLtoString(url), true); } /** * Method to return the pure file name of a file path. * The pure file name excludes the directory path and the extension. * It is used to find the library name from a file path. * For example, the file path "file:/users/strubin/gates.elib" has the pure file name "gates". * @param fileName full name of file. * @param removePath if only file name without path should be extracted * @return the pure file name. */ public static String getFileNameWithoutExtension(String fileName, boolean removePath) { if (removePath) { // special case if the library path came from a different computer system and still has separators while (fileName.endsWith("\\") || fileName.endsWith(":") || fileName.endsWith("/")) { fileName = fileName.substring(0, fileName.length() - 1); } int backSlashPos = fileName.lastIndexOf('\\'); int colonPos = fileName.lastIndexOf(':'); int slashPos = fileName.lastIndexOf('/'); int charPos = Math.max(backSlashPos, Math.max(colonPos, slashPos)); if (charPos >= 0) { fileName = fileName.substring(charPos + 1); } } int dotPos = fileName.lastIndexOf('.'); if (dotPos >= 0) { fileName = fileName.substring(0, dotPos); } // make sure the file name is legal StringBuffer buf = null; for (int i = 0; i < fileName.length(); i++) { char ch = fileName.charAt(i); if (ch == '\n' || ch == '|' || ch == ':' || ch == ' ' || ch == '{' || ch == '}' || ch == ';') { if (buf == null) { buf = new StringBuffer(); buf.append(fileName.substring(0, i)); } buf.append('-'); continue; } else if (buf != null) { buf.append(ch); } } if (buf != null) { String newS = buf.toString(); System.out.println("File name " + fileName + " was converted to " + newS); return newS; } return fileName; } /** * Method to return the extension of the file in a URL. * The extension is the part after the last dot. * For example, the URL "file:/users/strubin/gates.elib" has the extension "elib". * @param url the URL to the file. * @return the extension of the file ("" if none). */ public static String getExtension(URL url) { if (url == null) { return ""; } String fileName = URLtoString(url); return getExtension(fileName); } /** * Method to return the extension of the file. * The extension is the part after the last dot. * For example, the URL "file:/users/strubin/gates.elib" has the extension "elib". * @param fileName String containing the file name. * @return the extension of the file ("" if none). */ public static String getExtension(String fileName) { if (fileName.endsWith("/")) { // to handle "XXX.delib/" fileName = fileName.substring(0, fileName.length() - 1); } int dotPos = fileName.lastIndexOf('.'); if (dotPos < 0) { return ""; } return fileName.substring(dotPos + 1); } /** * Method to open an input stream to a URL. * @param url the URL to the file. * @return the InputStream, or null if the file cannot be found. */ public static InputStream getURLStream(URL url) { return getURLStream(url, null); } /** * Method to open an input stream to a URL. * @param url the URL to the file. * @param errorMsg a string buffer in which to print any error message. If null, * any error message is printed to System.out * @return the InputStream, or null if the file cannot be found. */ public static InputStream getURLStream(URL url, StringBuffer errorMsg) { if (url != null) { try { return url.openStream(); } catch (IOException e) { if (errorMsg != null) { errorMsg.append("Error: cannot open " + e.getMessage() + "\n"); } else { System.out.println("Error: cannot open " + e.getMessage()); } } } return null; } /** * Method to tell whether a given URL exists. * @param url the URL in question. * @return true if the file exists. */ public static boolean URLExists(URL url) { return URLExists(url, null); } /** * Method to tell whether a given URL exists. * @param url the URL in question. * @param errorMsg a string builder in which to print any error message. * If null, errors are not printed. * @return true if the file exists. */ public static boolean URLExists(URL url, StringBuilder errorMsg) { if (url == null) { return false; } try { URLConnection con = url.openConnection(); con.connect(); int conLength = con.getContentLength(); con.getInputStream().close(); if (conLength < 0) { return false; } } catch (Exception e) { if (errorMsg != null) { errorMsg.append("Error: cannot open " + e.getMessage() + "\n"); } return false; } return true; } /** * Method to examine a path and return all resources found there. * @param resourceName the path to examine. * @return a List of all resource names found there. */ public static List<String> getAllResources(String resourceName) { List<String> retval = new ArrayList<String>(); String cp = System.getProperty("java.class.path", "."); String[] cpElements = cp.split(File.pathSeparator); for (String cpElement : cpElements) { String classPath = cpElement + File.separator + resourceName.replace('.', File.separatorChar); File file = new File(classPath); if (file.isDirectory()) { retval.addAll(getResourcesFromDirectory(file)); } else { File jarFile = new File(cpElement); retval.addAll(getResourcesFromJarFile(jarFile, resourceName.replace('.', '/'))); } } return retval; } private static List<String> getResourcesFromJarFile(File file, String resName) { List<String> retval = new ArrayList<String>(); try { ZipFile zf = new ZipFile(file); Enumeration<? extends ZipEntry> e = zf.entries(); while (e.hasMoreElements()) { ZipEntry ze = e.nextElement(); String entry = ze.getName(); if (entry.startsWith(resName)) { retval.add(entry.substring(resName.length() + 1)); } } zf.close(); } catch (IOException e) { } return retval; } private static List<String> getResourcesFromDirectory(File directory) { List<String> retval = new ArrayList<String>(); File[] fileList = directory.listFiles(); for (File file : fileList) { if (file.isDirectory()) { retval.addAll(getResourcesFromDirectory(file)); } else { String fileName = file.getName(); retval.add(fileName); } } return retval; } /****************************** FOR SORTING OBJECTS ******************************/ /** * A comparator object for sorting Strings that may have numbers in them. * Created once because it is used often. */ public static final Comparator<String> STRING_NUMBER_ORDER = new Comparator<String>() { /** * Method to compare two names and give a sort order. * The comparison considers numbers in numeric order so that the * string "in10" comes after the string "in9". * * Formal definition of order. * Lets insert in string's character sequence number at start of digit sequences. * Consider that numbers in the sequence are less than chars. * * Examples below are in increasing order: * "" { } * "0" { 0, '0' } * "9" { 9, '9' } * "10" { 10, '1', '0' } * "2147483648" { 2147483648, '2', '1', '4', '7', '4', '8', '3', '6', '4', '8' } * " " { ' ' } * "-" { '-' } * "-1" { '-', 1, '1' } * "-2" { '-', 2, '2' } * "a" { 'a' } * "a0" { 'a', 0, '0' } * "a0-0" { 'a', 0, '0', '-', 0, '0' } * "a00" { 'a', 0, '0', '0' } * "a0a" { 'a', 0, '0', 'a' } * "a01" { 'a', 1, '0', '1' } * "a1" { 'a', 1, '1' } * "a[1]" { 'a', '[', 1, '1', ']' } * "a[10]" { 'a', '[', 10, '1', '0', ']' } * "in" { 'i', 'n' } * "in1" { 'i', 'n', 1, '1' } * "in1a" { 'i', 'n', 1, '1', 'a' } * "in9" { 'i', 'n', 9, '9' } * "in10" { 'i', 'n', 10, '1', '0' } * "in!" { 'i', 'n', '!' } * "ina" { 'i , 'n', 'a' } * * @param o1 the first string. * @param o2 the second string. * @return 0 if they are equal, nonzero according to order. */ public int compare(String name1, String name2) { int len1 = name1.length(); int len2 = name2.length(); int extent = Math.min(len1, len2); for (int pos = 0; pos < extent; pos++) { char ch1 = name1.charAt(pos); char ch2 = name2.charAt(pos); if (ch1 != ch2) { int digit1 = digit(ch1); int digit2 = digit(ch2); if (digit1 >= 0 || digit2 >= 0) { int pos1 = pos + 1, pos2 = pos + 1; // Positions in string to compare // One char is digit, another is not. Is previous digit ? int digit = pos > 0 ? digit(name1.charAt(--pos)) : -1; if (digit < 0 && (digit1 < 0 || digit2 < 0)) { // Previous is not digit. Number is less than non-number. return digit2 - digit1; } // Are previous digits all zeros ? while (digit == 0) { digit = pos > 0 ? digit(name1.charAt(--pos)) : -1; } if (digit < 0) { // All previous digits are zeros. Skip zeros further. while (digit1 == 0) { digit1 = pos1 < len1 ? digit(name1.charAt(pos1++)) : -1; } while (digit2 == 0) { digit2 = pos2 < len2 ? digit(name2.charAt(pos2++)) : -1; } } // skip matching digits while (digit1 == digit2 && digit1 >= 0) { digit1 = pos1 < len1 ? digit(name1.charAt(pos1++)) : -1; digit2 = pos2 < len2 ? digit(name2.charAt(pos2++)) : -1; } boolean dig1 = digit1 >= 0; boolean dig2 = digit2 >= 0; for (int i = 0; dig1 && dig2; i++) { dig1 = pos1 + i < len1 && digit(name1.charAt(pos1 + i)) >= 0; dig2 = pos2 + i < len2 && digit(name2.charAt(pos2 + i)) >= 0; } if (dig1 != dig2) { return dig1 ? 1 : -1; } if (digit1 != digit2) { return digit1 - digit2; } } return ch1 - ch2; } } return len1 - len2; } }; private static int digit(char ch) { if (ch < '\u0080') { return ch >= '0' && ch <= '9' ? ch - '0' : -1; } return Character.digit((int) ch, 10); } // /** // * Test of STRING_NUMBER_ORDER. // */ // private static String[] numericStrings = { // "", // { } // "0", // { 0, '0' } // "0-0", // { 0, '0', '-', 0, '0' } // "00", // { 0, '0', '0' } // "0a", // { 0, '0', 'a' } // "01", // { 1, '0', '1' } // "1", // { 1, '1' } // "9", // { 9, '9' } // "10", // { 10, '1', '0' } // "12", // { 12, '1', '2' } // "102", // { 102, '1', '0', '2' } // "2147483648", // { 2147483648, '2', '1', '4', '7', '4', '8', '3', '6', '4', '8' } // " ", // { ' ' } // "-", // { '-' } // "-1", // { '-', 1, '1' } // "-2", // { '-', 2, '2' } // "a", // { 'a' } // "a0", // { 'a', 0, '0' } // "a0-0", // { 'a', 0, '0', '-', 0, '0' } // "a00", // { 'a', 0, '0', '0' } // "a0a", // { 'a', 0, '0', 'a' } // "a01", // { 'a', 1, '0', '1' } // "a1", // { 'a', 1, '1' } // "a[1]", // { 'a', '[', 1, '1', ']' } // "a[10]", // { 'a', '[', 10, '1', '0', ']' } // "in", // { 'i', 'n' } // "in1", // { 'i', 'n', 1, '1' } // "in1a", // { 'i', 'n', 1, '1', 'a' } // "in9", // { 'i', 'n', 9, '9' } // "in10", // { 'i', 'n', 10, '1', '0' } // "in!", // { 'i', 'n', '!' } // "ina" // { 'i , 'n', 'a' } // }; // // static { // for (int i = 0; i < numericStrings.length; i++) // { // for (int j = 0; j < numericStrings.length; j++) // { // String s1 = numericStrings[i]; // String s2 = numericStrings[j]; // int cmp = STRING_NUMBER_ORDER.compare(s1, s2); // if (i == j && cmp != 0 || i < j && cmp >= 0 || i > j && cmp <= 0) // System.out.println("Error in TextUtils.nameSameNumeric(\"" + // s1 + "\", \"" + s2 + "\") = " + cmp); // } // } // } /** * Comparator class for sorting Objects by their string name. */ public static class ObjectsByToString implements Comparator<Object> { /** * Method to sort Objects by their string name. */ public int compare(Object o1, Object o2) { String s1 = o1.toString(); String s2 = o2.toString(); return s1.compareToIgnoreCase(s2); } } /** * Comparator class for sorting Cells by their view order. */ public static class CellsByView implements Comparator<Cell> { /** * Method to sort Cells by their view order. */ public int compare(Cell c1, Cell c2) { View v1 = c1.getView(); View v2 = c2.getView(); return v1.getOrder() - v2.getOrder(); } } /** * Comparator class for sorting Cells by their version number. */ public static class CellsByVersion implements Comparator<Cell> { /** * Method to sort Cells by their version number. */ public int compare(Cell c1, Cell c2) { return c2.getVersion() - c1.getVersion(); } } /** * Comparator class for sorting Cells by their name (NOT considering numbers in the names). */ public static class CellsByName implements Comparator<Cell> { /** * Method to sort Cells by their name. */ public int compare(Cell c1, Cell c2) { String r1 = c1.getName(); String r2 = c2.getName(); return r1.compareTo(r2); } } /** * Comparator class for sorting Cells by their date. */ public static class CellsByDate implements Comparator<Cell> { /** * Method to sort Cells by their date. */ public int compare(Cell c1, Cell c2) { Date r1 = c1.getRevisionDate(); Date r2 = c2.getRevisionDate(); return r1.compareTo(r2); } } /** * Comparator class for sorting Preferences by their name. */ public static class PrefsByName implements Comparator<Pref> { /** * Method to sort Preferences by their name. */ public int compare(Pref p1, Pref p2) { String s1 = p1.getPrefName(); String s2 = p2.getPrefName(); return s1.compareToIgnoreCase(s2); } } /** * Comparator class for sorting Networks by their name. */ public static class NetworksByName implements Comparator<Network> { /** * Method to sort Networks by their name. */ public int compare(Network n1, Network n2) { String s1 = n1.describe(false); String s2 = n2.describe(false); return s1.compareToIgnoreCase(s2); } } public static final Comparator<Connection> CONNECTIONS_ORDER = new Comparator<Connection>() { public int compare(Connection c1, Connection c2) { int i1 = c1.getPortInst().getPortProto().getPortIndex(); int i2 = c2.getPortInst().getPortProto().getPortIndex(); int cmp = i1 - i2; if (cmp != 0) { return cmp; } cmp = c1.getArc().getArcId() - c2.getArc().getArcId(); if (cmp != 0) { return cmp; } return c1.getEndIndex() - c2.getEndIndex(); } }; /** * Method to replace all special characters in the instance name coming from external files such as"/".. * @param n * @param onlyBrackets * @param correctBrackets * @return String where characters "/", "[", "]" are replaced by "_". "\" is removed. */ public static String correctName(String n, boolean onlyBrackets, boolean correctBrackets) { int index; // removing brackets only if ] is not the last item in the string // It doesn't correct brackets if {a[1],b[2],c[3]} in VerilogReading if (correctBrackets) { index = n.indexOf("]"); if (index != -1 && index < n.length() - 1) { n = n.replace('[', '-'); n = n.replace("]", "-"); } } if (onlyBrackets) { return n; } // First replace "/" for "-" index = n.indexOf("/"); if (index != -1) { n = n.replaceAll("/", "_"); } // Remove possible space character representing as \ // index = n.indexOf("\\"); // if (index != -1) // { //// assert(false); // detect this before // n = n.substring(index+1); // } return n; } /** * Class to define the kind of text string to search */ public enum WhatToSearch { ARC_NAME("Arc Name"), ARC_VAR("Arc Variable"), NODE_NAME("Node Name"), NODE_VAR("Node Variable"), EXPORT_NAME("Export Name"), EXPORT_VAR("Export Variable"), CELL_VAR("Cell Name"), TEMP_NAMES(null); private String descriptionOfObjectFound; private WhatToSearch(String descriptionOfObjectFound) { this.descriptionOfObjectFound = descriptionOfObjectFound; } public String toString() { return descriptionOfObjectFound; } } private static Set<String> missingComponentNames = new HashSet<String>(); private static Set<String> missingPrivateComponentNames = new HashSet<String>(); private static Set<String> missingTechnologyNames = new HashSet<String>(); /** * Method to report a missing component, not found in the classpath. * @param name a missing component, not found in the classpath. */ public static void recordMissingComponent(String name) { missingComponentNames.add(name); } /** * Method to report a missing technologies, not found in the classpath. * @param name a missing technologies, not found in the classpath. */ public static void recordMissingTechnology(String name) { missingTechnologyNames.add(name); } /** * Method to report a missing private component, not found in the classpath. * Private components are those not publicly available. * @param name a missing private component, not found in the classpath. */ public static void recordMissingPrivateComponent(String name) { missingPrivateComponentNames.add(name); } /** * Method to return a list of components that were not found in Electric plugins. * @return a list of components that were not found in Electric plugins. */ public static Set<String> getMissingComponentNames() { if (missingTechnologyNames.size() > 0) { String techNames = null; for(String tech : missingTechnologyNames) { if (techNames == null) techNames = "Technologies ("; else techNames += ", "; techNames += tech; } techNames += ")"; missingComponentNames.add(techNames); } return missingComponentNames; } /** * Method to return a list of private (internal) components that were not found in Electric plugins. * @return a list of private (internal) components that were not found in Electric plugins. */ public static Set<String> getMissingPrivateComponentNames() { return missingPrivateComponentNames; } }