/* * MicroEmulator * Copyright (C) 2001-2005 Bartek Teodorczyk <barteo@barteo.net> * * It is licensed under the following two licenses as alternatives: * 1. GNU Lesser General Public License (the "LGPL") version 2.1 or any newer version * 2. Apache License (the "AL") Version 2.0 * * You may not use this file except in compliance with at least one of * the above two licenses. * * You may obtain a copy of the LGPL at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt * * You may obtain a copy of the AL at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the LGPL or the AL for the specific language governing permissions and * limitations. */ package org.microemu.util; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import javax.microedition.rms.InvalidRecordIDException; import javax.microedition.rms.RecordComparator; import javax.microedition.rms.RecordEnumeration; import javax.microedition.rms.RecordFilter; import javax.microedition.rms.RecordListener; import javax.microedition.rms.RecordStore; import javax.microedition.rms.RecordStoreException; import javax.microedition.rms.RecordStoreFullException; import javax.microedition.rms.RecordStoreNotOpenException; import org.microemu.RecordStoreManager; import protocol.net.TcpSocket; public class RecordStoreImpl extends RecordStore { private static final byte[] fileIdentifier = { 0x4d, 0x49, 0x44, 0x52, 0x4d, 0x53 }; private static final byte versionMajor = 0x03; private static final byte versionMinor = 0x00; private int lastRecordId = 0; private int size = 0; private Hashtable<Integer, byte[]> records = new Hashtable<Integer, byte[]>(); private String recordStoreName; private int version = 0; private long lastModified = 0; private transient boolean open; private transient RecordStoreManager recordStoreManager; private transient Vector<RecordListener> recordListeners = new Vector<RecordListener>(); public RecordStoreImpl(RecordStoreManager recordStoreManager, String recordStoreName) { this.recordStoreManager = recordStoreManager; if (recordStoreName.length() <= 32) { this.recordStoreName = recordStoreName; } else { this.recordStoreName = recordStoreName.substring(0, 32); } this.open = false; } public RecordStoreImpl(RecordStoreManager recordStoreManager) throws IOException { this.recordStoreManager = recordStoreManager; } public int readHeader(DataInputStream dis) throws IOException { for (int i = 0; i < fileIdentifier.length; i++) { if (dis.read() != fileIdentifier[i]) { throw new IOException(); } } dis.read(); // Major version number dis.read(); // Minor version number dis.read(); // Encrypted flag recordStoreName = dis.readUTF(); lastModified = dis.readLong(); version = dis.readInt(); dis.readInt(); // TODO AuthMode dis.readByte(); // TODO Writable size = dis.readInt(); return size; } public void readRecord(DataInputStream dis) throws IOException { int recordId = dis.readInt(); if (recordId > lastRecordId) { lastRecordId = recordId; } dis.readInt(); // TODO Tag byte[] data = new byte[dis.readInt()]; TcpSocket.readFully(dis, data, 0, data.length); this.records.put(new Integer(recordId), data); } public void writeHeader(DataOutputStream dos) throws IOException { dos.write(fileIdentifier); dos.write(versionMajor); dos.write(versionMinor); dos.write(0); // Encrypted flag dos.writeUTF(recordStoreName); dos.writeLong(lastModified); dos.writeInt(version); dos.writeInt(0); // TODO AuthMode dos.writeByte(0); // TODO Writable dos.writeInt(size); } public void writeRecord(DataOutputStream dos, int recordId) throws IOException { dos.writeInt(recordId); dos.writeInt(0); // TODO Tag try { byte[] data = getRecord(recordId); if (data == null) { dos.writeInt(0); } else { dos.writeInt(data.length); dos.write(data); } } catch (RecordStoreException e) { throw new IOException(); } } public boolean isOpen() { return open; } public void setOpen(boolean open) { this.open = open; } public void closeRecordStore() throws RecordStoreNotOpenException, RecordStoreException { if (!open) { throw new RecordStoreNotOpenException(); } if (recordListeners != null) { recordListeners.removeAllElements(); } recordStoreManager.fireRecordStoreListener(ExtendedRecordListener.RECORDSTORE_CLOSE, this.getName()); records.clear(); open = false; } public String getName() throws RecordStoreNotOpenException { if (!open) { throw new RecordStoreNotOpenException(); } return recordStoreName; } public int getVersion() throws RecordStoreNotOpenException { if (!open) { throw new RecordStoreNotOpenException(); } synchronized (this) { return version; } } public int getNumRecords() throws RecordStoreNotOpenException { if (!open) { throw new RecordStoreNotOpenException(); } return size; } public int getSize() throws RecordStoreNotOpenException { if (!open) { throw new RecordStoreNotOpenException(); } // TODO include size overhead such as the data structures used to hold the state of the record store // Preload all records enumerateRecords(null, null, false); int result = 0; Enumeration keys = records.keys(); while (keys.hasMoreElements()) { int key = ((Integer) keys.nextElement()).intValue(); try { byte[] data = getRecord(key); if (data != null) { result += data.length; } } catch (RecordStoreException e) { e.printStackTrace(); } } return result; } public int getSizeAvailable() throws RecordStoreNotOpenException { if (!open) { throw new RecordStoreNotOpenException(); } return recordStoreManager.getSizeAvailable(this); } public long getLastModified() throws RecordStoreNotOpenException { if (!open) { throw new RecordStoreNotOpenException(); } synchronized (this) { return lastModified; } } public void addRecordListener(RecordListener listener) { if (!recordListeners.contains(listener)) { recordListeners.addElement(listener); } } public void removeRecordListener(RecordListener listener) { recordListeners.removeElement(listener); } public int getNextRecordID() throws RecordStoreNotOpenException, RecordStoreException { if (!open) { throw new RecordStoreNotOpenException(); } // lastRecordId needs to hold correct number, all records have to be preloaded enumerateRecords(null, null, false); synchronized (this) { return lastRecordId + 1; } } public int addRecord(byte[] data, int offset, int numBytes) throws RecordStoreNotOpenException, RecordStoreException, RecordStoreFullException { if (!open) { throw new RecordStoreNotOpenException(); } if (data == null && numBytes > 0) { throw new NullPointerException(); } if (numBytes > recordStoreManager.getSizeAvailable(this)) { throw new RecordStoreFullException(); } // lastRecordId needs to hold correct number, all records have to be preloaded enumerateRecords(null, null, false); byte[] recordData = new byte[numBytes]; if (data != null) { System.arraycopy(data, offset, recordData, 0, numBytes); } int nextRecordID = getNextRecordID(); synchronized (this) { records.put(new Integer(nextRecordID), recordData); version++; lastModified = System.currentTimeMillis(); lastRecordId++; size++; } recordStoreManager.saveRecord(this, nextRecordID); fireRecordListener(ExtendedRecordListener.RECORD_ADD, nextRecordID); return nextRecordID; } public void deleteRecord(int recordId) throws RecordStoreNotOpenException, InvalidRecordIDException, RecordStoreException { if (!open) { throw new RecordStoreNotOpenException(); } synchronized (this) { // throws InvalidRecordIDException when no record found getRecord(recordId); records.remove(new Integer(recordId)); version++; lastModified = System.currentTimeMillis(); size--; } recordStoreManager.deleteRecord(this, recordId); fireRecordListener(ExtendedRecordListener.RECORD_DELETE, recordId); } public int getRecordSize(int recordId) throws RecordStoreNotOpenException, InvalidRecordIDException, RecordStoreException { if (!open) { throw new RecordStoreNotOpenException(); } synchronized (this) { byte[] data = (byte[]) records.get(new Integer(recordId)); if (data == null) { recordStoreManager.loadRecord(this, recordId); data = (byte[]) records.get(new Integer(recordId)); if (data == null) { throw new InvalidRecordIDException(); } } return data.length; } } public int getRecord(int recordId, byte[] buffer, int offset) throws RecordStoreNotOpenException, InvalidRecordIDException, RecordStoreException { int recordSize; synchronized (this) { recordSize = getRecordSize(recordId); System.arraycopy(records.get(new Integer(recordId)), 0, buffer, offset, recordSize); } fireRecordListener(ExtendedRecordListener.RECORD_READ, recordId); return recordSize; } public byte[] getRecord(int recordId) throws RecordStoreNotOpenException, InvalidRecordIDException, RecordStoreException { if (!open) { throw new RecordStoreNotOpenException(); } byte[] data; synchronized (this) { data = new byte[getRecordSize(recordId)]; getRecord(recordId, data, 0); } return data.length < 1 ? null : data; } public void setRecord(int recordId, byte[] newData, int offset, int numBytes) throws RecordStoreNotOpenException, InvalidRecordIDException, RecordStoreException, RecordStoreFullException { if (!open) { throw new RecordStoreNotOpenException(); } // FIXME fixit if (numBytes > recordStoreManager.getSizeAvailable(this)) { throw new RecordStoreFullException(); } byte[] recordData = new byte[numBytes]; System.arraycopy(newData, offset, recordData, 0, numBytes); synchronized (this) { // throws InvalidRecordIDException when no record found getRecord(recordId); records.put(new Integer(recordId), recordData); version++; lastModified = System.currentTimeMillis(); } recordStoreManager.saveRecord(this, recordId); fireRecordListener(ExtendedRecordListener.RECORD_CHANGE, recordId); } public RecordEnumeration enumerateRecords(RecordFilter filter, RecordComparator comparator, boolean keepUpdated) throws RecordStoreNotOpenException { if (!open) { throw new RecordStoreNotOpenException(); } return new RecordEnumerationImpl(this, filter, comparator, keepUpdated); } public int getHeaderSize() { // TODO fixit return recordStoreName.length() + 4 + 8 + 4; } public int getRecordHeaderSize() { return 4 + 4; } private void fireRecordListener(int type, int recordId) { long timestamp = System.currentTimeMillis(); if (recordListeners != null) { for (Enumeration e = recordListeners.elements(); e.hasMoreElements();) { RecordListener l = (RecordListener) e.nextElement(); if (l instanceof ExtendedRecordListener) { ((ExtendedRecordListener) l).recordEvent(type, timestamp, this, recordId); } else { switch (type) { case ExtendedRecordListener.RECORD_ADD: l.recordAdded(this, recordId); break; case ExtendedRecordListener.RECORD_CHANGE: l.recordChanged(this, recordId); break; case ExtendedRecordListener.RECORD_DELETE: l.recordDeleted(this, recordId); } } } } } }