/*
* 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 com.kreative.ksfl.KSFLUtilities;
/**
* The <code>MacResourceFile</code> class represents a resource fork as defined
* by 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
*/
public class MacResourceFile 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, typeList, nameList, resData;
private int resMapLen, resDataLen;
private String textEncoding = "MACROMAN";
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 byte[] gb(String a) {
try {
return a.getBytes(textEncoding);
} catch (java.io.UnsupportedEncodingException uue) {
return a.getBytes();
}
}
private int[] locateType(int type) {
// 0 - offset to type record
// 1 - number of items of that type
// 2 - offset to reference list
try {
raf.seek(typeList);
int m = raf.readShort()+1;
for (int i=0; i<m; i++) {
int t = raf.readInt();
if (t == type) {
int cnt = raf.readShort()+1;
int lst = typeList + raf.readShort();
return new int[]{typeList+2+8*i, cnt, lst};
} else {
raf.readInt();
}
}
} catch (IOException e) {}
return null;
}
private int[] locate(int type, short 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[2]);
for (int i=0; i<t[1]; i++) {
short thisid = raf.readShort();
if (thisid == id) {
int n = raf.readShort();
int d = raf.readInt() & 0xFFFFFF;
return new int[]{
t[0], t[1], t[2], t[2]+12*i,
((n<0)?0:(nameList+n)),
((d<0)?0:(resData+d))
};
} else {
raf.skipBytes(10);
}
}
}
} catch (IOException e) {}
return null;
}
private int[] locate(int type, String name) {
// 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) {
for (int i=0; i<t[1]; i++) {
raf.seek(t[2]+12*i+2);
int n = raf.readShort();
if (n >= 0 && gps(nameList+n).equals(name)) {
raf.seek(t[2]+12*i+4);
int d = raf.readInt() & 0xFFFFFF;
return new int[]{
t[0], t[1], t[2], t[2]+12*i,
((n<0)?0:(nameList+n)),
((d<0)?0:(resData+d))
};
}
}
}
} catch (IOException e) {}
return null;
}
private static final int INSERTED_TYPE_RECORD = 1;
private static final int INSERTED_OBJECT_RECORD = 2;
private static final int INSERTED_NAME = 3;
private static final int INSERTED_DATA = 4;
private static final int REMOVED_TYPE_RECORD = 1;
private static final int REMOVED_OBJECT_RECORD = 2;
private static final int REMOVED_NAME = 3;
private static final int REMOVED_DATA = 4;
private void cut(int[] loc, int offset, int length, int what, int type, short id) throws IOException {
boolean typeListAdjusted=false, nameListAdjusted=false, resDataAdjusted=false;
KSFLUtilities.cut(raf, offset, length);
// update offsets
if (resMap > offset) resMap -= length;
if (typeList > offset) { typeList -= length; typeListAdjusted = true; }
if (nameList > offset) { nameList -= length; nameListAdjusted = true; }
if (resData > offset) { resData -= length; resDataAdjusted = true; }
if (what == REMOVED_TYPE_RECORD || what == REMOVED_OBJECT_RECORD || what == REMOVED_NAME) resMapLen -= length;
if (what == REMOVED_DATA) resDataLen -= length;
// update location
if (loc != null) {
if (loc.length > 0 && loc[0] > offset) loc[0] -= length;
if (loc.length > 1 && what == REMOVED_OBJECT_RECORD) loc[1]--;
if (loc.length > 2 && loc[2] > offset) loc[2] -= length;
if (loc.length > 3 && loc[3] > offset) loc[3] -= length;
if (loc.length > 4 && loc[4] > offset) loc[4] -= length;
if (loc.length > 5 && loc[5] > offset) loc[5] -= length;
}
// update header
raf.seek(0);
raf.writeInt(resData);
raf.writeInt(resMap);
raf.writeInt(resDataLen);
raf.writeInt(resMapLen);
// update resource map
raf.seek(resMap);
raf.writeInt(resData);
raf.writeInt(resMap);
raf.writeInt(resDataLen);
raf.writeInt(resMapLen);
raf.seek(resMap+24);
raf.writeShort(typeList - resMap);
raf.writeShort(nameList - resMap);
// update type list
raf.seek(typeList);
int numtypes = raf.readShort()+1;
if (what == REMOVED_TYPE_RECORD) {
numtypes--;
raf.seek(typeList);
raf.writeShort(numtypes-1);
}
for (int i=0; i<numtypes; i++) {
raf.seek(typeList+2+8*i);
int thistype = raf.readInt();
int numrefs = raf.readShort()+1;
if (what == REMOVED_OBJECT_RECORD && thistype == type) {
numrefs--;
raf.seek(typeList+2+8*i+4);
raf.writeShort(numrefs-1);
}
int reflist = typeList + raf.readShort();
if ((!typeListAdjusted) && (reflist > offset)) {
reflist -= length;
raf.seek(typeList+2+8*i+6);
raf.writeShort(reflist - typeList);
}
// update references
for (int j=0; j<numrefs; j++) {
raf.seek(reflist+12*j);
/*short thisid = */raf.readShort();
short name = raf.readShort();
int dattr = raf.readInt();
int data = dattr & 0x00FFFFFF;
int attr = dattr & 0xFF000000;
if (name >= 0) {
if ((!nameListAdjusted) && (nameList+name > offset)) {
raf.seek(reflist+12*j+2);
raf.writeShort(name-length);
}
}
if (data >= 0) {
if ((!resDataAdjusted) && (resData+data > offset)) {
raf.seek(reflist+12*j+4);
raf.writeInt(((data-length) & 0xFFFFFF) | attr);
}
}
}
}
}
private void paste(int[] loc, int offset, byte[] stuff, int what, int type, short id) throws IOException {
boolean typeListAdjusted=false, nameListAdjusted=false, resDataAdjusted=false;
KSFLUtilities.paste(raf, offset, stuff);
// update offsets
if (resMap >= offset) resMap += stuff.length;
if (typeList >= offset) { typeList += stuff.length; typeListAdjusted = true; }
if (nameList > offset || (nameList == offset && what != INSERTED_NAME)) { nameList += stuff.length; nameListAdjusted = true; }
if (resData > offset || (resData == offset && what != INSERTED_DATA)) { resData += stuff.length; resDataAdjusted = true; }
if (what == INSERTED_TYPE_RECORD || what == INSERTED_OBJECT_RECORD || what == INSERTED_NAME) resMapLen += stuff.length;
if (what == INSERTED_DATA) resDataLen += stuff.length;
// update location
if (loc != null) {
if (loc.length > 0 && (loc[0] > offset || (loc[0] == offset && what != INSERTED_TYPE_RECORD))) loc[0] += stuff.length;
if (loc.length > 1 && what == INSERTED_OBJECT_RECORD) loc[1]++;
if (loc.length > 2 && (loc[2] > offset || (loc[2] == offset && what != INSERTED_OBJECT_RECORD))) loc[2] += stuff.length;
if (loc.length > 3 && (loc[3] > offset || (loc[3] == offset && what != INSERTED_OBJECT_RECORD))) loc[3] += stuff.length;
if (loc.length > 4 && (loc[4] > offset || (loc[4] == offset && what != INSERTED_NAME))) loc[4] += stuff.length;
if (loc.length > 5 && (loc[5] > offset || (loc[5] == offset && what != INSERTED_DATA))) loc[5] += stuff.length;
}
// update header
raf.seek(0);
raf.writeInt(resData);
raf.writeInt(resMap);
raf.writeInt(resDataLen);
raf.writeInt(resMapLen);
// update resource map
raf.seek(resMap);
raf.writeInt(resData);
raf.writeInt(resMap);
raf.writeInt(resDataLen);
raf.writeInt(resMapLen);
raf.seek(resMap+24);
raf.writeShort(typeList - resMap);
raf.writeShort(nameList - resMap);
// update type list
raf.seek(typeList);
int numtypes = raf.readShort()+1;
if (what == INSERTED_TYPE_RECORD) {
numtypes++;
raf.seek(typeList);
raf.writeShort(numtypes-1);
}
for (int i=0; i<numtypes; i++) {
raf.seek(typeList+2+8*i);
int thistype = raf.readInt();
int numrefs = raf.readShort()+1;
if (what == INSERTED_OBJECT_RECORD && thistype == type) {
numrefs++;
raf.seek(typeList+2+8*i+4);
raf.writeShort(numrefs-1);
}
int reflist = typeList + raf.readShort();
if ((!typeListAdjusted) && (reflist > offset || (reflist == offset && !(what == INSERTED_OBJECT_RECORD && thistype == type)))) {
reflist += stuff.length;
raf.seek(typeList+2+8*i+6);
raf.writeShort(reflist - typeList);
}
// update references
for (int j=0; j<numrefs; j++) {
raf.seek(reflist+12*j);
short thisid = raf.readShort();
short name = raf.readShort();
int dattr = raf.readInt();
int data = dattr & 0x00FFFFFF;
int attr = dattr & 0xFF000000;
if (name >= 0) {
if ((!nameListAdjusted) && (nameList+name > offset || (nameList+name == offset && !(what == INSERTED_NAME && thistype == type && thisid == id)))) {
raf.seek(reflist+12*j+2);
raf.writeShort(name+stuff.length);
}
}
if (data >= 0) {
if ((!resDataAdjusted) && (resData+data > offset || (resData+data == offset && !(what == INSERTED_DATA && thistype == type && thisid == id)))) {
raf.seek(reflist+12*j+4);
raf.writeInt(((data+stuff.length) & 0xFFFFFF) | attr);
}
}
}
}
}
/**
* Creates a <code>MacResourceFile</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 MacResourceFile(File f, String mode, int create) throws IOException {
raf = new RandomAccessFile(f, mode);
readOnly = (mode.equalsIgnoreCase("r"));
if ((create == CREATE_ALWAYS) || ((create == CREATE_IF_EMPTY) && (raf.length() == 0))) {
raf.setLength(286);
raf.seek(0);
raf.writeInt(256);
raf.writeInt(256);
raf.writeInt(0);
raf.writeInt(30);
raf.seek(256);
raf.writeInt(256);
raf.writeInt(256);
raf.writeInt(0);
raf.writeInt(30);
raf.writeInt(0);
raf.writeShort(0);
raf.writeShort(0);
raf.writeShort(28);
raf.writeShort(30);
raf.writeShort(-1);
resData = 256;
resMap = 256;
resDataLen = 0;
resMapLen = 30;
typeList = 284;
nameList = 286;
} else {
raf.seek(0);
resData = raf.readInt();
resMap = raf.readInt();
resDataLen = raf.readInt();
resMapLen = raf.readInt();
raf.seek(resMap+24);
typeList = resMap + raf.readShort();
nameList = resMap + raf.readShort();
}
}
/**
* 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 short getResourceMapAttributes() {
try {
raf.seek(resMap+22);
return raf.readShort();
} catch (IOException e) {
return 0;
}
}
@Override
public synchronized void setResourceMapAttributes(short attr) {
try {
raf.seek(resMap+22);
raf.writeShort(attr);
} catch (IOException e) {}
}
@Override
public synchronized boolean add(MacResource r) throws MacResourceAlreadyExistsException {
try {
if (locate(r.type,r.id) != null) throw new MacResourceAlreadyExistsException();
//type record
if (locateType(r.type) == null) {
byte[] th = new byte[8];
KSFLUtilities.putInt(th, 0, r.type);
KSFLUtilities.putShort(th, 4, (short)-1);
KSFLUtilities.putShort(th, 6, (short)(nameList - typeList));
raf.seek(typeList);
int lasttype = typeList+2+8*(raf.readShort()+1);
paste(null, lasttype, th, INSERTED_TYPE_RECORD, r.type, r.id);
}
//object record
int[] t = locateType(r.type);
byte[] ref = new byte[12];
KSFLUtilities.putShort(ref, 0, r.id);
KSFLUtilities.putShort(ref, 2, (r.name != null && r.name.length()>0)?(short)(resMap+resMapLen-nameList):(short)-1);
KSFLUtilities.putInt(ref, 4, resDataLen);
ref[4] = r.getAttributes();
KSFLUtilities.putInt(ref, 8, 0);
paste(null, t[2]+12*t[1], ref, INSERTED_OBJECT_RECORD, r.type, r.id);
//name
if (r.name != null && r.name.length()>0) {
byte[] n = gb(r.name);
if (n.length > 255) n = KSFLUtilities.copy(n, 0, 255);
n = KSFLUtilities.paste(n, 0, new byte[]{(byte)n.length});
paste(null, resMap+resMapLen, n, INSERTED_NAME, r.type, r.id);
}
//data
byte[] d = KSFLUtilities.paste(r.data, 0, 4);
KSFLUtilities.putInt(d, 0, r.data.length);
paste(null, resData+resDataLen, d, INSERTED_DATA, r.type, r.id);
//done
return true;
} catch (IOException e) {}
return false;
}
@Override
public synchronized boolean contains(int type, short id) {
return (locate(type,id) != null);
}
@Override
public synchronized boolean contains(int type, String name) {
return (locate(type,name) != 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();
raf.seek(l[3]);
short i = raf.readShort();
raf.seek(l[3]+4);
byte a = raf.readByte();
raf.seek(l[5]);
int dl = raf.readInt();
return new MacResource(
t, i, a,
(l[4]>0)?gps(l[4]):"",
(l[5]>0)?KSFLUtilities.copy(raf, l[5]+4, dl):(new byte[0])
);
}
} catch (IOException e) {}
return null;
}
@Override
public synchronized MacResource get(int type, String name) {
try {
int[] l = locate(type,name);
if (l != null) {
raf.seek(l[0]);
int t = raf.readInt();
raf.seek(l[3]);
short i = raf.readShort();
raf.seek(l[3]+4);
byte a = raf.readByte();
raf.seek(l[5]);
int dl = raf.readInt();
return new MacResource(
t, i, a,
(l[4]>0)?gps(l[4]):"",
(l[5]>0)?KSFLUtilities.copy(raf, l[5]+4, dl):(new byte[0])
);
}
} catch (IOException e) {}
return null;
}
@Override
public synchronized MacResource getAttributes(int type, short id) {
try {
int[] l = locate(type,id);
if (l != null) {
raf.seek(l[0]);
int t = raf.readInt();
raf.seek(l[3]);
short i = raf.readShort();
raf.seek(l[3]+4);
byte a = raf.readByte();
return new MacResource(
t, i, a,
(l[4]>0)?gps(l[4]):"",
new byte[0]
);
}
} catch (IOException e) {}
return null;
}
@Override
public synchronized MacResource getAttributes(int type, String name) {
try {
int[] l = locate(type,name);
if (l != null) {
raf.seek(l[0]);
int t = raf.readInt();
raf.seek(l[3]);
short i = raf.readShort();
raf.seek(l[3]+4);
byte a = raf.readByte();
return new MacResource(
t, i, a,
(l[4]>0)?gps(l[4]):"",
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 byte[] getData(int type, String name) {
try {
int[] l = locate(type,name);
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 boolean remove(int type, short id) {
try {
int[] loc = locate(type,id);
if (loc != null) {
//delete data
if (loc[5] > 0) {
raf.seek(loc[5]);
int dlen = raf.readInt()+4;
cut(loc, loc[5], dlen, REMOVED_DATA, type, id);
}
//delete name
if (loc[4] > 0) {
raf.seek(loc[4]);
int nlen = (raf.readByte() & 0xFF)+1;
cut(loc, loc[4], nlen, REMOVED_NAME, type, id);
}
//delete object record
cut(loc, loc[3], 12, REMOVED_OBJECT_RECORD, type, id);
//delete type record
if (loc[1] <= 0) {
cut(loc, loc[0], 8, REMOVED_TYPE_RECORD, type, id);
}
return true;
}
} catch (IOException e) {}
return false;
}
@Override
public synchronized boolean remove(int type, String name) {
try {
int[] loc = locate(type,name);
if (loc != null) {
raf.seek(loc[3]);
short id = raf.readShort();
//delete data
if (loc[5] > 0) {
raf.seek(loc[5]);
int dlen = raf.readInt()+4;
cut(loc, loc[5], dlen, REMOVED_DATA, type, id);
}
//delete name
if (loc[4] > 0) {
raf.seek(loc[4]);
int nlen = (raf.readByte() & 0xFF)+1;
cut(loc, loc[4], nlen, REMOVED_NAME, type, id);
}
//delete object record
cut(loc, loc[3], 12, REMOVED_OBJECT_RECORD, type, id);
//delete type record
if (loc[1] <= 0) {
cut(loc, loc[0], 8, REMOVED_TYPE_RECORD, type, id);
}
return true;
}
} catch (IOException e) {}
return false;
}
@Override
public synchronized boolean set(int type, short id, MacResource r) throws MacResourceAlreadyExistsException {
if (!contains(type, id)) return false;
if (r.name == null) r.name = "";
if (r.data == null) r.data = new byte[0];
return setAttributes(type, id, r) && setData(r.type, r.id, r.data);
}
@Override
public synchronized boolean set(int type, String name, MacResource r) throws MacResourceAlreadyExistsException {
short id = getIDFromName(type, name);
if (!contains(type, id)) return false;
if (r.name == null) r.name = "";
if (r.data == null) r.data = new byte[0];
return setAttributes(type, id, r) && setData(r.type, r.id, r.data);
}
@Override
public synchronized boolean setAttributes(int type, short id, MacResource r) throws MacResourceAlreadyExistsException {
try {
int[] loc = locate(type,id);
if (loc != null) {
int[] loce = locate(r.type,r.id);
if ((loce != null) && (
(loce[0] != loc[0]) ||
(loce[1] != loc[1]) ||
(loce[2] != loc[2]) ||
(loce[3] != loc[3]) ||
(loce[4] != loc[4]) ||
(loce[5] != loc[5])
)) {
throw new MacResourceAlreadyExistsException();
} else {
if (type != r.type) {
//the hard part
//type record
if (locateType(r.type) == null) {
byte[] th = new byte[8];
KSFLUtilities.putInt(th, 0, r.type);
KSFLUtilities.putShort(th, 4, (short)-1);
KSFLUtilities.putShort(th, 6, (short)(nameList - typeList));
raf.seek(typeList);
int lasttype = typeList+2+8*(raf.readShort()+1);
paste(loc, lasttype, th, INSERTED_TYPE_RECORD, r.type, r.id);
}
//object record
int[] t = locateType(r.type);
byte[] ref = new byte[12];
KSFLUtilities.putShort(ref, 0, r.id);
KSFLUtilities.putShort(ref, 2, (loc[4]>0)?(short)(loc[4]-nameList):(short)-1);
KSFLUtilities.putInt(ref, 4, (loc[5]>0)?(loc[5]-resData):-1);
ref[4] = r.getAttributes();
KSFLUtilities.putInt(ref, 8, 0);
paste(loc, t[2]+12*t[1], ref, INSERTED_OBJECT_RECORD, r.type, r.id);
//delete object record
cut(loc, loc[3], 12, REMOVED_OBJECT_RECORD, type, id);
//delete type record
raf.seek(loc[0]+4);
if (raf.readShort() < 0) {
cut(loc, loc[0], 8, REMOVED_TYPE_RECORD, type, id);
}
//ResourceArray's auto-handling of counts and offsets makes this
//MUCH easier than DFFArray's setObjectAttributes
} else {
//the easy part
raf.seek(loc[3]);
raf.writeShort(r.id);
raf.skipBytes(2);
raf.writeByte(r.getAttributes());
}
//the other part
if (r.name != null && r.name.length() > 0) {
byte[] n = gb(r.name);
if (n.length > 255) n = KSFLUtilities.copy(n, 0, 255);
n = KSFLUtilities.paste(n, 0, new byte[]{(byte)n.length});
if (loc[4] > 0) {
paste(loc, loc[4], n, INSERTED_NAME, r.type, r.id);
raf.seek(loc[4]+n.length);
int nl = (raf.readByte()&0xFF)+1;
cut(loc, loc[4]+n.length, nl, REMOVED_NAME, r.type, r.id);
} else {
raf.seek(loc[3]+2);
raf.writeShort(resMap+resMapLen-nameList);
paste(loc, resMap+resMapLen, n, INSERTED_NAME, r.type, r.id);
}
} else if (loc[4] > 0) {
raf.seek(loc[4]);
int nl = (raf.readByte()&0xFF)+1;
cut(loc, loc[4], nl, REMOVED_NAME, r.type, r.id);
raf.seek(loc[3]+2);
raf.writeShort(-1);
loc[4] = 0;
}
return true;
}
}
} catch (IOException e) {}
return false;
}
@Override
public synchronized boolean setAttributes(int type, String name, MacResource r) throws MacResourceAlreadyExistsException {
try {
int[] loc = locate(type,name);
if (loc != null) {
int[] loce = locate(r.type,r.id);
if ((loce != null) && (
(loce[0] != loc[0]) ||
(loce[1] != loc[1]) ||
(loce[2] != loc[2]) ||
(loce[3] != loc[3]) ||
(loce[4] != loc[4]) ||
(loce[5] != loc[5])
)) {
throw new MacResourceAlreadyExistsException();
} else {
raf.seek(loc[3]);
short id = raf.readShort();
if (type != r.type) {
//the hard part
//type record
if (locateType(r.type) == null) {
byte[] th = new byte[8];
KSFLUtilities.putInt(th, 0, r.type);
KSFLUtilities.putShort(th, 4, (short)-1);
KSFLUtilities.putShort(th, 6, (short)(nameList - typeList));
raf.seek(typeList);
int lasttype = typeList+2+8*(raf.readShort()+1);
paste(loc, lasttype, th, INSERTED_TYPE_RECORD, r.type, r.id);
}
//object record
int[] t = locateType(r.type);
byte[] ref = new byte[12];
KSFLUtilities.putShort(ref, 0, r.id);
KSFLUtilities.putShort(ref, 2, (loc[4]>0)?(short)(loc[4]-nameList):(short)-1);
KSFLUtilities.putInt(ref, 4, (loc[5]>0)?(loc[5]-resData):-1);
ref[4] = r.getAttributes();
KSFLUtilities.putInt(ref, 8, 0);
paste(loc, t[2]+12*t[1], ref, INSERTED_OBJECT_RECORD, r.type, r.id);
//delete object record
cut(loc, loc[3], 12, REMOVED_OBJECT_RECORD, type, id);
//delete type record
raf.seek(loc[0]+4);
if (raf.readShort() < 0) {
cut(loc, loc[0], 8, REMOVED_TYPE_RECORD, type, id);
}
//ResourceArray's auto-handling of counts and offsets makes this
//MUCH easier than DFFArray's setObjectAttributes
} else {
//the easy part
raf.seek(loc[3]);
raf.writeShort(r.id);
raf.skipBytes(2);
raf.writeByte(r.getAttributes());
}
//the other part
if (r.name != null && r.name.length() > 0) {
byte[] n = gb(r.name);
if (n.length > 255) n = KSFLUtilities.copy(n, 0, 255);
n = KSFLUtilities.paste(n, 0, new byte[]{(byte)n.length});
if (loc[4] > 0) {
paste(loc, loc[4], n, INSERTED_NAME, r.type, r.id);
raf.seek(loc[4]+n.length);
int nl = (raf.readByte()&0xFF)+1;
cut(loc, loc[4]+n.length, nl, REMOVED_NAME, r.type, r.id);
} else {
raf.seek(loc[3]+2);
raf.writeShort(resMap+resMapLen-nameList);
paste(loc, resMap+resMapLen, n, INSERTED_NAME, r.type, r.id);
}
} else if (loc[4] > 0) {
raf.seek(loc[4]);
int nl = (raf.readByte()&0xFF)+1;
cut(loc, loc[4], nl, REMOVED_NAME, r.type, r.id);
raf.seek(loc[3]+2);
raf.writeShort(-1);
loc[4] = 0;
}
return true;
}
}
} catch (IOException e) {}
return false;
}
@Override
public synchronized boolean setData(int type, short id, byte[] data) {
try {
int[] loc = locate(type,id);
if (loc != null) {
//delete data
if (loc[5] > 0) {
raf.seek(loc[5]);
int dlen = raf.readInt()+4;
cut(loc, loc[5], dlen, REMOVED_DATA, type, id);
} else {
loc[5] = resData+resDataLen;
}
//insert data
byte[] d = KSFLUtilities.paste(data, 0, 4);
KSFLUtilities.putInt(d, 0, data.length);
paste(loc, loc[5], d, INSERTED_DATA, type, id);
return true;
}
} catch (IOException e) {}
return false;
}
@Override
public synchronized boolean setData(int type, String name, byte[] data) {
try {
int[] loc = locate(type,name);
if (loc != null) {
raf.seek(loc[3]);
short id = raf.readShort();
//delete data
if (loc[5] > 0) {
raf.seek(loc[5]);
int dlen = raf.readInt()+4;
cut(loc, loc[5], dlen, REMOVED_DATA, type, id);
} else {
loc[5] = resData+resDataLen;
}
//insert data
byte[] d = KSFLUtilities.paste(data, 0, 4);
KSFLUtilities.putInt(d, 0, data.length);
paste(loc, loc[5], d, INSERTED_DATA, type, id);
return true;
}
} catch (IOException e) {}
return false;
}
@Override
public synchronized int getTypeCount() {
try {
raf.seek(typeList);
return raf.readShort()+1;
} catch (IOException e) {}
return 0;
}
@Override
public synchronized int getType(int index) {
try {
raf.seek(typeList+2+8*index);
return raf.readInt();
} catch (IOException e) {}
return 0;
}
@Override
public synchronized int[] getTypes() {
try {
raf.seek(typeList);
int m = raf.readShort()+1;
int[] a = new int[m];
for (int i=0; i<m; i++) {
a[i]=raf.readInt();
raf.readInt();
}
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]+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[2]+12*i);
a[i] = raf.readShort();
}
return a;
}
} catch (IOException e) {}
return new short[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.readShort();
if (n < 0) return "";
else return gps(nameList + 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.readShort();
if (n < 0) a.add("");
else a.add(gps(nameList + 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]+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 synchronized short getIDFromName(int type, String name) {
try {
int[] l = locate(type,name);
if (l != null) {
raf.seek(l[3]);
return raf.readShort();
}
} catch (IOException e) {}
return 0;
}
}