blob: c7859e34483a246b3894a00bb2e6e623f56c4f7e [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Dynamic Runtime Environment"
*
* Copyright 2009, 2014 Oliver Frank and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0 *
* Please visit http://www.eclipse.org/objectteams for updates and contact.
*
* Contributors:
* Oliver Frank - Initial API and implementation
* Stephan Herrmann - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.otredyn.bytecode.asm;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.eclipse.objectteams.otredyn.bytecode.AbstractBoundClass;
import org.eclipse.objectteams.otredyn.bytecode.AbstractTeam;
import org.eclipse.objectteams.otredyn.bytecode.IBytecodeProvider;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.eclipse.jdt.annotation.*;
/**
* This class implements the bytecode parsing for {@link AbstractBoundClass}.
* It parses the bytecode with ASM.
* @author Oliver Frank
*/
public abstract class AsmBoundClass extends AbstractTeam {
public static final int ASM_API = Opcodes.ASM7;
private static final int DEFAULT_BUFFER_SIZE = 8192;
private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
private IBytecodeProvider bytecodeProvider;
/**
* just a temporary cache for the bytecode
*/
private byte[] bytecode;
/**
* ordered lists of qualified callin labels
*/
public List<String[]> precedenceses = new ArrayList<String[]>();
/**
* Set of base classes to which the current class or one of its roles as playedBy bindings.
* Qualified class names are '.' separated.
*/
public Set<@NonNull String> boundBaseClasses;
protected AsmBoundClass(@NonNull String name, String id, IBytecodeProvider bytecodeProvider, ClassLoader loader) {
super(name, id, loader);
this.bytecodeProvider = bytecodeProvider;
}
/**
* Parses the bytecode of a class and uses the set/add... Methods (e.g. addMethod)
* of {@link AbstractBoundClass} to set the information
*/
@Override
public synchronized void parseBytecode() {
if (parsed) {
// Already parsed, nothing to do
return;
}
bytecode = bytecodeProvider.getBytecode(getId());
if (bytecode == null) {
if (this.loader != null) {
try (InputStream stream = this.loader.getResourceAsStream(this.getInternalName()+".class")) {
if (stream != null) {
bytecode = readAllBytes(stream);
bytecodeProvider.setBytecode(getId(), bytecode);
}
} catch (IOException e) {
// silent (from automatic close()).
}
}
}
if (bytecode == null) {
//Class is not loaded yet.
return;
}
// Don't parse another time
parsed = true;
AsmClassVisitor cv = new AsmClassVisitor(this);
ClassReader cr = null;
cr = new ClassReader(bytecode);
cr.accept(cv, Attributes.attributes, 0);
//release the bytecode
bytecode = null;
}
public byte[] readAllBytes(InputStream is) throws IOException {
byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
int capacity = buf.length;
int nread = 0;
int n;
for (;;) {
while ((n = is.read(buf, nread, capacity - nread)) > 0)
nread += n;
if (n < 0)
break;
if (capacity <= MAX_BUFFER_SIZE - capacity) {
capacity = capacity << 1;
} else {
if (capacity == MAX_BUFFER_SIZE)
throw new OutOfMemoryError("Requested size too large");
capacity = MAX_BUFFER_SIZE;
}
buf = Arrays.copyOf(buf, capacity);
}
return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
}
@Override
public Collection<@NonNull String> getBoundBaseClasses() {
return this.boundBaseClasses;
}
/**
* Returns the bytecode of this class and cache it temporary.
* This method is only needed, if getBytecode of the {@link IBytecodeProvider}
* is an expensive operation.
* @return
*/
protected byte[] allocateAndGetBytecode() {
if (bytecode == null) {
bytecode = getBytecode();
}
return bytecode;
}
/**
* Get the bytecode directly from the {@link IBytecodeProvider}.
* This method can be used, if getBytecode of the {@link IBytecodeProvider}
* is not an expensive operation.
* @return
*/
@Override
public byte[] getBytecode() {
return bytecodeProvider.getBytecode(getId());
}
/**
* Releases the bytecode, if it's cached, an set it in the {@link IBytecodeProvider}
*/
protected void releaseBytecode() {
bytecodeProvider.setBytecode(getId(), bytecode);
bytecode = null;
}
/**
* Returns the {@link IBytecodeProvider} used for this class
* @return
*/
protected IBytecodeProvider getBytecodeProvider() {
return bytecodeProvider;
}
/**
* Sets the bytecode.
* If the bytecode is temporary cached, the cache is used.
* Otherwise this method give the bytecode directly to the {@link IBytecodeProvider}
* @param bytecode
*/
protected void setBytecode(byte[] bytecode) {
//Is the bytecode temporary cached?
if (this.bytecode == null) {
// no, then save the bytecode directly in the bytecode provider
bytecodeProvider.setBytecode(getId(), bytecode);
} else {
// yes, then use the cache
this.bytecode = bytecode;
}
}
public int compare(String callinLabel1, String callinLabel2) {
for (String[] precedences : this.precedenceses) {
boolean label1Seen = false, label2Seen = false;
for (String label : precedences) {
if (label.equals(callinLabel1)) {
if (label2Seen)
return -1; // saw two then one: one has lower priority than two
label1Seen = true;
} else if (label.equals(callinLabel2)) {
if (label1Seen)
return 1; // saw one then two: one has higher priority than two
label2Seen = true;
}
}
}
AbstractBoundClass enclosingClass = getEnclosingClass();
if (enclosingClass != null) {
String singleName = getInternalName();
int pos = singleName.lastIndexOf('$');
singleName = singleName.substring(pos+1);
if (singleName.startsWith("__OT__"))
singleName = singleName.substring("__OT__".length());
// check for precedence at outer level:
return enclosingClass.compare(singleName+'.'+callinLabel1, singleName+'.'+callinLabel2);
}
return callinLabel1.compareTo(callinLabel2);
}
}