Skip to content

Commit

Permalink
WELD-1853 Print member line on Definition/Deployment exception if pos…
Browse files Browse the repository at this point in the history
…sible
  • Loading branch information
mkouba authored and jharting committed May 25, 2015
1 parent 9c70a0f commit cc7512f
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 10 deletions.
5 changes: 3 additions & 2 deletions impl/src/main/java/org/jboss/weld/bootstrap/Validator.java
Expand Up @@ -101,6 +101,7 @@
import org.jboss.weld.logging.ValidatorLogger;
import org.jboss.weld.manager.BeanManagerImpl;
import org.jboss.weld.metadata.cache.MetaAnnotationStore;
import org.jboss.weld.resources.spi.ResourceLoader;
import org.jboss.weld.util.AnnotatedTypes;
import org.jboss.weld.util.BeanMethods;
import org.jboss.weld.util.Beans;
Expand Down Expand Up @@ -360,15 +361,15 @@ public void validateInjectionPointForDeploymentProblems(InjectionPoint ij, Bean<
ij,
Formats.formatAnnotations(ij.getQualifiers()),
Formats.formatInjectionPointType(ij.getType()),
Formats.formatAsStackTraceElement(ij),
Formats.formatAsStackTraceElement(ij, beanManager.getServices().get(ResourceLoader.class)),
getUnsatisfiedDependenciesAdditionalInfo(ij, beanManager));
}
if (resolvedBeans.size() > 1) {
throw ValidatorLogger.LOG.injectionPointHasAmbiguousDependencies(
ij,
Formats.formatAnnotations(ij.getQualifiers()),
Formats.formatInjectionPointType(ij.getType()),
Formats.formatAsStackTraceElement(ij),
Formats.formatAsStackTraceElement(ij, beanManager.getServices().get(ResourceLoader.class)),
WeldCollections.toMultiRowString(resolvedBeans));
}
// Account for the case this is disabled decorator
Expand Down
125 changes: 117 additions & 8 deletions impl/src/main/java/org/jboss/weld/util/reflection/Formats.java
Expand Up @@ -16,12 +16,15 @@
*/
package org.jboss.weld.util.reflection;

import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand All @@ -33,7 +36,9 @@
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.InjectionPoint;

import org.jboss.classfilewriter.util.DescriptorUtils;
import org.jboss.weld.ejb.spi.BusinessInterfaceDescriptor;
import org.jboss.weld.resources.spi.ResourceLoader;

