0001: /*
0002: *
0003: *
0004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: */
0026:
0027: package javax.microedition.rms;
0028:
0029: import com.sun.midp.midlet.MIDletSuite;
0030: import com.sun.midp.midlet.MIDletStateHandler;
0031:
0032: import com.sun.midp.midletsuite.MIDletSuiteStorage;
0033:
0034: import com.sun.midp.rms.RecordStoreImpl;
0035:
0036: import com.sun.midp.security.SecurityInitializer;
0037: import com.sun.midp.security.SecurityToken;
0038: import com.sun.midp.security.ImplicitlyTrustedClass;
0039:
0040: import com.sun.midp.log.Logging;
0041: import com.sun.midp.log.LogChannels;
0042:
0043: /**
0044: * A class representing a record store. A record store consists of a
0045: * collection of records which will remain persistent across multiple
0046: * invocations of the MIDlet. The platform is responsible for
0047: * making its best effort to maintain the integrity of the
0048: * MIDlet's record stores throughout the normal use of the
0049: * platform, including reboots, battery changes, etc.
0050: *
0051: * <p>Record stores are created in platform-dependent locations, which
0052: * are not exposed to the MIDlets. The naming space for record stores
0053: * is controlled at the MIDlet suite granularity. MIDlets within a
0054: * MIDlet suite are allowed to create multiple record stores, as long
0055: * as they are each given different names. When a MIDlet suite is
0056: * removed from a platform all the record stores associated with its
0057: * MIDlets will also be removed. MIDlets within a MIDlet suite can
0058: * access each other's record stores directly. New APIs in MIDP
0059: * allow for the explicit sharing of record stores if the MIDlet
0060: * creating the RecordStore chooses to give such permission.</p>
0061: *
0062: * <p> Sharing is accomplished through the ability to name a
0063: * RecordStore created by another MIDlet suite.</p>
0064: *
0065: * <P> RecordStores are uniquely named using the unique name of the
0066: * MIDlet suite plus the name of the RecordStore. MIDlet suites are
0067: * identified by the MIDlet-Vendor and MIDlet-Name attributes from the
0068: * application descriptor.</p>
0069: *
0070: * <p> Access controls are defined when RecordStores to be shared are
0071: * created. Access controls are enforced when RecordStores are
0072: * opened. The access modes allow private use or shareable
0073: * with any other MIDlet suite.</p>
0074: *
0075: * <p>Record store names are case sensitive and may consist of any
0076: * combination of between one and 32 Unicode characters
0077: * inclusive. Record store names must be unique within the scope of a
0078: * given MIDlet suite. In other words, MIDlets within a MIDlet suite
0079: * are not allowed to create more than one record store with the same
0080: * name, however a MIDlet in one MIDlet suite is allowed to have a
0081: * record store with the same name as a MIDlet in another MIDlet
0082: * suite. In that case, the record stores are still distinct and
0083: * separate.</p>
0084: *
0085: * <p>No locking operations are provided in this API. Record store
0086: * implementations ensure that all individual record store operations
0087: * are atomic, synchronous, and serialized, so no corruption will
0088: * occur with multiple accesses. However, if a MIDlet uses multiple
0089: * threads to access a record store, it is the MIDlet's responsibility
0090: * to coordinate this access or unintended consequences may result.
0091: * Similarly, if a platform performs transparent synchronization of a
0092: * record store, it is the platform's responsibility to enforce
0093: * exclusive access to the record store between the MIDlet and
0094: * synchronization engine.</p>
0095: *
0096: * <p>Records are uniquely identified within a given record store by
0097: * their recordId, which is an integer value. This recordId is used as
0098: * the primary key for the records. The first record created in a
0099: * record store will have recordId equal to one (1). Each subsequent
0100: * record added to a RecordStore will be assigned a recordId one
0101: * greater than the record added before it. That is, if two records
0102: * are added to a record store, and the first has a recordId of 'n',
0103: * the next will have a recordId of 'n + 1'. MIDlets can create other
0104: * sequences of the records in the RecordStore by using the
0105: * <code>RecordEnumeration</code> class.</p>
0106: *
0107: * <p>This record store uses long integers for time/date stamps, in
0108: * the format used by System.currentTimeMillis(). The record store is
0109: * time stamped with the last time it was modified. The record store
0110: * also maintains a <em>version</em> number, which is an integer that
0111: * is incremented for each operation that modifies the contents of the
0112: * RecordStore. These are useful for synchronization engines as well
0113: * as other things.</p>
0114: *
0115: * @since MIDP 1.0
0116: */
0117:
0118: public class RecordStore {
0119:
0120: /** cache of open RecordStore instances */
0121: private static java.util.Vector openRecordStores = new java.util.Vector(
0122: 3);
0123:
0124: /** The peer that performs the real functionallity. */
0125: private RecordStoreImpl peer;
0126:
0127: /** name of this record store */
0128: private String recordStoreName;
0129:
0130: /** unique id for suite that owns this record store */
0131: private int suiteId;
0132:
0133: /** number of open instances of this record store */
0134: private int opencount;
0135:
0136: /** recordListeners of this record store */
0137: private java.util.Vector recordListener;
0138:
0139: /**
0140: * Inner class to request security token from SecurityInitializer.
0141: * SecurityInitializer should be able to check this inner class name.
0142: */
0143: static private class SecurityTrusted implements
0144: ImplicitlyTrustedClass {
0145: };
0146:
0147: /**
0148: * The security token necessary to use RecordStoreImpl.
0149: * This is initialized in a static initialization block.
0150: */
0151: private static SecurityToken classSecurityToken = SecurityInitializer
0152: .requestToken(new SecurityTrusted());
0153:
0154: /*
0155: * RecordStore Constructors
0156: */
0157:
0158: /**
0159: * MIDlets must use <code>openRecordStore()</code> to get
0160: * a <code>RecordStore</code> object. If this constructor
0161: * is not declared (as private scope), Javadoc (and Java)
0162: * will assume a public constructor.
0163: *
0164: * @param suiteId the ID of the suite that owns this record store
0165: * @param recordStoreName the MIDlet suite unique name for the
0166: * record store, consisting of between one and 32 Unicode
0167: * characters inclusive.
0168: */
0169: private RecordStore(int suiteId, String recordStoreName) {
0170: this .suiteId = suiteId;
0171: this .recordStoreName = recordStoreName;
0172: recordListener = new java.util.Vector(3);
0173: }
0174:
0175: /**
0176: * Deletes the named record store. MIDlet suites are only allowed
0177: * to delete their own record stores. If the named record store is
0178: * open (by a MIDlet in this suite or a MIDlet in a different
0179: * MIDlet suite) when this method is called, a
0180: * RecordStoreException will be thrown. If the named record store
0181: * does not exist a RecordStoreNotFoundException will be
0182: * thrown. Calling this method does NOT result in recordDeleted
0183: * calls to any registered listeners of this RecordStore.
0184: *
0185: * @param recordStoreName the MIDlet suite unique record store to
0186: * delete
0187: *
0188: * @exception RecordStoreException if a record store-related
0189: * exception occurred
0190: * @exception RecordStoreNotFoundException if the record store
0191: * could not be found
0192: */
0193: public static void deleteRecordStore(String recordStoreName)
0194: throws RecordStoreException, RecordStoreNotFoundException {
0195: int id = MIDletStateHandler.getMidletStateHandler()
0196: .getMIDletSuite().getID();
0197:
0198: if (recordStoreName == null || recordStoreName.length() == 0) {
0199: throw new RecordStoreNotFoundException();
0200: }
0201:
0202: // Check the record store cache for a db with the same name
0203: synchronized (openRecordStores) {
0204: RecordStore db;
0205: int size = openRecordStores.size();
0206: for (int n = 0; n < size; n++) {
0207: db = (RecordStore) openRecordStores.elementAt(n);
0208: if (db.suiteId == id
0209: && db.recordStoreName.equals(recordStoreName)) {
0210: // cannot delete an open record store
0211: throw new RecordStoreException(
0212: "deleteRecordStore error:"
0213: + " record store is"
0214: + " still open");
0215: }
0216: }
0217:
0218: // this record store is not currently open
0219: RecordStoreImpl.deleteRecordStore(classSecurityToken, id,
0220: recordStoreName);
0221: }
0222: }
0223:
0224: /**
0225: * Open (and possibly create) a record store associated with the
0226: * given MIDlet suite. If this method is called by a MIDlet when
0227: * the record store is already open by a MIDlet in the MIDlet suite,
0228: * this method returns a reference to the same RecordStore object.
0229: *
0230: * @param recordStoreName the MIDlet suite unique name for the
0231: * record store, consisting of between one and 32 Unicode
0232: * characters inclusive.
0233: * @param createIfNecessary if true, the record store will be
0234: * created if necessary
0235: *
0236: * @return <code>RecordStore</code> object for the record store
0237: *
0238: * @exception RecordStoreException if a record store-related
0239: * exception occurred
0240: * @exception RecordStoreNotFoundException if the record store
0241: * could not be found
0242: * @exception RecordStoreFullException if the operation cannot be
0243: * completed because the record store is full
0244: * @exception IllegalArgumentException if
0245: * recordStoreName is invalid
0246: */
0247: public static RecordStore openRecordStore(String recordStoreName,
0248: boolean createIfNecessary) throws RecordStoreException,
0249: RecordStoreFullException, RecordStoreNotFoundException {
0250:
0251: int id = MIDletStateHandler.getMidletStateHandler()
0252: .getMIDletSuite().getID();
0253:
0254: return doOpen(id, recordStoreName, createIfNecessary);
0255: }
0256:
0257: /**
0258: * Open (and possibly create) a record store that can be shared
0259: * with other MIDlet suites. The RecordStore is owned by the
0260: * current MIDlet suite. The authorization mode is set when the
0261: * record store is created, as follows:
0262: *
0263: * <ul>
0264: * <li><code>AUTHMODE_PRIVATE</code> - Only allows the MIDlet
0265: * suite that created the RecordStore to access it. This
0266: * case behaves identically to
0267: * <code>openRecordStore(recordStoreName,
0268: * createIfNecessary)</code>.</li>
0269: * <li><code>AUTHMODE_ANY</code> - Allows any MIDlet to access the
0270: * RecordStore. Note that this makes your recordStore
0271: * accessible by any other MIDlet on the device. This
0272: * could have privacy and security issues depending on
0273: * the data being shared. Please use carefully.</li>
0274: * </ul>
0275: *
0276: * <p>The owning MIDlet suite may always access the RecordStore and
0277: * always has access to write and update the store.</p>
0278: *
0279: * <p> If this method is called by a MIDlet when the record store
0280: * is already open by a MIDlet in the MIDlet suite, this method
0281: * returns a reference to the same RecordStore object.</p>
0282: *
0283: * @param recordStoreName the MIDlet suite unique name for the
0284: * record store, consisting of between one and 32 Unicode
0285: * characters inclusive.
0286: * @param createIfNecessary if true, the record store will be
0287: * created if necessary
0288: * @param authmode the mode under which to check or create access.
0289: * Must be one of AUTHMODE_PRIVATE or AUTHMODE_ANY.
0290: * This argument is ignored if the RecordStore exists.
0291: * @param writable true if the RecordStore is to be writable by
0292: * other MIDlet suites that are granted access.
0293: * This argument is ignored if the RecordStore exists.
0294: *
0295: * @return <code>RecordStore</code> object for the record store
0296: *
0297: * @exception RecordStoreException if a record store-related
0298: * exception occurred
0299: * @exception RecordStoreNotFoundException if the record store
0300: * could not be found
0301: * @exception RecordStoreFullException if the operation
0302: * cannot be completed because the record store is full
0303: * @exception IllegalArgumentException if authmode or
0304: * recordStoreName is invalid
0305: */
0306: public static RecordStore openRecordStore(String recordStoreName,
0307: boolean createIfNecessary, int authmode, boolean writable)
0308: throws RecordStoreException, RecordStoreFullException,
0309: RecordStoreNotFoundException {
0310: RecordStore recordStore;
0311: boolean isExistingStorage = false;
0312:
0313: /*
0314: * First, we have to check if the record store already exists or not.
0315: * If we open an existing record store, "authmode" must be ignored!
0316: *
0317: */
0318: try {
0319: recordStore = openRecordStore(recordStoreName, false);
0320: isExistingStorage = true;
0321: } catch (RecordStoreNotFoundException ex) {
0322: recordStore = openRecordStore(recordStoreName,
0323: createIfNecessary);
0324: }
0325:
0326: if (!isExistingStorage) {
0327: try {
0328: recordStore.peer.setMode(authmode, writable);
0329: } catch (Exception e) {
0330: try {
0331: recordStore.closeRecordStore();
0332: } catch (Exception ex) {
0333: // do not overthrow the real exception
0334: }
0335:
0336: try {
0337: int id = MIDletStateHandler.getMidletStateHandler()
0338: .getMIDletSuite().getID();
0339: RecordStoreImpl.deleteRecordStore(
0340: classSecurityToken, id, recordStoreName);
0341: } catch (Exception ex) {
0342: // do not overthrow the real exception
0343: }
0344:
0345: if (e instanceof RecordStoreException) {
0346: throw (RecordStoreException) e;
0347: }
0348:
0349: throw (RuntimeException) e;
0350: }
0351: }
0352:
0353: return recordStore;
0354: }
0355:
0356: /**
0357: * Open a record store associated with the named MIDlet suite.
0358: * The MIDlet suite is identified by MIDlet vendor and MIDlet
0359: * name. Access is granted only if the authorization mode of the
0360: * RecordStore allows access by the current MIDlet suite. Access
0361: * is limited by the authorization mode set when the record store
0362: * was created:
0363: *
0364: * <ul>
0365: * <li><code>AUTHMODE_PRIVATE</code> - Succeeds only if vendorName
0366: * and suiteName identify the current MIDlet suite; this
0367: * case behaves identically to
0368: * <code>openRecordStore(recordStoreName,
0369: * createIfNecessary)</code>.</li>
0370: * <li><code>AUTHMODE_ANY</code> - Always succeeds.
0371: * Note that this makes your recordStore
0372: * accessible by any other MIDlet on the device. This
0373: * could have privacy and security issues depending on
0374: * the data being shared. Please use carefully.
0375: * Untrusted MIDlet suites are allowed to share data but
0376: * this is not recommended. The authenticity of the
0377: * origin of untrusted MIDlet suites cannot be verified
0378: * so shared data may be used unscrupulously.</li>
0379: * </ul>
0380: *
0381: * <p> If this method is called by a MIDlet when the record store
0382: * is already open by a MIDlet in the MIDlet suite, this method
0383: * returns a reference to the same RecordStore object.</p>
0384: *
0385: * <p> If a MIDlet calls this method to open a record store from
0386: * its own suite, the behavior is identical to calling:
0387: * <code>{@link #openRecordStore(String, boolean)
0388: * openRecordStore(recordStoreName, false)}</code></p>
0389: *
0390: * @param recordStoreName the MIDlet suite unique name for the
0391: * record store, consisting of between one and 32 Unicode
0392: * characters inclusive.
0393: * @param vendorName the vendor of the owning MIDlet suite
0394: * @param suiteName the name of the MIDlet suite
0395: *
0396: * @return <code>RecordStore</code> object for the record store
0397: *
0398: * @exception RecordStoreException if a record store-related
0399: * exception occurred
0400: * @exception RecordStoreNotFoundException if the record store
0401: * could not be found
0402: * @exception SecurityException if this MIDlet Suite is not
0403: * allowed to open the specified RecordStore.
0404: * @exception IllegalArgumentException if recordStoreName is
0405: * invalid
0406: */
0407: public static RecordStore openRecordStore(String recordStoreName,
0408: String vendorName, String suiteName)
0409: throws RecordStoreException, RecordStoreNotFoundException {
0410:
0411: int currentID = MIDletStateHandler.getMidletStateHandler()
0412: .getMIDletSuite().getID();
0413: int id;
0414: RecordStore recordStore;
0415:
0416: if (vendorName == null || suiteName == null) {
0417: throw new IllegalArgumentException("vendorName and "
0418: + "suiteName must be " + "non null");
0419: }
0420:
0421: if (recordStoreName.length() > 32
0422: || recordStoreName.length() == 0) {
0423: throw new IllegalArgumentException();
0424: }
0425:
0426: id = MIDletSuiteStorage.getSuiteID(vendorName, suiteName);
0427:
0428: if (id == MIDletSuite.UNUSED_SUITE_ID) {
0429: throw new RecordStoreNotFoundException();
0430: }
0431:
0432: recordStore = doOpen(id, recordStoreName, false);
0433: if ((currentID != id)
0434: && (recordStore.peer.getAuthMode() == AUTHMODE_PRIVATE)) {
0435: recordStore.closeRecordStore();
0436: throw new SecurityException();
0437: }
0438:
0439: return recordStore;
0440: }
0441:
0442: /**
0443: * Authorization to allow access only to the current MIDlet
0444: * suite. AUTHMODE_PRIVATE has a value of 0.
0445: */
0446: public final static int AUTHMODE_PRIVATE = 0;
0447:
0448: /**
0449: * Authorization to allow access to any MIDlet
0450: * suites. AUTHMODE_ANY has a value of 1.
0451: */
0452: public final static int AUTHMODE_ANY = 1;
0453:
0454: /**
0455: * Changes the access mode for this RecordStore. The authorization
0456: * mode choices are:
0457: *
0458: * <ul>
0459: * <li><code>AUTHMODE_PRIVATE</code> - Only allows the MIDlet
0460: * suite that created the RecordStore to access it. This
0461: * case behaves identically to
0462: * <code>openRecordStore(recordStoreName,
0463: * createIfNecessary)</code>.</li>
0464: * <li><code>AUTHMODE_ANY</code> - Allows any MIDlet to access the
0465: * RecordStore. Note that this makes your recordStore
0466: * accessible by any other MIDlet on the device. This
0467: * could have privacy and security issues depending on
0468: * the data being shared. Please use carefully.</li>
0469: * </ul>
0470: *
0471: * <p>The owning MIDlet suite may always access the RecordStore and
0472: * always has access to write and update the store. Only the
0473: * owning MIDlet suite can change the mode of a RecordStore.</p>
0474: *
0475: * @param authmode the mode under which to check or create access.
0476: * Must be one of AUTHMODE_PRIVATE or AUTHMODE_ANY.
0477: * @param writable true if the RecordStore is to be writable by
0478: * other MIDlet suites that are granted access
0479: *
0480: * @exception RecordStoreException if a record store-related
0481: * exception occurred
0482: * @exception SecurityException if this MIDlet Suite is not
0483: * allowed to change the mode of the RecordStore
0484: * @exception IllegalArgumentException if authmode is invalid
0485: */
0486: public void setMode(int authmode, boolean writable)
0487: throws RecordStoreException {
0488: checkOpen();
0489: if (!isRecordStoreOwner()) {
0490: throw new SecurityException("not the owner");
0491: } else if (authmode != AUTHMODE_PRIVATE
0492: && authmode != AUTHMODE_ANY) {
0493: throw new IllegalArgumentException();
0494: }
0495:
0496: peer.setMode(authmode, writable);
0497: }
0498:
0499: /**
0500: * This method is called when the MIDlet requests to have the
0501: * record store closed. Note that the record store will not
0502: * actually be closed until closeRecordStore() is called as many
0503: * times as openRecordStore() was called. In other words, the
0504: * MIDlet needs to make a balanced number of close calls as open
0505: * calls before the record store is closed.
0506: *
0507: * <p>When the record store is closed, all listeners are removed
0508: * and all RecordEnumerations associated with it become invalid.
0509: * If the MIDlet attempts to perform
0510: * operations on the RecordStore object after it has been closed,
0511: * the methods will throw a RecordStoreNotOpenException.
0512: *
0513: * @exception RecordStoreNotOpenException if the record store is
0514: * not open
0515: * @exception RecordStoreException if a different record
0516: * store-related exception occurred
0517: */
0518: public void closeRecordStore() throws RecordStoreNotOpenException,
0519: RecordStoreException {
0520:
0521: checkOpen();
0522: synchronized (openRecordStores) {
0523:
0524: if (--opencount <= 0) { // free stuff - final close
0525: openRecordStores.removeElement(this );
0526: peer.closeRecordStore();
0527:
0528: // mark this RecordStore as closed
0529: peer = null;
0530: }
0531: }
0532: }
0533:
0534: /**
0535: * Returns an array of the names of record stores owned by the
0536: * MIDlet suite. Note that if the MIDlet suite does not
0537: * have any record stores, this function will return null.
0538: *
0539: * The order of RecordStore names returned is implementation
0540: * dependent.
0541: *
0542: * @return array of the names of record stores owned by the
0543: * MIDlet suite. Note that if the MIDlet suite does not
0544: * have any record stores, this function will return null.
0545: */
0546: public static String[] listRecordStores() {
0547: MIDletSuite currentSuite = MIDletStateHandler
0548: .getMidletStateHandler().getMIDletSuite();
0549:
0550: if (currentSuite == null) {
0551: return null;
0552: }
0553:
0554: // static calls synchronize on openRecordStores
0555: synchronized (openRecordStores) {
0556: return RecordStoreImpl.listRecordStores(classSecurityToken,
0557: currentSuite.getID());
0558: }
0559: }
0560:
0561: /**
0562: * Returns the name of this RecordStore.
0563: *
0564: * @return the name of this RecordStore
0565: *
0566: * @exception RecordStoreNotOpenException if the record store is not open
0567: */
0568: public String getName() throws RecordStoreNotOpenException {
0569: checkOpen();
0570: return recordStoreName;
0571: }
0572:
0573: /**
0574: * Each time a record store is modified (by
0575: * <code>addRecord</code>, <code>setRecord</code>, or
0576: * <code>deleteRecord</code> methods) its <em>version</em> is
0577: * incremented. This can be used by MIDlets to quickly tell if
0578: * anything has been modified.
0579: *
0580: * The initial version number is implementation dependent.
0581: * The increment is a positive integer greater than 0.
0582: * The version number increases only when the RecordStore is updated.
0583: *
0584: * The increment value need not be constant and may vary with each
0585: * update.
0586: *
0587: * @return the current record store version
0588: *
0589: * @exception RecordStoreNotOpenException if the record store is
0590: * not open
0591: */
0592: public int getVersion() throws RecordStoreNotOpenException {
0593: checkOpen();
0594: return peer.getVersion();
0595: }
0596:
0597: /**
0598: * Returns the number of records currently in the record store.
0599: *
0600: * @return the number of records currently in the record store
0601: *
0602: * @exception RecordStoreNotOpenException if the record store is
0603: * not open
0604: */
0605: public int getNumRecords() throws RecordStoreNotOpenException {
0606: checkOpen();
0607: return peer.getNumRecords();
0608: }
0609:
0610: /**
0611: * Returns the amount of space, in bytes, that the record store
0612: * occupies. The size returned includes any overhead associated
0613: * with the implementation, such as the data structures
0614: * used to hold the state of the record store, etc.
0615: *
0616: * @exception RecordStoreNotOpenException if the record store is
0617: * not open
0618: *
0619: * @return the size of the record store in bytes
0620: */
0621: public int getSize() throws RecordStoreNotOpenException {
0622: checkOpen();
0623: return peer.getSize();
0624: }
0625:
0626: /**
0627: * Returns the amount of additional room (in bytes) available for
0628: * this record store to grow. Note that this is not necessarily
0629: * the amount of extra MIDlet-level data which can be stored,
0630: * as implementations may store additional data structures with
0631: * each record to support integration with native applications,
0632: * synchronization, etc.
0633: *
0634: * @exception RecordStoreNotOpenException if the record store is
0635: * not open
0636: *
0637: * @return the amount of additional room (in bytes) available for
0638: * this record store to grow
0639: */
0640: public int getSizeAvailable() throws RecordStoreNotOpenException {
0641: checkOpen();
0642: int sizeAvailable = peer.getSizeAvailable();
0643: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0644: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
0645: "getSizeAvailable() = " + sizeAvailable);
0646: }
0647: return sizeAvailable;
0648: }
0649:
0650: /**
0651: * Returns the last time the record store was modified, in the
0652: * format used by System.currentTimeMillis().
0653: *
0654: * @return the last time the record store was modified, in the
0655: * format used by System.currentTimeMillis()
0656: *
0657: * @exception RecordStoreNotOpenException if the record store is
0658: * not open
0659: */
0660: public long getLastModified() throws RecordStoreNotOpenException {
0661: checkOpen();
0662: return peer.getLastModified();
0663: }
0664:
0665: /**
0666: * Adds the specified RecordListener. If the specified listener
0667: * is already registered, it will not be added a second time.
0668: * When a record store is closed, all listeners are removed.
0669: *
0670: * @param listener the RecordChangedListener
0671: * @see #removeRecordListener
0672: */
0673: public void addRecordListener(RecordListener listener) {
0674: synchronized (recordListener) {
0675: if (!recordListener.contains(listener)) {
0676: recordListener.addElement(listener);
0677: }
0678: }
0679: }
0680:
0681: /**
0682: * Removes the specified RecordListener. If the specified listener
0683: * is not registered, this method does nothing.
0684: *
0685: * @param listener the RecordChangedListener
0686: * @see #addRecordListener
0687: */
0688: public void removeRecordListener(RecordListener listener) {
0689: synchronized (recordListener) {
0690: recordListener.removeElement(listener);
0691: }
0692: }
0693:
0694: /**
0695: * Returns the recordId of the next record to be added to the
0696: * record store. This can be useful for setting up pseudo-relational
0697: * relationships. That is, if you have two or more
0698: * record stores whose records need to refer to one another, you can
0699: * predetermine the recordIds of the records that will be created
0700: * in one record store, before populating the fields and allocating
0701: * the record in another record store. Note that the recordId returned
0702: * is only valid while the record store remains open and until a call
0703: * to <code>addRecord()</code>.
0704: *
0705: * @return the recordId of the next record to be added to the
0706: * record store
0707: *
0708: * @exception RecordStoreNotOpenException if the record store is
0709: * not open
0710: * @exception RecordStoreException if a different record
0711: * store-related exception occurred
0712: */
0713: public int getNextRecordID() throws RecordStoreNotOpenException,
0714: RecordStoreException {
0715:
0716: checkOpen();
0717: return peer.getNextRecordID();
0718: }
0719:
0720: /**
0721: * Adds a new record to the record store. The recordId for this
0722: * new record is returned. This is a blocking atomic operation.
0723: * The record is written to persistent storage before the
0724: * method returns.
0725: *
0726: * @param data the data to be stored in this record. If the record
0727: * is to have zero-length data (no data), this parameter may be
0728: * null.
0729: * @param offset the index into the data buffer of the first
0730: * relevant byte for this record
0731: * @param numBytes the number of bytes of the data buffer to use
0732: * for this record (may be zero)
0733: *
0734: * @return the recordId for the new record
0735: *
0736: * @exception RecordStoreNotOpenException if the record store is
0737: * not open
0738: * @exception RecordStoreException if a different record
0739: * store-related exception occurred
0740: * @exception RecordStoreFullException if the operation cannot be
0741: * completed because the record store has no more room
0742: * @exception SecurityException if the MIDlet has read-only access
0743: * to the RecordStore
0744: */
0745: public int addRecord(byte[] data, int offset, int numBytes)
0746: throws RecordStoreNotOpenException, RecordStoreException,
0747: RecordStoreFullException {
0748: checkOpen();
0749: checkWritable();
0750:
0751: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0752: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
0753: "addRecord(data=" + data + ", offset=" + offset
0754: + ", numBytes=" + numBytes + ")");
0755: }
0756:
0757: // validate parameters
0758: if ((data == null) && (numBytes > 0)) {
0759: throw new NullPointerException("illegal arguments: null "
0760: + "data, numBytes > 0");
0761: }
0762: if ((offset < 0)
0763: || (numBytes < 0)
0764: || ((data != null) && (offset + numBytes > data.length))) {
0765: throw new ArrayIndexOutOfBoundsException();
0766: }
0767:
0768: int recordId = peer.addRecord(data, offset, numBytes);
0769:
0770: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0771: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
0772: "recordId = " + recordId);
0773: }
0774:
0775: // tell listeners a record has been added
0776: notifyRecordAddedListeners(recordId);
0777:
0778: return recordId;
0779: }
0780:
0781: /**
0782: * The record is deleted from the record store. The recordId for
0783: * this record is NOT reused.
0784: *
0785: * @param recordId the ID of the record to delete
0786: *
0787: * @exception RecordStoreNotOpenException if the record store is
0788: * not open
0789: * @exception InvalidRecordIDException if the recordId is invalid
0790: * @exception RecordStoreException if a general record store
0791: * exception occurs
0792: * @exception SecurityException if the MIDlet has read-only access
0793: * to the RecordStore
0794: */
0795: public void deleteRecord(int recordId)
0796: throws RecordStoreNotOpenException,
0797: InvalidRecordIDException, RecordStoreException {
0798:
0799: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0800: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
0801: "deleteRecord(" + recordId + ")");
0802: }
0803: checkOpen();
0804: checkWritable();
0805: peer.deleteRecord(recordId);
0806:
0807: // tell listeners a record has been deleted
0808: notifyRecordDeletedListeners(recordId);
0809: }
0810:
0811: /**
0812: * Returns the size (in bytes) of the MIDlet data available
0813: * in the given record.
0814: *
0815: * @param recordId the ID of the record to use in this operation
0816: *
0817: * @return the size (in bytes) of the MIDlet data available
0818: * in the given record
0819: *
0820: * @exception RecordStoreNotOpenException if the record store is
0821: * not open
0822: * @exception InvalidRecordIDException if the recordId is invalid
0823: * @exception RecordStoreException if a general record store
0824: * exception occurs
0825: */
0826: public int getRecordSize(int recordId)
0827: throws RecordStoreNotOpenException,
0828: InvalidRecordIDException, RecordStoreException {
0829: checkOpen();
0830: int size = peer.getRecordSize(recordId);
0831: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0832: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
0833: "getSize(" + recordId + ") = " + size);
0834: }
0835: return size;
0836: }
0837:
0838: /**
0839: * Returns the data stored in the given record.
0840: *
0841: * @param recordId the ID of the record to use in this operation
0842: * @param buffer the byte array in which to copy the data
0843: * @param offset the index into the buffer in which to start copying
0844: *
0845: * @exception RecordStoreNotOpenException if the record store is
0846: * not open
0847: * @exception InvalidRecordIDException if the recordId is invalid
0848: * @exception RecordStoreException if a general record store
0849: * exception occurs
0850: * @exception ArrayIndexOutOfBoundsException if the record is
0851: * larger than the buffer supplied
0852: *
0853: * @return the number of bytes copied into the buffer, starting at
0854: * index <code>offset</code>
0855: * @see #setRecord
0856: */
0857: public int getRecord(int recordId, byte[] buffer, int offset)
0858: throws RecordStoreNotOpenException,
0859: InvalidRecordIDException, RecordStoreException {
0860: checkOpen();
0861: return peer.getRecord(recordId, buffer, offset);
0862: }
0863:
0864: /**
0865: * Returns a copy of the data stored in the given record.
0866: *
0867: * @param recordId the ID of the record to use in this operation
0868: *
0869: * @exception RecordStoreNotOpenException if the record store is
0870: * not open
0871: * @exception InvalidRecordIDException if the recordId is invalid
0872: * @exception RecordStoreException if a general record store
0873: * exception occurs
0874: *
0875: * @return the data stored in the given record. Note that if the
0876: * record has no data, this method will return null.
0877: * @see #setRecord
0878: */
0879: public byte[] getRecord(int recordId)
0880: throws RecordStoreNotOpenException,
0881: InvalidRecordIDException, RecordStoreException {
0882: checkOpen();
0883: return peer.getRecord(recordId);
0884: }
0885:
0886: /**
0887: * Sets the data in the given record to that passed in. After
0888: * this method returns, a call to <code>getRecord(int recordId)</code>
0889: * will return an array of numBytes size containing the data
0890: * supplied here.
0891: *
0892: * @param recordId the ID of the record to use in this operation
0893: * @param newData the new data to store in the record
0894: * @param offset the index into the data buffer of the first
0895: * relevant byte for this record
0896: * @param numBytes the number of bytes of the data buffer to use
0897: * for this record
0898: *
0899: * @exception RecordStoreNotOpenException if the record store is
0900: * not open
0901: * @exception InvalidRecordIDException if the recordId is invalid
0902: * @exception RecordStoreException if a general record store
0903: * exception occurs
0904: * @exception RecordStoreFullException if the operation cannot be
0905: * completed because the record store has no more room
0906: * @exception SecurityException if the MIDlet has read-only access
0907: * to the RecordStore
0908: * @see #getRecord
0909: */
0910: public void setRecord(int recordId, byte[] newData, int offset,
0911: int numBytes) throws RecordStoreNotOpenException,
0912: InvalidRecordIDException, RecordStoreException,
0913: RecordStoreFullException {
0914:
0915: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0916: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
0917: "setRecord(" + recordId + ")");
0918: }
0919:
0920: // validate parameters
0921: if ((newData == null) && (numBytes > 0)) {
0922: throw new NullPointerException("illegal arguments: null "
0923: + "data, numBytes > 0");
0924: }
0925: if ((offset < 0)
0926: || (numBytes < 0)
0927: || ((newData != null) && (offset + numBytes > newData.length))) {
0928: throw new ArrayIndexOutOfBoundsException();
0929: }
0930:
0931: checkOpen();
0932: checkWritable();
0933:
0934: peer.setRecord(recordId, newData, offset, numBytes);
0935:
0936: // update database header info and sync to file
0937: notifyRecordChangedListeners(recordId);
0938: }
0939:
0940: /**
0941: * Returns an enumeration for traversing a set of records in the
0942: * record store in an optionally specified order.<p>
0943: *
0944: * The filter, if non-null, will be used to determine what
0945: * subset of the record store records will be used.<p>
0946: *
0947: * The comparator, if non-null, will be used to determine the
0948: * order in which the records are returned.<p>
0949: *
0950: * If both the filter and comparator is null, the enumeration
0951: * will traverse all records in the record store in an undefined
0952: * order. This is the most efficient way to traverse all of the
0953: * records in a record store. If a filter is used with a null
0954: * comparator, the enumeration will traverse the filtered records
0955: * in an undefined order.
0956: *
0957: * The first call to <code>RecordEnumeration.nextRecord()</code>
0958: * returns the record data from the first record in the sequence.
0959: * Subsequent calls to <code>RecordEnumeration.nextRecord()</code>
0960: * return the next consecutive record's data. To return the record
0961: * data from the previous consecutive from any
0962: * given point in the enumeration, call <code>previousRecord()</code>.
0963: * On the other hand, if after creation the first call is to
0964: * <code>previousRecord()</code>, the record data of the last element
0965: * of the enumeration will be returned. Each subsequent call to
0966: * <code>previousRecord()</code> will step backwards through the
0967: * sequence.
0968: *
0969: * @param filter if non-null, will be used to determine what
0970: * subset of the record store records will be used
0971: * @param comparator if non-null, will be used to determine the
0972: * order in which the records are returned
0973: * @param keepUpdated if true, the enumerator will keep its enumeration
0974: * current with any changes in the records of the record
0975: * store. Use with caution as there are possible
0976: * performance consequences. If false the enumeration
0977: * will not be kept current and may return recordIds for
0978: * records that have been deleted or miss records that
0979: * are added later. It may also return records out of
0980: * order that have been modified after the enumeration
0981: * was built. Note that any changes to records in the
0982: * record store are accurately reflected when the record
0983: * is later retrieved, either directly or through the
0984: * enumeration. The thing that is risked by setting this
0985: * parameter false is the filtering and sorting order of
0986: * the enumeration when records are modified, added, or
0987: * deleted.
0988: *
0989: * @exception RecordStoreNotOpenException if the record store is
0990: * not open
0991: *
0992: * @see RecordEnumeration#rebuild
0993: *
0994: * @return an enumeration for traversing a set of records in the
0995: * record store in an optionally specified order
0996: */
0997: public RecordEnumeration enumerateRecords(RecordFilter filter,
0998: RecordComparator comparator, boolean keepUpdated)
0999: throws RecordStoreNotOpenException {
1000:
1001: checkOpen();
1002: return new RecordEnumerationImpl(this , filter, comparator,
1003: keepUpdated);
1004: }
1005:
1006: /**
1007: * Get the open status of this record store. (Package accessible
1008: * for use by record enumeration objects.)
1009: *
1010: * @return true if record store is open, false otherwise.
1011: */
1012: boolean isOpen() {
1013: synchronized (openRecordStores) {
1014: return (peer != null);
1015: }
1016: }
1017:
1018: /**
1019: * Returns all of the recordId's currently in the record store.
1020: *
1021: * @return an array of the recordId's currently in the record store
1022: * or null if the record store is closed.
1023: */
1024: int[] getRecordIDs() {
1025: return peer.getRecordIDs();
1026: }
1027:
1028: /**
1029: * Throws a RecordStoreNotOpenException if the RecordStore
1030: * is closed. (A RecordStore is closed if the RecordStoreFile
1031: * instance variable <code>dbraf</code> is null.
1032: *
1033: * @exception RecordStoreNotOpenException if RecordStore is closed
1034: */
1035: private void checkOpen() throws RecordStoreNotOpenException {
1036: if (!isOpen()) {
1037: throw new RecordStoreNotOpenException();
1038: }
1039: }
1040:
1041: /**
1042: * Internal method to determine if writing to this record store
1043: * is allowed for the calling MIDlet. Returns <code>true</code>
1044: * if <code>isRecordStoreOwner()</code> returns <code>true</code> or
1045: * <code>dbAuthMode</code> == 1 when <code>isRecordStoreOwner()</code>
1046: * returns <code>false</code>.
1047: *
1048: * @exception SecurityException if the MIDlet has read-only access
1049: * to the RecordStore
1050: */
1051: private void checkWritable() {
1052: if (isRecordStoreOwner()) {
1053: return;
1054: } else {
1055: if (peer.getAuthMode() == AUTHMODE_ANY) { // Read-Write mode
1056: return;
1057: }
1058: }
1059:
1060: throw new SecurityException("no write access");
1061: }
1062:
1063: /**
1064: * Internal method to check record store owner vs. the vendor and suite
1065: * of the currently running midlet
1066: *
1067: * @return <code>true</code> if vendor and suite name both match,
1068: * <code>false</code> otherwise
1069: */
1070: private boolean isRecordStoreOwner() {
1071: int currentId = MIDletStateHandler.getMidletStateHandler()
1072: .getMIDletSuite().getID();
1073:
1074: return (suiteId == currentId);
1075: }
1076:
1077: /**
1078: * Notifies all registered listeners that a record changed.
1079: *
1080: * @param recordId the record id of the changed record.
1081: */
1082: private void notifyRecordChangedListeners(int recordId) {
1083: synchronized (recordListener) {
1084:
1085: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
1086: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
1087: "notify Change # listener = "
1088: + recordListener.size());
1089: }
1090: int numListeners = recordListener.size();
1091: for (int i = 0; i < numListeners; i++) {
1092: RecordListener rl = (RecordListener) recordListener
1093: .elementAt(i);
1094: rl.recordChanged(this , recordId);
1095: }
1096: }
1097: }
1098:
1099: /**
1100: * Notifies all registered listeners that a record was added.
1101: *
1102: * @param recordId the record id of the added record.
1103: */
1104: private void notifyRecordAddedListeners(int recordId) {
1105: synchronized (recordListener) {
1106: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
1107: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
1108: "notify Add # listener = "
1109: + recordListener.size());
1110: }
1111: int numListeners = recordListener.size();
1112: for (int i = 0; i < numListeners; i++) {
1113: RecordListener rl = (RecordListener) recordListener
1114: .elementAt(i);
1115: rl.recordAdded(this , recordId);
1116: }
1117: }
1118: }
1119:
1120: /**
1121: * Notifies all registered listeners that a record was deleted.
1122: *
1123: * @param recordId the record id of the changed record.
1124: */
1125: private void notifyRecordDeletedListeners(int recordId) {
1126: synchronized (recordListener) {
1127: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
1128: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
1129: "notify Delete # listener = "
1130: + recordListener.size());
1131: }
1132: int numListeners = recordListener.size();
1133: for (int i = 0; i < numListeners; i++) {
1134: RecordListener rl = (RecordListener) recordListener
1135: .elementAt(i);
1136: rl.recordDeleted(this , recordId);
1137: }
1138: }
1139: }
1140:
1141: /**
1142: * Internal method to open (and possibly create) a record store associated
1143: * with the given MIDlet suite. If this method is called by a MIDlet when
1144: * the record store is already open by a MIDlet in the MIDlet suite,
1145: * this method returns a reference to the same RecordStoreImpl object.
1146: *
1147: * @param suiteId ID of the MIDlet suite that owns the record store
1148: * @param recordStoreName the MIDlet suite unique name for the
1149: * record store, consisting of between one and 32 Unicode
1150: * characters inclusive.
1151: * @param createIfNecessary if true, the record store will be
1152: * created if necessary
1153: *
1154: * @return <code>RecordStore</code> object for the record store
1155: *
1156: * @exception RecordStoreException if a record store-related
1157: * exception occurred
1158: * @exception RecordStoreNotFoundException if the record store
1159: * could not be found
1160: * @exception RecordStoreFullException if the operation cannot be
1161: * completed because the record store is full
1162: * @exception IllegalArgumentException if
1163: * recordStoreName is invalid
1164: */
1165: private static RecordStore doOpen(int suiteId,
1166: String recordStoreName, boolean createIfNecessary)
1167: throws RecordStoreException, RecordStoreFullException,
1168: RecordStoreNotFoundException {
1169:
1170: RecordStore recordStore;
1171:
1172: if (recordStoreName.length() > 32
1173: || recordStoreName.length() == 0) {
1174: throw new IllegalArgumentException();
1175: }
1176:
1177: synchronized (openRecordStores) {
1178: // Save record store instances and ensure that there is only
1179: // one record store object in memory for any given record
1180: // store file. This is good for memory use. This is NOT safe
1181: // in the situation where multiple VM's may be executing code
1182: // concurrently. In that case, you have to sync things through
1183: // file locking or something similar.
1184:
1185: // Check the record store instance list for a db with the same name
1186: int size = openRecordStores.size();
1187: for (int n = 0; n < size; n++) {
1188: recordStore = (RecordStore) openRecordStores
1189: .elementAt(n);
1190: if (recordStore.suiteId == suiteId
1191: && recordStore.recordStoreName
1192: .equals(recordStoreName)) {
1193: recordStore.opencount++; // increment the open count
1194: return recordStore; // return ref to cached record store
1195: }
1196: }
1197:
1198: /*
1199: * Record store not found in cache, so create it.
1200: * If createIfNecessary is FALSE and the RecordStore
1201: * does not exists, a RecordStoreNotFoundException is
1202: * thrown.
1203: */
1204: recordStore = new RecordStore(suiteId, recordStoreName);
1205: recordStore.peer = RecordStoreImpl.openRecordStore(
1206: classSecurityToken, suiteId, recordStoreName,
1207: createIfNecessary);
1208:
1209: /*
1210: * Now add the new record store to the cache
1211: */
1212: recordStore.opencount = 1;
1213: openRecordStores.addElement(recordStore);
1214: }
1215:
1216: return recordStore;
1217: }
1218:
1219: /**
1220: * Internal method to open a record store for lock testing. When
1221: * lock testing the record store will be obtained from the cache or
1222: * put in the cache, this allows testing of lower level locking without
1223: * the need to run multiple Isolates.
1224: *
1225: * @param recordStoreName the MIDlet suite unique name for the
1226: * record store, consisting of between one and 32 Unicode
1227: * characters inclusive.
1228: *
1229: * @return <code>RecordStore</code> object for the record store
1230: *
1231: * @exception RecordStoreException if a record store-related
1232: * exception occurred
1233: * @exception RecordStoreNotFoundException if the record store
1234: * could not be found
1235: * @exception RecordStoreFullException if the operation cannot be
1236: * completed because the record store is full
1237: * @exception IllegalArgumentException if
1238: * recordStoreName is invalid
1239: */
1240: static RecordStore openForLockTesting(String recordStoreName)
1241: throws RecordStoreException, RecordStoreFullException,
1242: RecordStoreNotFoundException {
1243:
1244: RecordStore recordStore;
1245: int suiteId = MIDletStateHandler.getMidletStateHandler()
1246: .getMIDletSuite().getID();
1247:
1248: recordStore = new RecordStore(suiteId, recordStoreName);
1249: recordStore.peer = RecordStoreImpl.openRecordStore(
1250: classSecurityToken, suiteId, recordStoreName, false);
1251: recordStore.opencount = 1;
1252: return recordStore;
1253: }
1254: }
|