001: /*
002: * TclObjectBase.java
003: *
004: * Copyright (c) 1997 Sun Microsystems, Inc.
005: *
006: * See the file "license.terms" for information on usage and
007: * redistribution of this file, and for a DISCLAIMER OF ALL
008: * WARRANTIES.
009: *
010: * RCS: @(#) $Id: TclObjectBase.java,v 1.14 2006/06/20 01:48:23 mdejong Exp $
011: *
012: */
013:
014: package tcl.lang;
015:
016: import java.util.Hashtable;
017: import java.util.Enumeration;
018:
019: /**
020: * This class implements the basic notion of an "object" in Tcl. The
021: * fundamental representation of an object is its string value. However,
022: * an object can also have an internal representation, which is a "cached"
023: * reprsentation of this object in another form. The type of the internal
024: * rep of Tcl objects can mutate. This class provides the storage of the
025: * string rep and the internal rep, as well as the facilities for mutating
026: * the internal rep. The Jacl or TclBlend specific implementation of
027: * TclObject will extend this abstract base class.
028: */
029:
030: abstract class TclObjectBase {
031:
032: // Internal representation of the object. A valid TclObject
033: // will always have a non-null internal rep.
034:
035: protected InternalRep internalRep;
036:
037: // Reference count of this object. When 0 the object will be deallocated.
038:
039: protected int refCount;
040:
041: // String representation of the object.
042:
043: protected String stringRep;
044:
045: // Setting to true will enable a feature that keeps
046: // track of TclObject allocation, internal rep allocation,
047: // and transitions from one internal rep to another.
048:
049: static final boolean saveObjRecords = false;
050: static Hashtable objRecordMap = (saveObjRecords ? new Hashtable()
051: : null);
052:
053: // Only set this to true if running test code and
054: // the user wants to run extra ref count checks.
055: // Setting this to true will make key methods larger.
056:
057: static final boolean validate = false;
058:
059: // The ivalue field is used for a TclObject that contains
060: // an integer value. This implementation uses less
061: // memory than the previous approach that stored an
062: // int value in a internal rep of type TclInteger.
063: // This implementation executes integer operations
064: // more quickly since instanceof and upcast operations
065: // are no longer needed in the critical execution path.
066: // The ivalue field is always set after a call to
067: // setInternalRep() in the TclInteger class.
068:
069: int ivalue;
070:
071: // Note that the isIntType() and isDoubleType()
072: // methods are public because they need to be
073: // invoked by TJC compiled code. User code
074: // should not need to query the internal rep of
075: // a TclObject before operating on it.
076:
077: // Return true if the TclObject contains an int.
078:
079: public final boolean isIntType() {
080: return (internalRep == TclInteger.dummy);
081: }
082:
083: // Return true if the TclObject contains a TclString.
084:
085: final boolean isStringType() {
086: return (internalRep instanceof TclString);
087: }
088:
089: // Return true if the TclObject contains a TclDouble.
090:
091: public final boolean isDoubleType() {
092: return (internalRep instanceof TclDouble);
093: }
094:
095: // Return true if the TclObject contains a TclList.
096:
097: final boolean isListType() {
098: return (internalRep instanceof TclList);
099: }
100:
101: /**
102: * Creates a TclObject with the given InternalRep. This method should be
103: * called only by an InternalRep implementation.
104: *
105: * @param rep the initial InternalRep for this object.
106: */
107: protected TclObjectBase(final InternalRep rep) {
108: if (validate) {
109: if (rep == null) {
110: throw new TclRuntimeError("null InternalRep");
111: }
112: }
113: internalRep = rep;
114: //ivalue = 0;
115: //stringRep = null;
116: //refCount = 0;
117:
118: if (validate) {
119: if (internalRep == null) {
120: throw new TclRuntimeError("null internalRep");
121: }
122: if (ivalue != 0) {
123: throw new TclRuntimeError("non-zero ivalue");
124: }
125: if (stringRep != null) {
126: throw new TclRuntimeError("non-null stringRep");
127: }
128: if (refCount != 0) {
129: throw new TclRuntimeError("non-zero refCount");
130: }
131: }
132:
133: if (TclObjectBase.saveObjRecords) {
134: String key = "TclObject";
135: Integer num = (Integer) TclObject.objRecordMap.get(key);
136: if (num == null) {
137: num = new Integer(1);
138: } else {
139: num = new Integer(num.intValue() + 1);
140: }
141: TclObject.objRecordMap.put(key, num);
142: }
143: }
144:
145: /**
146: * Creates a TclObject with the given InternalRep and stringRep.
147: * This constructor is used by the TclString class only.
148: * No other code should call this constructor.
149: *
150: * @param rep the initial InternalRep for this object.
151: * @param s the initial string rep for this object.
152: */
153: protected TclObjectBase(final TclString rep, final String s) {
154: if (validate) {
155: if (rep == null) {
156: throw new TclRuntimeError("null InternalRep");
157: }
158: if (s == null) {
159: throw new TclRuntimeError("null String");
160: }
161: }
162: internalRep = rep;
163: //ivalue = 0;
164: stringRep = s;
165: //refCount = 0;
166:
167: if (validate) {
168: if (internalRep == null) {
169: throw new TclRuntimeError("null internalRep");
170: }
171: if (ivalue != 0) {
172: throw new TclRuntimeError("non-zero ivalue");
173: }
174: if (stringRep == null) {
175: throw new TclRuntimeError("null stringRep");
176: }
177: if (refCount != 0) {
178: throw new TclRuntimeError("non-zero refCount");
179: }
180: }
181:
182: if (TclObjectBase.saveObjRecords) {
183: String key = "TclObject";
184: Integer num = (Integer) TclObject.objRecordMap.get(key);
185: if (num == null) {
186: num = new Integer(1);
187: } else {
188: num = new Integer(num.intValue() + 1);
189: }
190: TclObject.objRecordMap.put(key, num);
191: }
192: }
193:
194: /**
195: * Creates a TclObject with the given integer value.
196: * This constructor is used by the TclInteger class only.
197: * No other code should call this constructor.
198: *
199: * @param ivalue the integer value
200: */
201: protected TclObjectBase(final int ivalue) {
202: internalRep = TclInteger.dummy;
203: this .ivalue = ivalue;
204: //stringRep = null;
205: //refCount = 0;
206:
207: if (validate) {
208: if (internalRep == null) {
209: throw new TclRuntimeError("null internalRep");
210: }
211: if (stringRep != null) {
212: throw new TclRuntimeError("non-null stringRep");
213: }
214: if (refCount != 0) {
215: throw new TclRuntimeError("non-zero refCount");
216: }
217: }
218:
219: if (TclObjectBase.saveObjRecords) {
220: String key = "TclObject";
221: Integer num = (Integer) TclObject.objRecordMap.get(key);
222: if (num == null) {
223: num = new Integer(1);
224: } else {
225: num = new Integer(num.intValue() + 1);
226: }
227: TclObject.objRecordMap.put(key, num);
228: }
229: }
230:
231: /**
232: * Returns the handle to the current internal rep. This method should be
233: * called only by an InternalRep implementation.
234: *
235: * @return the handle to the current internal rep.
236: */
237: public final InternalRep getInternalRep() {
238: if (validate) {
239: if (internalRep == null) {
240: disposedError();
241: }
242: }
243:
244: return internalRep;
245: }
246:
247: /**
248: * Change the internal rep of the object. The old internal rep
249: * will be deallocated as a result. This method should be
250: * called only by an InternalRep implementation.
251: *
252: * @param rep the new internal rep.
253: */
254: public void setInternalRep(InternalRep rep) {
255: if (internalRep == null) {
256: disposedError();
257: }
258: if (rep == null) {
259: throw new TclRuntimeError("null InternalRep");
260: }
261: if (rep == internalRep) {
262: return;
263: }
264:
265: //System.out.println("TclObject setInternalRep for \"" + stringRep + "\"");
266: //System.out.println("from \"" + internalRep.getClass().getName() +
267: // "\" to \"" + rep.getClass().getName() + "\"");
268: internalRep.dispose();
269: internalRep = rep;
270: ivalue = 0;
271: }
272:
273: /**
274: * Returns the string representation of the object.
275: *
276: * @return the string representation of the object.
277: */
278:
279: public final String toString() {
280: if (stringRep == null) {
281: // If this object has been deallocated, then
282: // the stringRep and the internalRep would
283: // have both been set to null. Generate
284: // a specific error in this case.
285:
286: if (internalRep == null) {
287: disposedError();
288: }
289:
290: if (isIntType()) {
291: stringRep = Integer.toString(ivalue);
292: } else {
293: stringRep = internalRep.toString();
294: }
295: }
296: return stringRep;
297: }
298:
299: /**
300: * Sets the string representation of the object to null. Next
301: * time when toString() is called, getInternalRep().toString() will
302: * be called. This method should be called ONLY when an InternalRep
303: * is about to modify the value of a TclObject.
304: *
305: * @exception TclRuntimeError if object is not exclusively owned.
306: */
307: public final void invalidateStringRep() throws TclRuntimeError {
308: if (internalRep == null) {
309: disposedError();
310: }
311: if (refCount > 1) {
312: throw new TclRuntimeError(
313: "string representation of object \"" + toString()
314: + "\" cannot be invalidated: refCount = "
315: + refCount);
316: }
317: stringRep = null;
318: }
319:
320: /**
321: * Returns true if the TclObject is shared, false otherwise.
322: *
323: */
324: public final boolean isShared() {
325: if (validate) {
326: if (internalRep == null) {
327: disposedError();
328: }
329: }
330:
331: return (refCount > 1);
332: }
333:
334: /**
335: * Tcl_DuplicateObj -> duplicate
336: *
337: * Duplicate a TclObject, this method provides the preferred
338: * means to deal with modification of a shared TclObject.
339: * It should be invoked in conjunction with isShared instead
340: * of using the deprecated takeExclusive method.
341: *
342: * Example:
343: *
344: * if (tobj.isShared()) {
345: * tobj = tobj.duplicate();
346: * }
347: * TclString.append(tobj, "hello");
348: *
349: * @return an TclObject with a refCount of 0.
350: */
351:
352: public final TclObject duplicate() {
353: if (validate) {
354: if (internalRep == null) {
355: disposedError();
356: }
357: }
358: if (isStringType() && (stringRep == null)) {
359: stringRep = internalRep.toString();
360: }
361: TclObject newObj;
362: if (isIntType()) {
363: newObj = new TclObject(TclInteger.dummy);
364: newObj.ivalue = ivalue;
365: } else {
366: newObj = new TclObject(internalRep.duplicate());
367: }
368:
369: if (stringRep != null) {
370: newObj.stringRep = stringRep;
371: }
372: //newObj.refCount = 0;
373: return newObj;
374: }
375:
376: /**
377: * @deprecated The takeExclusive method has been deprecated
378: * in favor of the new duplicate() method. The takeExclusive
379: * method would modify the ref count of the original object
380: * and return an object with a ref count of 1 instead of 0.
381: * These two behaviors lead to lots of useless duplication
382: * of objects that could be modified directly. This method
383: * exists only for backwards compatibility and will be
384: * removed at some point.
385: */
386:
387: public final TclObject takeExclusive() throws TclRuntimeError {
388: if (internalRep == null) {
389: disposedError();
390: }
391: if (refCount == 1) {
392: return (TclObject) this ;
393: } else if (refCount > 1) {
394: if (isStringType() && (stringRep == null)) {
395: stringRep = internalRep.toString();
396: }
397: TclObject newObj;
398: if (isIntType()) {
399: newObj = new TclObject(TclInteger.dummy);
400: newObj.ivalue = ivalue;
401: } else {
402: newObj = new TclObject(internalRep.duplicate());
403: }
404:
405: newObj.stringRep = stringRep;
406: newObj.refCount = 1;
407: refCount--;
408: return newObj;
409: } else {
410: throw new TclRuntimeError(
411: "takeExclusive() called on object \"" + toString()
412: + "\" with: refCount = 0");
413: }
414: }
415:
416: /**
417: * Returns the refCount of this object.
418: *
419: * @return refCount.
420: */
421: final int getRefCount() {
422: return refCount;
423: }
424:
425: /**
426: * Dispose of the TclObject when the refCount reaches 0.
427: *
428: * @exception TclRuntimeError if the object has already been deallocated.
429: */
430: protected final void disposeObject() {
431: if (internalRep == null) {
432: throw DEALLOCATED;
433: }
434: internalRep.dispose();
435:
436: // Setting the internalRep to null means any further
437: // use of the object will generate an error. Set the
438: // refCount to -1 so that the preserve() method
439: // can determine if the object has been deallocated
440: // by looking only at the refCount.
441:
442: internalRep = null;
443: stringRep = null;
444: refCount = -1;
445: }
446:
447: /**
448: * Raise a TclRuntimeError in the case where a
449: * TclObject was already disposed of because the last
450: * ref was released.
451: */
452:
453: protected final static RuntimeException DEALLOCATED = new TclRuntimeError(
454: "TclObject has been deallocated");
455:
456: protected final void disposedError() {
457: throw DEALLOCATED;
458: }
459:
460: /**
461: * Return a String that describes TclObject and internal
462: * rep type allocations and conversions. The string is
463: * in lines separated by newlines. The saveObjRecords
464: * needs to be set to true and Jacl recompiled for
465: * this method to return a useful value.
466: */
467:
468: static String getObjRecords() {
469: if (TclObjectBase.saveObjRecords) {
470: StringBuffer sb = new StringBuffer(64);
471: for (Enumeration keys = TclObject.objRecordMap.keys(); keys
472: .hasMoreElements();) {
473: String key = (String) keys.nextElement();
474: Integer num = (Integer) TclObject.objRecordMap.get(key);
475: sb.append(key);
476: sb.append(" ");
477: sb.append(num.intValue());
478: sb.append("\n");
479: }
480: TclObject.objRecordMap = new Hashtable();
481: return sb.toString();
482: } else {
483: return "";
484: }
485: }
486:
487: // Return true if this TclObject has no string rep.
488: // This could be the case with a "pure" integer
489: // or double, or boolean, that was created from
490: // a primitive value. Once the toString() method
491: // has been invoked, this method will return true
492: // unless the string rep is again invalidated.
493:
494: final boolean hasNoStringRep() {
495: return (stringRep == null);
496: }
497: }
|