/*
* 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.util.ArrayList;
import com.kreative.ksfl.KSFLUtilities;
/**
* The <code>MacResourceArray</code> class represents a resource fork as defined by
* the Mac OS Resource Manager in an array of bytes. 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 MacResourceArray extends MacResourceProvider {
private byte[] arr;
private int resMap, typeList, nameList, resData;
private int resMapLen, resDataLen;
private String textEncoding = "MACROMAN";
private String gps(byte[] a, int b) {
try {
return KSFLUtilities.getPString(a,b,textEncoding);
} catch (java.io.UnsupportedEncodingException uue) {
return KSFLUtilities.getPString(a,b);
}
}
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
int m = KSFLUtilities.getShort(arr, typeList)+1;
for (int i=0; i<m; i++) {
int t = KSFLUtilities.getInt(arr, typeList+2+8*i);
if (t == type) {
int cnt = KSFLUtilities.getShort(arr, typeList+2+8*i+4)+1;
int lst = typeList + KSFLUtilities.getShort(arr, typeList+2+8*i+6);
return new int[]{typeList+2+8*i, cnt, lst};
}
}
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
int[] t = locateType(type);
if (t != null) {
for (int i=0; i<t[1]; i++) {
short thisid = KSFLUtilities.getShort(arr, t[2]+12*i);
if (thisid == id) {
int n = KSFLUtilities.getShort(arr, t[2]+12*i+2);
int d = KSFLUtilities.getInt(arr, t[2]+12*i+4) & 0xFFFFFF;
return new int[]{
t[0], t[1], t[2], t[2]+12*i,
((n<0)?0:(nameList+n)),
((d<0)?0:(resData+d))
};
}
}
}
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
int[] t = locateType(type);
if (t != null) {
for (int i=0; i<t[1]; i++) {
int n = KSFLUtilities.getShort(arr, t[2]+12*i+2);
if (n >= 0 && gps(arr, nameList+n).equals(name)) {
int d = KSFLUtilities.getInt(arr, t[2]+12*i+4) & 0xFFFFFF;
return new int[]{
t[0], t[1], t[2], t[2]+12*i,
((n<0)?0:(nameList+n)),
((d<0)?0:(resData+d))
};
}
}
}
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) {
boolean typeListAdjusted=false, nameListAdjusted=false, resDataAdjusted=false;
arr = KSFLUtilities.cut(arr, 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
KSFLUtilities.putInt(arr, 0, resData);
KSFLUtilities.putInt(arr, 4, resMap);
KSFLUtilities.putInt(arr, 8, resDataLen);
KSFLUtilities.putInt(arr, 12, resMapLen);
// update resource map
KSFLUtilities.putInt(arr, resMap+0, resData);
KSFLUtilities.putInt(arr, resMap+4, resMap);
KSFLUtilities.putInt(arr, resMap+8, resDataLen);
KSFLUtilities.putInt(arr, resMap+12, resMapLen);
KSFLUtilities.putShort(arr, resMap+24, (short)(typeList - resMap));
KSFLUtilities.putShort(arr, resMap+26, (short)(nameList - resMap));
// update type list
int numtypes = KSFLUtilities.getShort(arr, typeList)+1;
if (what == REMOVED_TYPE_RECORD) {
numtypes--;
KSFLUtilities.putShort(arr, typeList, (short)(numtypes-1));
}
for (int i=0; i<numtypes; i++) {
int thistype = KSFLUtilities.getInt(arr, typeList+2+8*i);
int numrefs = KSFLUtilities.getShort(arr, typeList+2+8*i+4)+1;
if (what == REMOVED_OBJECT_RECORD && thistype == type) {
numrefs--;
KSFLUtilities.putShort(arr, typeList+2+8*i+4, (short)(numrefs-1));
}
int reflist = typeList + KSFLUtilities.getShort(arr, typeList+2+8*i+6);
if ((!typeListAdjusted) && (reflist > offset)) {
reflist -= length;
KSFLUtilities.putShort(arr, typeList+2+8*i+6, (short)(reflist - typeList));
}
// update references
for (int j=0; j<numrefs; j++) {
//short thisid = KSFLUtilities.getShort(arr, reflist+12*j);
short name = KSFLUtilities.getShort(arr, reflist+12*j+2);
int data = KSFLUtilities.getInt(arr, reflist+12*j+4) & 0xFFFFFF;
if (name >= 0) {
if ((!nameListAdjusted) && (nameList+name > offset))
KSFLUtilities.putShort(arr, reflist+12*j+2, (short)(name-length));
}
if (data >= 0) {
if ((!resDataAdjusted) && (resData+data > offset))
KSFLUtilities.putInt(arr, reflist+12*j+4, ((data-length) & 0xFFFFFF) | (arr[reflist+12*j+4] << 24));
}
}
}
}
private void paste(int[] loc, int offset, byte[] stuff, int what, int type, short id) {
boolean typeListAdjusted=false, nameListAdjusted=false, resDataAdjusted=false;
arr = KSFLUtilities.paste(arr, 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
KSFLUtilities.putInt(arr, 0, resData);
KSFLUtilities.putInt(arr, 4, resMap);
KSFLUtilities.putInt(arr, 8, resDataLen);
KSFLUtilities.putInt(arr, 12, resMapLen);
// update resource map
KSFLUtilities.putInt(arr, resMap+0, resData);
KSFLUtilities.putInt(arr, resMap+4, resMap);
KSFLUtilities.putInt(arr, resMap+8, resDataLen);
KSFLUtilities.putInt(arr, resMap+12, resMapLen);
KSFLUtilities.putShort(arr, resMap+24, (short)(typeList - resMap));
KSFLUtilities.putShort(arr, resMap+26, (short)(nameList - resMap));
// update type list
int numtypes = KSFLUtilities.getShort(arr, typeList)+1;
if (what == INSERTED_TYPE_RECORD) {
numtypes++;
KSFLUtilities.putShort(arr, typeList, (short)(numtypes-1));
}
for (int i=0; i<numtypes; i++) {
int thistype = KSFLUtilities.getInt(arr, typeList+2+8*i);
int numrefs = KSFLUtilities.getShort(arr, typeList+2+8*i+4)+1;
if (what == INSERTED_OBJECT_RECORD && thistype == type) {
numrefs++;
KSFLUtilities.putShort(arr, typeList+2+8*i+4, (short)(numrefs-1));
}
int reflist = typeList + KSFLUtilities.getShort(arr, typeList+2+8*i+6);
if ((!typeListAdjusted) && (reflist > offset || (reflist == offset && !(what == INSERTED_OBJECT_RECORD && thistype == type)))) {
reflist += stuff.length;
KSFLUtilities.putShort(arr, typeList+2+8*i+6, (short)(reflist - typeList));
}
// update references
for (int j=0; j<numrefs; j++) {
short thisid = KSFLUtilities.getShort(arr, reflist+12*j);
short name = KSFLUtilities.getShort(arr, reflist+12*j+2);
int data = KSFLUtilities.getInt(arr, reflist+12*j+4) & 0xFFFFFF;
if (name >= 0) {
if ((!nameListAdjusted) && (nameList+name > offset || (nameList+name == offset && !(what == INSERTED_NAME && thistype == type && thisid == id))))
KSFLUtilities.putShort(arr, reflist+12*j+2, (short)(name+stuff.length));
}
if (data >= 0) {
if ((!resDataAdjusted) && (resData+data > offset || (resData+data == offset && !(what == INSERTED_DATA && thistype == type && thisid == id))))
KSFLUtilities.putInt(arr, reflist+12*j+4, ((data+stuff.length) & 0xFFFFFF) | (arr[reflist+12*j+4] << 24));
}
}
}
}
/**
* Creates a new <code>MacResourceArray</code> containing no resources.
*/
public MacResourceArray() {
arr = new byte[286];
KSFLUtilities.putInt(arr, 0, 256);
KSFLUtilities.putInt(arr, 4, 256);
KSFLUtilities.putInt(arr, 8, 0);
KSFLUtilities.putInt(arr, 12, 30);
KSFLUtilities.putInt(arr, 256, 256);
KSFLUtilities.putInt(arr, 260, 256);
KSFLUtilities.putInt(arr, 264, 0);
KSFLUtilities.putInt(arr, 268, 30);
KSFLUtilities.putInt(arr, 272, 0);
KSFLUtilities.putShort(arr, 276, (short)0);
KSFLUtilities.putShort(arr, 278, (short)0);
KSFLUtilities.putShort(arr, 280, (short)28);
KSFLUtilities.putShort(arr, 282, (short)30);
KSFLUtilities.putShort(arr, 284, (short)-1);
resData = 256;
resMap = 256;
resDataLen = 0;
resMapLen = 30;
typeList = 284;
nameList = 286;
}
/**
* Creates a <code>MacResourceArray</code> wrapped around the specified byte array.
* The array should not be modified or read after this call.
* <p>
* No verification of the integrity of the data is performed.
* If the data does not contain a resource structure, calls to the
* constructed <code>MacResourceArray</code> will throw <code>ArrayIndexOutOfBoundsException</code>s.
* @param stuff a byte array containing a resource fork.
*/
public MacResourceArray(byte[] stuff) {
arr = stuff;
resData = KSFLUtilities.getInt(stuff, 0);
resMap = KSFLUtilities.getInt(stuff, 4);
resDataLen = KSFLUtilities.getInt(stuff, 8);
resMapLen = KSFLUtilities.getInt(stuff, 12);
typeList = resMap + KSFLUtilities.getShort(stuff, resMap+24);
nameList = resMap + KSFLUtilities.getShort(stuff, resMap+26);
}
/**
* 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 byte array this <code>MacResourceArray</code> is wrapped around.
* The array should not be modified after this call.
* Subsequent calls to this method may not return the same array.
* @return a byte array containing this resource structure.
*/
public byte[] getBytes() {
return arr;
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public synchronized void flush() {
// nothing
}
@Override
public synchronized void close() {
// nothing
}
@Override
public synchronized short getResourceMapAttributes() {
return KSFLUtilities.getShort(arr, resMap+22);
}
@Override
public synchronized void setResourceMapAttributes(short attr) {
KSFLUtilities.putShort(arr, resMap+22, attr);
}
@Override
public synchronized boolean add(MacResource r) throws MacResourceAlreadyExistsException {
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));
int lasttype = typeList+2+8*(KSFLUtilities.getShort(arr, typeList)+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;
}
@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) {
int[] l = locate(type,id);
if (l != null) {
return new MacResource(
KSFLUtilities.getInt(arr, l[0]),
KSFLUtilities.getShort(arr, l[3]),
arr[l[3]+4],
(l[4]>0)?gps(arr, l[4]):"",
(l[5]>0)?KSFLUtilities.copy(arr, l[5]+4, KSFLUtilities.getInt(arr, l[5])):(new byte[0])
);
}
return null;
}
@Override
public synchronized MacResource get(int type, String name) {
int[] l = locate(type,name);
if (l != null) {
return new MacResource(
KSFLUtilities.getInt(arr, l[0]),
KSFLUtilities.getShort(arr, l[3]),
arr[l[3]+4],
(l[4]>0)?gps(arr, l[4]):"",
(l[5]>0)?KSFLUtilities.copy(arr, l[5]+4, KSFLUtilities.getInt(arr, l[5])):(new byte[0])
);
}
return null;
}
@Override
public synchronized MacResource getAttributes(int type, short id) {
int[] l = locate(type,id);
if (l != null) {
return new MacResource(
KSFLUtilities.getInt(arr, l[0]),
KSFLUtilities.getShort(arr, l[3]),
arr[l[3]+4],
(l[4]>0)?gps(arr, l[4]):"",
new byte[0]
);
}
return null;
}
@Override
public synchronized MacResource getAttributes(int type, String name) {
int[] l = locate(type,name);
if (l != null) {
return new MacResource(
KSFLUtilities.getInt(arr, l[0]),
KSFLUtilities.getShort(arr, l[3]),
arr[l[3]+4],
(l[4]>0)?gps(arr, l[4]):"",
new byte[0]
);
}
return null;
}
@Override
public synchronized byte[] getData(int type, short id) {
int[] l = locate(type,id);
if (l != null) {
return (l[5]>0)?KSFLUtilities.copy(arr, l[5]+4, KSFLUtilities.getInt(arr, l[5])):(new byte[0]);
}
return null;
}
@Override
public synchronized byte[] getData(int type, String name) {
int[] l = locate(type,name);
if (l != null) {
return (l[5]>0)?KSFLUtilities.copy(arr, l[5]+4, KSFLUtilities.getInt(arr, l[5])):(new byte[0]);
}
return null;
}
@Override
public synchronized boolean remove(int type, short id) {
int[] loc = locate(type,id);
if (loc != null) {
//delete data
if (loc[5] > 0) {
int dlen = KSFLUtilities.getInt(arr, loc[5])+4;
cut(loc, loc[5], dlen, REMOVED_DATA, type, id);
}
//delete name
if (loc[4] > 0) {
int nlen = (arr[loc[4]] & 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;
}
return false;
}
@Override
public synchronized boolean remove(int type, String name) {
int[] loc = locate(type,name);
if (loc != null) {
short id = KSFLUtilities.getShort(arr, loc[3]);
//delete data
if (loc[5] > 0) {
int dlen = KSFLUtilities.getInt(arr, loc[5])+4;
cut(loc, loc[5], dlen, REMOVED_DATA, type, id);
}
//delete name
if (loc[4] > 0) {
int nlen = (arr[loc[4]] & 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;
}
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 {
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));
int lasttype = typeList+2+8*(KSFLUtilities.getShort(arr, typeList)+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
if (KSFLUtilities.getShort(arr, loc[0]+4) < 0) {
cut(loc, loc[0], 8, REMOVED_TYPE_RECORD, type, id);
}
//MacResourceArray's auto-handling of counts and offsets makes this
//MUCH easier than DFFArray's setObjectAttributes
} else {
//the easy part
KSFLUtilities.putShort(arr, loc[3], (short)r.id);
arr[loc[3]+4] = 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);
cut(loc, loc[4]+n.length, (arr[loc[4]+n.length]&0xFF)+1, REMOVED_NAME, r.type, r.id);
} else {
KSFLUtilities.putShort(arr, loc[3]+2, (short)(resMap+resMapLen-nameList));
paste(loc, resMap+resMapLen, n, INSERTED_NAME, r.type, r.id);
}
} else if (loc[4] > 0) {
cut(loc, loc[4], (arr[loc[4]]&0xFF)+1, REMOVED_NAME, r.type, r.id);
KSFLUtilities.putShort(arr, loc[3]+2, (short)-1);
loc[4] = 0;
}
return true;
}
}
return false;
}
@Override
public synchronized boolean setAttributes(int type, String name, MacResource r) throws MacResourceAlreadyExistsException {
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 {
short id = KSFLUtilities.getShort(arr, loc[3]);
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));
int lasttype = typeList+2+8*(KSFLUtilities.getShort(arr, typeList)+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
if (KSFLUtilities.getShort(arr, loc[0]+4) < 0) {
cut(loc, loc[0], 8, REMOVED_TYPE_RECORD, type, id);
}
//MacResourceArray's auto-handling of counts and offsets makes this
//MUCH easier than DFFArray's setObjectAttributes
} else {
//the easy part
KSFLUtilities.putShort(arr, loc[3], (short)r.id);
arr[loc[3]+4] = 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);
cut(loc, loc[4]+n.length, (arr[loc[4]+n.length]&0xFF)+1, REMOVED_NAME, r.type, r.id);
} else {
KSFLUtilities.putShort(arr, loc[3]+2, (short)(resMap+resMapLen-nameList));
paste(loc, resMap+resMapLen, n, INSERTED_NAME, r.type, r.id);
}
} else if (loc[4] > 0) {
cut(loc, loc[4], (arr[loc[4]]&0xFF)+1, REMOVED_NAME, r.type, r.id);
KSFLUtilities.putShort(arr, loc[3]+2, (short)-1);
loc[4] = 0;
}
return true;
}
}
return false;
}
@Override
public synchronized boolean setData(int type, short id, byte[] data) {
int[] loc = locate(type,id);
if (loc != null) {
//delete data
if (loc[5] > 0) {
int dlen = KSFLUtilities.getInt(arr, loc[5])+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;
}
return false;
}
@Override
public synchronized boolean setData(int type, String name, byte[] data) {
int[] loc = locate(type,name);
if (loc != null) {
short id = KSFLUtilities.getShort(arr, loc[3]);
//delete data
if (loc[5] > 0) {
int dlen = KSFLUtilities.getInt(arr, loc[5])+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;
}
return false;
}
@Override
public synchronized int getTypeCount() {
return KSFLUtilities.getShort(arr, typeList)+1;
}
@Override
public synchronized int getType(int index) {
return KSFLUtilities.getInt(arr, typeList+2+8*index);
}
@Override
public synchronized int[] getTypes() {
int m = KSFLUtilities.getShort(arr, typeList)+1;
int[] a = new int[m];
for (int i=0; i<m; i++) a[i]=KSFLUtilities.getInt(arr, typeList+2+8*i);
return a;
}
@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) {
int[] t = locateType(type);
if (t != null) return KSFLUtilities.getShort(arr, t[2]+12*index);
else return 0;
}
@Override
public synchronized short[] getIDs(int type) {
int[] t = locateType(type);
if (t != null) {
short[] a = new short[t[1]];
for (int i=0; i<t[1]; i++) a[i] = KSFLUtilities.getShort(arr, t[2]+12*i);
return a;
}
return new short[0];
}
@Override
public String getName(int type, int index) {
int[] t = locateType(type);
if (t != null) {
int n = KSFLUtilities.getShort(arr, t[2]+12*index+2);
if (n < 0) return "";
else return gps(arr, nameList + n);
}
return "";
}
@Override
public String[] getNames(int type) {
ArrayList<String> a = new ArrayList<String>();
int[] t = locateType(type);
if (t != null) for (int i=0; i<t[1]; i++) {
int n = KSFLUtilities.getShort(arr, t[2]+12*i+2);
if (n < 0) a.add("");
else a.add(gps(arr, nameList + n));
}
return a.toArray(new String[0]);
}
@Override
public synchronized short getNextAvailableID(int type, short start) {
ArrayList<Short> a = new ArrayList<Short>();
int[] t = locateType(type);
if (t != null) for (int i=0; i<t[1]; i++) a.add(KSFLUtilities.getShort(arr, t[2]+12*i));
short next = start;
while (a.contains(next)) next++;
return next;
}
@Override
public synchronized String getNameFromID(int type, short id) {
int[] l = locate(type,id);
if (l != null) {
return (l[4]>0)?gps(arr, l[4]):"";
}
return "";
}
@Override
public synchronized short getIDFromName(int type, String name) {
int[] l = locate(type,name);
if (l != null) {
return KSFLUtilities.getShort(arr, l[3]);
}
return 0;
}
}