diff --git a/src/de/inetsoftware/jwebassembly/JWebAssembly.java b/src/de/inetsoftware/jwebassembly/JWebAssembly.java index 0694a54..24a5e0a 100644 --- a/src/de/inetsoftware/jwebassembly/JWebAssembly.java +++ b/src/de/inetsoftware/jwebassembly/JWebAssembly.java @@ -71,6 +71,11 @@ public class JWebAssembly { */ public static final String TEXTCODE_ANNOTATION = "de.inetsoftware.jwebassembly.api.annotation.WasmTextCode"; + /** + * The name of the annotation for replacing a single method of the Java runtime. + */ + public static final String REPLACE_ANNOTATION = "de.inetsoftware.jwebassembly.api.annotation.Replace"; + /** * Create a instance. */ diff --git a/src/de/inetsoftware/jwebassembly/module/FunctionManager.java b/src/de/inetsoftware/jwebassembly/module/FunctionManager.java index 625beec..a9147fd 100644 --- a/src/de/inetsoftware/jwebassembly/module/FunctionManager.java +++ b/src/de/inetsoftware/jwebassembly/module/FunctionManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Volker Berlin (i-net software) + * Copyright 2018 - 2019 Volker Berlin (i-net software) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,14 @@ */ package de.inetsoftware.jwebassembly.module; +import java.util.HashMap; import java.util.HashSet; +import javax.annotation.Nonnull; import javax.annotation.Nullable; +import de.inetsoftware.classparser.MethodInfo; + /** * Manage the required function/methods * @@ -30,6 +34,8 @@ public class FunctionManager { private HashSet toWriteLater = new HashSet<>(); + private HashMap replacement = new HashMap<>(); + /** * Mark the a function as written to the wasm file. * @@ -76,4 +82,31 @@ public class FunctionManager { boolean isToWrite( FunctionName name ) { return toWriteLater.contains( name ); } + + /** + * Add a replacement for a method + * + * @param name + * the name of the method which should be replaced + * @param method + * the new implementation + */ + void addReplacement( FunctionName name, MethodInfo method ) { + replacement.put( name, method ); + } + + /** + * Check if there is a replacement method + * + * @param name + * the name + * @param method + * the current method + * @return the method that should be write + */ + @Nonnull + MethodInfo replace( FunctionName name, MethodInfo method ) { + MethodInfo newMethod = replacement.get( name ); + return newMethod != null ? newMethod : method; + } } diff --git a/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java b/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java index 6d945e5..0ccfa98 100644 --- a/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java +++ b/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java @@ -15,15 +15,21 @@ */ package de.inetsoftware.jwebassembly.module; +import java.io.BufferedInputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.function.Consumer; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -38,8 +44,8 @@ import de.inetsoftware.classparser.MethodInfo; import de.inetsoftware.jwebassembly.JWebAssembly; import de.inetsoftware.jwebassembly.WasmException; import de.inetsoftware.jwebassembly.module.TypeManager.StructType; -import de.inetsoftware.jwebassembly.wasm.NamedStorageType; import de.inetsoftware.jwebassembly.wasm.AnyType; +import de.inetsoftware.jwebassembly.wasm.NamedStorageType; import de.inetsoftware.jwebassembly.wasm.ValueType; import de.inetsoftware.jwebassembly.wasm.ValueTypeParser; import de.inetsoftware.jwebassembly.watparser.WatParser; @@ -77,11 +83,57 @@ public class ModuleGenerator { * @param libraries * libraries */ - public ModuleGenerator( @Nonnull ModuleWriter writer, List libraries ) { + public ModuleGenerator( @Nonnull ModuleWriter writer, @Nonnull List libraries ) { this.writer = writer; this.libraries = new URLClassLoader( libraries.toArray( new URL[libraries.size()] ) ); javaCodeBuilder.init( types ); ((WasmCodeBuilder)watParser).init( types ); + scanLibraries( libraries ); + } + + /** + * Scan the libraries for annotated methods + * + * @param libraries + * libraries + */ + private void scanLibraries( @Nonnull List libraries ) { + // search for replacement methods in the libraries + for( URL url : libraries ) { + try { + File file = new File(url.toURI()); + if( file.isDirectory() ) { + for( Iterator iterator = Files.walk( file.toPath() ).iterator(); iterator.hasNext(); ) { + Path path = iterator.next(); + if( path.toString().endsWith( ".class" ) ) { + ClassFile classFile = new ClassFile( new BufferedInputStream( Files.newInputStream( path ) ) ); + prepare( classFile ); + } + }; + } + } catch( Exception e ) { + e.printStackTrace(); + } + + try (ZipInputStream input = new ZipInputStream( url.openStream() )) { + do { + ZipEntry entry = input.getNextEntry(); + if( entry == null ) { + break; + } + if( entry.getName().endsWith( ".class" ) ) { + ClassFile classFile = new ClassFile( new BufferedInputStream( input ) { + @Override + public void close() { + } // does not close the zip stream + } ); + prepare( classFile ); + } + } while( true ); + } catch( IOException e ) { + e.printStackTrace(); + } + } } /** @@ -135,9 +187,10 @@ public class ModuleGenerator { name = new FunctionName( method, signature ); } else { name = new FunctionName( method ); + method = functions.replace( name, method ); } if( functions.isToWrite( name ) ) { - writeMethod( method ); + writeMethod( name, method ); } } catch (IOException ioex){ throw WasmException.create( ioex, sourceFile, className, -1 ); @@ -235,8 +288,8 @@ public class ModuleGenerator { private void prepareMethod( MethodInfo method ) throws WasmException { try { FunctionName name = new FunctionName( method ); - Map annotationValues = method.getAnnotation( JWebAssembly.IMPORT_ANNOTATION ); - if( annotationValues != null ) { + Map annotationValues; + if( (annotationValues = method.getAnnotation( JWebAssembly.IMPORT_ANNOTATION )) != null ) { if( !method.isStatic() ) { throw new WasmException( "Import method must be static: " + name.fullName, -1 ); } @@ -245,14 +298,22 @@ public class ModuleGenerator { String importName = (String)annotationValues.get( "name" ); writer.prepareImport( name, impoarModule, importName ); writeMethodSignature( name, true, null, null ); - } else { - annotationValues = method.getAnnotation( JWebAssembly.EXPORT_ANNOTATION ); - if( annotationValues != null ) { - if( !method.isStatic() ) { - throw new WasmException( "Export method must be static: " + name.fullName, -1 ); - } - functions.functionCall( name ); + return; + } + if( (annotationValues = method.getAnnotation( JWebAssembly.EXPORT_ANNOTATION )) != null ) { + if( !method.isStatic() ) { + throw new WasmException( "Export method must be static: " + name.fullName, -1 ); } + functions.functionCall( name ); + return; + } + if( (annotationValues = method.getAnnotation( JWebAssembly.REPLACE_ANNOTATION )) != null ) { + String className = ((String)annotationValues.get( "className" )).replace( ".", "/" ); + String methodName = (String)annotationValues.get( "methodName" ); + String signature = (String)annotationValues.get( "signature" ); + name = new FunctionName( className, methodName, signature ); + functions.addReplacement( name, method ); + return; } } catch( Exception ioex ) { throw WasmException.create( ioex, sourceFile, className, -1 ); @@ -267,7 +328,7 @@ public class ModuleGenerator { * @throws WasmException * if some Java code can't converted */ - private void writeMethod( MethodInfo method ) throws WasmException { + private void writeMethod( FunctionName name, MethodInfo method ) throws WasmException { CodeInputStream byteCode = null; try { if( method.getAnnotation( JWebAssembly.IMPORT_ANNOTATION ) != null ) { @@ -276,7 +337,6 @@ public class ModuleGenerator { WasmCodeBuilder codeBuilder; Code code = method.getCode(); LocalVariableTable localVariableTable; - FunctionName name; if( method.getAnnotation( JWebAssembly.TEXTCODE_ANNOTATION ) != null ) { Map wat = method.getAnnotation( JWebAssembly.TEXTCODE_ANNOTATION ); String watCode = (String)wat.get( "value" ); @@ -284,12 +344,10 @@ public class ModuleGenerator { if( signature == null ) { signature = method.getType(); } - name = new FunctionName( method, signature ); watParser.parse( watCode, code == null ? -1 : code.getFirstLineNr() ); codeBuilder = watParser; localVariableTable = null; } else if( code != null ) { // abstract methods and interface methods does not have code - name = new FunctionName( method ); javaCodeBuilder.buildCode( code, !method.getType().endsWith( ")V" ) ); codeBuilder = javaCodeBuilder; localVariableTable = code.getLocalVariableTable(); diff --git a/test/de/inetsoftware/jwebassembly/WasmRule.java b/test/de/inetsoftware/jwebassembly/WasmRule.java index 5cca708..023a8ac 100644 --- a/test/de/inetsoftware/jwebassembly/WasmRule.java +++ b/test/de/inetsoftware/jwebassembly/WasmRule.java @@ -111,6 +111,15 @@ public class WasmRule extends TemporaryFolder { wasm.addFile( url ); } wasm.setProperty( JWebAssembly.DEBUG_NAMES, "true" ); + + // add the libraries that it can be scanned for annotations + final String[] libraries = System.getProperty("java.class.path").split(File.pathSeparator); + for( String lib : libraries ) { + if( lib.endsWith( ".jar" ) || lib.toLowerCase().contains( "jwebassembly-api" ) ) { + wasm.addLibrary( new File(lib) ); + } + } + textCompiled = wasm.compileToText(); try { create();