/* Invid.java Final immutable object pointer type for the Ganymede system. Created: 11 April 1996 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2014 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 This program 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 2 of the License, or (at your option) any later version. This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package arlut.csd.ganymede.common; import java.io.Externalizable; import java.io.DataOutput; import java.io.DataInput; import java.io.ObjectOutput; import java.io.ObjectInput; import java.io.IOException; /*------------------------------------------------------------------------------ class Invid ------------------------------------------------------------------------------*/ /** * <p>An Invid is an immutable object id (an INVariant ID) in the * Ganymede system. All objects created in the database have a unique * and permanent Invid that identify the object's type and identity. * Because of these properties, the Invid can be used as a persistent * object pointer type.</p> * * <p>Invids are used extensively in the server to track pointer * relationships between objects. Invids are also used by the client * to identify objects to be viewed, edited, deleted, etc. Basically * whenever any code in Ganymede deals with a reference to an object, * it is done through the use of Invids.</p> * * @see arlut.csd.ganymede.server.InvidDBField * @see arlut.csd.ganymede.rmi.Session */ public final class Invid implements java.io.Externalizable { static private InvidAllocator allocator = null; static private int counter = 0; static private int reuseCounter = 0; static final public void printCount() { System.err.println("I've seen " + counter + " invids created, and seen " + reuseCounter + " reuses of interned invids."); } static final public int getCount() { return counter; } /** * <p>This method can be used to prep the Invid class with an {@link * arlut.csd.ganymede.common.InvidAllocator} that will return a * possibly pre-existing Invid object, given a short/int * combination.</p> * * <p>The purpose of this allocator is to allow the Ganymede server * to re-use previously created Invids in the server to minimize * memory usage, in a fashion similar to the Java language's * java.lang.String.intern() scheme.</p> */ static final public void setAllocator(InvidAllocator newAllocator) { Invid.allocator = newAllocator; } /** * Factory method for Invids. Can do caching/object reuse if an * {@link arlut.csd.ganymede.common.InvidAllocator} has been set. */ static final public Invid createInvid(short type, int num) { if (allocator == null) { counter++; return new Invid(type, num); } else { return new Invid(type, num).intern(); } } /** * Receive Factory method for Invids. Can do caching/object reuse * if an {@link arlut.csd.ganymede.common.InvidAllocator} has been * set. */ static final public Invid createInvid(DataInput in) throws IOException { return createInvid(in.readShort(), in.readInt()); } /** * String Factory method for Invids. String should be a pair of * colon separated numbers, in the form 5:134 where the first number * is the short type and the second is the int object number. Can do * efficient memory re-use if an {@link * arlut.csd.ganymede.common.InvidAllocator} has been set. */ static final public Invid createInvid(String string) { String first = string.substring(0, string.indexOf(':')); String last = string.substring(string.indexOf(':') + 1); try { return createInvid(Short.parseShort(first), Integer.parseInt(last)); } catch (NumberFormatException ex) { throw new IllegalArgumentException("bad string format " + ex); } } /** * Returns true if the Invid class is configured to use a pooled * allocator. */ static final public boolean hasAllocator() { return allocator != null; } // --- private short type; private int num; private transient boolean interned = false; // constructor /** * Default no-arg public constructor for externalization */ public Invid() { } /** * Private constructor. Use the static createInvid factory methods * to create Invids, please. */ private Invid(short type, int num) { this.type = type; this.num = num; } // equals public boolean equals(Object obj) { if (obj == null) { return false; } if (obj instanceof Invid) { return equals((Invid) obj); } else { return false; } } public boolean equals(Invid invid) { // in case some one casts the param if (invid == null) { return false; } if ((invid.type == type) && (invid.num == num)) { return true; } return false; } // hashcode public int hashCode() { // we'll mix type and num.. since we allocate invids in // incrementing order starting at 0, we'll shove the type numbers, // (which also start at 0 and go up) to the left of our 32 bit // hash so that when we hash invids of differing type together // they won't all collide at the low end of the range return (type << 23) ^ num; } /** * <p>As with java.lang.String, Invids are immutable objects that we * may be able to usefully pool for object re-use. The result of an * intern method is a single immutable Invid that will be reused by * all other Invids that point to the same object in the Ganymede * server (if you're calling intern() on the server, that is.)</p> * * <p>If an Invid Allocator has not been set with setAllocator(), * intern() will do nothing, and will return this.</p> * * <p>Note that there only two ways to create an Invid.. to use the * public static createInvid factory methods, in which case the * Invids you get will already be interned, or through * deserialization, in which case the JVM creates the Invid from the * serialized representation without going through the Invid class * constructors or factory methods.</p> * * <p>The intern method is to provide for the latter case, so that * Ganymede code which receives Invids from serialization streams * can look up a pooled reference pointing to the content of the * Invid and use that for in-heap storage, rather than a possibly * redundant copy.</p> */ public synchronized Invid intern() { if (interned || allocator == null) { return this; } Invid result = allocator.findInvid(this); if (result != null) { reuseCounter++; return result; } counter++; allocator.storeInvid(this); this.interned = true; return this; } // pull the values public short getType() { return type; } public int getNum() { return num; } public boolean isInterned() { return this.interned; } // externalization methods public void writeExternal(ObjectOutput out) throws IOException { out.writeShort(this.type); out.writeInt(this.num); } public void readExternal(ObjectInput in) throws IOException { if (this.interned) { throw new RuntimeException("Can't change interned invid."); } this.type = in.readShort(); this.num = in.readInt(); } /** * <p>Method to write this Invid out to a stream.</p> */ public void emit(DataOutput out) throws IOException { out.writeShort(type); out.writeInt(num); } public String toString() { return type + ":" + num; } }