blob: 62dd5543140e115bdf45c491082eac8a8c842877 [file] [log] [blame]
/* Copyright (c) 2006-2009 Jan S. Rellermeyer
* Systems Group,
* Department of Computer Science, ETH Zurich.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of ETH Zurich nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package ch.ethz.iks.r_osgi.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import ch.ethz.iks.r_osgi.messages.DeliverServiceMessage;
import ch.ethz.iks.util.StringUtils;
/**
* <p>
* The code analyzer takes a service interface together with additionally
* registered parts (like smart proxies) and determines the injection set by
* static code analysis.
* </p>
* <p>
* The idea is to inject all types that are referenced by the service interface
* so that the proxy becomes self-contained. However, at the current state, only
* own types are injected. Imported types are not, since they are considered to
* belong to the required environment.
* <p>
* <p>
* For the moment, the code analysis does not cover accessed resources from the
* bundle. In later revisions, it is likely that this will be introduced.
* </p>
*
* @author Jan S. Rellermeyer, ETH Zurich
* @since 0.6
*/
final class CodeAnalyzer implements ClassVisitor {
/**
* the class loader of the service provider bundle.
*/
private final ClassLoader loader;
/**
* the closure list.
*/
private final ArrayList closure = new ArrayList();
/**
* the set of already visited types.
*/
final HashSet visited = new HashSet();
/**
* the injection set.
*/
private final HashMap injections = new HashMap();
/**
* import map.
*/
private final HashMap importsMap;
/**
* export map.
*/
private final HashMap exportsMap;
/**
* imports of the proxy. Is generated during code analyis.
*/
private final HashSet proxyImports = new HashSet(0);
/**
* exports of the proxy. Is generated during code analysis.
*/
private final HashSet proxyExports = new HashSet(0);
/**
* the method analyzer.
*/
private final MethodVisitor methodVisitor = new MethodAnalyzer();
private String currentClass;
/**
* create a new code analyzer instance.
*
* @param loader
* the class loader of the service bundle.
* @param imports
* the imports of the service bundle.
* @param exports
* the exports of the service bundle.
*/
CodeAnalyzer(final ClassLoader loader, final String imports,
final String exports) {
this.loader = loader;
if (imports != null) {
final String[] tokens = StringUtils.stringToArray(imports, ","); //$NON-NLS-1$
importsMap = new HashMap(tokens.length);
for (int i = 0; i < tokens.length; i++) {
final int pos = tokens[i].indexOf(";"); //$NON-NLS-1$
if (pos > -1) {
importsMap.put(tokens[i].substring(0, pos), tokens[i]
.substring(pos + 1, tokens[i].length()));
} else {
importsMap.put(tokens[i], null);
}
}
} else {
importsMap = new HashMap(0);
}
if (exports != null) {
final String[] tokens = StringUtils.stringToArray(exports, ","); //$NON-NLS-1$
exportsMap = new HashMap(tokens.length);
for (int i = 0; i < tokens.length; i++) {
final int pos = tokens[i].indexOf(";"); //$NON-NLS-1$
if (pos > -1) {
exportsMap.put(tokens[i].substring(0, pos), tokens[i]
.substring(pos + 1, tokens[i].length()));
} else {
exportsMap.put(tokens[i], null);
}
}
} else {
exportsMap = new HashMap(0);
}
}
/**
* analyze the service.
*
* @param iface
* the service interface class name.
* @param smartProxy
* the smart proxy class name or <code>null</code>
* @param explicitInjections
* the injection array or <code>null</code>
* @param presentation
* the presentation class name or <code>null</code>
* @return the <code>DeliverServiceMessage</code> that contains all
* information required to build a proxy on the client side. This
* message is generic, can be cached and initialized for a concrete
* requesting peer by calling the <code>init</code> method.
* @throws ClassNotFoundException
* @throws IOException
*/
DeliverServiceMessage analyze(final String[] ifaces,
final String smartProxy, final String[] explicitInjections,
final String presentation) throws ClassNotFoundException,
IOException {
closure.addAll(Arrays.asList(ifaces));
if (smartProxy != null) {
closure.add(smartProxy);
}
if (presentation != null) {
closure.add(presentation);
}
if (explicitInjections != null) {
closure.addAll(Arrays.asList(explicitInjections));
}
while (!closure.isEmpty()) {
visit((String) closure.remove(0));
}
for (int i = 0; i < ifaces.length; i++) {
proxyImports.add(packageOf(ifaces[i]));
proxyExports.add(packageOf(ifaces[i]));
}
// remove the obvious imports to save network bandwidth
proxyImports.remove("org.osgi.framework"); //$NON-NLS-1$
proxyImports.remove("ch.ethz.iks.r_osgi"); //$NON-NLS-1$
proxyImports.remove("ch.ethz.iks.r_osgi.types"); //$NON-NLS-1$
proxyImports.remove("ch.ethz.iks.r_osgi.channels"); //$NON-NLS-1$
final StringBuffer importDeclaration = new StringBuffer();
final StringBuffer exportDeclaration = new StringBuffer();
final String[] pi = (String[]) proxyImports
.toArray(new String[proxyImports.size()]);
for (int i = 0; i < pi.length; i++) {
importDeclaration.append(pi[i]);
final Object v = importsMap.get(pi[i]);
if (v != null) {
importDeclaration.append("; "); //$NON-NLS-1$
importDeclaration.append(v);
}
if (i < pi.length - 1) {
importDeclaration.append(", "); //$NON-NLS-1$
}
}
final String[] pe = (String[]) proxyExports
.toArray(new String[proxyExports.size()]);
for (int i = 0; i < pe.length; i++) {
exportDeclaration.append(pe[i]);
final Object v = exportsMap.get(pe[i]);
if (v != null) {
exportDeclaration.append("; "); //$NON-NLS-1$
exportDeclaration.append(v);
}
if (i < pe.length - 1) {
exportDeclaration.append(", "); //$NON-NLS-1$
}
}
final DeliverServiceMessage message = new DeliverServiceMessage();
message.setInterfaceNames(ifaces);
message.setSmartProxyName(smartProxy);
message.setInjections((HashMap) injections.clone());
message.setImports(importDeclaration.toString());
message.setExports(exportDeclaration.toString());
visited.clear();
injections.clear();
closure.clear();
return message;
}
/**
* visit a class.
*
* @param className
* the class name.
* @throws ClassNotFoundException
* if the class is unknown.
* @throws IOException
* if the classes bytecode cannot be found and accessed.
*/
private void visit(final String className) throws ClassNotFoundException {
currentClass = className.replace('.', '/');
// remove array indicators
if(currentClass.startsWith("[L")) {
currentClass = currentClass.substring(2);
} else if (currentClass.startsWith("L")) {
currentClass = currentClass.substring(1);
}
final String classFile = currentClass + ".class"; //$NON-NLS-1$
final String pkg = packageOf(className);
if (importsMap.containsKey(pkg) || exportsMap.containsKey(pkg)) {
proxyExports.add(pkg);
}
try {
final ClassReader reader = new ClassReader(loader
.getResourceAsStream(classFile));
injections.put(classFile, reader.b);
if (exportsMap.containsKey(pkg)) {
proxyExports.add(pkg);
}
reader.accept(this, ClassReader.SKIP_DEBUG
+ ClassReader.SKIP_FRAMES);
} catch (final IOException ioe) {
throw new ClassNotFoundException(className);
}
}
/**
* get the package of a class.
*
* @param cls
* the class.
* @return the package name.
*/
private String packageOf(final String cls) {
final int p = cls.lastIndexOf("."); //$NON-NLS-1$
return p > -1 ? cls.substring(0, p).trim() : ""; //$NON-NLS-1$
}
/**
* visit a type.
*
* @param t
* the type.
*/
void visitType(final Type t) {
if (t.getSort() < Type.ARRAY) {
visited.add(t.getClassName());
return;
}
if (t.getSort() == Type.ARRAY) {
visitType(t.getElementType());
return;
}
if ("null".equals(t.getClassName())) { //$NON-NLS-1$
return;
}
final String className = t.getClassName();
final String iClassName = className.replace('.', '/');
if (visited.contains(iClassName)) {
return;
}
// do not inject java* classes
if (className.startsWith("java")) { //$NON-NLS-1$
visited.add(iClassName);
return;
}
final String pkg = packageOf(className);
if (importsMap.containsKey(pkg)) {
visited.add(iClassName);
proxyImports.add(pkg);
return;
}
// do not inject osgi classes
if (className.startsWith("org.osgi")) { //$NON-NLS-1$
visited.add(iClassName);
return;
}
visited.add(iClassName);
closure.add(className);
}
/**
*
* @see org.objectweb.asm.ClassVisitor#visit(int, int, java.lang.String,
* java.lang.String, java.lang.String, java.lang.String[])
*/
public void visit(final int version, final int access, final String name,
final String signature, final String superName,
final String[] interfaces) {
if (superName != null && !visited.contains(superName)) {
visitType(Type.getType('L' + superName + ';'));
}
for (int i = 0; i < interfaces.length; i++) {
if (!visited.contains(interfaces[i])) {
visitType(Type.getType('L' + interfaces[i] + ';'));
}
}
}
/**
*
* @see org.objectweb.asm.ClassVisitor#visitAnnotation(java.lang.String,
* boolean)
*/
public AnnotationVisitor visitAnnotation(final String desc,
final boolean visible) {
return null;
}
/**
*
* @see org.objectweb.asm.ClassVisitor#visitAttribute(org.objectweb.asm.Attribute)
*/
public void visitAttribute(final Attribute attr) {
}
/**
*
* @see org.objectweb.asm.ClassVisitor#visitEnd()
*/
public void visitEnd() {
}
/**
*
* @see org.objectweb.asm.ClassVisitor#visitField(int, java.lang.String,
* java.lang.String, java.lang.String, java.lang.Object)
*/
public FieldVisitor visitField(final int access, final String name,
final String desc, final String signature, final Object value) {
if (!visited.contains(desc)) {
visitType(Type.getType(desc));
}
return null;
}
/**
*
* @see org.objectweb.asm.ClassVisitor#visitInnerClass(java.lang.String,
* java.lang.String, java.lang.String, int)
*/
public void visitInnerClass(final String name, final String outerName,
final String innerName, final int access) {
if (!name.equals(currentClass) && !visited.contains(currentClass)) {
closure.add(name.replace('/', '.'));
}
}
/**
*
* @see org.objectweb.asm.ClassVisitor#visitMethod(int, java.lang.String,
* java.lang.String, java.lang.String, java.lang.String[])
*/
public MethodVisitor visitMethod(final int access, final String name,
final String desc, final String signature, final String[] exceptions) {
final Type[] methodTypes = Type.getArgumentTypes(desc);
for (int i = 0; i < methodTypes.length; i++) {
if (!visited.contains(methodTypes[i].getClassName().replace('.',
'/'))) {
visitType(methodTypes[i]);
}
}
final Type returnType = Type.getReturnType(desc);
if (!visited.contains(returnType.getClassName().replace('.', '/'))) {
visitType(returnType);
}
if (exceptions != null) {
for (int i = 0; i < exceptions.length; i++) {
if (!visited.contains(exceptions[i])) {
visitType(Type.getType('L' + exceptions[i] + ';'));
}
}
}
if ((access & Opcodes.ACC_ABSTRACT) == 0) {
return methodVisitor;
} else {
return null;
}
}
/**
*
* @see org.objectweb.asm.ClassVisitor#visitOuterClass(java.lang.String,
* java.lang.String, java.lang.String)
*/
public void visitOuterClass(final String owner, final String name,
final String desc) {
}
/**
*
* @see org.objectweb.asm.ClassVisitor#visitSource(java.lang.String,
* java.lang.String)
*/
public void visitSource(final String source, final String debug) {
}
/**
* the method analyzer.
*
* @author Jan S. Rellermeyer, ETH Zurich
*/
final class MethodAnalyzer implements MethodVisitor {
/**
*
* @see org.objectweb.asm.MethodVisitor#visitAnnotation(java.lang.String,
* boolean)
*/
public AnnotationVisitor visitAnnotation(final String desc,
final boolean visible) {
if (!visited.contains(desc)) {
visitType(Type.getType("L" + desc + ";")); //$NON-NLS-1$ //$NON-NLS-2$
}
return null;
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitAnnotationDefault()
*/
public AnnotationVisitor visitAnnotationDefault() {
return null;
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitAttribute(org.objectweb.asm.Attribute)
*/
public void visitAttribute(final Attribute attr) {
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitCode()
*/
public void visitCode() {
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitEnd()
*/
public void visitEnd() {
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitFieldInsn(int,
* java.lang.String, java.lang.String, java.lang.String)
*/
public void visitFieldInsn(final int opcode, final String owner,
final String name, final String desc) {
if (!visited.contains(owner)) {
visitType(Type.getType("L" + owner + ";")); //$NON-NLS-1$ //$NON-NLS-2$
}
if (!visited.contains(desc)) {
visitType(Type.getType(desc)); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitIincInsn(int, int)
*/
public void visitIincInsn(final int var, final int increment) {
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitInsn(int)
*/
public void visitInsn(final int opcode) {
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitIntInsn(int, int)
*/
public void visitIntInsn(final int opcode, final int operand) {
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitJumpInsn(int,
* org.objectweb.asm.Label)
*/
public void visitJumpInsn(final int opcode, final Label label) {
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitLabel(org.objectweb.asm.Label)
*/
public void visitLabel(final Label label) {
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitLdcInsn(java.lang.Object)
*/
public void visitLdcInsn(final Object cst) {
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitLineNumber(int,
* org.objectweb.asm.Label)
*/
public void visitLineNumber(final int line, final Label start) {
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitLocalVariable(java.lang.String,
* java.lang.String, java.lang.String, org.objectweb.asm.Label,
* org.objectweb.asm.Label, int)
*/
public void visitLocalVariable(final String name, final String desc,
final String signature, final Label start, final Label end,
final int index) {
if (!visited.contains(desc)) {
visitType(Type.getType("L" + desc + ";")); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitLookupSwitchInsn(org.objectweb.asm.Label,
* int[], org.objectweb.asm.Label[])
*/
public void visitLookupSwitchInsn(final Label dflt, final int[] keys,
final Label[] labels) {
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitMaxs(int, int)
*/
public void visitMaxs(final int maxStack, final int maxLocals) {
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitMethodInsn(int,
* java.lang.String, java.lang.String, java.lang.String)
*/
public void visitMethodInsn(final int opcode, final String owner,
final String name, final String desc) {
if (!visited.contains(owner)) {
visitType(Type.getType("L" + owner + ";")); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitMultiANewArrayInsn(java.lang.String,
* int)
*/
public void visitMultiANewArrayInsn(final String desc, final int dims) {
if (!visited.contains(desc)) {
visitType(Type.getType("L" + desc + ";")); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitParameterAnnotation(int,
* java.lang.String, boolean)
*/
public AnnotationVisitor visitParameterAnnotation(final int parameter,
final String desc, final boolean visible) {
if (!visited.contains(desc)) {
visitType(Type.getType("L" + desc + ";")); //$NON-NLS-1$ //$NON-NLS-2$
}
return null;
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitTableSwitchInsn(int, int,
* org.objectweb.asm.Label, org.objectweb.asm.Label[])
*/
public void visitTableSwitchInsn(final int min, final int max,
final Label dflt, final Label[] labels) {
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitTryCatchBlock(org.objectweb.asm.Label,
* org.objectweb.asm.Label, org.objectweb.asm.Label,
* java.lang.String)
*/
public void visitTryCatchBlock(final Label start, final Label end,
final Label handler, final String type) {
if (!visited.contains(type)) {
visitType(Type.getType("L" + type + ";")); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitTypeInsn(int,
* java.lang.String)
*/
public void visitTypeInsn(final int opcode, final String desc) {
if (!visited.contains(desc)) {
visitType(Type.getType("L" + desc + ";")); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitVarInsn(int, int)
*/
public void visitVarInsn(final int opcode, final int var) {
}
/**
*
* @see org.objectweb.asm.MethodVisitor#visitFrame(int, int,
* java.lang.Object[], int, java.lang.Object[])
*/
public void visitFrame(final int arg0, final int arg1,
final Object[] arg2, final int arg3, final Object[] arg4) {
}
}
}