/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: Name.java
* Written by Eric Kim, Sun Microsystems.
*
* Copyright (c) 2004 Sun Microsystems and Static Free Software
*
* 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.tool.simulation.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A Name is a text-parsing object for port, node and arc names.
* These names can use bus notation:<BR>
* <CENTER>name = itemname { ',' itemname }</CENTER>
* <CENTER>itemname = simplename { '[' index ']' }</CENTER>
* <CENTER>index = indexitem { ',' indexitem ']' }</CENTER>
* <CENTER>indexitem = simplename | number ':' number</CENTER><BR>
* <CENTER>simplename = basename [ numericalSuffix ]</CENTER><BR>
* <CENTER>basename = string</CENTER><BR>
* <CENTER>numericalSuffix = number</CENTER><BR>
* string doesn't contain '[', ']', ',', ':'.
* Bus names are expanded into a list of subnames.
*/
public class Name implements Comparable
{
/** the original name */ private final String ons;
/** the canonical name */ private final String ns;
/** the lowercase name */ private final Name lowerCase;
/** list of subnames */ private Name[] subnames;
/** basename */ private final Name basename;
/** numerical suffix */ private final int numSuffix;
/** the flags */ private int flags;
/** Map String -> Name */ private static Map allNames = new HashMap();
/**
* Method to return the name object for this string.
* @param ns given string
* @return the name object for the string.
*/
public static synchronized final Name findName(String ns) { return findTrimmedName(trim(ns)); }
/**
* Method to check whether or not string is a valid name.
* @param ns given string
* @return the error description or null if string is correct name.
*/
public static String checkName(String ns)
{
try
{
checkNameThrow(ns);
return null;
} catch (NumberFormatException e)
{
return e.getMessage();
}
}
/**
* Returns a printable version of this Name.
* @return a printable version of this Name.
*/
public final String toString() { return ons; }
/**
* Returns lowerCase equivalent of this Name.
* @return lowerCase equivalent of this Name.
*/
public final Name lowerCase() { return lowerCase; }
/**
* Compares this Name with the specified Name for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.<p>
* @param name the Name to be compared.
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
*/
public int compareTo(Name name)
{
if (lowerCase == name.lowerCase) return 0;
return lowerCase.ns.compareTo(name.lowerCase.ns);
}
/**
* Compares this Name with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.<p>
* @param o the Object to be compared.
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
*
* @throws ClassCastException if the specified object's type prevents it
* from being compared to this Object.
*/
public int compareTo(Object o)
{
return compareTo((Name)o);
}
/**
* Compares this <code>Name</code> to another <code>Name</code>,
* ignoring case considerations. Two strings are considered equal
* ignoring case if they are of the same length, and corresponding
* characters in the two strings are equal ignoring case.
* @return <code>true</code> if names are equal,
* ignoring case; <code>false</code> otherwise.
*/
public boolean equals(Object anObject)
{
if (this == anObject) return true;
if (anObject instanceof Name)
{
Name anotherName = (Name)anObject;
return lowerCase == anotherName.lowerCase;
}
return false;
}
/**
* Returns a hash code for this TextDescriptor. The hash code for a
* <code>TextDescriptor</code> object is computed as sum of its fields.
* @return a hash code value for this object.
*/
public int hashCode() { return lowerCase == this ? super.hashCode() : lowerCase.hashCode(); }
/**
* Tells whether or not this Name is a valid bus or signal name.
* @return true if Name is a valid name.
*/
public final boolean isValid() { return (flags & ERROR) == 0; }
/**
* Tells whether or not this Name is a temporary name
* @return true if Name is a temporary name.
*/
public final boolean isTempname() { return (flags & TEMP) != 0; }
/**
* Tells whether Name has duplicate subnames.
* @return true if Name has duplicate subnames.
*/
public final boolean hasDuplicates() { return (flags & DUPLICATES) != 0; }
/**
* Tells whether Name has duplicate subnames.
* @return true if Name has duplicate subnames.
*/
public final boolean hasEmptySubnames() { return (flags & HAS_EMPTIES) != 0; }
/**
* Tells whether or not this Name is a list of names separated by comma.
* @return true if Name is a list of names separated by comma.
*/
public final boolean isList() { return (flags & LIST) != 0; }
/**
* Tells whether or not this Name is a bus name.
* @return true if name is a bus name.
*/
public final boolean isBus() { return subnames != null; }
/**
* Returns subname of a bus name.
* @param i an index of subname.
* @return the view part of a parsed Cell name.
*/
public final Name subname(int i) { return subnames == null ? this : subnames[i]; }
/**
* Returns number of subnames of a bus.
* @return the number of subnames of a bus.
*/
public final int busWidth() { return subnames == null ? 1 : subnames.length; }
/**
* Returns basename of simple Name.
* Returns null if not simple Name.
* @return base of name.
*/
public final Name getBasename() { return basename; }
/**
* Returns numerical suffix of simple Name.
* Returns zero if numerical suffix is absent or name is not simple.
* @return numerical suffix.
*/
public final int getNumSuffix() { return numSuffix; }
/**
* Returns the name obtained from base of this simple name by adding numerical suffix.
* Returns null if name is not simple or if i is negative.
* @param i numerical suffix
* @return suffixed name.
*/
public final Name findSuffixed(int i)
{
if (i < 0 || basename == null) return null;
return findName(basename.toString()+i);
}
// ------------------ protected and private methods -----------------------
private static final int ERROR = 0x01;
private static final int LIST = 0x02;
private static final int BUS = 0x04;
private static final int SIMPLE = 0x08;
private static final int TEMP = 0x10;
private static final int DUPLICATES = 0x20;
private static final int HAS_EMPTIES = 0x40;
/**
* Returns the name object for this string, assuming that is is trimmed.
* @param ns given trimmed string
* @return the name object for the string.
*/
private static Name findTrimmedName(String ns)
{
Name name = (Name)allNames.get(ns);
if (name == null && ns != null)
{
name = new Name(ns);
allNames.put(name.ons, name);
}
return name;
}
/**
* Returns the trimmed string for given string.
* @param ns given string
* @return trimmed string, or null if argument is null
*/
private static String trim(String ns)
{
if (ns == null) return null;
int len = ns.length();
int newLen = 0;
for (int i = 0; i < len; i++)
{
if (ns.charAt(i) > ' ') newLen++;
}
if (newLen == len) return ns;
StringBuffer buf = new StringBuffer(newLen);
for (int i = 0; i < len; i++)
{
if (ns.charAt(i) > ' ') buf.append(ns.charAt(i));
}
return buf.toString();
}
/**
* Returns the trimmed string for given string.
* @param ns given string
* @return trimmed string.
*/
private static String trimPlusMinus(String ns)
{
int len = ns.length();
int newLen = 0;
for (int i = 0; i < len; i++)
{
char ch = ns.charAt(i);
if (ch != '+' && ch != '-') newLen++;
}
if (newLen == len) return ns;
StringBuffer buf = new StringBuffer(newLen);
for (int i = 0; i < len; i++)
{
char ch = ns.charAt(i);
if (ch != '+' && ch != '-') buf.append(ns.charAt(i));
}
return buf.toString();
}
/**
* Constructs a <CODE>Name</CODE> (cannot be called).
*/
private Name(String ons)
{
this.ons = ons;
this.ns = trimPlusMinus(ons);
int suffix = 0;
Name base = this;
String lower = ns.toLowerCase();
this.lowerCase = (ns.equals(lower) ? this : findTrimmedName(lower));
try
{
flags = checkNameThrow(ns);
} catch (NumberFormatException e)
{
flags = ERROR;
}
if ((flags & SIMPLE) != 0)
{
int l = ns.length();
while (l > 0 && TextUtils.isDigit(ns.charAt(l-1))) l--;
if (l == ns.length())
{
base = this;
} else {
base = findTrimmedName(ns.substring(0,l));
suffix = TextUtils.atoi(ns.substring(l));
}
}
this.numSuffix = suffix;
this.basename = base;
if (flags == ERROR) return;
if ((flags & BUS) == 0) return;
// Make subnames
if (isList())
{
makeListSubNames();
return;
}
int split = ns.indexOf('[');
if (split == 0) split = ns.lastIndexOf('[');
if (split == 0)
makeBracketSubNames();
else
makeSplitSubNames(split);
}
/**
* Makes subnames of a bus whose name is a list of names separated by commas.
*/
private void makeListSubNames()
{
List subs = new ArrayList();
for (int beg = 0; beg <= ns.length(); )
{
int end = beg;
while (end < ns.length() && ns.charAt(end) != ',')
{
if (ns.charAt(end) == '[')
{
while (ns.charAt(end) != ']') end++;
}
end++;
}
Name nm = findTrimmedName(ns.substring(beg,end));
for (int j = 0; j < nm.busWidth(); j++)
subs.add(nm.subname(j));
beg = end + 1;
}
setSubnames(subs);
}
/**
* Makes subnames of a bus whose name is indices list in brackets.
*/
private void makeBracketSubNames()
{
List subs = new ArrayList();
for (int beg = 1; beg < ns.length(); )
{
int end = ns.indexOf(',', beg);
if (end < 0) end = ns.length() - 1; /* index of ']' */
int colon = ns.indexOf(':', beg);
if (colon < 0 || colon >= end)
{
Name nm = findTrimmedName("["+ns.substring(beg,end)+"]");
subs.add(nm);
} else
{
int ind1 = Integer.parseInt(ns.substring(beg, colon));
int ind2 = Integer.parseInt(ns.substring(colon+1, end));
if (ind1 < ind2)
{
for (int i = ind1; i <= ind2; i++)
subs.add(findTrimmedName("["+i+"]"));
} else
{
for (int i = ind1; i >= ind2; i--)
subs.add(findTrimmedName("["+i+"]"));
}
}
beg = end+1;
}
setSubnames(subs);
}
private void setSubnames(List subs)
{
subnames = new Name[subs.size()];
subs.toArray(subnames);
// check duplicates
Name[] sorted = new Name[subs.size()];
subs.toArray(sorted);
Arrays.sort(sorted);
for (int i = 1; i < sorted.length; i++)
{
if (sorted[i].equals(sorted[i-1]))
{
flags |= DUPLICATES;
break;
}
}
}
/**
* Makes subnames of a bus whose name consists of simpler names.
* @param split index dividing name into simpler names.
*/
private void makeSplitSubNames(int split)
{
// if (ns.length() == 0) return;
if (split < 0 || split >= ns.length())
{
System.out.println("HEY! string is '"+ns+"' but want index "+split);
return;
}
Name baseName = findTrimmedName(ns.substring(0,split));
Name indexList = findTrimmedName(ns.substring(split));
subnames = new Name[baseName.busWidth()*indexList.busWidth()];
for (int i = 0; i < baseName.busWidth(); i++)
{
String bs = baseName.subname(i).toString();
for (int j = 0; j < indexList.busWidth(); j++)
{
String is = indexList.subname(j).toString();
subnames[i*indexList.busWidth()+j] = findTrimmedName(bs+is);
}
}
if (baseName.hasDuplicates() || indexList.hasDuplicates())
flags |= DUPLICATES;
}
/**
* Method to check whether or not string is a valid name.
* Throws exception on invaliod string
* @param ns given string
* @return flags describing the string.
*/
private static int checkNameThrow(String ns) throws NumberFormatException
{
int flags = SIMPLE;
int bracket = -1;
boolean wasBrackets = false;
int colon = -1;
if (ns.length() == 0) flags |= HAS_EMPTIES;
for (int i = 0; i < ns.length(); i++)
{
char c = ns.charAt(i);
if (bracket < 0)
{
colon = -1;
if (c == ']') throw new NumberFormatException("unmatched ']' in name");
if (c == ':') throw new NumberFormatException("':' out of brackets");
if (c == '[')
{
bracket = i;
flags &= ~SIMPLE;
if (i == 0 || ns.charAt(i-1) == ',') flags |= HAS_EMPTIES;
wasBrackets = true;
} else if (c == ',')
{
flags |= (LIST|BUS);
flags &= ~SIMPLE;
if (i == 0 || ns.charAt(i-1) == ',') flags |= HAS_EMPTIES;
wasBrackets = false;
} else if (wasBrackets) throw new NumberFormatException("Wrong character after brackets");
if (c == '@') flags |= TEMP;
continue;
}
if (c == '[') throw new NumberFormatException("nested bracket '[' in name");
if (c == ':')
{
if (colon >= 0) throw new NumberFormatException("too many ':' inside brackets");
if (i == bracket + 1) throw new NumberFormatException("has missing start of index range");
if (ns.charAt(bracket+1) == '-') throw new NumberFormatException("has negative start of index range");
for (int j = bracket + 1; j < i; j++)
{
if (!TextUtils.isDigit(ns.charAt(j)))
throw new NumberFormatException("has nonnumeric start of index range");
}
colon = i;
flags |= BUS;
}
if (colon >= 0 && (c == ']' || c == ','))
{
if (i == colon + 1) throw new NumberFormatException("has missing end of index range");
if (ns.charAt(colon+1) == '-') throw new NumberFormatException("has negative end of index range");
for (int j = colon + 1; j < i; j++)
{
if (!TextUtils.isDigit(ns.charAt(j)))
throw new NumberFormatException("has nonnumeric end of index range");
}
if (Integer.parseInt(ns.substring(bracket+1,colon)) == Integer.parseInt(ns.substring(colon+1,i)))
throw new NumberFormatException("has equal start and end indices");
colon = -1;
}
if (c == ']') bracket = -1;
if (c == ',')
{
bracket = i;
flags |= BUS;
}
if (c == '@') throw new NumberFormatException("'@' in brackets");
}
if ((flags & TEMP) != 0 && (flags & LIST) != 0) throw new NumberFormatException("list of temporary names");
if (bracket != -1) throw new NumberFormatException("Unclosed bracket");
return flags;
}
}