![JAR search and dependency download from the Maven repository](/logo.png)
com.sun.electric.database.variable.VarContext Maven / Gradle / Ivy
/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: VarContext.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.database.variable;
import com.sun.electric.database.ImmutableNodeInst;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Nodable;
import com.sun.electric.database.text.Name;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.lang.EvalJavaBsh;
import com.sun.electric.tool.lang.EvalSpice;
import com.sun.electric.util.TextUtils;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
/**
* VarContext represents a hierarchical path of NodeInsts. Its
* primary use is to determine the value of variables which contain
* Java code. In particular, the syntax @foo expands to P("foo")
* which looks for the variable called foo in the NodeInst of this
* VarContext.
*
* A VarContext can also be used to recover the names of instances
* along a hierarchical path. LE.getdrive() is an example of a
* method that does this.
*
*
The VarContext object that represents the base top level is
* VarContext.globalContext. You can get a new VarContext with an extra
* NodeInst context attached by calling push(NodeInst) on the parent
* context. You can get a VarContext with the most recent NodeInst
* removed by calling pop(). Note that individual VarContexts are
* immutable: you get new ones by calling push and pop; push and pop
* do not edit their own VarContexts.
*
*
Retrieve a Variable by calling getVar(String name) on any
* ElectricObject.
*
*
If the one knows that the Variable contains an object that
* does not need to be evaluated, that object can be retrieved using
* Variable.getObject().
*
*
On the other hand, if the object may need to be evaluated because
* it is type Java, TCL, or Lisp, then such evaluation may be hierarchy
* dependent and one must call context.evalVar(variable).
*
*
Extra variables defined in the interpreter:
*
*
Extra functions defined in the interpreter:
*
* P(name) -- get the value of variable name on the most recent NodeInst.
* Defaults to Integer(0).
*
* Methods PD(), PAR(), and PARD() are either gone or deprecated (RKao). Here
* are what they used to do:
*
* PD(name, default) -- get the value of variable name on the most recent
* NodeInst. Defaults to default.
* PAR(name) -- get the value of variable name on any NodeInst, starting
* with the most recent. Defaults to Integer(0).
* PARD(name, default) -- get the value of variable name on any NodeInst,
* starting with the most recent. Defaults to default.
*
* This class is thread-safe.
*/
public class VarContext implements Serializable {
private static class ValueCache {
private static class EvalPair {
private final CodeExpression ce;
private final Object info;
public EvalPair(CodeExpression e, Object i) {
ce = e;
info = i;
}
public int hashCode() {
return ce.hashCode() * info.hashCode();
}
public boolean equals(Object o) {
if (!(o instanceof EvalPair)) {
return false;
}
EvalPair ep = (EvalPair) o;
return ce == ep.ce && info == ep.info;
}
}
private final Map cache = new HashMap();
public synchronized boolean containsKey(CodeExpression ce, Object info) {
return cache.containsKey(new EvalPair(ce, info));
}
public synchronized Object get(CodeExpression ce, Object info) {
return cache.get(new EvalPair(ce, info));
}
public synchronized void put(CodeExpression ce, Object info, Object value) {
EvalPair key = new EvalPair(ce, info);
Job.error(cache.containsKey(key), "duplicate keys in ValueCache?");
cache.put(key, value);
}
}
private static final Object FAST_EVAL_FAILED = new Object();
private final VarContext prev;
private final Cell cell;
private final int nodeId;
private Nodable ni;
private transient ValueCache cache;
// ------------------------ private methods -------------------------------
// For the global context.
private VarContext() {
this.cell = null;
this.nodeId = -1;
this.prev = this;
this.cache = null;
}
private VarContext(Nodable ni, VarContext prev, boolean caching) {
this.cell = ni.getParent();
this.nodeId = ni instanceof NodeInst ? ((NodeInst)ni).getD().nodeId : -1;
this.ni = ni;
this.prev = prev;
this.cache = caching ? new ValueCache() : null;
}
private VarContext(Cell cell, ImmutableNodeInst n, VarContext prev) {
if (cell == null)
throw new NullPointerException();
if (n == null)
throw new NullPointerException();
this.cell = cell;
this.nodeId = n.nodeId;
this.prev = prev;
this.cache = null;
}
private Object readResolve() {
return prev != this ? this : globalContext;
}
private void throwNotFound(String name) throws EvalException {
throw new EvalException(name.replaceFirst("ATTR_", "") + " not found");
}
private Object ifNotNumberTryToConvertToNumber(Object val) {
if (val == null) {
return val;
}
if (val instanceof Number) {
return val;
}
try {
Number d = TextUtils.parsePostFixNumber(val.toString(), null);
val = d;
} catch (java.lang.NumberFormatException e) {
// just return original val object
}
return val;
}
/** If expression is a simple variable reference then return the name of
* the variable; otherwise return null */
private String getSimpleVarRef(String expr) {
final String pOpen = "P(\"";
final int pOpenLen = pOpen.length();
final String pClose = "\")";
final int pCloseLen = pClose.length();
if (expr.startsWith(pOpen) && expr.endsWith(pClose)) {
String varNm = expr.substring(pOpenLen, expr.length() - pCloseLen);
return isValidIdentifier(varNm) ? varNm : null;
}
if (expr.startsWith("@")) {
String varNm = expr.substring(1);
return isValidIdentifier(varNm) ? varNm : null;
}
return null;
}
private boolean isValidIdentifier(String identifier) {
final int len = identifier.length();
for (int i = 0; i < len; i++) {
if (!TextUtils.isLetterOrDigit(identifier.charAt(i))) {
return false;
}
}
return true;
}
private Object fastJavaVarEval(CodeExpression ce, Object info) throws EvalException {
// Avoid re-computing the value if it is already in the cache.
// Use "contains" because the value might be null.
synchronized (this) {
if (cache != null && cache.containsKey(ce, info)) {
return cache.get(ce, info);
}
}
// Avoid calling bean shell if value is just a reference to another
// variable.
String expr = ce.getExpr();
String varNm = getSimpleVarRef(expr);
if (varNm == null) {
return FAST_EVAL_FAILED;
}
return lookupVarEval("ATTR_" + varNm);
}
// private void printValueCheckValue(Object value, Object checkValue) {
// System.out.println("fast eval mismatch");
// System.out.println(" fast value: "+value.toString());
// System.out.println(" slow value: "+checkValue.toString());
// }
// private void checkFastValue(Object value, Variable var, Object info) throws EvalException {
// if (value==FAST_EVAL_FAILED) return;
// Object checkValue = EvalJavaBsh.evalJavaBsh.evalVarObject(var.getObject(), this, info);
// if (value==null) {
// LayoutLib.error(value!=checkValue, "fast eval null mismatch");
// } else {
// if (!value.equals(checkValue)) {
// System.out.println("fast eval mismatch");
// printValueCheckValue(value, checkValue);
// LayoutLib.error(true, "fast eval mismatch");
// }
// }
// }
// ----------------------------- public methods ---------------------------
/**
* The blank VarContext that is the parent of all VarContext chains.
*/
public static final VarContext globalContext = new VarContext();
/**
* get a new VarContext that consists of the current VarContext with
* the given NodeInst pushed onto the stack
*/
public VarContext push(Nodable ni) {
return new VarContext(ni, this, false);
}
/**
* Push a new level onto the VarContext stack. If the value of any of
* ni's parameters is requested and if the computation of that value
* requires a call to the bean shell then the value is saved in this
* VarContext so that future requests don't result in additional calls
* to the bean shell. Note that this is implementing Call-By-Value
* semantics whereas the non-caching VarContext implements Call-By-Name.
*
* Be warned that there is no mechanism to automatically flush the caches
* when the design is modified. The design MUST NOT change over the
* life time of this VarContext. If the design might change then you should
* use the non-caching VarContext.
*/
public VarContext pushCaching(Nodable ni) {
return new VarContext(ni, this, true);
}
/**
* get a new VarContext that consists of the current VarContext with
* the given pair (Cell,ImmutableNodeInst) pushed onto the stack
*/
public VarContext push(Cell cell, ImmutableNodeInst n) {
return new VarContext(cell, n, this);
}
/**
* get the VarContext that existed before you called push on it.
* may return globalContext if the stack is empty.
*/
public VarContext pop() {
return prev;
}
/**
* Return the Node Instance that provides the context for the
* variable evaluation for this level.
*/
public Nodable getNodable() {
if (ni == null && nodeId >= 0) {
ni = cell.getNodeById(nodeId);
}
return ni;
}
/**
* Does deep comparison of two VarContexts. Matches
* hierarchy traversal. Returns true if they both represent
* the same hierarchy traversal, false otherwise. (Recursive method).
* Does not compare PortInsts.
* @param o the VarContext to compare against.
* @return true if equal, false otherwise.
*/
public boolean equals(Object o) {
if (!(o instanceof VarContext)) {
return false;
}
VarContext c = (VarContext) o;
if (this == c) {
return true; // both globalContext, or the same object
}
// the following line doesn't work (R KAO, IvanM)
//if (ni != c.getNodable()) return false; // compare nodeinsts
Cell c1 = cell;
Cell c2 = c.cell;
if (c1 == null || c2 == null) {
return c1 == c2;
}
String name1 = getNodable().getName();
String name2 = c.getNodable().getName();
if (!((c1 == c2) && (name1.equals(name2)))) {
return false;
}
return prev.equals(c.pop()); // compare parents
}
/**
* Get an iterator over the Nodables that describe this context.
* This iterator starts from the top of the hierarchy, and goes down.
* @return an iterator over the context path
*/
public Iterator getPathIterator() {
Stack stack = new Stack();
VarContext context = this;
while (context != VarContext.globalContext) {
Nodable no = context.getNodable();
stack.push(no);
context = context.pop();
}
List path = new ArrayList();
while (!stack.isEmpty()) {
path.add(stack.pop());
}
return path.iterator();
}
/**
* Remove N levels of parent context from this VarContext. Returns
* a new VarContext. This will return VarContext.globalContext
* if the 'levels' is greater than or equal to the number of levels
* in this context.
* @param levels the number of levels of parent context to remove
* @return a new VarContext
*/
public VarContext removeParentContext(int levels) {
Stack nodes = new Stack();
VarContext acontext = this;
while (acontext != VarContext.globalContext) {
nodes.push(acontext.getNodable());
acontext = acontext.pop();
}
for (int i = 0; i < levels; i++) {
nodes.pop();
}
acontext = VarContext.globalContext;
int size = nodes.size();
for (int i = 0; i < size; i++) {
Nodable no = nodes.pop();
acontext = acontext.push(no);
}
return acontext;
}
/**
* Get the number of levels of context in this VarContext
* @return the number of levels of context in this VarContext
*/
public int getNumLevels() {
int i = 0;
VarContext acontext = this;
while (acontext != VarContext.globalContext) {
i++;
acontext = acontext.pop();
}
return i;
}
/** Get rid of the variable cache thereby release its storage */
public synchronized void deleteVariableCache() {
cache = null;
}
// ------------------------------ Variable Evaluation -----------------------
/**
* Gets the value of Variable @param var.
* If variable is Java, uses EvalJavaBsh to evaluate
* If variable is TCL, uses ... to evaluate
* If variable is Lisp, uses ... to evaluate
* otherwise, just returns the Variable's object
* @return the evlauated Object. Returns null if the variable
* is code and evaluation fails.
*/
public Object evalVar(Variable var) {
return evalVar(var, null);
}
/** Same as evalVar, except an additional object 'info'
* is passed to the evaluator. 'info' may be or contain
* additional information necessary for proper evaluation.
* Usually info is the NodeInst on which the var exists.
* @return the evlauated Object. Returns null if the variable
* is code and evaluation fails.
*/
public Object evalVar(Variable var, Object info) {
if (var == null) {
return null;
}
try {
return evalVarRecurse(var, info);
} catch (EvalException e) {
printException(e, var, this, info);
return null;
}
}
public static class EvalException extends Exception {
public EvalException() {
super();
}
public EvalException(String message) {
super(message);
}
public EvalException(String message, Throwable cause) {
super(message, cause);
}
public EvalException(Throwable cause) {
super(cause);
}
}
public static void printException(EvalException e, Variable var, VarContext context, Object info) {
// If cause is null, this was an error in electric (var lookup etc)
if (e.getCause() == null) {
return;
}
// otherwise, this is an error in the bean shell
String msg = "Exception caught evaluating " + var.getCode()
+ " var " + var.getTrueName() + (e.getMessage() == null ? "" : ": " + e.getMessage());
if (info instanceof Nodable) {
NodeInst ni = ((Nodable) info).getNodeInst();
System.out.println("In Cell " + ni.getParent().describe(false) + ", on Node " + ni.describe(false) + ": " + msg);
} else {
System.out.println(msg);
}
}
/**
* This is the recursive version of evalVar that may throw an EvalException.
* The message of the EvalException states the reason that evaluation failed.
* This is made public so code elsewhere that is meant to be used in Attribute's java code
* can call this method. This is useful such that the first EvalException encountered is
* thrown all the way to the top of the eval caller, rather than getting caught and handled
* somewhere inbetween.
* @param var the variable to evaluate
* @param info an info object that may be needed by the evaluator
* @return the variable's object if not code, otherwise an evaluated result object if
* evaluation succeeds
* @throws EvalException an exception whose message contains the reason evaluation failed
*/
public Object evalVarRecurse(Variable var, Object info) throws EvalException {
CodeExpression ce = var.getCodeExpression();
if (ce == null) {
return ifNotNumberTryToConvertToNumber(var.getObject());
}
switch (ce.getCode()) {
case JAVA:
Object value = fastJavaVarEval(ce, info);
// testing code
//checkFastValue(value, var, info);
if (value == FAST_EVAL_FAILED) {
// OK, I give up. Call the darn bean shell.
value = EvalJavaBsh.evalJavaBsh.evalVarObject(ce, this, info);
synchronized (this) {
if (cache != null) {
cache.put(ce, info, value);
}
}
}
return ifNotNumberTryToConvertToNumber(value);
case SPICE:
// if (ce.getExpr().equals("")) // empty expression
// return new String("?");
Object obj = evalSpice_(ce, true);
if (obj instanceof Number) {
Number n = (Number) obj;
if (n.doubleValue() < 0.001) {
return TextUtils.formatDoublePostFix(n.doubleValue());
}
}
if (obj instanceof EvalSpice.SimpleEq) {
// couldn't parse, just return original
return ce.getExpr();
}
return obj;
case TCL:
return ifNotNumberTryToConvertToNumber(ce.getExpr());
default:
throw new AssertionError();
}
}
/**
* Evaluate a spice variable
* @param var the variable to evaluate
* @param recurse false to not recursively evaluate (only if param call is SPICE code).
* @return the evaluated string, or null on eval error
*/
public Object evalSpice(Variable var, boolean recurse) {
//if (var.getCode() != TextDescriptor.Code.SPICE) return null;
try {
return evalSpice_(var.getCodeExpression(), recurse);
} catch (EvalException e) {
return null;
}
}
/** For replacing @variable */
private static final Pattern pPat = Pattern.compile("P\\(\"(\\w+)\"\\)");
private Object evalSpice_(CodeExpression ce, boolean recurse) throws EvalException {
assert ce.getCode() == CodeExpression.Code.SPICE;
String expr = EvalJavaBsh.replace(ce.getExpr());
Matcher pMat = pPat.matcher(expr);
StringBuffer sb = new StringBuffer();
while (pMat.find()) {
Object value = pMat.group(1).substring(5);
Variable parentVar = null;
Nodable no = getNodable();
if (no != null) {
parentVar = no.getParameter(Variable.findKey(pMat.group(1)));
}
if (parentVar != null) {
// see if param is spice code by looking at instance code
boolean isSpiceCode = parentVar.getCode() == CodeExpression.Code.SPICE;
// if (no.isCellInstance()) {
// Cell c = (Cell)no.getProto();
// if (c.contentsView() != null) c = c.contentsView();
// Variable protoVar = c.getParameterOrVariable(parentVar.getKey());
// if (protoVar != null)
// isSpiceCode = protoVar.getCode() == TextDescriptor.Code.SPICE;
// }
if (recurse || !isSpiceCode) {
value = pop().evalVarRecurse(parentVar, getNodable());
if (value == null) {
value = "";
}
}
}
pMat.appendReplacement(sb, value.toString());
}
pMat.appendTail(sb);
EvalSpice sp = new EvalSpice(sb.toString());
return sp.evaluate();
}
/**
* Lookup Variable one level up the hierarchy and evaluate.
* Looks for the var on the most recent NodeInst on the
* hierarchy stack. If not found, look for the default
* Variable of the same name on the NodeProto.
* @param name the name of the variable
* @return an object representing the evaluated variable,
* or null if no var or default var found.
*/
public Object lookupVarEval(String name) throws EvalException {
Nodable ni = getNodable();
if (ni == null) {
throwNotFound(name);
}
Variable.Key key = Variable.findKey(name);
if (key == null) {
throwNotFound(name);
}
// Topology.java concludes that all instances of a Cell have
// the same transistor sizes if that Cell has no parameters.
// Let's see if we can enforce these semantics. A Variable has
// a value that is visible in a Cell only if that Variable is
// is a "parameter".
// RKao
Variable var = ni.getParameter(key);
// Variable var = ni.getVar(key);
//
// if (var == null && ni.isCellInstance()) {
// // look up default var on prototype
// Cell cell = (Cell)ni.getProto();
// Cell equiv = cell.getEquivalent();
// if (equiv != null) cell = equiv;
// var = cell.getVar(key);
// }
if (var == null) {
throwNotFound(name);
}
// evaluate var in it's context
Object val = this.pop().evalVarRecurse(var, ni);
if (val == null) {
throwNotFound(name);
}
val = ifNotNumberTryToConvertToNumber(val);
if (val == null) {
throwNotFound(name);
}
return val;
}
// lookupVarFarEval() is deprecated. Topology.java concludes that all instances
// of a Cell have the same transistor sizes if that Cell has no parameters.
// lookupVarFarEval() violates this assumption. Luckily we believe that no
// designs use PAR() or lookupVarFarEval(). I'm commenting out this method to
// guarantee that Topology.java always works correctly. RKao
// /**
// * Lookup Variable on all levels up the hierarchy and evaluate.
// * Looks for var on all NodeInsts on the stack, starting
// * with the most recent. At each NodeInst, if no Variable
// * found, looks for default Variable on NodeProto.
// * @param name the name of the variable
// * @return evaluated Object, or null if not found
// */
// protected Object lookupVarFarEval(String name) throws EvalException
// {
// Variable.Key key = Variable.findKey(name);
// if (key == null) throwNotFound(name);
//
// // look up the entire stack, starting with end
// VarContext scan = this;
// Object value = null;
// while (true)
// {
// Nodable sni = scan.getNodable();
// if (sni == null) break;
//
// Variable var = sni.getVar(key); // look up var
// if (var != null) {
// value = scan.pop().evalVarRecurse(var, sni);
// break;
// }
//
// // look up default var value on prototype
// if (sni.isCellInstance()) {
// Cell cell = (Cell)sni.getProto();
// Cell equiv = cell.getEquivalent();
// if (equiv != null) cell = equiv;
// var = cell.getVar(key);
// }
// if (var != null) {
// value = scan.pop().evalVarRecurse(var, sni);
// break;
// }
// scan = scan.prev;
// }
// if (value == null) throwNotFound(name);
//
// value = ifNotNumberTryToConvertToNumber(value);
// if (value == null) throwNotFound(name);
//
// return value;
// }
// ---------------------------------- Utility Methods ----------------------------
/** Return the concatonation of all instances names left to right
* from the root to the leaf. Begin with the string with a separator
* and place a separator between adjacent instance names.
* @param sep the separator string.
*/
public String getInstPath(String sep) {
if (this == globalContext) {
return "";
}
String prefix = pop() == globalContext ? "" : pop().getInstPath(sep);
Nodable no = getNodable();
if (no == null) {
System.out.println("VarContext.getInstPath: context with null NodeInst?");
}
String me = no.getName();
if (no instanceof NodeInst) {
// nodeInst, we want netlisted name, assume zero index of arrayed node
//no = Netlist.getNodableFor((NodeInst)no, 0);
Name name = no.getNameKey();
me = name.subname(0).toString();
}
// if (me==null) {
// //System.out.println("VarContext.getInstPath: NodeInst in VarContext with no name!!!");
// me = ni.describe();
// }
if (prefix.equals("")) {
return me;
}
return prefix + sep + me;
}
/** Helper method to convert an Object to a float, if possible.
* if not possible, return @param def.
*/
public static float objectToFloat(Object obj, float def) {
if (obj == null) {
return def;
}
if (obj instanceof Number) {
return ((Number) obj).floatValue();
}
try {
Number n = TextUtils.parsePostFixNumber(obj.toString(), null);
return n.floatValue();
} catch (NumberFormatException e) {
}
return def;
}
/** Helper method to convert an Object to an integer, if possible.
* if not possible, return @param def.
*/
public static int objectToInt(Object obj, int def) {
if (obj == null) {
return def;
}
if (obj instanceof Number) {
return ((Number) obj).intValue();
}
try {
Number n = TextUtils.parsePostFixNumber(obj.toString(), null);
return n.intValue();
} catch (NumberFormatException e) {
}
return def;
}
/** Helper method to convert an Object to a short, if possible.
* if not possible, return @param def.
*/
public static short objectToShort(Object obj, short def) {
if (obj == null) {
return def;
}
if (obj instanceof Number) {
return ((Number) obj).shortValue();
}
try {
Number n = TextUtils.parsePostFixNumber(obj.toString(), null);
return n.shortValue();
} catch (NumberFormatException e) {
}
return def;
}
/** Helper method to convert an Object to a double, if possible.
* if not possible, return @param def.
*/
public static double objectToDouble(Object obj, double def) {
if (obj == null) {
return def;
}
if (obj instanceof Number) {
return ((Number) obj).doubleValue();
}
try {
Number n = TextUtils.parsePostFixNumber(obj.toString(), null);
return n.doubleValue();
} catch (NumberFormatException e) {
}
return def;
}
}