/**
* Utility class to produce friendly names e.g. for debugging
Expand All @@ -47,14 +52,28 @@ public class Formats {
private static final String SNAPSHOT = "SNAPSHOT";
private static final String NULL = "null";

private static final String BCEL_CLASS_PARSER_FQCN = "com.sun.org.apache.bcel.internal.classfile.ClassParser";
private static final String BCEL_JAVA_CLASS_FQCN = "com.sun.org.apache.bcel.internal.classfile.JavaClass";
private static final String BCEL_METHOD_FQCN = "com.sun.org.apache.bcel.internal.classfile.Method";
private static final String BCEL_LINE_NUMBER_TABLE_FQCN = "com.sun.org.apache.bcel.internal.classfile.LineNumberTable";
private static final String BCEL_M_PARSE = "parse";
private static final String BCEL_M_GET_METHODS = "getMethods";
private static final String BCEL_M_GET_LINE_NUMBER_TABLE = "getLineNumberTable";
private static final String BCEL_M_GET_SOURCE_LINE = "getSourceLine";
private static final String BCEL_M_GET_NAME = "getName";
private static final String BCEL_M_GET_MODIFIERS = "getModifiers";
private static final String BCEL_M_GET_SIGNATURE = "getSignature";

private static final String INIT_METHOD_NAME = "<init>";

private Formats() {
}

// see WELD-1454
public static String formatAsStackTraceElement(InjectionPoint ij) {
public static String formatAsStackTraceElement(InjectionPoint ij, ResourceLoader resourceLoader) {
Member member;
if (ij.getAnnotated() instanceof AnnotatedField) {
AnnotatedField annotatedField = (AnnotatedField) ij.getAnnotated();
AnnotatedField<?> annotatedField = (AnnotatedField<?>) ij.getAnnotated();
member = annotatedField.getJavaMember();
} else if (ij.getAnnotated() instanceof AnnotatedParameter<?>) {
AnnotatedParameter<?> annotatedParameter = (AnnotatedParameter<?>) ij.getAnnotated();
Expand All @@ -65,13 +84,103 @@ public static String formatAsStackTraceElement(InjectionPoint ij) {
return "-";
}
return member.getDeclaringClass().getName()
+ "." + (member instanceof Constructor<?> ? "<init>" : member.getName())
+ "(" + getFileName(member.getDeclaringClass()) + ":" + getLineNumber(member) + ")";
+ "." + (member instanceof Constructor<?> ? INIT_METHOD_NAME : member.getName())
+ "(" + getFileName(member.getDeclaringClass()) + ":" + getLineNumber(member, resourceLoader) + ")";
}

private static int getLineNumber(Member member) {
// TODO find the actual line number where the member is declared
return 0;
/**
* Try to get the line number associated with the given member.
*
* The reflection API does not expose such an info and so we need to analyse the bytecode. Unfortunately, it seems there is no way to get this kind of
* information for fields. Moreover, the <code>LineNumberTable</code> attribute is just optional, i.e. the compiler is not required to store this
* information at all. See also <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1">Java Virtual Machine Specification</a>
*
* Implementation note: it wouldn't be appropriate to add a bytecode scanning dependency just for this functionality, therefore Apache BCEL included in
* Oracle JDK 1.5+ and OpenJDK 1.6+ is used. Other JVMs should not crash as we only use it if it's on the classpath and by means of reflection calls.
*
* @param member
* @param resourceLoader
* @return the line number or 0 if it's not possible to find it
*/
public static int getLineNumber(Member member, ResourceLoader resourceLoader) {

if (!(member instanceof Method || member instanceof Constructor)) {
// We are not able to get this info for fields
return 0;
}

if (!Reflections.isClassLoadable(BCEL_JAVA_CLASS_FQCN, resourceLoader)) {
// Apache BCEL classes not on the classpath
return 0;
}

String classFile = member.getDeclaringClass().getName().replace('.', '/');
InputStream in = null;

try {
URL classFileUrl = resourceLoader.getResource(classFile + ".class");

if (classFileUrl == null) {
// The class file is not available
return 0;
}
in = classFileUrl.openStream();

Class<?> classParserClass = Reflections.loadClass(BCEL_CLASS_PARSER_FQCN, resourceLoader);
Class<?> javaClassClass = Reflections.loadClass(BCEL_JAVA_CLASS_FQCN, resourceLoader);
Class<?> methodClass = Reflections.loadClass(BCEL_METHOD_FQCN, resourceLoader);
Class<?> lntClass = Reflections.loadClass(BCEL_LINE_NUMBER_TABLE_FQCN, resourceLoader);

Object parser = classParserClass.getConstructor(InputStream.class, String.class).newInstance(in, classFile);
Object javaClass = classParserClass.getMethod(BCEL_M_PARSE).invoke(parser);

// First get all declared methods and constructors
// Note that in bytecode constructor is translated into a method
Object[] methods = (Object[]) javaClassClass.getMethod(BCEL_M_GET_METHODS).invoke(javaClass);
Object match = null;

String signature;
String name;
if (member instanceof Method) {
signature = DescriptorUtils.methodDescriptor((Method) member);
name = member.getName();
} else if (member instanceof Constructor) {
signature = DescriptorUtils.makeDescriptor((Constructor<?>) member);
name = INIT_METHOD_NAME;
} else {
return 0;
}

for (Object method : methods) {
// Matching method must have the same name, modifiers and signature
if (methodClass.getMethod(BCEL_M_GET_NAME).invoke(method).equals(name)
&& methodClass.getMethod(BCEL_M_GET_MODIFIERS).invoke(method).equals(member.getModifiers())
&& methodClass.getMethod(BCEL_M_GET_SIGNATURE).invoke(method).equals(signature)) {
match = method;
}
}
if (match != null) {
// If a method is found, try to obtain the optional LineNumberTable attribute
Object lineNumberTable = methodClass.getMethod(BCEL_M_GET_LINE_NUMBER_TABLE).invoke(match);
if (lineNumberTable != null) {
Integer line = (Integer) lntClass.getMethod(BCEL_M_GET_SOURCE_LINE, int.class).invoke(lineNumberTable, 0);
return line == -1 ? 0 : line;
}
}
// No suitable method found
return 0;

} catch (Throwable t) {
return 0;
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
return 0;
}
}
}
}

