/*
* Copyright 2004-2010 Brian S O'Neill
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.cojen.classfile.attribute;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.cojen.classfile.Attribute;
import org.cojen.classfile.ConstantPool;
import org.cojen.classfile.FixedLocation;
import org.cojen.classfile.LocalVariable;
import org.cojen.classfile.Location;
import org.cojen.classfile.LocationRange;
import org.cojen.classfile.LocationRangeImpl;
import org.cojen.classfile.TypeDesc;
import org.cojen.classfile.constant.ConstantUTFInfo;
/**
* This class corresponds to the LocalVariableTable_attribute structure as
* defined in section 4.7.7 of <i>The Java Virtual Machine Specification</i>.
*
* @author Brian S O'Neill
*/
public class LocalVariableTableAttr extends Attribute {
private List<Entry> mEntries = new ArrayList<Entry>(10);
private List<Entry> mCleanEntries;
private int mRangeCount;
public LocalVariableTableAttr(ConstantPool cp) {
super(cp, LOCAL_VARIABLE_TABLE);
}
public LocalVariableTableAttr(ConstantPool cp, String name) {
super(cp, name);
}
public LocalVariableTableAttr(ConstantPool cp, String name, int length, DataInput din)
throws IOException
{
super(cp, name);
int size = din.readUnsignedShort();
for (int i=0; i<size; i++) {
int start_pc = din.readUnsignedShort();
int end_pc = start_pc + din.readUnsignedShort() + 1;
int name_index = din.readUnsignedShort();
int descriptor_index = din.readUnsignedShort();
final int index = din.readUnsignedShort();
final ConstantUTFInfo varName = (ConstantUTFInfo)cp.getConstant(name_index);
final ConstantUTFInfo varDesc = (ConstantUTFInfo)cp.getConstant(descriptor_index);
if (varDesc == null) {
continue;
}
final Location startLocation = new FixedLocation(start_pc);
final Location endLocation = new FixedLocation(end_pc);
final Set<LocationRange> ranges = Collections
.singleton((LocationRange) new LocationRangeImpl(startLocation, endLocation));
LocalVariable localVar = new LocalVariable() {
private String mName;
private TypeDesc mType;
{
mName = varName == null ? null : varName.getValue();
mType = TypeDesc.forDescriptor(varDesc.getValue());
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
public TypeDesc getType() {
return mType;
}
public boolean isDoubleWord() {
return mType.isDoubleWord();
}
public int getNumber() {
return index;
}
public Set<LocationRange> getLocationRangeSet() {
return ranges;
}
};
mEntries.add(new Entry(localVar, varName, varDesc));
}
}
public LocalVariable getLocalVariable(Location useLocation, int number) {
return getLocalVariable(useLocation.getLocation(), number);
}
public LocalVariable getLocalVariable(int useLocation, int number) {
// TODO: Build some sort of index to improve performance.
for (Entry entry : mEntries) {
LocalVariable var = entry.mLocalVar;
if (var.getNumber() == number) {
for (LocationRange range : var.getLocationRangeSet()) {
int start = range.getStartLocation().getLocation();
int end = range.getEndLocation().getLocation();
if (start >= 0 && end >= 0) {
if (start <= useLocation && useLocation < end) {
return var;
}
}
}
}
}
return null;
}
/**
* Add an entry into the LocalVariableTableAttr.
*/
public void addEntry(LocalVariable localVar) {
String varName = localVar.getName();
if (varName == null) {
int num = localVar.getNumber();
varName = num < 0 ? "_" : ("v" + num + '$');
}
ConstantUTFInfo name = getConstantPool().addConstantUTF(varName);
ConstantUTFInfo descriptor =
getConstantPool().addConstantUTF(localVar.getType().getDescriptor());
mEntries.add(new Entry(localVar, name, descriptor));
mCleanEntries = null;
}
public int getLength() {
clean();
return 2 + 10 * mRangeCount;
}
public void writeDataTo(DataOutput dout) throws IOException {
dout.writeShort(mRangeCount);
int size = mCleanEntries.size();
for (int i=0; i<size; i++) {
Entry entry = mCleanEntries.get(i);
LocalVariable localVar = entry.mLocalVar;
Set<LocationRange> ranges = localVar.getLocationRangeSet();
if (ranges == null) {
continue;
}
int name_index = entry.mName.getIndex();
int descriptor_index = entry.mDescriptor.getIndex();
int index = localVar.getNumber();
check("local variable table entry name index", name_index);
check("local variable table entry descriptor index", descriptor_index);
check("local variable table entry index", index);
for (LocationRange range : ranges) {
Location startLocation = range.getStartLocation();
Location endLocation = range.getEndLocation();
int start_pc = startLocation.getLocation();
int length = endLocation.getLocation() - start_pc - 1;
check("local variable table entry start PC", start_pc);
dout.writeShort(start_pc);
dout.writeShort(length);
dout.writeShort(name_index);
dout.writeShort(descriptor_index);
dout.writeShort(index);
}
}
}
private void check(String type, int addr) throws IllegalStateException {
if (addr < 0 || addr > 65535) {
throw new IllegalStateException("Value for " + type + " out of " +
"valid range: " + addr);
}
}
private void clean() {
if (mCleanEntries != null) {
return;
}
// Clean out entries that are incomplete or bogus.
int size = mEntries.size();
mCleanEntries = new ArrayList<Entry>(size);
mRangeCount = 0;
outer: for (int i=0; i<size; i++) {
Entry entry = mEntries.get(i);
LocalVariable localVar = entry.mLocalVar;
Set<LocationRange> ranges = localVar.getLocationRangeSet();
if (ranges == null || ranges.size() == 0) {
continue;
}
for (LocationRange range : ranges) {
Location startLocation = range.getStartLocation();
Location endLocation = range.getEndLocation();
if (startLocation == null || endLocation == null) {
continue outer;
}
int start_pc = startLocation.getLocation();
int length = endLocation.getLocation() - start_pc - 1;
if (length < 0) {
continue outer;
}
}
mCleanEntries.add(entry);
mRangeCount += entry.getRangeCount();
}
}
private static class Entry {
public LocalVariable mLocalVar;
public ConstantUTFInfo mName;
public ConstantUTFInfo mDescriptor;
public Entry(LocalVariable localVar,
ConstantUTFInfo name, ConstantUTFInfo descriptor) {
mLocalVar = localVar;
mName = name;
mDescriptor = descriptor;
}
public int getRangeCount() {
return mLocalVar.getLocationRangeSet().size();
}
}
}