/*
* Copyright © 2007-2011 Rebecca G. Bettencourt / Kreative Software
* <p>
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* <a href="http://www.mozilla.org/MPL/">http://www.mozilla.org/MPL/</a>
* <p>
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* <p>
* Alternatively, the contents of this file may be used under the terms
* of the GNU Lesser General Public License (the "LGPL License"), in which
* case the provisions of LGPL License are applicable instead of those
* above. If you wish to allow use of your version of this file only
* under the terms of the LGPL License and not to allow others to use
* your version of this file under the MPL, indicate your decision by
* deleting the provisions above and replace them with the notice and
* other provisions required by the LGPL License. If you do not delete
* the provisions above, a recipient may use your version of this file
* under either the MPL or the LGPL License.
* @since KSFL 1.0
* @author Rebecca G. Bettencourt, Kreative Software
*/
package com.kreative.rsrc;
import java.io.File;
import java.io.RandomAccessFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import com.kreative.ksfl.KSFLUtilities;
/**
* The <code>BerkeleyResourceFile</code> class represents a resource fork as defined
* by the Berkeley Systems modification of the Mac OS Resource Manager in a <code>RandomAccessFile</code>.
* The Resource Manager was originally designed and implemented by Bruce Horn
* for use in the original Macintosh Operating System. It remains today as a
* feature unique to the Mac OS. Other operating systems have the concept of
* resources, but the Mac OS implementation remains unique.
* @since KSFL 1.0
* @author Rebecca G. Bettencourt, Kreative Software, modified James Wallace
*/
public class BerkeleyResourceFile extends MacResourceProvider {
/**
* Never modify the file upon opening. Throw an exception if the file is empty.
*/
public static final int CREATE_NEVER = 0;
/**
* Only create a new resource structure if the file is empty.
*/
public static final int CREATE_IF_EMPTY = 1;
/**
* Create a new resource structure, overwriting the existing file contents.
*/
public static final int CREATE_ALWAYS = 2;
private RandomAccessFile raf;
private boolean readOnly = false;
private int resMap, resData;
private int resMapLen;
private String textEncoding = "MACROMAN";
private Hashtable<Integer, Long> typeoffs = new Hashtable<Integer,Long>();
private String gps(int b) {
try {
raf.seek(b);
int l = raf.readByte() & 0xFF;
byte[] s = new byte[l];
raf.read(s);
try {
return new String(s,textEncoding);
} catch (java.io.UnsupportedEncodingException uue) {
return new String(s);
}
} catch (IOException e) {
return "";
}
}
private int[] locateType(int type) {
// 0 - offset to type record
// 1 - number of items of that type
// 2 - offset to first data
try {
Long seekval = typeoffs.get(type);
int offset = seekval.intValue();
raf.seek(seekval);
int items = raf.readInt();
return new int[]{offset, items, offset+8};
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
return null;
}
}
private int[] locate(int type, int id) {
// 0 - offset to type record
// 1 - number of items of that type
// 2 - offset to reference list
// 3 - offset to reference record
// 4 - offset to name
// 5 - offset to data
try {
int[] t = locateType(type);
if (t != null) {
raf.seek(t[0]+4);
for (int i=0; i<t[1]; i++) {
int thisid = raf.readInt();
if (thisid == id) {
Long nameloc = raf.getFilePointer();
int n = nameloc.intValue();
int d = raf.readInt();
return new int[]{
t[0]-4, t[1], t[0], t[0],
(n),
(d)
};
} else {
raf.skipBytes(8);
}
}
}
} catch (IOException e) {}
return null;
}
/**
* Creates a <code>BerkeleyResourceFile</code> around a file.
* The file will not be modified until the resource structure is modified.
* If the file does not exist or is empty, a new resource structure is created.
* @param f the file object.
* @param mode the access mode, as described by <code>RandomAccessFile(File, String)</code>.
* @param create <code>CREATE_ALWAYS</code> if a new resource structure should be created, <code>CREATE_IF_EMPTY</code> if a new resource should be created if the file is empty, <code>CREATE_NEVER</code> if the file should not be modified.
* @throws IOException if an I/O error occurs.
*/
public BerkeleyResourceFile(File f, String mode, int create) throws IOException {
raf = new RandomAccessFile(f, mode);
readOnly = (mode.equalsIgnoreCase("r"));
{
raf.seek(4);//seek past srf1 magic.
int totalfile = raf.readInt();
resMapLen = raf.readInt();
resData = resMapLen +11;
resMap = 12;
raf.seek(resMap);
getTypes();
}
}
/**
* Returns the name of the text encoding used for resource names.
* Defaults to MACROMAN. If your system does not support this encoding,
* you will want to change it, since things will slow to a crawl.
* @return the name of the text encoding used for resource names.
*/
public synchronized String getTextEncoding() {
return textEncoding;
}
/**
* Sets the text encoding used for resource names.
* Defaults to MACROMAN. If your system does not support this encoding,
* you will want to change it, since things will slow to a crawl.
* @param encoding the name of the text encoding used for resource names.
*/
public synchronized void setTextEncoding(String encoding) {
textEncoding = encoding;
}
/**
* Returns the <code>RandomAccessFile</code> this is wrapped around.
* This should be used for debugging purposes only.
* @return the <code>RandomAccessFile</code> this is wrapped around.
*/
public RandomAccessFile getRandomAccessFile() {
return raf;
}
@Override
public boolean isReadOnly() {
return readOnly;
}
@Override
public synchronized void flush() {
// nothing
}
@Override
public synchronized void close() {
try { raf.close(); } catch (Exception e) {}
}
@Override
public synchronized boolean contains(int type, short id) {
return (locate(type,id) != null);
}
@Override
public synchronized MacResource get(int type, short id) {
try {
int[] l = locate(type,id);
if (l != null) {
raf.seek(l[0]);
int t = raf.readInt();
short i = id;
raf.seek(l[3]+4);
byte a = raf.readByte();
raf.seek(l[4]+4);
int dl = raf.readInt();
return new MacResource(
t, i, a,
String.valueOf(i),
(l[5]>0)?KSFLUtilities.copy(raf, l[5], dl):(new byte[0])
);
}
} catch (IOException e) {}
return null;
}
public synchronized MacResource getFromFullID(int type, int id) {
try {
int[] l = locate(type,id);
if (l != null) {
raf.seek(l[0]);
int t = raf.readInt();
short i = (short) id;
raf.seek(l[3]+4);
byte a = raf.readByte();
raf.seek(l[4]+4);
int dl = raf.readInt();
return new MacResource(
t, i, a,
String.valueOf(i),
(l[5]>0)?KSFLUtilities.copy(raf, l[5], dl):(new byte[0])
);
}
} catch (IOException e) {}
return null;
}
@Override
public synchronized byte[] getData(int type, short id) {
try {
int[] l = locate(type,id);
if (l != null) {
raf.seek(l[5]);
int dl = raf.readInt();
return (l[5]>0)?KSFLUtilities.copy(raf, l[5]+4, dl):(new byte[0]);
}
} catch (IOException e) {}
return null;
}
@Override
public synchronized int getTypeCount() {
return getTypes().length;
}
@Override
public synchronized int getType(int index) {
int[] types = getTypes();
return types[index];
}
@Override
public synchronized int[] getTypes() {
try {
ArrayList<Integer> types = new ArrayList<Integer>();
typeoffs = new Hashtable<Integer,Long>();
raf.seek(resMap);
int typeval = raf.readInt();
types.add(typeval);
typeoffs.put(typeval, raf.getFilePointer());
int nooffirsts = raf.readInt();//we have to know how many to skip to the next name.
long skip = nooffirsts *12;
raf.seek(raf.getFilePointer()+skip);
while (raf.getFilePointer() < resData)
{
typeval = raf.readInt();
types.add(typeval);
typeoffs.put(typeval, raf.getFilePointer());
nooffirsts = raf.readInt();//we have to know how many to skip to the next name.
skip = nooffirsts *12;
raf.seek(raf.getFilePointer()+skip);
}
int[] a = new int[types.size()];
for (int i =0; i < types.size(); i++)
{
a[i]= types.get(i);
}
return a;
} catch (IOException e) {}
return new int[0];
}
@Override
public synchronized int getResourceCount(int type) {
int[] t = locateType(type);
if (t != null) return t[1];
else return 0;
}
@Override
public synchronized short getID(int type, int index) {
try {
int[] t = locateType(type);
if (t != null) {
raf.seek(t[2]+2+(12*index));
return raf.readShort();
}
else return 0;
} catch (IOException e) {}
return 0;
}
@Override
public synchronized short[] getIDs(int type) {
try {
int[] t = locateType(type);
if (t != null) {
short[] a = new short[t[1]];
for (int i=0; i<t[1]; i++) {
raf.seek(t[0]+6+(12*i));
a[i] = raf.readShort();
}
return a;
}
} catch (IOException e) {}
return new short[0];
}
public synchronized int[] getfullIDs(int type) {
try {
int[] t = locateType(type);
if (t != null) {
int[] a = new int[t[1]];
for (int i=0; i<t[1]; i++) {
raf.seek(t[0]+4+(12*i));
a[i] = raf.readInt();
}
return a;
}
} catch (IOException e) {}
return new int[0];
}
@Override
public synchronized String getName(int type, int index) {
try {
int[] t = locateType(type);
if (t != null) {
raf.seek(t[2]+12*index+2);
int n = raf.readInt();
if (n < 0) return "";
else return gps(n);
}
} catch (IOException e) {}
return "";
}
@Override
public synchronized String[] getNames(int type) {
ArrayList<String> a = new ArrayList<String>();
try {
int[] t = locateType(type);
if (t != null) for (int i=0; i<t[1]; i++) {
raf.seek(t[2]+12*i+2);
int n = raf.readInt();
if (n < 0) a.add("");
else a.add(gps(n));
}
} catch (IOException e) {}
return a.toArray(new String[0]);
}
@Override
public synchronized short getNextAvailableID(int type, short start) {
try {
ArrayList<Short> a = new ArrayList<Short>();
int[] t = locateType(type);
if (t != null) for (int i=0; i<t[1]; i++) {
raf.seek(t[2]+2+(12*i));
a.add(raf.readShort());
}
short next = start;
while (a.contains(next)) next++;
return next;
} catch (IOException e) {}
return 0;
}
@Override
public synchronized String getNameFromID(int type, short id) {
int[] l = locate(type,id);
if (l != null) {
return (l[4]>0)?gps(l[4]):"";
}
return "";
}
@Override
public boolean contains(int type, String name) {
// TODO Auto-generated method stub
return false;
}
@Override
public MacResource get(int type, String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public MacResource getAttributes(int type, String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public byte[] getData(int type, String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean remove(int type, String name) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean setAttributes(int type, String name, MacResource r)
throws MacResourceAlreadyExistsException {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean setData(int type, String name, byte[] data) {
// TODO Auto-generated method stub
return false;
}
@Override
public short getIDFromName(int type, String name) {
// TODO Auto-generated method stub
return 0;
}
public int readInt(int offset) {
try {
raf.seek(offset);
return raf.readInt();
} catch (IOException e) {
return 0;
}
}
@Override
public short getResourceMapAttributes() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void setResourceMapAttributes(short attr) {
// TODO Auto-generated method stub
}
@Override
public boolean add(MacResource r) throws MacResourceAlreadyExistsException {
// TODO Auto-generated method stub
return false;
}
@Override
public MacResource getAttributes(int type, short id) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean remove(int type, short id) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean set(int type, short id, MacResource r)
throws MacResourceAlreadyExistsException {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean set(int type, String name, MacResource r)
throws MacResourceAlreadyExistsException {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean setAttributes(int type, short id, MacResource r)
throws MacResourceAlreadyExistsException {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean setData(int type, short id, byte[] data) {
// TODO Auto-generated method stub
return false;
}
}