blob: 515218729ed58a6606b3613999bcba664368d193 [file] [log] [blame]
/*
* Copyright (c) 2003, 2005, 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.java.util.jar.pack;
import java.io.*;
import java.util.*;
import com.sun.java.util.jar.pack.Package.Class;
import com.sun.java.util.jar.pack.ConstantPool.*;
/**
* Represents an attribute in a class-file.
* Takes care to remember where constant pool indexes occur.
* Implements the "little language" of Pack200 for describing
* attribute layouts.
* @author John Rose
*/
class Attribute implements Comparable, Constants {
// Attribute instance fields.
Layout def; // the name and format of this attr
byte[] bytes; // the actual bytes
Object fixups; // reference relocations, if any are required
public String name() { return def.name(); }
public Layout layout() { return def; }
public byte[] bytes() { return bytes; }
public int size() { return bytes.length; }
public Entry getNameRef() { return def.getNameRef(); }
private Attribute(Attribute old) {
this.def = old.def;
this.bytes = old.bytes;
this.fixups = old.fixups;
}
public Attribute(Layout def, byte[] bytes, Object fixups) {
this.def = def;
this.bytes = bytes;
this.fixups = fixups;
Fixups.setBytes(fixups, bytes);
}
public Attribute(Layout def, byte[] bytes) {
this(def, bytes, null);
}
public Attribute addContent(byte[] bytes, Object fixups) {
assert(isCanonical());
if (bytes.length == 0 && fixups == null)
return this;
Attribute res = new Attribute(this);
res.bytes = bytes;
res.fixups = fixups;
Fixups.setBytes(fixups, bytes);
return res;
}
public Attribute addContent(byte[] bytes) {
return addContent(bytes, null);
}
public void finishRefs(Index ix) {
if (fixups != null) {
Fixups.finishRefs(fixups, bytes, ix);
fixups = null;
}
}
public boolean isCanonical() {
return this == def.canon;
}
public int compareTo(Object o) {
Attribute that = (Attribute) o;
return this.def.compareTo(that.def);
}
static private final byte[] noBytes = {};
static private final HashMap canonLists = new HashMap();
static private final HashMap attributes = new HashMap();
static private final HashMap standardDefs = new HashMap();
// Canonicalized lists of trivial attrs (Deprecated, etc.)
// are used by trimToSize, in order to reduce footprint
// of some common cases. (Note that Code attributes are
// always zero size.)
public static List getCanonList(List al) {
synchronized (canonLists) {
List cl = (List) canonLists.get(al);
if (cl == null) {
cl = new ArrayList(al.size());
cl.addAll(al);
cl = Collections.unmodifiableList(cl);
canonLists.put(al, cl);
}
return cl;
}
}
// Find the canonical empty attribute with the given ctype, name, layout.
public static Attribute find(int ctype, String name, String layout) {
Layout key = Layout.makeKey(ctype, name, layout);
synchronized (attributes) {
Attribute a = (Attribute) attributes.get(key);
if (a == null) {
a = new Layout(ctype, name, layout).canonicalInstance();
attributes.put(key, a);
}
return a;
}
}
public static Object keyForLookup(int ctype, String name) {
return Layout.makeKey(ctype, name);
}
// Find canonical empty attribute with given ctype and name,
// and with the standard layout.
public static Attribute lookup(Map defs, int ctype, String name) {
if (defs == null) defs = standardDefs;
return (Attribute) defs.get(Layout.makeKey(ctype, name));
}
public static Attribute define(Map defs, int ctype, String name, String layout) {
Attribute a = find(ctype, name, layout);
defs.put(Layout.makeKey(ctype, name), a);
return a;
}
static {
Map sd = standardDefs;
define(sd, ATTR_CONTEXT_CLASS, "Signature", "RSH");
define(sd, ATTR_CONTEXT_CLASS, "Synthetic", "");
define(sd, ATTR_CONTEXT_CLASS, "Deprecated", "");
define(sd, ATTR_CONTEXT_CLASS, "SourceFile", "RUH");
define(sd, ATTR_CONTEXT_CLASS, "EnclosingMethod", "RCHRDNH");
define(sd, ATTR_CONTEXT_CLASS, "InnerClasses", "NH[RCHRCNHRUNHFH]");
define(sd, ATTR_CONTEXT_FIELD, "Signature", "RSH");
define(sd, ATTR_CONTEXT_FIELD, "Synthetic", "");
define(sd, ATTR_CONTEXT_FIELD, "Deprecated", "");
define(sd, ATTR_CONTEXT_FIELD, "ConstantValue", "KQH");
define(sd, ATTR_CONTEXT_METHOD, "Signature", "RSH");
define(sd, ATTR_CONTEXT_METHOD, "Synthetic", "");
define(sd, ATTR_CONTEXT_METHOD, "Deprecated", "");
define(sd, ATTR_CONTEXT_METHOD, "Exceptions", "NH[RCH]");
//define(sd, ATTR_CONTEXT_METHOD, "Code", "HHNI[B]NH[PHPOHPOHRCNH]NH[RUHNI[B]]");
define(sd, ATTR_CONTEXT_CODE, "StackMapTable",
("[NH[(1)]]" +
"[TB" +
"(64-127)[(2)]" +
"(247)[(1)(2)]" +
"(248-251)[(1)]" +
"(252)[(1)(2)]" +
"(253)[(1)(2)(2)]" +
"(254)[(1)(2)(2)(2)]" +
"(255)[(1)NH[(2)]NH[(2)]]" +
"()[]" +
"]" +
"[H]" +
"[TB(7)[RCH](8)[PH]()[]]"));
define(sd, ATTR_CONTEXT_CODE, "LineNumberTable", "NH[PHH]");
define(sd, ATTR_CONTEXT_CODE, "LocalVariableTable", "NH[PHOHRUHRSHH]");
define(sd, ATTR_CONTEXT_CODE, "LocalVariableTypeTable", "NH[PHOHRUHRSHH]");
//define(sd, ATTR_CONTEXT_CODE, "CharacterRangeTable", "NH[PHPOHIIH]");
//define(sd, ATTR_CONTEXT_CODE, "CoverageTable", "NH[PHHII]");
// Note: Code and InnerClasses are special-cased elsewhere.
// Their layout specs. are given here for completeness.
// The Code spec is incomplete, in that it does not distinguish
// bytecode bytes or locate CP references.
}
// Metadata.
//
// We define metadata using similar layouts
// for all five kinds of metadata attributes.
//
// Regular annotations are a counted list of [RSHNH[RUH(1)]][...]
// pack.method.attribute.RuntimeVisibleAnnotations=[NH[(1)]][RSHNH[RUH(1)]][TB...]
//
// Parameter annotations are a counted list of regular annotations.
// pack.method.attribute.RuntimeVisibleParameterAnnotations=[NH[(1)]][NH[(1)]][RSHNH[RUH(1)]][TB...]
//
// RuntimeInvisible annotations are defined similarly...
// Non-method annotations are defined similarly...
//
// Annotation are a simple tagged value [TB...]
// pack.attribute.method.AnnotationDefault=[TB...]
//
static {
String mdLayouts[] = {
Attribute.normalizeLayoutString
(""
+"\n # parameter_annotations :="
+"\n [ NB[(1)] ] # forward call to annotations"
),
Attribute.normalizeLayoutString
(""
+"\n # annotations :="
+"\n [ NH[(1)] ] # forward call to annotation"
+"\n "
+"\n # annotation :="
+"\n [RSH"
+"\n NH[RUH (1)] # forward call to value"
+"\n ]"
),
Attribute.normalizeLayoutString
(""
+"\n # value :="
+"\n [TB # Callable 2 encodes one tagged value."
+"\n (\\B,\\C,\\I,\\S,\\Z)[KIH]"
+"\n (\\D)[KDH]"
+"\n (\\F)[KFH]"
+"\n (\\J)[KJH]"
+"\n (\\c)[RSH]"
+"\n (\\e)[RSH RUH]"
+"\n (\\s)[RUH]"
+"\n (\\[)[NH[(0)]] # backward self-call to value"
+"\n (\\@)[RSH NH[RUH (0)]] # backward self-call to value"
+"\n ()[] ]"
)
};
Map sd = standardDefs;
String defaultLayout = mdLayouts[2];
String annotationsLayout = mdLayouts[1] + mdLayouts[2];
String paramsLayout = mdLayouts[0] + annotationsLayout;
for (int ctype = 0; ctype < ATTR_CONTEXT_LIMIT; ctype++) {
if (ctype == ATTR_CONTEXT_CODE) continue;
define(sd, ctype,
"RuntimeVisibleAnnotations", annotationsLayout);
define(sd, ctype,
"RuntimeInvisibleAnnotations", annotationsLayout);
if (ctype == ATTR_CONTEXT_METHOD) {
define(sd, ctype,
"RuntimeVisibleParameterAnnotations", paramsLayout);
define(sd, ctype,
"RuntimeInvisibleParameterAnnotations", paramsLayout);
define(sd, ctype,
"AnnotationDefault", defaultLayout);
}
}
}
public static String contextName(int ctype) {
switch (ctype) {
case ATTR_CONTEXT_CLASS: return "class";
case ATTR_CONTEXT_FIELD: return "field";
case ATTR_CONTEXT_METHOD: return "method";
case ATTR_CONTEXT_CODE: return "code";
}
return null;
}
public static Map getStandardDefs() {
return new HashMap(standardDefs);
}
/** Base class for any attributed object (Class, Field, Method, Code).
* Flags are included because they are used to help transmit the
* presence of attributes. That is, flags are a mix of modifier
* bits and attribute indicators.
*/
public static abstract
class Holder {
// We need this abstract method to interpret embedded CP refs.
protected abstract Entry[] getCPMap();
protected int flags; // defined here for convenience
protected List attributes;
public int attributeSize() {
return (attributes == null) ? 0 : attributes.size();
}
public void trimToSize() {
if (attributes == null) {
return;
}
if (attributes.size() == 0) {
attributes = null;
return;
}
if (attributes instanceof ArrayList) {
ArrayList al = (ArrayList) attributes;
al.trimToSize();
boolean allCanon = true;
for (Iterator i = al.iterator(); i.hasNext(); ) {
Attribute a = (Attribute) i.next();
if (!a.isCanonical()) {
allCanon = false;
}
if (a.fixups != null) {
assert(!a.isCanonical());
a.fixups = Fixups.trimToSize(a.fixups);
}
}
if (allCanon) {
// Replace private writable attribute list
// with only trivial entries by public unique
// immutable attribute list with the same entries.
attributes = getCanonList(al);
}
}
}
public void addAttribute(Attribute a) {
if (attributes == null)
attributes = new ArrayList(3);
else if (!(attributes instanceof ArrayList))
attributes = new ArrayList(attributes); // unfreeze it
attributes.add(a);
}
public Attribute removeAttribute(Attribute a) {
if (attributes == null) return null;
if (!attributes.contains(a)) return null;
if (!(attributes instanceof ArrayList))
attributes = new ArrayList(attributes); // unfreeze it
attributes.remove(a);
return a;
}
public Attribute getAttribute(int n) {
return (Attribute) attributes.get(n);
}
protected void visitRefs(int mode, Collection refs) {
if (attributes == null) return;
for (Iterator i = attributes.iterator(); i.hasNext(); ) {
Attribute a = (Attribute) i.next();
a.visitRefs(this, mode, refs);
}
}
static final List noAttributes = Arrays.asList(new Object[0]);
public List getAttributes() {
if (attributes == null)
return noAttributes;
return attributes;
}
public void setAttributes(List attrList) {
if (attrList.isEmpty())
attributes = null;
else
attributes = attrList;
}
public Attribute getAttribute(String attrName) {
if (attributes == null) return null;
for (Iterator i = attributes.iterator(); i.hasNext(); ) {
Attribute a = (Attribute) i.next();
if (a.name().equals(attrName))
return a;
}
return null;
}
public Attribute getAttribute(Layout attrDef) {
if (attributes == null) return null;
for (Iterator i = attributes.iterator(); i.hasNext(); ) {
Attribute a = (Attribute) i.next();
if (a.layout() == attrDef)
return a;
}
return null;
}
public Attribute removeAttribute(String attrName) {
return removeAttribute(getAttribute(attrName));
}
public Attribute removeAttribute(Layout attrDef) {
return removeAttribute(getAttribute(attrDef));
}
public void strip(String attrName) {
removeAttribute(getAttribute(attrName));
}
}
// Lightweight interface to hide details of band structure.
// Also used for testing.
public static abstract
class ValueStream {
public int getInt(int bandIndex) { throw undef(); }
public void putInt(int bandIndex, int value) { throw undef(); }
public Entry getRef(int bandIndex) { throw undef(); }
public void putRef(int bandIndex, Entry ref) { throw undef(); }
// Note: decodeBCI goes w/ getInt/Ref; encodeBCI goes w/ putInt/Ref
public int decodeBCI(int bciCode) { throw undef(); }
public int encodeBCI(int bci) { throw undef(); }
public void noteBackCall(int whichCallable) { /* ignore by default */ }
private RuntimeException undef() {
return new UnsupportedOperationException("ValueStream method");
}
}
// Element kinds:
static final byte EK_INT = 1; // B H I SH etc.
static final byte EK_BCI = 2; // PH POH etc.
static final byte EK_BCO = 3; // OH etc.
static final byte EK_FLAG = 4; // FH etc.
static final byte EK_REPL = 5; // NH[...] etc.
static final byte EK_REF = 6; // RUH, RUNH, KQH, etc.
static final byte EK_UN = 7; // TB(...)[...] etc.
static final byte EK_CASE = 8; // (...)[...] etc.
static final byte EK_CALL = 9; // (0), (1), etc.
static final byte EK_CBLE = 10; // [...][...] etc.
static final byte EF_SIGN = 1<<0; // INT is signed
static final byte EF_DELTA = 1<<1; // BCI/BCI value is diff'ed w/ previous
static final byte EF_NULL = 1<<2; // null REF is expected/allowed
static final byte EF_BACK = 1<<3; // call, callable, case is backward
static final int NO_BAND_INDEX = -1;
/** A "class" of attributes, characterized by a context-type, name
* and format. The formats are specified in a "little language".
*/
public static
class Layout implements Comparable {
int ctype; // attribute context type, e.g., ATTR_CONTEXT_CODE
String name; // name of attribute
boolean hasRefs; // this kind of attr contains CP refs?
String layout; // layout specification
int bandCount; // total number of elems
Element[] elems; // tokenization of layout
Attribute canon; // canonical instance of this layout
public int ctype() { return ctype; }
public String name() { return name; }
public String layout() { return layout; }
public Attribute canonicalInstance() { return canon; }
// Cache of name reference.
private Entry nameRef; // name, for use by visitRefs
public Entry getNameRef() {
Entry nameRef = this.nameRef;
if (nameRef == null) {
this.nameRef = nameRef = ConstantPool.getUtf8Entry(name());
}
return nameRef;
}
public boolean isEmpty() { return layout == ""; }
public Layout(int ctype, String name, String layout) {
this.ctype = ctype;
this.name = name.intern();
this.layout = layout.intern();
assert(ctype < ATTR_CONTEXT_LIMIT);
boolean hasCallables = layout.startsWith("[");
try {
if (!hasCallables) {
this.elems = tokenizeLayout(this, -1, layout);
} else {
String[] bodies = splitBodies(layout);
// Make the callables now, so they can be linked immediately.
Element[] elems = new Element[bodies.length];
this.elems = elems;
for (int i = 0; i < elems.length; i++) {
Element ce = this.new Element();
ce.kind = EK_CBLE;
ce.removeBand();
ce.bandIndex = NO_BAND_INDEX;
ce.layout = bodies[i];
elems[i] = ce;
}
// Next fill them in.
for (int i = 0; i < elems.length; i++) {
Element ce = elems[i];
ce.body = tokenizeLayout(this, i, bodies[i]);
}
//System.out.println(Arrays.asList(elems));
}
} catch (StringIndexOutOfBoundsException ee) {
// simplest way to catch syntax errors...
throw new RuntimeException("Bad attribute layout: "+layout, ee);
}
// Some uses do not make a fresh one for each occurrence.
// For example, if layout == "", we only need one attr to share.
canon = new Attribute(this, noBytes);
}
private Layout() {}
static Layout makeKey(int ctype, String name, String layout) {
Layout def = new Layout();
def.ctype = ctype;
def.name = name.intern();
def.layout = layout.intern();
assert(ctype < ATTR_CONTEXT_LIMIT);
return def;
}
static Layout makeKey(int ctype, String name) {
return makeKey(ctype, name, "");
}
public Attribute addContent(byte[] bytes, Object fixups) {
return canon.addContent(bytes, fixups);
}
public Attribute addContent(byte[] bytes) {
return canon.addContent(bytes, null);
}
public boolean equals(Object x) {
return x instanceof Layout && equals((Layout)x);
}
public boolean equals(Layout that) {
return this.name == that.name
&& this.layout == that.layout
&& this.ctype == that.ctype;
}
public int hashCode() {
return (((17 + name.hashCode())
* 37 + layout.hashCode())
* 37 + ctype);
}
public int compareTo(Object o) {
Layout that = (Layout) o;
int r;
r = this.name.compareTo(that.name);
if (r != 0) return r;
r = this.layout.compareTo(that.layout);
if (r != 0) return r;
return this.ctype - that.ctype;
}
public String toString() {
String str = contextName(ctype)+"."+name+"["+layout+"]";
// If -ea, print out more informative strings!
assert((str = stringForDebug()) != null);
return str;
}
private String stringForDebug() {
return contextName(ctype)+"."+name+Arrays.asList(elems);
}
public
class Element {
String layout; // spelling in the little language
byte flags; // EF_SIGN, etc.
byte kind; // EK_UINT, etc.
byte len; // scalar length of element
byte refKind; // CONSTANT_String, etc.
int bandIndex; // which band does this element govern?
int value; // extra parameter
Element[] body; // extra data (for replications, unions, calls)
boolean flagTest(byte mask) { return (flags & mask) != 0; }
Element() {
bandIndex = bandCount++;
}
void removeBand() {
--bandCount;
assert(bandIndex == bandCount);
bandIndex = NO_BAND_INDEX;
}
public boolean hasBand() {
return bandIndex >= 0;
}
public String toString() {
String str = layout;
// If -ea, print out more informative strings!
assert((str = stringForDebug()) != null);
return str;
}
private String stringForDebug() {
Element[] body = this.body;
switch (kind) {
case EK_CALL:
body = null;
break;
case EK_CASE:
if (flagTest(EF_BACK))
body = null;
break;
}
return layout
+ (!hasBand()?"":"#"+bandIndex)
+ "<"+ (flags==0?"":""+flags)+kind+len
+ (refKind==0?"":""+refKind) + ">"
+ (value==0?"":"("+value+")")
+ (body==null?"": ""+Arrays.asList(body));
}
}
public boolean hasCallables() {
return (elems.length > 0 && elems[0].kind == EK_CBLE);
}
static private final Element[] noElems = {};
public Element[] getCallables() {
if (hasCallables())
return elems;
else
return noElems; // no callables at all
}
public Element[] getEntryPoint() {
if (hasCallables())
return elems[0].body; // body of first callable
else
return elems; // no callables; whole body
}
/** Return a sequence of tokens from the given attribute bytes.
* Sequence elements will be 1-1 correspondent with my layout tokens.
*/
public void parse(Holder holder,
byte[] bytes, int pos, int len, ValueStream out) {
int end = parseUsing(getEntryPoint(),
holder, bytes, pos, len, out);
if (end != pos + len)
throw new InternalError("layout parsed "+(end-pos)+" out of "+len+" bytes");
}
/** Given a sequence of tokens, return the attribute bytes.
* Sequence elements must be 1-1 correspondent with my layout tokens.
* The returned object is a cookie for Fixups.finishRefs, which
* must be used to harden any references into integer indexes.
*/
public Object unparse(ValueStream in, ByteArrayOutputStream out) {
Object[] fixups = { null };
unparseUsing(getEntryPoint(), fixups, in, out);
return fixups[0]; // return ref-bearing cookie, if any
}
public String layoutForPackageMajver(int majver) {
if (majver <= JAVA5_PACKAGE_MAJOR_VERSION) {
// Disallow layout syntax in the oldest protocol version.
return expandCaseDashNotation(layout);
}
return layout;
}
}
public static
class FormatException extends IOException {
private int ctype;
private String name;
String layout;
public FormatException(String message,
int ctype, String name, String layout) {
super(ATTR_CONTEXT_NAME[ctype]+"."+name
+(message == null? "": (": "+message)));
this.ctype = ctype;
this.name = name;
this.layout = layout;
}
public FormatException(String message,
int ctype, String name) {
this(message, ctype, name, null);
}
}
void visitRefs(Holder holder, int mode, final Collection refs) {
if (mode == VRM_CLASSIC) {
refs.add(getNameRef());
}
// else the name is owned by the layout, and is processed elsewhere
if (bytes.length == 0) return; // quick exit
if (!def.hasRefs) return; // quick exit
if (fixups != null) {
Fixups.visitRefs(fixups, refs);
return;
}
// References (to a local cpMap) are embedded in the bytes.
def.parse(holder, bytes, 0, bytes.length,
new ValueStream() {
public void putInt(int bandIndex, int value) {
}
public void putRef(int bandIndex, Entry ref) {
refs.add(ref);
}
public int encodeBCI(int bci) {
return bci;
}
});
}
public void parse(Holder holder, byte[] bytes, int pos, int len, ValueStream out) {
def.parse(holder, bytes, pos, len, out);
}
public Object unparse(ValueStream in, ByteArrayOutputStream out) {
return def.unparse(in, out);
}
public String toString() {
return def
+"{"+(bytes == null ? -1 : size())+"}"
+(fixups == null? "": fixups.toString());
}
/** Remove any informal "pretty printing" from the layout string.
* Removes blanks and control chars.
* Removes '#' comments (to end of line).
* Replaces '\c' by the decimal code of the character c.
* Replaces '0xNNN' by the decimal code of the hex number NNN.
*/
static public
String normalizeLayoutString(String layout) {
StringBuffer buf = new StringBuffer();
for (int i = 0, len = layout.length(); i < len; ) {
char ch = layout.charAt(i++);
if (ch <= ' ') {
// Skip whitespace and control chars
continue;
} else if (ch == '#') {
// Skip to end of line.
int end1 = layout.indexOf('\n', i);
int end2 = layout.indexOf('\r', i);
if (end1 < 0) end1 = len;
if (end2 < 0) end2 = len;
i = Math.min(end1, end2);
} else if (ch == '\\') {
// Map a character reference to its decimal code.
buf.append((int) layout.charAt(i++));
} else if (ch == '0' && layout.startsWith("0x", i-1)) {
// Map a hex numeral to its decimal code.
int start = i-1;
int end = start+2;
while (end < len) {
int dig = layout.charAt(end);
if ((dig >= '0' && dig <= '9') ||
(dig >= 'a' && dig <= 'f'))
++end;
else
break;
}
if (end > start) {
String num = layout.substring(start, end);
buf.append(Integer.decode(num));
i = end;
} else {
buf.append(ch);
}
} else {
buf.append(ch);
}
}
String result = buf.toString();
if (false && !result.equals(layout)) {
Utils.log.info("Normalizing layout string");
Utils.log.info(" From: "+layout);
Utils.log.info(" To: "+result);
}
return result;
}
/// Subroutines for parsing and unparsing:
/** Parse the attribute layout language.
<pre>
attribute_layout:
( layout_element )* | ( callable )+
layout_element:
( integral | replication | union | call | reference )
callable:
'[' body ']'
body:
( layout_element )+
integral:
( unsigned_int | signed_int | bc_index | bc_offset | flag )
unsigned_int:
uint_type
signed_int:
'S' uint_type
any_int:
( unsigned_int | signed_int )
bc_index:
( 'P' uint_type | 'PO' uint_type )
bc_offset:
'O' any_int
flag:
'F' uint_type
uint_type:
( 'B' | 'H' | 'I' | 'V' )
replication:
'N' uint_type '[' body ']'
union:
'T' any_int (union_case)* '(' ')' '[' (body)? ']'
union_case:
'(' union_case_tag (',' union_case_tag)* ')' '[' (body)? ']'
union_case_tag:
( numeral | numeral '-' numeral )
call:
'(' numeral ')'
reference:
reference_type ( 'N' )? uint_type
reference_type:
( constant_ref | schema_ref | utf8_ref | untyped_ref )
constant_ref:
( 'KI' | 'KJ' | 'KF' | 'KD' | 'KS' | 'KQ' )
schema_ref:
( 'RC' | 'RS' | 'RD' | 'RF' | 'RM' | 'RI' )
utf8_ref:
'RU'
untyped_ref:
'RQ'
numeral:
'(' ('-')? (digit)+ ')'
digit:
( '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' )
</pre>
*/
static //private
Layout.Element[] tokenizeLayout(Layout self, int curCble, String layout) {
ArrayList col = new ArrayList(layout.length());
tokenizeLayout(self, curCble, layout, col);
Layout.Element[] res = new Layout.Element[col.size()];
col.toArray(res);
return res;
}
static //private
void tokenizeLayout(Layout self, int curCble, String layout, ArrayList col) {
boolean prevBCI = false;
for (int len = layout.length(), i = 0; i < len; ) {
int start = i;
int body;
Layout.Element e = self.new Element();
byte kind;
//System.out.println("at "+i+": ..."+layout.substring(i));
// strip a prefix
switch (layout.charAt(i++)) {
/// layout_element: integral
case 'B': case 'H': case 'I': case 'V': // unsigned_int
kind = EK_INT;
--i; // reparse
i = tokenizeUInt(e, layout, i);
break;
case 'S': // signed_int
kind = EK_INT;
--i; // reparse
i = tokenizeSInt(e, layout, i);
break;
case 'P': // bc_index
kind = EK_BCI;
if (layout.charAt(i++) == 'O') {
// bc_index: 'PO' tokenizeUInt
e.flags |= EF_DELTA;
// must follow P or PO:
if (!prevBCI)
{ i = -i; continue; } // fail
i++; // move forward
}
--i; // reparse
i = tokenizeUInt(e, layout, i);
break;
case 'O': // bc_offset
kind = EK_BCO;
e.flags |= EF_DELTA;
// must follow P or PO:
if (!prevBCI)
{ i = -i; continue; } // fail
i = tokenizeSInt(e, layout, i);
break;
case 'F': // flag
kind = EK_FLAG;
i = tokenizeUInt(e, layout, i);
break;
case 'N': // replication: 'N' uint '[' elem ... ']'
kind = EK_REPL;
i = tokenizeUInt(e, layout, i);
if (layout.charAt(i++) != '[')
{ i = -i; continue; } // fail
i = skipBody(layout, body = i);
e.body = tokenizeLayout(self, curCble,
layout.substring(body, i++));
break;
case 'T': // union: 'T' any_int union_case* '(' ')' '[' body ']'
kind = EK_UN;
i = tokenizeSInt(e, layout, i);
ArrayList cases = new ArrayList();
for (;;) {
// Keep parsing cases until we hit the default case.
if (layout.charAt(i++) != '(')
{ i = -i; break; } // fail
int beg = i;
i = layout.indexOf(')', i);
String cstr = layout.substring(beg, i++);
int cstrlen = cstr.length();
if (layout.charAt(i++) != '[')
{ i = -i; break; } // fail
// Check for duplication.
if (layout.charAt(i) == ']')
body = i; // missing body, which is legal here
else
i = skipBody(layout, body = i);
Layout.Element[] cbody
= tokenizeLayout(self, curCble,
layout.substring(body, i++));
if (cstrlen == 0) {
Layout.Element ce = self.new Element();
ce.body = cbody;
ce.kind = EK_CASE;
ce.removeBand();
cases.add(ce);
break; // done with the whole union
} else {
// Parse a case string.
boolean firstCaseNum = true;
for (int cp = 0, endp;; cp = endp+1) {
// Look for multiple case tags:
endp = cstr.indexOf(',', cp);
if (endp < 0) endp = cstrlen;
String cstr1 = cstr.substring(cp, endp);
if (cstr1.length() == 0)
cstr1 = "empty"; // will fail parse
int value0, value1;
// Check for a case range (new in 1.6).
int dash = findCaseDash(cstr1, 0);
if (dash >= 0) {
value0 = parseIntBefore(cstr1, dash);
value1 = parseIntAfter(cstr1, dash);
if (value0 >= value1)
{ i = -i; break; } // fail
} else {
value0 = value1 = Integer.parseInt(cstr1);
}
// Add a case for each value in value0..value1
for (;; value0++) {
Layout.Element ce = self.new Element();
ce.body = cbody; // all cases share one body
ce.kind = EK_CASE;
ce.removeBand();
if (!firstCaseNum)
// "backward case" repeats a body
ce.flags |= EF_BACK;
firstCaseNum = false;
ce.value = value0;
cases.add(ce);
if (value0 == value1) break;
}
if (endp == cstrlen) {
break; // done with this case
}
}
}
}
e.body = new Layout.Element[cases.size()];
cases.toArray(e.body);
e.kind = kind;
for (int j = 0; j < e.body.length-1; j++) {
Layout.Element ce = e.body[j];
if (matchCase(e, ce.value) != ce) {
// Duplicate tag.
{ i = -i; break; } // fail
}
}
break;
case '(': // call: '(' '-'? digit+ ')'
kind = EK_CALL;
e.removeBand();
i = layout.indexOf(')', i);
String cstr = layout.substring(start+1, i++);
int offset = Integer.parseInt(cstr);
int target = curCble + offset;
if (!(offset+"").equals(cstr) ||
self.elems == null ||
target < 0 ||
target >= self.elems.length)
{ i = -i; continue; } // fail
Layout.Element ce = self.elems[target];
assert(ce.kind == EK_CBLE);
e.value = target;
e.body = new Layout.Element[]{ ce };
// Is it a (recursive) backward call?
if (offset <= 0) {
// Yes. Mark both caller and callee backward.
e.flags |= EF_BACK;
ce.flags |= EF_BACK;
}
break;
case 'K': // reference_type: constant_ref
kind = EK_REF;
switch (layout.charAt(i++)) {
case 'I': e.refKind = CONSTANT_Integer; break;
case 'J': e.refKind = CONSTANT_Long; break;
case 'F': e.refKind = CONSTANT_Float; break;
case 'D': e.refKind = CONSTANT_Double; break;
case 'S': e.refKind = CONSTANT_String; break;
case 'Q': e.refKind = CONSTANT_Literal; break;
default: { i = -i; continue; } // fail
}
break;
case 'R': // schema_ref
kind = EK_REF;
switch (layout.charAt(i++)) {
case 'C': e.refKind = CONSTANT_Class; break;
case 'S': e.refKind = CONSTANT_Signature; break;
case 'D': e.refKind = CONSTANT_NameandType; break;
case 'F': e.refKind = CONSTANT_Fieldref; break;
case 'M': e.refKind = CONSTANT_Methodref; break;
case 'I': e.refKind = CONSTANT_InterfaceMethodref; break;
case 'U': e.refKind = CONSTANT_Utf8; break; //utf8_ref
case 'Q': e.refKind = CONSTANT_All; break; //untyped_ref
default: { i = -i; continue; } // fail
}
break;
default: { i = -i; continue; } // fail
}
// further parsing of refs
if (kind == EK_REF) {
// reference: reference_type -><- ( 'N' )? tokenizeUInt
if (layout.charAt(i++) == 'N') {
e.flags |= EF_NULL;
i++; // move forward
}
--i; // reparse
i = tokenizeUInt(e, layout, i);
self.hasRefs = true;
}
prevBCI = (kind == EK_BCI);
// store the new element
e.kind = kind;
e.layout = layout.substring(start, i);
col.add(e);
}
}
static //private
String[] splitBodies(String layout) {
ArrayList bodies = new ArrayList();
// Parse several independent layout bodies: "[foo][bar]...[baz]"
for (int i = 0; i < layout.length(); i++) {
if (layout.charAt(i++) != '[')
layout.charAt(-i); // throw error
int body;
i = skipBody(layout, body = i);
bodies.add(layout.substring(body, i));
}
String[] res = new String[bodies.size()];
bodies.toArray(res);
return res;
}
static private
int skipBody(String layout, int i) {
assert(layout.charAt(i-1) == '[');
if (layout.charAt(i) == ']')
// No empty bodies, please.
return -i;
// skip balanced [...[...]...]
for (int depth = 1; depth > 0; ) {
switch (layout.charAt(i++)) {
case '[': depth++; break;
case ']': depth--; break;
}
}
--i; // get before bracket
assert(layout.charAt(i) == ']');
return i; // return closing bracket
}
static private
int tokenizeUInt(Layout.Element e, String layout, int i) {
switch (layout.charAt(i++)) {
case 'V': e.len = 0; break;
case 'B': e.len = 1; break;
case 'H': e.len = 2; break;
case 'I': e.len = 4; break;
default: return -i;
}
return i;
}
static private
int tokenizeSInt(Layout.Element e, String layout, int i) {
if (layout.charAt(i) == 'S') {
e.flags |= EF_SIGN;
++i;
}
return tokenizeUInt(e, layout, i);
}
static private
boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
/** Find an occurrence of hyphen '-' between two numerals. */
static //private
int findCaseDash(String layout, int fromIndex) {
if (fromIndex <= 0) fromIndex = 1; // minimum dash pos
int lastDash = layout.length() - 2; // maximum dash pos
for (;;) {
int dash = layout.indexOf('-', fromIndex);
if (dash < 0 || dash > lastDash) return -1;
if (isDigit(layout.charAt(dash-1))) {
char afterDash = layout.charAt(dash+1);
if (afterDash == '-' && dash+2 < layout.length())
afterDash = layout.charAt(dash+2);
if (isDigit(afterDash)) {
// matched /[0-9]--?[0-9]/; return position of dash
return dash;
}
}
fromIndex = dash+1;
}
}
static
int parseIntBefore(String layout, int dash) {
int end = dash;
int beg = end;
while (beg > 0 && isDigit(layout.charAt(beg-1))) --beg;
if (beg == end) return Integer.parseInt("empty");
// skip backward over a sign
if (beg >= 1 && layout.charAt(beg-1) == '-') --beg;
assert(beg == 0 || !isDigit(layout.charAt(beg-1)));
return Integer.parseInt(layout.substring(beg, end));
}
static
int parseIntAfter(String layout, int dash) {
int beg = dash+1;
int end = beg;
int limit = layout.length();
if (end < limit && layout.charAt(end) == '-') ++end;
while (end < limit && isDigit(layout.charAt(end))) ++end;
if (beg == end) return Integer.parseInt("empty");
return Integer.parseInt(layout.substring(beg, end));
}
/** For compatibility with 1.5 pack, expand 1-5 into 1,2,3,4,5. */
static
String expandCaseDashNotation(String layout) {
int dash = findCaseDash(layout, 0);
if (dash < 0) return layout; // no dashes (the common case)
StringBuffer result = new StringBuffer(layout.length() * 3);
int sofar = 0; // how far have we processed the layout?
for (;;) {
// for each dash, collect everything up to the dash
result.append(layout.substring(sofar, dash));
sofar = dash+1; // skip the dash
// then collect intermediate values
int value0 = parseIntBefore(layout, dash);
int value1 = parseIntAfter(layout, dash);
assert(value0 < value1);
result.append(","); // close off value0 numeral
for (int i = value0+1; i < value1; i++) {
result.append(i);
result.append(","); // close off i numeral
}
dash = findCaseDash(layout, sofar);
if (dash < 0) break;
}
result.append(layout.substring(sofar)); // collect the rest
return result.toString();
}
static {
assert(expandCaseDashNotation("1-5").equals("1,2,3,4,5"));
assert(expandCaseDashNotation("-2--1").equals("-2,-1"));
assert(expandCaseDashNotation("-2-1").equals("-2,-1,0,1"));
assert(expandCaseDashNotation("-1-0").equals("-1,0"));
}
// Parse attribute bytes, putting values into bands. Returns new pos.
// Used when reading a class file (local refs resolved with local cpMap).
// Also used for ad hoc scanning.
static
int parseUsing(Layout.Element[] elems, Holder holder,
byte[] bytes, int pos, int len, ValueStream out) {
int prevBCI = 0;
int prevRBCI = 0;
int end = pos + len;
int[] buf = { 0 }; // for calls to parseInt, holds 2nd result
for (int i = 0; i < elems.length; i++) {
Layout.Element e = elems[i];
int bandIndex = e.bandIndex;
int value;
int BCI, RBCI;
switch (e.kind) {
case EK_INT:
pos = parseInt(e, bytes, pos, buf);
value = buf[0];
out.putInt(bandIndex, value);
break;
case EK_BCI: // PH, POH
pos = parseInt(e, bytes, pos, buf);
BCI = buf[0];
RBCI = out.encodeBCI(BCI);
if (!e.flagTest(EF_DELTA)) {
// PH: transmit R(bci), store bci
value = RBCI;
} else {
// POH: transmit D(R(bci)), store bci
value = RBCI - prevRBCI;
}
prevBCI = BCI;
prevRBCI = RBCI;
out.putInt(bandIndex, value);
break;
case EK_BCO: // OH
assert(e.flagTest(EF_DELTA));
// OH: transmit D(R(bci)), store D(bci)
pos = parseInt(e, bytes, pos, buf);
BCI = prevBCI + buf[0];
RBCI = out.encodeBCI(BCI);
value = RBCI - prevRBCI;
prevBCI = BCI;
prevRBCI = RBCI;
out.putInt(bandIndex, value);
break;
case EK_FLAG:
pos = parseInt(e, bytes, pos, buf);
value = buf[0];
out.putInt(bandIndex, value);
break;
case EK_REPL:
pos = parseInt(e, bytes, pos, buf);
value = buf[0];
out.putInt(bandIndex, value);
for (int j = 0; j < value; j++) {
pos = parseUsing(e.body, holder, bytes, pos, end-pos, out);
}
break; // already transmitted the scalar value
case EK_UN:
pos = parseInt(e, bytes, pos, buf);
value = buf[0];
out.putInt(bandIndex, value);
Layout.Element ce = matchCase(e, value);
pos = parseUsing(ce.body, holder, bytes, pos, end-pos, out);
break; // already transmitted the scalar value
case EK_CALL:
// Adjust band offset if it is a backward call.
assert(e.body.length == 1);
assert(e.body[0].kind == EK_CBLE);
if (e.flagTest(EF_BACK))
out.noteBackCall(e.value);
pos = parseUsing(e.body[0].body, holder, bytes, pos, end-pos, out);
break; // no additional scalar value to transmit
case EK_REF:
pos = parseInt(e, bytes, pos, buf);
int localRef = buf[0];
Entry globalRef;
if (localRef == 0) {
globalRef = null; // N.B. global null reference is -1
} else {
globalRef = holder.getCPMap()[localRef];
if (e.refKind == CONSTANT_Signature
&& globalRef.getTag() == CONSTANT_Utf8) {
// Cf. ClassReader.readSignatureRef.
String typeName = globalRef.stringValue();
globalRef = ConstantPool.getSignatureEntry(typeName);
} else if (e.refKind == CONSTANT_Literal) {
assert(globalRef.getTag() >= CONSTANT_Integer);
assert(globalRef.getTag() <= CONSTANT_String);
} else if (e.refKind != CONSTANT_All) {
assert(e.refKind == globalRef.getTag());
}
}
out.putRef(bandIndex, globalRef);
break;
default: assert(false); continue;
}
}
return pos;
}
static
Layout.Element matchCase(Layout.Element e, int value) {
assert(e.kind == EK_UN);
int lastj = e.body.length-1;
for (int j = 0; j < lastj; j++) {
Layout.Element ce = e.body[j];
assert(ce.kind == EK_CASE);
if (value == ce.value)
return ce;
}
return e.body[lastj];
}
static private
int parseInt(Layout.Element e, byte[] bytes, int pos, int[] buf) {
int value = 0;
int loBits = e.len * 8;
// Read in big-endian order:
for (int bitPos = loBits; (bitPos -= 8) >= 0; ) {
value += (bytes[pos++] & 0xFF) << bitPos;
}
if (loBits < 32 && e.flagTest(EF_SIGN)) {
// sign-extend subword value
int hiBits = 32 - loBits;
value = (value << hiBits) >> hiBits;
}
buf[0] = value;
return pos;
}
// Format attribute bytes, drawing values from bands.
// Used when emptying attribute bands into a package model.
// (At that point CP refs. are not yet assigned indexes.)
static
void unparseUsing(Layout.Element[] elems, Object[] fixups,
ValueStream in, ByteArrayOutputStream out) {
int prevBCI = 0;
int prevRBCI = 0;
for (int i = 0; i < elems.length; i++) {
Layout.Element e = elems[i];
int bandIndex = e.bandIndex;
int value;
int BCI, RBCI; // "RBCI" is R(BCI), BCI's coded representation
switch (e.kind) {
case EK_INT:
value = in.getInt(bandIndex);
unparseInt(e, value, out);
break;
case EK_BCI: // PH, POH
value = in.getInt(bandIndex);
if (!e.flagTest(EF_DELTA)) {
// PH: transmit R(bci), store bci
RBCI = value;
} else {
// POH: transmit D(R(bci)), store bci
RBCI = prevRBCI + value;
}
assert(prevBCI == in.decodeBCI(prevRBCI));
BCI = in.decodeBCI(RBCI);
unparseInt(e, BCI, out);
prevBCI = BCI;
prevRBCI = RBCI;
break;
case EK_BCO: // OH
value = in.getInt(bandIndex);
assert(e.flagTest(EF_DELTA));
// OH: transmit D(R(bci)), store D(bci)
assert(prevBCI == in.decodeBCI(prevRBCI));
RBCI = prevRBCI + value;
BCI = in.decodeBCI(RBCI);
unparseInt(e, BCI - prevBCI, out);
prevBCI = BCI;
prevRBCI = RBCI;
break;
case EK_FLAG:
value = in.getInt(bandIndex);
unparseInt(e, value, out);
break;
case EK_REPL:
value = in.getInt(bandIndex);
unparseInt(e, value, out);
for (int j = 0; j < value; j++) {
unparseUsing(e.body, fixups, in, out);
}
break;
case EK_UN:
value = in.getInt(bandIndex);
unparseInt(e, value, out);
Layout.Element ce = matchCase(e, value);
unparseUsing(ce.body, fixups, in, out);
break;
case EK_CALL:
assert(e.body.length == 1);
assert(e.body[0].kind == EK_CBLE);
unparseUsing(e.body[0].body, fixups, in, out);
break;
case EK_REF:
Entry globalRef = in.getRef(bandIndex);
int localRef;
if (globalRef != null) {
// It's a one-element array, really an lvalue.
fixups[0] = Fixups.add(fixups[0], null, out.size(),
Fixups.U2_FORMAT, globalRef);
localRef = 0; // placeholder for fixups
} else {
localRef = 0; // fixed null value
}
unparseInt(e, localRef, out);
break;
default: assert(false); continue;
}
}
}
static private
void unparseInt(Layout.Element e, int value, ByteArrayOutputStream out) {
int loBits = e.len * 8;
if (loBits == 0) {
// It is not stored at all ('V' layout).
return;
}
if (loBits < 32) {
int hiBits = 32 - loBits;
int codedValue;
if (e.flagTest(EF_SIGN))
codedValue = (value << hiBits) >> hiBits;
else
codedValue = (value << hiBits) >>> hiBits;
if (codedValue != value)
throw new InternalError("cannot code in "+e.len+" bytes: "+value);
}
// Write in big-endian order:
for (int bitPos = loBits; (bitPos -= 8) >= 0; ) {
out.write((byte)(value >>> bitPos));
}
}
/*
/// Testing.
public static void main(String av[]) {
int maxVal = 12;
int iters = 0;
boolean verbose;
int ap = 0;
while (ap < av.length) {
if (!av[ap].startsWith("-")) break;
if (av[ap].startsWith("-m"))
maxVal = Integer.parseInt(av[ap].substring(2));
else if (av[ap].startsWith("-i"))
iters = Integer.parseInt(av[ap].substring(2));
else
throw new RuntimeException("Bad option: "+av[ap]);
ap++;
}
verbose = (iters == 0);
if (iters <= 0) iters = 1;
if (ap == av.length) {
av = new String[] {
"HH", // ClassFile.version
"RUH", // SourceFile
"RCHRDNH", // EnclosingMethod
"KQH", // ConstantValue
"NH[RCH]", // Exceptions
"NH[PHH]", // LineNumberTable
"NH[PHOHRUHRSHH]", // LocalVariableTable
"NH[PHPOHIIH]", // CharacterRangeTable
"NH[PHHII]", // CoverageTable
"NH[RCHRCNHRUNHFH]", // InnerClasses
"HHNI[B]NH[PHPOHPOHRCNH]NH[RUHNI[B]]", // Code
"=AnnotationDefault",
// Like metadata, but with a compact tag set:
"[NH[(1)]]"
+"[NH[(2)]]"
+"[RSHNH[RUH(3)]]"
+"[TB(0,1,3)[KIH](2)[KDH](5)[KFH](4)[KJH](7)[RSH](8)[RSHRUH](9)[RUH](10)[(2)](6)[NH[(3)]]()[]]",
""
};
ap = 0;
}
final int[][] counts = new int[2][3]; // int bci ref
final Entry[] cpMap = new Entry[maxVal+1];
for (int i = 0; i < cpMap.length; i++) {
if (i == 0) continue; // 0 => null
cpMap[i] = ConstantPool.getLiteralEntry(new Integer(i));
}
Class cls = new Package().new Class("");
cls.cpMap = cpMap;
class TestValueStream extends ValueStream {
Random rand = new Random(0);
ArrayList history = new ArrayList();
int ckidx = 0;
int maxVal;
boolean verbose;
void reset() { history.clear(); ckidx = 0; }
public int getInt(int bandIndex) {
counts[0][0]++;
int value = rand.nextInt(maxVal+1);
history.add(new Integer(bandIndex));
history.add(new Integer(value));
return value;
}
public void putInt(int bandIndex, int token) {
counts[1][0]++;
if (verbose)
System.out.print(" "+bandIndex+":"+token);
// Make sure this put parallels a previous get:
int check0 = ((Integer)history.get(ckidx+0)).intValue();
int check1 = ((Integer)history.get(ckidx+1)).intValue();
if (check0 != bandIndex || check1 != token) {
if (!verbose)
System.out.println(history.subList(0, ckidx));
System.out.println(" *** Should be "+check0+":"+check1);
throw new RuntimeException("Failed test!");
}
ckidx += 2;
}
public Entry getRef(int bandIndex) {
counts[0][2]++;
int value = getInt(bandIndex);
if (value < 0 || value > maxVal) {
System.out.println(" *** Unexpected ref code "+value);
return ConstantPool.getLiteralEntry(new Integer(value));
}
return cpMap[value];
}
public void putRef(int bandIndex, Entry ref) {
counts[1][2]++;
if (ref == null) {
putInt(bandIndex, 0);
return;
}
Number refValue = null;
if (ref instanceof ConstantPool.NumberEntry)
refValue = ((ConstantPool.NumberEntry)ref).numberValue();
int value;
if (!(refValue instanceof Integer)) {
System.out.println(" *** Unexpected ref "+ref);
value = -1;
} else {
value = ((Integer)refValue).intValue();
}
putInt(bandIndex, value);
}
public int encodeBCI(int bci) {
counts[1][1]++;
// move LSB to MSB of low byte
int code = (bci >> 8) << 8; // keep high bits
code += (bci & 0xFE) >> 1;
code += (bci & 0x01) << 7;
return code ^ (8<<8); // mark it clearly as coded
}
public int decodeBCI(int bciCode) {
counts[0][1]++;
bciCode ^= (8<<8); // remove extra mark
int bci = (bciCode >> 8) << 8; // keep high bits
bci += (bciCode & 0x7F) << 1;
bci += (bciCode & 0x80) >> 7;
return bci;
}
}
TestValueStream tts = new TestValueStream();
tts.maxVal = maxVal;
tts.verbose = verbose;
ByteArrayOutputStream buf = new ByteArrayOutputStream();
for (int i = 0; i < (1 << 30); i = (i + 1) * 5) {
int ei = tts.encodeBCI(i);
int di = tts.decodeBCI(ei);
if (di != i) System.out.println("i="+Integer.toHexString(i)+
" ei="+Integer.toHexString(ei)+
" di="+Integer.toHexString(di));
}
while (iters-- > 0) {
for (int i = ap; i < av.length; i++) {
String layout = av[i];
if (layout.startsWith("=")) {
String name = layout.substring(1);
for (Iterator j = standardDefs.values().iterator(); j.hasNext(); ) {
Attribute a = (Attribute) j.next();
if (a.name().equals(name)) {
layout = a.layout().layout();
break;
}
}
if (layout.startsWith("=")) {
System.out.println("Could not find "+name+" in "+standardDefs.values());
}
}
Layout self = new Layout(0, "Foo", layout);
if (verbose) {
System.out.print("/"+layout+"/ => ");
System.out.println(Arrays.asList(self.elems));
}
buf.reset();
tts.reset();
Object fixups = self.unparse(tts, buf);
byte[] bytes = buf.toByteArray();
// Attach the references to the byte array.
Fixups.setBytes(fixups, bytes);
// Patch the references to their frozen values.
Fixups.finishRefs(fixups, bytes, new Index("test", cpMap));
if (verbose) {
System.out.print(" bytes: {");
for (int j = 0; j < bytes.length; j++) {
System.out.print(" "+bytes[j]);
}
System.out.println("}");
}
if (verbose) {
System.out.print(" parse: {");
}
self.parse(0, cls, bytes, 0, bytes.length, tts);
if (verbose) {
System.out.println("}");
}
}
}
for (int j = 0; j <= 1; j++) {
System.out.print("values "+(j==0?"read":"written")+": {");
for (int k = 0; k < counts[j].length; k++) {
System.out.print(" "+counts[j][k]);
}
System.out.println(" }");
}
}
//*/
}