// // PersistentObject.java // // WELD Java Client Persistent Object Management Package // Copyright Francis Chan (fchan@ic.eecs.berkeley.edu), 1997 // University of California, Berkeley // // // This program is in the public domain. // Permission to use, copy, modify, and distribute this software // and its documentation for NON-COMMERCIAL purposes and // without fee is hereby granted, as long as credit is given. import java.io.*; import java.util.*; import java.net.*; /** * * Persistent Object Class * (derived from BackendUtil.java) * * This is a class which other objects that need network * access extends. * There are several abstract methods that the application developer * must overload in order to use this class. * * @version 1.5 Feb 1997 * @author Francis Chan */ abstract public class PersistentObject{ private Vector objFieldVector = null; protected Vector loadVector = null; /** * Vector which holds the context information after a getContext */ protected Vector contextVector = null; static protected PO_CommandConstructor comCons; static private boolean firstObj = true; // should be "static" for optimal memory management // cannot be static if different servers and/or ports are used for // for network operations static protected PO_NetClient netClient; static private String server = "yoyodyne.eecs.berkeley.edu"; static private int port = 7010; protected Socket netSocket = null; //------------------------------------------- // variable for testing with and without the network static public boolean testNoNet = false; // with network // static private boolean testNoNet = true; // NO network //------------------------------------------ protected String outStreamString = null; protected boolean successfulTransmit = false; // for the type-casting of arguments from Input Stream protected int numField = 0; // used for the allocation of the array /** * The number of object fields the object has */ protected int numObjField = 0; /** * The array whose contents indicate the type of the field in the * corresponding field position of the object */ private int[] fieldTypeArray; // internal data structures /** * The class name of the object which inherits the Persistent Object * class. */ protected String objectClassName; // for db backend identification purposes /** * User Id of the Persistent Object (for network transactions) */ // protected String userId; protected String userId = "fchan"; // hard-coded right now /** * Unique identifier returned by the data server after a Save() */ protected int dbId = 0; /** * String indicating the version of the object (release_name.number) */ protected String version; /** * Access permission of the object (READ_ONLY, READ_WRITE) * usage to be determined */ protected int permission = PO.READ_WRITE; /** * Vector that contains the contents' */ public Vector contents; /** * Vector that contains the object's parent node */ public Vector containers; /** * The number of parents (objects that it attaches to) that the object has */ int numParents; /** * The number of children (objects that attaches to it) that the object has */ protected int numChildren; /** * Indicates whether the object's containers are in memory */ protected boolean containersInMem = false; /** * Indicates whether the object's contents are in memory */ protected boolean contentsInMem = false; /** * The name of the current class returned during parsing */ protected String currentRelativeClass; /** * The Id of the object that is being loaded from the data server */ protected int attachObjId; /** * Variable indicating whether the object has been saved before */ public boolean isPersistent = false; /** * Flag which may be used to determine whether the object requires updating */ public boolean dirty = true; /** * Flag to print out debugging statements */ //protected boolean debugFlag = true; static public boolean debugFlag = true; /** * Constructor of PersistentObject * Should never be called explicitly */ public PersistentObject(){ netClient = new PO_NetClient(server, port); containers = new Vector(); contents = new Vector(); if (firstObj == true){ comCons = new PO_CommandConstructor(); } firstObj = false; } /* ------------------------------------------------ * * Methods used to manipulate internal data structures * * ------------------------------------------------ */ /** * * Returns the Object Class Name * */ public String getClassName() { return this.objectClassName; } /** * * Sets the isPersistent flag which indicates that this object * has persistence (has a copy at the data backend) * * Future operations will then make use of the object's unique id. * */ protected void setPersistent(boolean flag) { this.isPersistent = flag; } /** * Sets the server name for network operations * * @param server Server name */ protected void setServer(String server) { this.server = server; netClient = new PO_NetClient(server, port); } /** * Sets the port for network operations * * @param port Port Number */ static public void setPort(int portNum) { port = portNum; netClient = new PO_NetClient(server, portNum); } /** * Sets the flag for debug statement * * @param flag true to turn on debugging * false otherwise */ static public void setDebug(boolean flag) { debugFlag = flag; } /** * Sets the flag for network operation * * @param flag true if there are no network operations * false otherwise */ static public void setTestNoNet(boolean flag) { testNoNet = flag; } /* ------------------------------------------------ * * Methods used to set up internal data structures * * ------------------------------------------------ */ /** * * Network Object setup * method that needs to be run before the object can operate * as a persistent object over the network * * @param port Port number of the server * @param numFields Number of fields the object has * @param objectClassName Name of the class */ protected void netObjSetup(int numFields, int numObjs, String objectClassName) { // number of fields must be pre-determined by object developer this.numField = numFields + 3; // +3 for contents, parents and uniqueId //this.numField = numFields + 2; // +2 for contents and uniqueId this.numObjField = numObjs; this.objectClassName = objectClassName; fieldTypeArray = new int[this.numField]; objFieldVector = new Vector(this.numField); setObjFields(); setField(numFields, 0, PO.UNIQUE_ID); setField(numFields+1, this.contents, PO.CHILDREN); setField(numFields+2, this.containers, PO.PARENTS); } private void netSetObj(){ System.out.println("objectClassName, numField, numObjField: " + objectClassName + numField + numObjField); } /** * Sets up the object's fields into internal data structure. * * Calls setField with the respective parameters for different types * Needs to be overloaded by the object developer */ abstract protected void setObjFields(); /** * * sets the type of each field. * This is a method the Object designer has to overload. * * @param nthField The position of the field * @param type The type of the field */ protected void setType(int nthField, int type) { fieldTypeArray[nthField] = type; } /** * * Sets the fields in the Vector holding the fields of the * object * * Non-object types are stored in object that matches mostly closely * its type. * * @param nthField The position of the field * @param Second_Argument Different data types may be used * @param type The type of the field */ protected void setField(int nthField, String stringField, int type) { objFieldVector.insertElementAt(stringField, nthField); setType(nthField, PO.STRING); } protected void setField(int nthField, int intField, int type) { Integer intObj = new Integer(intField); objFieldVector.insertElementAt(intObj, nthField); if (type != PO.UNIQUE_ID) { setType(nthField, PO.INT); } else { setType(nthField, PO.UNIQUE_ID); } } protected void setField(int nthField, double doubleField, int type) { Double doubleObj = new Double(doubleField); objFieldVector.insertElementAt(doubleObj, nthField); setType(nthField, PO.DOUBLE); } protected void setField(int nthField, char charField, int type) { String charString = new String(); charString = charString.valueOf(charField); objFieldVector.insertElementAt(charString, nthField); setType(nthField, PO.CHAR); } protected void setField(int nthField, boolean boolField, int type) { if (boolField == true) { objFieldVector.insertElementAt("1", nthField); } else objFieldVector.insertElementAt("0", nthField); setType(nthField, PO.BOOLEAN); } protected void setField(int nthField, float floatField, int type) { Float floatObj = new Float(floatField); objFieldVector.insertElementAt(floatObj, nthField); setType(nthField, PO.FLOAT); } protected void setField(int nthField, Vector vectorField, int type) { objFieldVector.insertElementAt(vectorField, nthField); setType(nthField, type); } protected void setField(int nthField, Object objField, int type) { objFieldVector.insertElementAt(objField, nthField); setType(nthField, type); } protected void setField(int nthField, double[] arrayField, int type) { objFieldVector.insertElementAt(arrayField, nthField); setType(nthField, type); } /* ------------------------------------------------ * * Methods for network operations * * ------------------------------------------------ */ /** * * Calls openConnection to connect to remote server * * If testNoNet flag is set, then the connection is ignored and * operations (load, save, etc.) will be done locally * */ protected boolean connectServer(){ if (testNoNet == true) return true; netSocket = netClient.openConnection(); if (netSocket == null) { DEBUG("Can't open connection, abort, I repeat, ABORT NOW!"); return false; } return true; } /** * * Calls closeConnection to close a remote server connection * * If testNoNet flag is set, then the close is ignored and * operations (load, save, etc.) will be done locally * */ protected boolean closeServer(Socket socket){ if (testNoNet == true) return true; if (netSocket == null) { DEBUG("Socket is null, can't close connection."); return false; } netClient.closeConnection(socket); return true; } /** * * Sends the specified string to the remote server * * If testNoNet flag is set, then the message will be outputed * to the screen * * @param msg The message/string to be sent */ protected void send(String msg){ if (testNoNet == true) System.out.println(msg); else netClient.sendString(msg); } /* ------------------------------------------------ * * Methods for Save * * ------------------------------------------------ */ /** * * Save (Object) * * Encapsulation of the actual call to SaveObject * (returns either OK, ERROR or the Object's UniqueID) * */ public int save() { DEBUG("PerObj: Saving... " + this.objectClassName); return saveObject(this.objectClassName, PO.NO_VERSION); } /** * Saves the object as a new version of the "basic object" * * @param versionFlag Indicator of whether a "point" (single object) * is to be saved or the whole "tree", rooted * at this point */ public int versionSave(int versionFlag) { if ((versionFlag < PO.NO_VERSION) || (versionFlag > PO.TREE_VERSION)){ System.out.println("Invalid version save option, save not completed"); return PO.ERROR; } DEBUG("Saving Version... " + this.objectClassName); return saveObject(this.objectClassName, versionFlag); } /** * * Save Object * * returns Unique DbId if save is completed * ERROR if not * * @param objectClassName Name of the class */ protected synchronized int saveObject(String objectClassName, int versionFlag) { // immediately don't save if SavePolicy indicates the // object has not been modified Vector objVector; if (savePolicy(this) == PO.OK) { //DEBUG(objectClassName + " SavePolicy passed! "); if (connectServer() == false) return PO.ERROR; // clear obj field vector for new fields objFieldVector = new Vector(); setObjFields(); setField(numField-3, 0, PO.UNIQUE_ID); setField(numField-2, this.contents, PO.CHILDREN); if (versionFlag == PO.NO_VERSION){ if (isPersistent == false) { // save/modify plain object outStreamString = comCons.prepareSaveString(PO.PUT, userId, objectClassName, dbId, null, objFieldVector, fieldTypeArray, PO.NO_VERSION); } else { outStreamString = comCons.prepareSaveString(PO.MODIFY, userId, objectClassName, dbId, null, objFieldVector, fieldTypeArray, PO.NO_VERSION); } } else { // version save outStreamString = comCons.prepareSaveString(PO.PUTVER, userId, objectClassName, dbId, null, objFieldVector, fieldTypeArray, versionFlag); } send(outStreamString); // parse and verify the OK message if (isPersistent == false) { objVector = parse(PO.PUT); } else { objVector = parse(PO.MODIFY); } closeServer(netSocket); if (objVector == null) { // something went wrong, return System.out.println("Saving of " + objectClassName + " cannot be completed"); return PO.ERROR; } // dbId should be set DEBUG("DbID after save: " + this.dbId); // traverse all fields and look for vector fields for (int i=0; i 1!"); return null; } inputVector.addElement(strToke.sval); DEBUG("Para " + (i-2) + " is " + strToke.sval); break; // need to deal with strings that contain // "whitespace" case PO.ARRAY: double[] dArray = parseArray(strToke); if (dArray == null) return null; else inputVector.addElement(dArray); break; case PO.OBJECT: // needs to get and parse string // find out what the object type is and look it up // correspondingly (can I not hard code it in // Persistent Object, and instead write a ObjectLookupTable // class to handle the casting, parsing and returns the object // to this function) // also a consideration is // just-in-time loading, return the object despite one or more // fields may not be set, but will load when the user requests // access // need to add a dummy object so that ordering // in the vector is preserved strToke.pushBack(); // push token back since objects are //handled in a different way String dummyStr = new String("dummy String"); inputVector.addElement(dummyStr); break; // should pad the vector in the future althought // it doesn't make any difference in the current design case PO.UNIQUE_ID: if (strToke.ttype != strToke.TT_NUMBER) { System.out.println("ID field not num!"); return null; } else { dbId = (int) strToke.nval; DEBUG(objectClassName + "'s Id value is: " + dbId); break; } case PO.PARENTS: if (strToke.ttype != strToke.TT_NUMBER) { System.out.println("Parent field not num!"); return null; } else { numParents = (int) strToke.nval; DEBUG(objectClassName + " has " + numParents + " parents"); break; } case PO.CHILDREN: if (strToke.ttype != strToke.TT_NUMBER) { System.out.println("Children field not num!"); return null; } else { numChildren = (int) strToke.nval; DEBUG(objectClassName + " has " + numChildren + " children"); break; } default: break; } } } } catch (Exception e) { System.out.println("Can't Tokenize Stream"); e.printStackTrace(); } } } catch (Exception e) { System.out.println("Input file/stream error!"); } DEBUG("Message code: " + status + " length: " + length); successfulTransmit = true; closeServer(netSocket); return inputVector; } /** * * Verifies that the token is a number * Prints out the msg if it is not a number, returns false * * @param strToke StreamTokenizer to be verified * @param msg Error message to be outputed */ protected boolean parseNumber(StreamTokenizer strToke, String msg){ if(strToke.ttype != strToke.TT_NUMBER) { System.out.println(msg); return false; } return true; } /** * Method that parses and constructs a string given that * \n (which acts as delimiters in StreamTokenizer * could exist in a string */ private String parseString(StreamTokenizer strToke, InputStream inputStream){ String str = new String(); byte[] b = new byte[1]; int b_int; /* Strange logic, but working code is a result of: Java java.io limitations StreamTokenizer can't parse "\r"s InputStream.read() skips a "\n" at first */ try { if (13 == (int) b[0]){ System.out.println("Carriage in String"); return str; } char pre = '\0'; char curr = '\n'; while(b[0] != '\r'){ b_int = inputStream.read(b); pre = curr; curr = (char) b[0]; if ((pre == '\n') && (curr == '\r')){ return str; } str += pre; } } catch (Exception e){ System.out.println("Parse String Error!"); return str; } return str; } /** * Parses an array (arrangement) */ protected double[] parseArray(StreamTokenizer strToke){ int token; if (strToke.ttype != strToke.TT_NUMBER) { System.out.println("Array size/NumContents not a number!"); return null; } else { int numEle = (int) strToke.nval; double[] dArray = new double[numEle+1]; DEBUG("Size of Array: " + numEle); if (numEle == 0){ System.out.println("Size of contents/array = 0"); } dArray[0] = (double) numEle; try{ for (int j=1; j<=numEle; j++) { token = strToke.nextToken(); if (strToke.ttype != strToke.TT_NUMBER) { System.out.println("Array element not num!"); return null; } else { dArray[j] = (double) strToke.nval; DEBUG("Array element no. " + j + " is " + dArray[j]); } } } catch (Exception e){ System.out.println("Parsing (array) contents error"); } return dArray; } } /** * * Verifies that the token is a word/String * Prints out the msg if it is not a string, return null * returns the String parsed otherwise * * @param strToke StreamTokenizer to be verified * @param msg Error message to be outputed */ protected String parseForWord(StreamTokenizer strToke, String msg){ int token = 0; try { token = strToke.nextToken(); if (strToke.ttype != strToke.TT_WORD) { System.out.println(msg); return null; } } catch (Exception e) { System.out.println(msg); return null; } return strToke.sval; } /** * * Method that parses stream into (className, str, id) information * for DirObjects, stringID = content's 1st identifier * for Version Object, stringId = content's version string * */ protected Vector parseContext(StreamTokenizer strToke) { Vector infoVector = new Vector(); int token; int numContents; try { token = strToke.nextToken(); if (parseNumber(strToke, "Number (of contents) expected!") != true){ return null; } numContents = (int) strToke.nval; String className; String str; int id; for (int i=0; i