private static String getFileName(Class<?> clazz) {
Expand Down Expand Up @@ -142,7 +251,7 @@ private static <T> Function<T> commaDelimiterFunction() {

public static String formatInjectionPointType(Type type) {
if (type instanceof Class<?>) {
return ((Class) type).getSimpleName();
return ((Class<?>) type).getSimpleName();
} else {
return Formats.formatType(type);
}
Expand Down
@@ -0,0 +1,32 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.jboss.weld.tests.unit.util.reflection;

import javax.inject.Inject;

public class Bean extends Superbean {

@Inject
public Bean(Vanilla vanilla) {
// Dummy
}

@Inject
void init(@Small Vanilla vanilla) {
}

}
@@ -0,0 +1,91 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2015, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.jboss.weld.tests.unit.util.reflection;

import static org.junit.Assert.assertTrue;

import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.util.Arrays;
import java.util.List;

import javax.enterprise.inject.spi.BeanManager;

import org.jboss.weld.resources.ClassLoaderResourceLoader;
import org.jboss.weld.resources.spi.ResourceLoader;
import org.jboss.weld.security.GetDeclaredConstructorsAction;
import org.jboss.weld.security.GetDeclaredMethodsAction;
import org.jboss.weld.util.reflection.Formats;
import org.junit.Test;

/**
*
* @author Martin Kouba
*/
public class FormatsTest {

@Test
public void testGetLineNumber() throws NoSuchMethodException, SecurityException {

Class<Bean> beanClass = Bean.class;
ResourceLoader resourceLoader = new ClassLoaderResourceLoader(Bean.class.getClassLoader());

// Initializers
assertLineFound(findMethod(beanClass, "init", Vanilla.class), resourceLoader);
assertLineFound(findMethod(beanClass, "foo", Vanilla.class, BeanManager.class, List.class), resourceLoader);
// Constructors
assertLineFound(findConstructor(beanClass, Vanilla.class), resourceLoader);
}

void assertLineFound(Member member, ResourceLoader resourceLoader) {
int line = Formats.getLineNumber(member, resourceLoader);
// We can't test the exact line as it doesn't seem to be precise and portable
// E.g. for methods it's declaration line + 1 and for constructors it's declaration line
assertTrue("Line: " + line, line > 0);
// System.out.println(member + " :" + line);
}


Constructor<?> findConstructor(Class<?> javaClass, Class<?>... parameterTypes) {
Class<?> clazz = javaClass;
while (clazz != Object.class && clazz != null) {
for (Constructor<?> constructor : AccessController.doPrivileged(new GetDeclaredConstructorsAction(clazz))) {
if (Arrays.equals(constructor.getParameterTypes(), parameterTypes)) {
return constructor;
}
}
clazz = clazz.getSuperclass();
}
return null;
}

Method findMethod(Class<?> javaClass, String name, Class<?>... parameterTypes) {
Class<?> clazz = javaClass;
while (clazz != Object.class && clazz != null) {
for (Method method : AccessController.doPrivileged(new GetDeclaredMethodsAction(clazz))) {
if (method.getName().equals(name) && Arrays.equals(method.getParameterTypes(), parameterTypes)) {
return method;
}
}
clazz = clazz.getSuperclass();
}
return null;
}

}
@@ -0,0 +1,35 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.jboss.weld.tests.unit.util.reflection;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.inject.Qualifier;

@Target({ TYPE, METHOD, PARAMETER, FIELD })
@Retention(RUNTIME)
@Qualifier
public @interface Small {

}
@@ -0,0 +1,32 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.jboss.weld.tests.unit.util.reflection;

import java.util.List;

import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;

public class Superbean {

// Dummy comment
@Inject
void foo(@Small Vanilla vanilla, BeanManager beanManager, List<String> list) {
return;
}

}

0 comments on commit cc7512f

Please sign in to comment.