interface Framework { <T> Class<? extends T> secure(Class<T> type); } @interface Secured { String user(); } class UserHolder { static String user = "ANONYMOUS"; }
class Service { @Secured(user = "ADMIN") void deleteEverything() { // delete everything... } }
(build time, agent)
class Service { @Secured(user = "ADMIN") void deleteEverything() { // delete everything... } }
class Service { @Secured(user = "ADMIN") void deleteEverything() { if(!"ADMIN".equals(UserHolder.user)) { throw new IllegalStateException("Wrong user"); } // delete everything... } }
(Liskov substitution)
class Service { @Secured(user = "ADMIN") void deleteEverything() { // delete everything... } }
class SecuredService extends Service { @Override void deleteEverything() { if(!"ADMIN".equals(UserHolder.user)) { throw new IllegalStateException("Wrong user"); } super.deleteEverything(); } }
var service = { /* @Secured(user = "ADMIN") */ deleteEverything: function () { // delete everything ... } }
function run(service) { //No type, no problem. (“duck typing”) service.deleteEverything(); }
The Java language comes with a comparatively strict type system. Java requires all variables and objects to be of a specific type and any attempt to assign incompatible types always causes an error. These errors are usually emitted by the Java compiler or at the very least by the Java runtime when casting a type illegally. Such strict typing is often desirable, for example when writing business applications. Business domains can usually be described in such an explicit manner where any domain item represents its own type. This way, we can use Java to build very readable and robust applications where mistakes are caught close to their source. Among other things, it is Java's type system that is responsible for Java's popularity in enterprise programming.
However, by enforcing its strict type system, Java imposes limitations that restrict the language's scope in other domains. For example, when writing a general-purpose library that is to be used by other Java applications, we are normally not able to reference any type that is defined in the user's application because these types are unknown to us when our library is compiled.
class Service { void deleteEverything() { if(!"ADMIN".equals(UserHolder.user)) { throw new IllegalStateException("Wrong user"); } // delete everything... } }
Working with POJOs reduces complexity. Reducing infrastructure code as a goal
An Abstract Syntax Tree is a data structure representing source code.
SrcML provides XML-based ASTs that can be analyzed with any DOM technology
<unit> <comment type="line">// copy the input to the output</comment> <while>while <condition>(<expr><name><name>std</name>::<name>cin</name></name> >> <name>n</name></expr>)</condition> <expr_stmt><expr><name><name>std</name>::<name>cout</name></name> << <name>n</name> << '\n'</expr>;</expr_stmt></while> </unit>
java spoon.Launcher -g -i srcDir
public class Dojo { public int b; public double c; void foo(Object t, int i) { t.hashCode(); b=i+3; b=i-5; b=i-98; b=b+i; } }
// navigation class.getFields() field.getDefaultExpression() if.getThenBranch(); method.getBody(); ...
// queries list1 = methodBody.getElements(new TypeFilter(CtAssignment.class)); // collecting all deprecated classes list2 = rootPackage.getElements(new AnnotationFilter(Deprecated.class)); // creating a custom filter to select all public fields list3 = rootPackage.getElements( new AbstractFilter<CtField>(CtField.class) { public boolean matches(CtField field) { return field.getModifiers.contains(ModifierKind.PUBLIC); }});
public class CatchProcessor extends AbstractProcessor<CtCatch> { public void process(CtCatch element) { if (element.getBody().getStatements().size() == 0) { getFactory().getEnvironment().report(this, Severity.WARNING, element, "empty catch clause"); } } }
java -cp spoon.jar spoon.Launcher -i sourceFolder -p CatchProcessor
public class PublicFieldProcessorWarning extends AbstractProcessor<CtField<?>>{ @Override public void process(CtField<?> arg0) { if (arg0.hasModifier(ModifierKind.PUBLIC)) { getEnvironment().report(this, Severity.WARNING, arg0, "Found a public field"); } } } `
A source code transformation tool provides you with an API.
public class NotNullCheckAdderProcessor extends AbstractProcessor<CtParameter<?>> { @Override public boolean isToBeProcessed(CtParameter<?> element) { return !element.getType().isPrimitive();// only for objects } public void process(CtParameter<?> element) { // we declare a new snippet of code to be inserted CtCodeSnippetStatement snippet = getFactory().Core().createCodeSnippetStatement(); // this snippet contains an if check snippet.setValue("if(" + element.getSimpleName() + " == null " + ") throw new IllegalArgumentException( \"[Spoon inserted check] null passed as parameter\");"); // we insert the snippet at the beginning of the method boby element.getParent(CtMethod.class).getBody().insertBegin(snippet); } }
public @interface Bound { double min(); } .... public void openUserSpacePort(@Bound(min = 1025) int a) { // code to open a port }
public class Bound2Processor extends AbstractAnnotationProcessor<Bound, CtParameter<?>> { public void process(Bound annotation, CtParameter<?> element) { // we declare a new snippet of code to be inserted CtCodeSnippetStatement snippet = getFactory().Core() .createCodeSnippetStatement(); // this snippet contains an if check snippet.setValue("if(" + element.getSimpleName() + " < " + annotation.min() + ")" +" throw new RuntimeException(\"[Spoon check] Bound violation\");"); // we insert the snippet at the beginning of the method boby element.getParent(CtMethod.class).getBody().insertBegin(snippet); } // end process }
class SecuredService extends Service { @Override void deleteEverything() { methodInterceptor.intercept(this, Service.class.getDeclaredMethod("deleteEverything"), new Object[0], new $MethodProxy()); } class $MethodProxy implements MethodProxy { // inner class semantics, can call super } } interface MethodInterceptor { Object intercept(Object object, Method method, Object[] arguments, MethodProxy proxy) throws Throwable }
Class<?> dynamicType = new ByteBuddy() .subclass(Object.class) .method(named("toString")) .intercept(value("Hello World!")) .make() .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded(); .... assertThat(dynamicType.newInstance().toString(), is("Hello World!"));
Class<?> dynamicType = new ByteBuddy() .subclass(Object.class) .method(named("toString")) .intercept(to(MyInterceptor.class)) .make() .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded();
Class<?> dynamicType = new ByteBuddy() .subclass(Object.class) .method(named("toString")) .intercept(to(MyInterceptor.class)) .make() .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded();
class MyInterceptor { static String intercept(@Origin Method m) { return "Hello World from " + m.getName(); } }
//Provides caller information @Origin Method|Class<?>|String //Allows super method call @SuperCall Runnable|Callable<?> //Allows default method call @DefaultCall Runnable|Callable<?> //Provides boxed method arguments @AllArguments T[] //Provides argument at the given index @Argument(index) T
//Provides caller instance @This T //Provides super method proxy @Super T
class Foo { String bar() { return "bar"; } } Foo foo = new Foo(); new ByteBuddy() .redefine(Foo.class) .method(named("bar")) .intercept(value("Hello World!")) .make() .load(Foo.class.getClassLoader(), ClassReloadingStrategy.installedAgent()); assertThat(foo.bar(), is("Hello World!"));
class Foo { String bar() { return "bar"; } } assertThat(new Foo().bar(), is("Hello World!"));
public static void premain(String arguments, Instrumentation instrumentation) { new AgentBuilder.Default() .rebase(named("Foo")) .transform( (builder, type) -> builder .method(named("bar")) .intercept(value("Hello World!")); ) .installOn(instrumentation);}
class Foo { @Qux void baz(List<Bar> list) { } } Method dynamicMethod = new ByteBuddy() .subclass(Foo.class) .method(named("baz")) .intercept(StubMethod.INSTANCE) .attribute(new MethodAttributeAppender .ForInstrumentedMethod()) .make() .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded() .getDeclaredMethod("baz", List.class); assertThat(dynamicMethod.isAnnotatedWith(Qux.class), is(true)); assertThat(dynamicMethod.getGenericParameterTypes()[0], instanceOf(ParameterizedType.class));