/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.sun.lwuit.io.impl;
import com.sun.lwuit.io.util.BufferedInputStream;
import com.sun.lwuit.io.util.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import javax.microedition.io.Connection;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.io.file.FileConnection;
import javax.microedition.io.file.FileSystemRegistry;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
/**
* Implementation of the IO framework on top of MIDP/CLDC GCF
*
* @author Shai Almog
*/
public class MIDPImpl extends IOImplementation {
private short currentKey = 1;
/**
* The File Allocation Table assigns user based file names to RMS storage
*/
private Hashtable fat = new Hashtable();
/**
* Initializes various internal states
*/
protected MIDPImpl() {
RecordEnumeration e = null;
RecordStore r = null;
try {
r = RecordStore.openRecordStore("FAT", true);
if (r.getNumRecords() > 0) {
e = r.enumerateRecords(null, null, false);
while (e.hasNextElement()) {
byte[] rec = e.nextRecord();
ByteArrayInputStream bi = new ByteArrayInputStream(rec);
DataInputStream di = new DataInputStream(bi);
String name = di.readUTF();
short key = di.readShort();
di.close();
bi.close();
fat.put(name, new Short(key));
if(key >= currentKey) {
currentKey += key;
}
}
e.destroy();
e = null;
}
r.closeRecordStore();
r = null;
} catch (Exception ex) {
//#ifndef RIM
ex.printStackTrace();
//#else
//# System.out.println("Exception in object store constructor " + ex);
//#endif
cleanup(r);
cleanup(e);
}
}
/**
* @inheritDoc
*/
public Object connect(String url, boolean read, boolean write) throws IOException {
int mode;
if(read && write) {
mode = Connector.READ_WRITE;
} else {
if(write) {
mode = Connector.WRITE;
} else {
mode = Connector.READ;
}
}
return Connector.open(url, mode);
}
/**
* @inheritDoc
*/
public void setHeader(Object connection, String key, String val) {
try {
((HttpConnection)connection).setRequestProperty(key, val);
} catch(IOException err) {
// this exception doesn't make sense since at this point no connection is in place
err.printStackTrace();
}
}
/**
* @inheritDoc
*/
public int getContentLength(Object connection) {
return (int)((HttpConnection)connection).getLength();
}
/**
* @inheritDoc
*/
public void cleanup(Object o) {
try {
if(o != null) {
if(o instanceof Connection) {
((Connection) o).close();
return;
}
if(o instanceof RecordEnumeration) {
((RecordEnumeration) o).destroy();
return;
}
if(o instanceof RecordStore) {
((RecordStore) o).closeRecordStore();
return;
}
super.cleanup(o);
}
} catch (Throwable ex) {
ex.printStackTrace();
}
}
/**
* @inheritDoc
*/
public OutputStream openOutputStream(Object connection) throws IOException {
if(connection instanceof String) {
FileConnection fc = (FileConnection)Connector.open((String)connection, Connector.READ_WRITE);
if(!fc.exists()) {
fc.create();
}
BufferedOutputStream o = new BufferedOutputStream(fc.openOutputStream(), (String)connection);
o.setConnection(fc);
return o;
}
return new BufferedOutputStream(((HttpConnection)connection).openOutputStream(), ((HttpConnection)connection).getURL());
}
/**
* @inheritDoc
*/
public OutputStream openOutputStream(Object connection, int offset) throws IOException {
FileConnection fc = (FileConnection)Connector.open((String)connection, Connector.READ_WRITE);
if(!fc.exists()) {
fc.create();
}
BufferedOutputStream o = new BufferedOutputStream(fc.openOutputStream(offset), (String)connection);
o.setConnection(fc);
return o;
}
/**
* @inheritDoc
*/
public InputStream openInputStream(Object connection) throws IOException {
if(connection instanceof String) {
FileConnection fc = (FileConnection)Connector.open((String)connection, Connector.READ);
BufferedInputStream o = new BufferedInputStream(fc.openInputStream(), (String)connection);
o.setConnection(fc);
return o;
}
return new BufferedInputStream(((HttpConnection)connection).openInputStream(), ((HttpConnection)connection).getURL());
}
/**
* @inheritDoc
*/
public void setPostRequest(Object connection, boolean p) {
try {
if(p) {
((HttpConnection)connection).setRequestMethod(HttpConnection.POST);
} else {
((HttpConnection)connection).setRequestMethod(HttpConnection.GET);
}
} catch(IOException err) {
// an exception here doesn't make sense
err.printStackTrace();
}
}
/**
* @inheritDoc
*/
public int getResponseCode(Object connection) throws IOException {
return ((HttpConnection)connection).getResponseCode();
}
/**
* @inheritDoc
*/
public String getResponseMessage(Object connection) throws IOException {
return ((HttpConnection)connection).getResponseMessage();
}
/**
* @inheritDoc
*/
public String getHeaderField(String name, Object connection) throws IOException {
return ((HttpConnection)connection).getHeaderField(name);
}
/**
* @inheritDoc
*/
public String[] getHeaderFields(String name, Object connection) throws IOException {
HttpConnection c = (HttpConnection)connection;
Vector r = new Vector();
int i = 0;
while (c.getHeaderFieldKey(i) != null) {
if (c.getHeaderFieldKey(i).equalsIgnoreCase(name)) {
String val = c.getHeaderField(i);
r.addElement(val);
}
i++;
}
if(r.size() == 0) {
return null;
}
String[] response = new String[r.size()];
for(int iter = 0 ; iter < response.length ; iter++) {
response[iter] = (String)r.elementAt(iter);
}
return response;
}
/**
* @inheritDoc
*/
public void deleteStorageFile(String name) {
Short key = (Short)fat.get(name);
fat.remove(name);
resaveFat();
if(key != null) {
try {
for(char c = 'A' ; c < 'Z' ; c++) {
RecordStore.deleteRecordStore("" + c + key);
}
} catch(RecordStoreException e) {}
}
}
private void resaveFat() {
RecordStore r = null;
RecordEnumeration e = null;
try {
r = RecordStore.openRecordStore("FAT", true);
Vector keys = new Vector();
Enumeration fatKeys = fat.keys();
while(fatKeys.hasMoreElements()) {
keys.addElement(fatKeys.nextElement());
}
e = r.enumerateRecords(null, null, false);
while (e.hasNextElement()) {
int recordId = e.nextRecordId();
byte[] rec = r.getRecord(recordId);
ByteArrayInputStream bi = new ByteArrayInputStream(rec);
DataInputStream di = new DataInputStream(bi);
String name = di.readUTF();
short key = di.readShort();
di.close();
bi.close();
Short fatKey = (Short)fat.get(name);
if(fatKey == null) {
// we need to remove this record...
r.deleteRecord(recordId);
} else {
// we need to update the record
if(fatKey.shortValue() != key) {
byte[] bd = toRecord(name, fatKey.shortValue());
r.setRecord(recordId, bd, 0, bd.length);
}
}
keys.removeElement(name);
}
e.destroy();
e = null;
Enumeration remainingKeys = keys.elements();
while(remainingKeys.hasMoreElements()) {
String name = (String)remainingKeys.nextElement();
Short key = (Short)fat.get(name);
byte[] bd = toRecord(name, key.shortValue());
r.addRecord(bd, 0, bd.length);
}
r.closeRecordStore();
} catch(Exception err) {
// This might be a valid exception and some platforms (e..g. RIM) don't respond well to PST
//err.printStackTrace();
cleanup(e);
cleanup(r);
}
}
private byte[] toRecord(String name, short key) throws IOException {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
DataOutputStream d = new DataOutputStream(bo);
d.writeUTF(name);
d.writeShort(key);
d.close();
bo.close();
return bo.toByteArray();
}
/**
* @inheritDoc
*/
public OutputStream openFileOutputStream(String file) throws IOException {
return openOutputStream(file);
}
/**
* @inheritDoc
*/
public InputStream openFileInputStream(String file) throws IOException {
return openInputStream(file);
}
private class RMSOutputStream extends OutputStream {
private short key;
private char letter = 'A';
private ByteArrayOutputStream cache;
private int offset;
public RMSOutputStream(short key) {
this.key = key;
// first we need to cleanup existing files
try {
for(char c = 'A' ; c < 'Z' ; c++) {
RecordStore.deleteRecordStore("" + c + key);
}
} catch(RecordStoreException e) {}
cache = new ByteArrayOutputStream();
}
public void close() throws IOException {
flush();
cache = null;
}
public void flush() throws IOException {
if(cache != null) {
byte[] data = cache.toByteArray();
if(data.length > 0) {
RecordStore r = null;
try {
r = RecordStore.openRecordStore("" + letter + key, true);
r.addRecord(data, 0, data.length);
r.closeRecordStore();
if(letter == 'Z') {
letter = 'a';
} else {
letter++;
}
cache.reset();
} catch (RecordStoreException ex) {
ex.printStackTrace();
cleanup(r);
throw new IOException(ex.toString());
}
}
}
}
public void write(byte[] arg0) throws IOException {
cache.write(arg0);
if(cache.size() > 32536) {
flush();
}
}
public void write(byte[] arg0, int arg1, int arg2) throws IOException {
cache.write(arg0, arg1, arg2);
if(cache.size() > 32536) {
flush();
}
}
public void write(int arg0) throws IOException {
cache.write(arg0);
if(cache.size() > 32536) {
flush();
}
}
}
private class RMSInputStream extends InputStream {
private InputStream current;
private int offset;
private short key;
public RMSInputStream(short key) throws IOException {
this.key = key;
RecordStore r = null;
RecordEnumeration e = null;
char letter = 'A';
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
r = open("" + letter + key);
while(r != null) {
e = r.enumerateRecords(null, null, false);
while(e.hasNextElement()) {
byte[] data = e.nextRecord();
os.write(data);
}
e.destroy();
r.closeRecordStore();
letter++;
r = open("" + letter + key);
if(letter == 'Z') {
letter = 'a' - ((char)1);
}
}
os.close();
current = new ByteArrayInputStream(os.toByteArray());
} catch (RecordStoreException ex) {
//#ifndef RIM
ex.printStackTrace();
//#else
//# System.out.println("Exception in object store input stream constructor: " + ex);
//#endif
cleanup(r);
cleanup(e);
throw new IOException(ex.toString());
}
}
private RecordStore open(String s) {
try {
return RecordStore.openRecordStore(s, false);
} catch (RecordStoreException ex) {
return null;
}
}
public long skip(long n) throws IOException {
return super.skip(n);
}
public void close() throws IOException {
current.close();
}
public int read(byte[] arg0) throws IOException {
int r = current.read(arg0);
offset += r;
return r;
}
public int read(byte[] arg0, int arg1, int arg2) throws IOException {
int r = current.read(arg0, arg1, arg2);
offset += r;
return r;
}
public int read() throws IOException {
offset++;
return current.read();
}
}
/**
* @inheritDoc
*/
public OutputStream createStorageOutputStream(String name) throws IOException {
RecordStore r = null;
RMSOutputStream os = null;
DataOutputStream out = null;
try {
Short key = (Short)fat.get(name);
if(key == null) {
// need to add a key to the FAT
key = new Short(currentKey);
fat.put(name, key);
r = RecordStore.openRecordStore("FAT", true);
byte[] data = toRecord(name, currentKey);
currentKey++;
r.addRecord(data, 0, data.length);
r.closeRecordStore();
r = null;
}
os = new RMSOutputStream(key.shortValue());
return os;
} catch(Exception err) {
cleanup(r);
cleanup(os);
cleanup(out);
throw new IOException(err.getMessage());
}
}
/**
* @inheritDoc
*/
public InputStream createStorageInputStream(String name) throws IOException {
Short key = (Short)fat.get(name);
if(key == null) {
return null;
}
try {
return new RMSInputStream(key.shortValue());
} catch(Exception err) {
err.printStackTrace();
}
return null;
}
/**
* @inheritDoc
*/
public boolean storageFileExists(String name) {
if(name == null) {
return false;
}
return fat.containsKey(name);
}
/**
* @inheritDoc
*/
public String[] listStorageEntries() {
String[] a = new String[fat.size()];
Enumeration e = fat.keys();
int i = 0;
while(e.hasMoreElements()) {
a[i] = (String)e.nextElement();
i++;
}
return a;
}
/**
* @inheritDoc
*/
public String[] listFilesystemRoots() {
String[] res = enumToStringArr(FileSystemRegistry.listRoots());
for(int iter = 0 ; iter < res.length ; iter++) {
res[iter] = "file:///" + res[iter];
}
return res;
}
private String[] enumToStringArr(Enumeration e) {
Vector v = new Vector();
while(e.hasMoreElements()) {
v.addElement(e.nextElement());
}
String[] response = new String[v.size()];
for(int iter = 0 ; iter < response.length ; iter++) {
response[iter] = (String)v.elementAt(iter);
}
return response;
}
/**
* @inheritDoc
*/
public String[] listFiles(String directory) throws IOException {
FileConnection fc = null;
try {
fc = (FileConnection)Connector.open(directory, Connector.READ);
return enumToStringArr(fc.list());
} finally {
cleanup(fc);
}
}
/**
* @inheritDoc
*/
public long getRootSizeBytes(String root) {
FileConnection fc = null;
try {
fc = (FileConnection)Connector.open(root, Connector.READ);
return fc.totalSize();
} catch(IOException err) {
err.printStackTrace();
return -1;
} finally {
cleanup(fc);
}
}
/**
* @inheritDoc
*/
public long getRootAvailableSpace(String root) {
FileConnection fc = null;
try {
fc = (FileConnection)Connector.open(root, Connector.READ);
return fc.availableSize();
} catch(IOException err) {
err.printStackTrace();
return -1;
} finally {
cleanup(fc);
}
}
/**
* @inheritDoc
*/
public void mkdir(String directory) {
FileConnection fc = null;
try {
fc = (FileConnection)Connector.open(directory, Connector.READ_WRITE);
fc.mkdir();
} catch(IOException err) {
err.printStackTrace();
} finally {
cleanup(fc);
}
}
/**
* @inheritDoc
*/
public void deleteFile(String file) {
FileConnection fc = null;
try {
fc = (FileConnection)Connector.open(file, Connector.WRITE);
fc.delete();
} catch(IOException err) {
err.printStackTrace();
} finally {
cleanup(fc);
}
}
/**
* @inheritDoc
*/
public boolean isHidden(String file) {
FileConnection fc = null;
try {
fc = (FileConnection)Connector.open(file, Connector.READ);
return fc.isHidden();
} catch(IOException err) {
err.printStackTrace();
return false;
} finally {
cleanup(fc);
}
}
/**
* @inheritDoc
*/
public void setHidden(String file, boolean h) {
FileConnection fc = null;
try {
fc = (FileConnection)Connector.open(file, Connector.READ_WRITE);
fc.setHidden(h);
} catch(IOException err) {
err.printStackTrace();
} finally {
cleanup(fc);
}
}
/**
* @inheritDoc
*/
public long getFileLength(String file) {
FileConnection fc = null;
try {
fc = (FileConnection)Connector.open(file, Connector.READ);
return fc.fileSize();
} catch(IOException err) {
err.printStackTrace();
return -1;
} finally {
cleanup(fc);
}
}
/**
* @inheritDoc
*/
public boolean isDirectory(String file) {
FileConnection fc = null;
try {
fc = (FileConnection)Connector.open(file, Connector.READ);
return fc.isDirectory();
} catch(IOException err) {
err.printStackTrace();
return false;
} finally {
cleanup(fc);
}
}
/**
* @inheritDoc
*/
public char getFileSystemSeparator() {
return '/';
}
/**
* @inheritDoc
*/
public boolean exists(String file) {
FileConnection fc = null;
try {
fc = (FileConnection)Connector.open(file, Connector.READ);
return fc.exists();
} catch(IOException err) {
err.printStackTrace();
return false;
} finally {
cleanup(fc);
}
}
/**
* @inheritDoc
*/
public void rename(String file, String newName) {
FileConnection fc = null;
try {
fc = (FileConnection)Connector.open(file, Connector.READ_WRITE);
fc.rename(newName);
} catch(IOException err) {
err.printStackTrace();
} finally {
cleanup(fc);
}
}
}