![JAR search and dependency download from the Maven repository](/logo.png)
com.adobe.internal.mac.resource.ResourceParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
package com.adobe.internal.mac.resource;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import com.adobe.internal.io.CountingInputStream;
import com.adobe.internal.io.ExtendedDataInputStream;
import com.adobe.internal.io.RangedInputStream;
/**
* A general framework for parsing Mac resource files. The location of the resource (data fork,
* resource fork, or other) doesn't matter as long the full location of the resource is given.
*
* This resource parser will read the basic structure of the resource (see
* http://developer.apple.com/documentation/mac/MoreToolbox/MoreToolbox-99.html for Apple's Resource File Format})
* and will call back to handlers for each type of resource (e.g. 'FOND', 'sfnt', 'DITL', etc.) that are
* provided to the parser.
*
* Because Resource files are non-linear and the I/O interface is linear (a stream) the parsing is done in a
* two pass process. In the first pass the parser collects data about all of the individual resources in the
* Resource file. In the second pass the handlers for each resource type are called as the resources
* occur in the stream.
*
*/
public class ResourceParser
{
private static final boolean DEBUG = false;
/**
* Information about a resource type that is extracted from the resource type list.
*
*/
private static class ResourceTypeEntry
{
private byte[] type;
private int numberOfEntries;
private List /**/ resources;
private int offset;
public ResourceTypeEntry(byte[] type, int number, int offset)
{
this.type = type;
this.numberOfEntries = number;
this.resources = new ArrayList(this.numberOfEntries + 1);
this.offset = offset;
}
public int getNumberOfEntries()
{
return this.numberOfEntries;
}
public byte[] getResourceType()
{
return this.type;
}
public int getOffset()
{
return this.offset;
}
public void addResource(ResourceEntry resource)
{
this.resources.add(resource);
}
public String toString()
{
StringBuilder sb = new StringBuilder("type = ");
sb.append(new String(this.type)).append("\n").
append("number of entries = ").append(this.numberOfEntries).append("\n").
append("offset = ").append(this.offset).append("\n");
return sb.toString();
}
}
/**
* Information about a specific resource. This information is used internally for parsing and is
* also given to the callback resource type handlers.
*/
public static class ResourceEntry
{
private byte[] type;
private int id;
private byte attributes;
private int dataOffset;
private int nameOffset;
private String name;
private byte[] nameBytes;
private int script;
protected ResourceEntry(byte[] type, int id, byte attributes, int dataOffset, int nameOffset)
{
this.type = type;
this.id = id;
this.attributes = attributes;
this.dataOffset = dataOffset;
this.nameOffset = nameOffset;
}
/**
* Get the offset to the resource data for this resource.
* @return the offset of the data for this resource from the beginning of the resource table
*/
public int getDataOffset()
{
return this.dataOffset;
}
/**
* Get the offset to the name data for this resource.
* @return the offset to the name of this resource from the beginning of the name table; -1 if no name entry
*/
public int getNameOffset()
{
return this.nameOffset;
}
/**
* Put this ResourceEntry
into a string format for debugging or logging purposes.
* @return the string representation
*/
public String toString()
{
StringBuilder sb = new StringBuilder("type = ");
sb.append(new String(this.type)).append("\n").
append("id = ").append(this.id).append("\n").
append("attributes = ").append(this.attributes).append("\n").
append("data offset = ").append(this.dataOffset).append("\n").
append("name offset = ").append(this.nameOffset).append("\n").
append("name = ").append(this.name);
return sb.toString();
}
protected void setNameBytes(byte[] nameBytes)
{
this.nameBytes = nameBytes;
generateName();
}
private void generateName()
{
this.script = ScriptUtility.scriptCodeFromRsrcID(this.id);
try {
this.name = new String(this.nameBytes, ScriptUtility.scriptCodeToCharset(script));
} catch (UnsupportedEncodingException e) {
}
}
/**
* Get the type of this resource. The type is a 4 byte identifier unique to a specific resource
* type.
* @return the type of this resource
*/
public byte[] getType()
{
return this.type;
}
/**
* Get the attributes for this resource.
* @return the bit flags containing the attributes
*/
public byte getAttributes()
{
return this.attributes;
}
/**
* Get the bytes of the resource name. These are the raw bytes exactly as encoded in the name table.
* @return the raw bytes of the resource name
*/
public byte[] getNameBytes()
{
return this.nameBytes;
}
/**
* Get the name of this resource. This name is interpreted from the name bytes by using heuristics
* to guess the encoding. If no guess was possible or it was not possible to decode those bytes this
* will return null.
* @return the resource name
*/
public String getName()
{
return this.name;
}
/**
* Get the script code used to turn the name bytes for this resource into a name string.
* @return the script code used for the name of this resource
*/
public int getScriptCode()
{
return this.script;
}
/**
* Get the id of this resource.
* @return the id of this resource
*/
public int getID()
{
return this.id;
}
}
/**
* The interface that a callback resource handler must support.
*
*/
public interface ResourceHandler
{
/**
* The callback that is executed when a resource of the type supported by this handler
* is encountered in a resource file.
* @param entry the information about this resource
* @param length the length of the data stream
* @param stream the data stream
*/
void handleResource(ResourceEntry entry, long length, InputStream stream);
/**
* Get the resource type supported by this resource handler.
* @return the resource type supported by this handler
*/
byte[] getResourceType();
}
/**
* The key for the resource handlers stored in the resource handler map.
*
*/
private static final class ResouceHandlerKey
{
private final byte[] type;
public ResouceHandlerKey(byte[] type)
{
this.type = type;
}
public int hashCode() {
final int prime = 31;
int result = 1;
for (int index = 0; index < this.type.length; index++)
{
result = prime * result + type[index];
}
return result;
}
public boolean equals(Object obj) {
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (!(obj instanceof ResouceHandlerKey))
{
return false;
}
final ResouceHandlerKey other = (ResouceHandlerKey) obj;
if (!Arrays.equals(type, other.type))
{
return false;
}
return true;
}
}
// The url pointing to the Resource file.
private URL url;
// The resource data gathered during the first pass of the Resource file.
private List /**/ resourceTypes;
private List /**/ resources;
private List /**/ resourceNames;
// The handlers for the specific resource types.
private Map /**/ resourceHandlers;
/**
* Constructor.
*
* This parser can be reused to parse multiple Resource files.
*/
public ResourceParser()
{
this.resourceHandlers = new HashMap();
}
/**
* Set the URL for the resource file to be parsed.
* @param url location of the resource data
*/
public void setURL(URL url)
{
this.url = url;
}
/**
* Adds a resource handler to the parser. If a handler for that resource type has already been added
* then it is removed and the one being added replaces it.
* @param handler a handler for a specific resource type
*/
public void addHandler(ResourceHandler handler)
{
this.resourceHandlers.put(new ResouceHandlerKey(handler.getResourceType()), handler);
}
private void init()
{
// clean out the data from any previous parsing
this.resourceTypes = new ArrayList();
this.resources = new LinkedList();
this.resourceNames = new LinkedList();
}
/**
* Parse the Resource file.
*
* @throws IOException
*/
public void parse()
throws IOException
{
init();
InputStream is = this.url.openStream();
CountingInputStream cis = new CountingInputStream(is);
ExtendedDataInputStream dis = new ExtendedDataInputStream(cis);
// read the header
long dataOffset = dis.readUnsignedInt();
long mapOffset = dis.readUnsignedInt();
long dataLength = dis.readUnsignedInt();
long mapLength = dis.readUnsignedInt();
if (DEBUG)
{
System.out.println("=== RSRC Header");
System.out.println("dataOffset = " + dataOffset);
System.out.println("mapOffset = " + mapOffset);
System.out.println("dataLength = " + dataLength);
System.out.println("mapLength = " + mapLength);
System.out.println();
}
// skip to the map
long bytesToSkip = mapOffset - 16 /*bytes already read*/ + 22 /*unused bytes at front of map*/;
dis.skipFully(bytesToSkip);
// read the map header
int rsrcForkAttributes = dis.readUnsignedShort();
int resourceTypeListOffset = dis.readUnsignedShort();
int resourceNameListOffset = dis.readUnsignedShort();
int numberOfTypes = dis.readUnsignedShort();
if (DEBUG)
{
System.out.println("=== Map Header");
System.out.println("rsrcForkAttributes = " + rsrcForkAttributes);
System.out.println("resourceTypeListOffset = " + resourceTypeListOffset);
System.out.println("resourceNameListOffset = " + resourceNameListOffset);
System.out.println("numberOfTypes = " + numberOfTypes);
System.out.println();
}
// read the resource type list
for (int typeIndex = 0; typeIndex <= numberOfTypes; typeIndex++)
{
byte[] type = new byte[4];
dis.readFully(type);
int number = dis.readUnsignedShort();
int offset = dis.readShort();
ResourceTypeEntry typeEntry = new ResourceTypeEntry(type, number, offset);
insertIntoList(this.resourceTypes, typeEntry,
new OrderInt() { public int getOrderInt(Object obj)
{ return ((ResourceTypeEntry) obj).getOffset(); }
}
);
}
if (DEBUG)
{
dumpResourceTypeList();
}
// read the reference lists
for (int typeIndex = 0; typeIndex <= numberOfTypes; typeIndex++)
{
ResourceTypeEntry currentType = (ResourceTypeEntry) this.resourceTypes.get(typeIndex);
for (int resourceEntry = 0; resourceEntry <= currentType.getNumberOfEntries(); resourceEntry++)
{
int id = dis.readUnsignedShort();
int resourceNameOffset = dis.readShort();
byte attributes = dis.readByte();
int resourceDataOffset = dis.readUnsigned3ByteInt();
dis.readInt(); // reserved for handle - throw away
ResourceEntry resource = new ResourceEntry(
currentType.getResourceType(), id, attributes, resourceDataOffset, resourceNameOffset);
currentType.addResource(resource);
// insert into the resource list in order of offset in the resource table
insertIntoList(this.resources, resource,
new OrderInt() { public int getOrderInt(Object obj)
{ return ((ResourceEntry) obj).getDataOffset(); }
}
);
// insert into the name list in order of the offset in the name list
insertIntoList(this.resourceNames, resource,
new OrderInt() { public int getOrderInt(Object obj)
{ return ((ResourceEntry) obj).getNameOffset(); }
}
);
}
}
if (DEBUG)
{
dumpResourceList();
}
// read the name list
for (int resourceIndex = 0; resourceIndex < this.resourceNames.size(); resourceIndex++)
{
ResourceEntry currentResource = (ResourceEntry) this.resourceNames.get(resourceIndex);
int length = dis.readUnsignedByte();
byte[] nameBytes = new byte[length];
dis.readFully(nameBytes);
currentResource.setNameBytes(nameBytes);
}
if (DEBUG)
{
dumpResourceList();
}
// close the original streams
dis.close();
dis = null;
cis.close();
cis = null;
is.close();
is = null;
// read the data
InputStream dataIS = this.url.openStream();
CountingInputStream dataCIS = new CountingInputStream(dataIS);
ExtendedDataInputStream dataDIS = new ExtendedDataInputStream(dataCIS);
if (DEBUG)
{
System.out.println("\n#### Processing Resource Data");
System.out.println("#########################");
}
// TODO check to see if the data offset is after the current position and then no need for a second stream
dataDIS.skipFully(dataOffset);
if (DEBUG)
{
System.out.println("dataOffset = " + dataOffset);
System.out.println("stream position = " + dataCIS.getOffset());
}
for (int resourceIndex = 0; resourceIndex < this.resources.size(); resourceIndex++)
{
ResourceEntry currentResource = (ResourceEntry) this.resources.get(resourceIndex);
long dataToSkip = (currentResource.getDataOffset() + dataOffset) - dataCIS.getOffset();
dataDIS.skipFully(dataToSkip);
long length = dataDIS.readUnsignedInt();
if (DEBUG)
{
System.out.println(currentResource);
System.out.println("dataToSkip = " + dataToSkip);
System.out.println("length = " + length);
}
//long length = dataDIS.readInt();
// call the resource type plugin
RangedInputStream subStream = new RangedInputStream(dataDIS, length);
ResourceHandler handler =
(ResourceHandler) this.resourceHandlers.get(new ResouceHandlerKey(currentResource.getType()));
if (handler != null)
{
handler.handleResource(currentResource, length, subStream);
}
// skip to the end of the resource
//dataDIS.skip(length - subStream.getOffset());
if (DEBUG)
{
System.out.println("============");
}
}
}
private void dumpResourceList()
{
Iterator iter = this.resources.listIterator();
System.out.println("Resources");
System.out.println("===============");
while (iter.hasNext())
{
ResourceEntry entry = (ResourceEntry) iter.next();
System.out.println(entry);
System.out.println("---------------");
}
}
private void dumpResourceTypeList()
{
Iterator iter = this.resourceTypes.listIterator();
System.out.println("Resource Types");
System.out.println("===============");
while (iter.hasNext())
{
ResourceTypeEntry entry = (ResourceTypeEntry) iter.next();
System.out.println(entry);
System.out.println("---------------");
}
}
/**
* Used for inserting into a list in an ordering given by the ordering of the integers.
*
*/
interface OrderInt
{
int getOrderInt(Object obj);
}
void insertIntoList(List/*
© 2015 - 2025 Weber Informatics LLC | Privacy Policy