Skip to content

Commit

Permalink
Merge pull request #5902 from pkriens/feature/class-analysis
Browse files Browse the repository at this point in the history
Minor convenience functions to classfile and libg
  • Loading branch information
pkriens authored Nov 25, 2023
2 parents c305847 + e481a9e commit 696ecac
Show file tree
Hide file tree
Showing 14 changed files with 349 additions and 19 deletions.
59 changes: 51 additions & 8 deletions aQute.libg/src/aQute/libg/parameters/Attributes.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,24 +88,27 @@ public interface DataType<T> {
public static final DataType<List<Double>> LIST_DOUBLE = () -> Type.DOUBLES;
public static final DataType<List<String>> LIST_VERSION = () -> Type.VERSIONS;

public static final Attributes EMPTY_ATTRS = new Attributes(Collections.emptyMap(),
Collections.emptyMap(), ';');

/**
* Pattern for List with list type
*/
private static final Pattern TYPED = Pattern
static final Pattern TYPED = Pattern
.compile("List\\s*<\\s*(String|Version|Long|Double)\\s*>");

private final Map<String, String> map;
private final Map<String, Type> types;
public static final Attributes EMPTY_ATTRS = new Attributes(Collections.emptyMap(),
Collections.emptyMap());
final Map<String, String> map;
final Map<String, Type> types;
final char separator;

private Attributes(Map<String, String> map, Map<String, Type> types) {
Attributes(Map<String, String> map, Map<String, Type> types, char separator) {
this.map = map;
this.types = types;
this.separator = separator;
}

public Attributes() {
this(new LinkedHashMap<>(), new HashMap<>());
this(new LinkedHashMap<>(), new HashMap<>(), ';');
}

public Attributes(Attributes... attrs) {
Expand All @@ -129,6 +132,42 @@ public Attributes(Map<String, String> v) {
putAll(v);
}

/**
* <pre>
* attributes ::= keyvalye ( ',' keyvalue )*
* keyvalue ::= key '=' value
* </pre>
*
* @param s attributes or null or empty
*/
public Attributes(String s) {
this(new LinkedHashMap<>(), new HashMap<>(), ',');
if (s != null && !s.isEmpty() && !s.isBlank()) {

QuotedTokenizer qt = new QuotedTokenizer(s, "");

do {

String keyOrAttribute = qt.nextToken("=,");
ParameterMap.error(qt, keyOrAttribute == null, "expected a clause key or attribute key");

switch (qt.getSeparator()) {
case '=' :
String attributeValue = qt.nextToken(",");
ParameterMap.error(qt, attributeValue == null, "expected an attribute value");
put(keyOrAttribute, attributeValue);
break;

case ',' :
break;

default :
ParameterMap.error(qt, true, "unrecognized separator ");
}
} while (qt.getSeparator() == ',');
}
}

public void putAllTyped(Map<String, Object> attrs) {

for (Map.Entry<String, Object> entry : attrs.entrySet()) {
Expand Down Expand Up @@ -400,8 +439,12 @@ public String toString() {
}

public void append(StringBuilder appendable) {
boolean first = true;
for (Map.Entry<String, String> e : entrySet()) {
appendable.append(";");
if (!first || separator == ';') {
appendable.append(separator);
}
first = false;
append(appendable, e);
}
}
Expand Down
13 changes: 9 additions & 4 deletions aQute.libg/src/aQute/libg/parameters/ParameterMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ void parse() {
error(token != null && !token.isEmpty(), "Parsing header: '" + parameters + "' has trailing " + qt);
}

private void error(boolean b, String string) {
ParameterMap.error(qt, b, string);
}

/*
* clause = key ( ';' keyOrAttribute )* ( '=' attributeValue )
* attributes *
Expand Down Expand Up @@ -121,10 +125,6 @@ private void attribute(Map<String, String> attrs) {
attrs.put(attributeKey, attributeValue);
}

private void error(boolean b, String string) {
if (b)
throw new IllegalArgumentException(string + " : " + qt);
}
}

public Map<String, String> put(String key, Map<String, String> attrs) {
Expand Down Expand Up @@ -202,4 +202,9 @@ public ParameterMap restrict(Collection<String> matchers) {
return result;

}

static void error(QuotedTokenizer qt, boolean b, String string) {
if (b)
throw new IllegalArgumentException(string + " : " + qt);
}
}
2 changes: 1 addition & 1 deletion aQute.libg/src/aQute/libg/parameters/package-info.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@Version("1.0.0")
@Version("1.1.0")
package aQute.libg.parameters;

import org.osgi.annotation.versioning.Version;
23 changes: 23 additions & 0 deletions aQute.libg/test/aQute/libg/parameters/AttributesTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package aQute.libg.parameters;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;

import org.junit.jupiter.api.Test;

class AttributesTest {

@SuppressWarnings("unchecked")
@Test
void test() {
Attributes a = new Attributes("foo = 1 , bar:Long = 42, l:List<Long> = '1,2,3,4'");
assertThat(a.toString()).isEqualTo("foo=1,bar:Long=42,l:List<Long>=\"1,2,3,4\"");
assertThat(a.get("foo")).isEqualTo("1");
assertThat(a.getTyped("foo")).isEqualTo("1");
assertThat(a.getTyped("bar")).isEqualTo(42L);
assertThat(a.get("l")).isEqualTo("1,2,3,4");
assertThat((List<Long>) a.getTyped("l")).containsExactly(1L, 2L, 3L, 4L);
}

}
15 changes: 15 additions & 0 deletions biz.aQute.bnd.util/src/aQute/bnd/classfile/ClassFile.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package aQute.bnd.classfile;

import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.util.Arrays;
Expand Down Expand Up @@ -152,4 +155,16 @@ static ByteBuffer slice(DataInput in, int length) throws IOException {
in.readFully(array, 0, length);
return ByteBuffer.wrap(array, 0, length);
}

public void write(OutputStream out) throws IOException {
DataOutput dout = new DataOutputStream(out);
write(dout);
}

public byte[] write() throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutput dout = new DataOutputStream(bout);
write(dout);
return bout.toByteArray();
}
}
102 changes: 102 additions & 0 deletions biz.aQute.bnd.util/src/aQute/bnd/classfile/builder/ReflectBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package aQute.bnd.classfile.builder;

import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import aQute.bnd.classfile.Attribute;
import aQute.bnd.classfile.FieldInfo;
import aQute.bnd.classfile.MethodInfo;

/**
* In some cases it is necessary to have a ClassFile for a built in Java class.
* This builder will create a reflect builder that is based on a Class object.
* Unfortunately, restrictions in the Java API make it impossible to get all the
* details. For example, there are no code attributes.
* <p>
* This is far from complete but it can be used in analysis cases. All
* information about the types of methods and fields are captured.
*/
public class ReflectBuilder {
final static int javaSpecificationVersion = Integer
.parseInt(System.getProperty("java.specification.version"));
final static int major_version = javaSpecificationVersion + 44;

/**
* Create a ClassFileBuilder based on the given class
*
* @param c the given class
* @return a ClassFileBuilder initialized with the class
*/
public static ClassFileBuilder of(Class<?> c) {
ClassFileBuilder cfb = new ClassFileBuilder(c.getModifiers(), major_version, 0, toName(c),
toName(c.getSuperclass()), Stream.of(c.getInterfaces())
.map(ReflectBuilder::toName)
.collect(Collectors.toSet()));

for (Field f : c.getDeclaredFields()) {
FieldInfo fi = new FieldInfo(f.getModifiers(), f.getName(), descriptor(f, f.getType()), attributes(f));
cfb.fields(fi);
}

for (Method m : c.getDeclaredMethods()) {
MethodInfo mi = new MethodInfo(m.getModifiers(), m.getName(),
descriptor(m, m.getReturnType(), m.getParameterTypes()), attributes(m));
cfb.methods(mi);
}

cfb.attributes(attributes(c));

return cfb;
}

private static Attribute[] attributes(Object f) {
return new Attribute[0];
}

@SuppressWarnings("rawtypes")
private static String descriptor(Member m, Class<?> type, Class... parameters) {
StringBuilder sb = new StringBuilder();
if (m instanceof Method) {
sb.append("(");
for (Class p : parameters) {
encode(p);
}
sb.append(")");
}
sb.append(encode(type));
return sb.toString();
}

private static String encode(Class<?> type) {
if (type == void.class)
return "V";
if (type == boolean.class)
return "Z";
if (type == byte.class)
return "B";
if (type == short.class)
return "S";
if (type == char.class)
return "C";
if (type == int.class)
return "I";
if (type == long.class)
return "J";
if (type == float.class)
return "F";
if (type == double.class)
return "D";

return "L".concat(toName(type))
.concat(";");
}

private static String toName(Class<?> c) {
if (c == null)
return null;
return c.getName();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@Version("1." + ClassFile.MAJOR_VERSION + "0.0")
@Version("1." + ClassFile.MAJOR_VERSION + "1.0")
package aQute.bnd.classfile.builder;

import org.osgi.annotation.versioning.Version;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* This package provides an object model and parser for Java class files.
*/
@Version("1." + ClassFile.MAJOR_VERSION + "1.0")
@Version("1." + ClassFile.MAJOR_VERSION + "2.0")
package aQute.bnd.classfile;

import org.osgi.annotation.versioning.Version;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package aQute.bnd.classfile.builder;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import java.util.function.Function;
import java.util.function.Supplier;

import org.junit.jupiter.api.Test;

import aQute.bnd.classfile.ClassFile;

class ReflectBuilderTest {

@Test
void testSupplier() {
ClassFileBuilder cfb = ReflectBuilder.of(Supplier.class);
ClassFile build = cfb.build();
assertThat(build).isNotNull();
assertThat(build.methods).hasSize(1);
assertThat(build.methods[0].name).isEqualTo("get");
assertThat(build.methods[0].descriptor).isEqualTo("()Ljava.lang.Object;");
}

@Test
void testFunction() throws IOException {
ClassFileBuilder cfb = ReflectBuilder.of(Function.class);
ClassFile build = cfb.build();
assertThat(build).isNotNull();
assertThat(build.methods).hasSize(7);
byte[] write = build.write();
assertThat(write).isNotNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ public void testBasic() throws Exception {
assertTrue(dv.contains("v=1.2.3"));
assertTrue(dv.contains("p=test.annotationheaders"));
assertTrue(dv.contains("c=test.annotationheaders.AnnotationHeadersTest$C"));
assertTrue(dv.contains("s=AnnotationHeadersTest$C"));
assertTrue(dv.contains("s=C"));

dv = manifest.getMainAttributes()
.getValue(Constants.BUNDLE_DOCURL);
Expand Down
3 changes: 3 additions & 0 deletions biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -3207,6 +3207,9 @@ public PackageRef getPackageRef(String binaryName) {
return descriptors.getPackageRef(binaryName);
}

public TypeRef getTypeRefFrom(Class<?> clazz) {
return descriptors.getTypeRefFromFQN(clazz.getName());
}
public TypeRef getTypeRefFromFQN(String fqn) {
return descriptors.getTypeRefFromFQN(fqn);
}
Expand Down
Loading

0 comments on commit 696ecac

Please sign in to comment.