From ba6de7cb7e8f5e972e627497a435007b34674fa6 Mon Sep 17 00:00:00 2001 From: Peter Kriens Date: Tue, 28 Nov 2023 17:43:18 +0100 Subject: [PATCH] Added a facade source generator --- Signed-off-by: Peter Kriens Signed-off-by: Peter Kriens --- .../src/biz/aQute/bnd/facade/api/Binder.java | 89 +-- .../biz/aQute/bnd/facade/api/Delegate.java | 29 +- .../aQute/bnd/facade/api/FacadeManager.java | 25 +- .../biz/aQute/bnd/facade/api/Instance.java | 12 +- .../src/biz/aQute/bnd/facade/api/Memento.java | 3 - .../provider/FacadeManagerProvider.java | 5 +- .../provider/OSGiPrototypeDelegate.java | 10 +- .../provider/FacadeManagerProviderTest.java | 2 +- biz.aQute.bnd.javagen/bnd.bnd | 6 +- biz.aQute.bnd.javagen/launch.bndrun | 15 - .../bnd/javagen/util/JavaSourceBuilder.java | 562 ++++++++++++------ .../bnd/proxy/generator/FacadeGenerator.java | 24 +- .../generator/FacadeSourceGenOptions.java | 21 +- .../biz/aQute/bnd/proxy/generator/Source.java | 55 +- .../biz/aQute/bnd/proxy/generator/Spec.java | 5 +- .../bnd/proxy/generator/FacadeInterface.java | 5 + .../proxy/generator/FacadeSourceGenTest.java | 14 +- .../generator/JavaSourceBuilderTest.java | 8 +- .../aQute/bnd/proxy/generator/TestFacade.java | 7 + .../src/aQute/bnd/main/MbrCommand.java | 2 +- .../project/ProjectFacadeGenerateTest.java | 67 +++ .../bnd/project/ProjectGenerateTest.java | 20 - bndtools.facades/bnd.bnd | 16 +- .../facades/IAdapterFactoryFacade.java | 38 ++ .../facades/core/AdapterFactoryFacade.java | 25 - .../ClasspathContainerInitializerFacade.java | 33 - .../jdt/IncrementalProjectBuilderFacade.java | 59 -- .../bndtools/facades/jdt/package-info.java | 5 - .../bndtools/facades/util/EclipseBinder.java | 92 --- .../facades/util/EclipseFacadeProxy.java | 220 ------- .../src/bndtools/facades/util/FacadeUtil.java | 262 +++----- .../test/bndtools/facades/JTAGFacades.java | 12 + .../facades/util/EclipseFacadeProxyTest.java | 16 - .../facades/util/EclipseFacadeTest.java | 47 ++ 34 files changed, 852 insertions(+), 959 deletions(-) delete mode 100644 biz.aQute.bnd.javagen/launch.bndrun create mode 100644 biz.aQute.bndall.tests/test/biz/aQute/bnd/project/ProjectFacadeGenerateTest.java create mode 100644 bndtools.facades/src-gen/bndtools/facades/IAdapterFactoryFacade.java delete mode 100644 bndtools.facades/src/bndtools/facades/core/AdapterFactoryFacade.java delete mode 100644 bndtools.facades/src/bndtools/facades/jdt/ClasspathContainerInitializerFacade.java delete mode 100644 bndtools.facades/src/bndtools/facades/jdt/IncrementalProjectBuilderFacade.java delete mode 100644 bndtools.facades/src/bndtools/facades/jdt/package-info.java delete mode 100644 bndtools.facades/src/bndtools/facades/util/EclipseBinder.java delete mode 100644 bndtools.facades/src/bndtools/facades/util/EclipseFacadeProxy.java create mode 100644 bndtools.facades/test/bndtools/facades/JTAGFacades.java delete mode 100644 bndtools.facades/test/bndtools/facades/util/EclipseFacadeProxyTest.java create mode 100644 bndtools.facades/test/bndtools/facades/util/EclipseFacadeTest.java diff --git a/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Binder.java b/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Binder.java index fd108444a2..3e91c1fae4 100644 --- a/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Binder.java +++ b/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Binder.java @@ -2,23 +2,29 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Supplier; +import org.eclipse.jdt.annotation.Nullable; import org.osgi.annotation.versioning.ProviderType; /** - * A Binder establishes a connection between an object used in a non-OSGi - * environment and a corresponding backing service. This connection is - * identified by an opaque id. There should be mostly one unique - * backing service associated with each id. However, multiple binders can - * utilize the same backing service, and each binder receives a distinct object - * representing from the backing service. This is achieved through the use of - * PROTOTYPE services or by explicitly managing the lifecycle with a - * {@link Delegate}. + * A Binder establishes a connection between a facade object used in a + * non-OSGi environment and a delegate implemented as a backing + * service. Any call in the facade is delegated to the delegate service. + * Since the delegate is a service, the service can be absent. The Binder will + * wait a {@link #setTimeout(long)} time before it will throw an exception. + *

+ * The facade must a {@link FacadeManager#FACADE_ID} during construction. This + * id is assumed to be registered as a service by the delegate backing service. + * The Facade Manager service will track the different parties and inject the + * binder when the service is available. *

* The primary use case for Binders is in the context of Eclipse Extension * Points. In this subsystem, objects are created dynamically without contextual @@ -28,10 +34,12 @@ * locates the appropriate backing service through the Facade Manager. Given the * highly dynamic nature of this environment, the Binder handles the absence of * a backing service by delaying delegation if a backing service is not - * immediately available. + * immediately available. See @see Facade {@link FacadeManager} + *

+ * The Binder uses statics to be able to interface with the brain dead world out + * there. * - * @param - * the domain type + * @param the domain type */ @ProviderType public class Binder implements Supplier, Consumer, AutoCloseable { @@ -76,20 +84,17 @@ public static void purge() { * Create a binder and register it locally. If a Facaade Manager is set, * register there. * - * @param facade - * the object acting as a facade, only a weak - * reference will be maintained - * @param domainType - * the actual domain type - * @param id - * the service object facade id, see - * {@link FacadeManager#FACADE_ID} - * @param description - * a general description of the facade + * @param facade the object acting as a facade, only a weak reference will + * be maintained + * @param domainType the actual domain type + * @param id the service object facade id, see + * {@link FacadeManager#FACADE_ID} + * @param attributes optional attributes of the facade */ - public static Binder create(Object facade, Class domainType, String id, String description) { + public static Binder create(Object facade, Class domainType, String id, @Nullable + Map attributes) { - Binder binder = new Binder<>(facade, domainType, id, description); + Binder binder = new Binder<>(facade, domainType, id, attributes); synchronized (binders) { binders.add(binder); @@ -103,11 +108,12 @@ public static Binder create(Object facade, Class domainType, String id return binder; } - final String description; + final Map attributes; final WeakReference facade; final String id; final Object lock = new Object(); final AtomicReference registration = new AtomicReference<>(); + final Class domainType; long timeout = TimeUnit.SECONDS.toNanos(30); D current; @@ -115,9 +121,11 @@ public static Binder create(Object facade, Class domainType, String id int cycles; boolean closed; - Binder(Object facade, Class domainType, String id, String description) { + Binder(Object facade, Class domainType, String id, @Nullable + Map attributes) { + this.domainType = domainType; this.id = id; - this.description = description; + this.attributes = attributes == null ? Collections.emptyMap() : new HashMap<>(attributes); this.facade = new WeakReference<>(facade); } @@ -168,7 +176,7 @@ public D get() { synchronized (lock) { if (closed) - throw new IllegalStateException("the binder with id " + id + " has been closed for " + description); + throw new IllegalStateException("the binder with id " + id + " has been closed for " + attributes); if (current != null) return current; @@ -178,19 +186,19 @@ public D get() { while (current == null) { if ((System.nanoTime() - start) > timeout) { - throw new IllegalStateException( - "no backing service " + id + " can be found for " + description); + throw new IllegalStateException("no backing service " + id + " can be found for " + attributes); } lock.wait(1_000); if (closed) throw new IllegalStateException( - "the binder with id " + id + " has been closed for " + description); + "the binder with id " + id + " has been closed for " + attributes); } return current; } catch (InterruptedException e) { - Thread.currentThread().interrupt(); + Thread.currentThread() + .interrupt(); throw new IllegalStateException( - "interrupted while waiting for backing service " + id + " for " + description); + "interrupted while waiting for backing service " + id + " for " + attributes); } } } @@ -224,17 +232,16 @@ public String getId() { } /** - * Get a description of the facade object + * Get the attributes of the facade object */ - public String getDescription() { - return description; + public Map getAttributes() { + return attributes; } /** * Set the timeout to wait for the backing service to be injected. * - * @param timeout - * timeout in milliseconds + * @param timeout timeout in milliseconds */ public void setTimeout(long timeout) { this.timeout = TimeUnit.MILLISECONDS.toNanos(timeout); @@ -275,4 +282,12 @@ public WeakReference getFacade() { return facade; } + public long getTimeout() { + return timeout; + } + + public Class getDomainType() { + return domainType; + } + } diff --git a/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Delegate.java b/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Delegate.java index 3b0a1442b3..b7fccf796d 100644 --- a/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Delegate.java +++ b/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Delegate.java @@ -1,27 +1,26 @@ package biz.aQute.bnd.facade.api; -import java.lang.ref.WeakReference; - -import org.eclipse.jdt.annotation.Nullable; import org.osgi.annotation.versioning.ConsumerType; /** - * Normally it is sufficient to register a PROTOTYPE scoped component to provide - * the backing service for a facade. However, sometimes it is necessary to - * manage the life cycle in more detail. + * This service interface provides managed control of the creation of the + * delegate objects. Normally it is sufficient to register a PROTOTYPE scoped + * component to provide the backing service for a facade. However, sometimes it + * is necessary to manage the life cycle in more detail. *

* A Delegate is responsible for creating instances of the facade backing * object. It is associated with a unique ID. There should be only one Delegate * service with the given id. A Delegate service must be registered with the - * service property {@link FacadeManager#FACADE_ID} + * service property {@link FacadeManager#FACADE_ID} with this id. * - * @param - * the domain type of the delegate + * @param the domain type of the delegate */ @ConsumerType public interface Delegate { /** - * The ID of the delegate. This ID must match the ID of the facade. + * The ID of the delegate. This ID must match the ID of the facade. The must + * only be one Delegate or PROTOTYPE scoped service registered with a given + * id. */ String getId(); @@ -31,12 +30,10 @@ public interface Delegate { * previous instance, maybe from a previous delegate or even Facade Manager, * will be past to this method. * - * @param description - * a description about the caller - * @param state - * the current state or null + * @param description a description about the caller + * @param state the current state or null + * @param attributes a set of attributes, use case specific * @return an instance */ - Instance create(String description, WeakReference facade, @Nullable - Object state); + Instance create(Binder binder); } diff --git a/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/FacadeManager.java b/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/FacadeManager.java index 292dfc9df1..d5fcdb0c93 100644 --- a/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/FacadeManager.java +++ b/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/FacadeManager.java @@ -3,30 +3,35 @@ import org.osgi.annotation.versioning.ProviderType; /** - * The Facade Manager manages facades that delegate to a backing service. The - * facades are constant objects that are maintained by ignorant code that has no - * clue about dynamics. In OSGi, however, the backing service can come and go, - * and event the Facade Manger is not required to be constant. + * The Facade Manager manages Binder objects that connect a facade (a normal + * object) to a delegate, dynamic OSGi service. The Binder mediates between a + * facade that delegates all its methods to a delegate. Since the delegate is + * dynamic, this Facade Manager will inform the Binder of the presence of the + * service so delegation can be postponed until the service is available. *

- * A Binder is created by the facade object. This binder, tracks the Facade - * Manager and registers when found. + * Since the facade is a normal Java object, there is no explicit life cycle. + * This Facade Manager will periodically poll the Binder objects to detect when + * the facade object is garbage collected. + *

+ * This is a singleton service, not other instance is allowed. */ @ProviderType public interface FacadeManager { /** * The key for a service property. This is the unique id of a backing - * service. Only one service should be registered with this id at any moment - * in time. The value must be a String. + * service. At most one service should be registered with this id at any + * moment in time. The value must be a String. */ String FACADE_ID = "facade.id"; /** - * Register a binder. For a given binder, this should happen only once per + * Register a binder. For a given Binder, this must happen only once per * registered Facade Manager. * * @param binder the binder to register - * @return a closeable that will unregister this binder when closed. + * @return a closeable that will unregister this binder is closed from the + * facade. */ AutoCloseable register(Binder binder); } diff --git a/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Instance.java b/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Instance.java index 430650eeab..fa083e758f 100644 --- a/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Instance.java +++ b/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Instance.java @@ -5,30 +5,28 @@ /** * Represents an instance created by a Delegate. The instance must automatically - * be closed when the Delegate is unregistered. + * be closed when the Delegate is unregistered. It can also be closed manually. * - * @param - * the type of the domain + * @param the type of the domain */ @ConsumerType public interface Instance extends AutoCloseable { /** - * Get the backing object. This is never null. + * Get the delegate object. This is never null. */ D get(); /** * Get the current state of the backing object. This state is passed to the - * {@link Delegate#create(String, java.lang.ref.WeakReference, Object)} when - * it needs to be recreated. + * {@link Delegate#create(Binder)} when it needs to be recreated. */ @Nullable Object getState(); /** * Close the instance. This will free the backing object. This method can be - * called multiple times but will only work the first time. + * called multiple times but will only do work the first time. */ @Override void close(); diff --git a/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Memento.java b/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Memento.java index 510e0c861e..7620470e8b 100644 --- a/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Memento.java +++ b/biz.aQute.bnd.api/src/biz/aQute/bnd/facade/api/Memento.java @@ -5,9 +5,6 @@ import org.osgi.annotation.versioning.ConsumerType; /** - * An interface that can optionally be implemented by the backing service. The - * Delegate can use it to get the state when the backing service goes away and - * restore the state when it returns. *

* The return object must take care not to use domain classes. It should be * possible to recycle the bundle of the implementation between diff --git a/biz.aQute.bnd.facade.provider/src/biz/aQute/bnd/facade/provider/FacadeManagerProvider.java b/biz.aQute.bnd.facade.provider/src/biz/aQute/bnd/facade/provider/FacadeManagerProvider.java index e404d8a46d..e06dc89a0e 100644 --- a/biz.aQute.bnd.facade.provider/src/biz/aQute/bnd/facade/provider/FacadeManagerProvider.java +++ b/biz.aQute.bnd.facade.provider/src/biz/aQute/bnd/facade/provider/FacadeManagerProvider.java @@ -1,6 +1,5 @@ package biz.aQute.bnd.facade.provider; -import java.lang.ref.WeakReference; import java.util.IdentityHashMap; import java.util.Map; import java.util.Map.Entry; @@ -125,9 +124,7 @@ private Instance bind(Delegate delegate, Binder binder) { assert binder.getId().equals(delegate.getId()); - Object state = binder.getState(); - WeakReference ref = binder.getFacade(); - Instance instance = delegate.create(binder.getDescription(), binder.getFacade(), state); + Instance instance = delegate.create(binder); binder.setState(null); binder.accept(instance.get()); return instance; diff --git a/biz.aQute.bnd.facade.provider/src/biz/aQute/bnd/facade/provider/OSGiPrototypeDelegate.java b/biz.aQute.bnd.facade.provider/src/biz/aQute/bnd/facade/provider/OSGiPrototypeDelegate.java index 10847e6b4b..a70b32f09e 100644 --- a/biz.aQute.bnd.facade.provider/src/biz/aQute/bnd/facade/provider/OSGiPrototypeDelegate.java +++ b/biz.aQute.bnd.facade.provider/src/biz/aQute/bnd/facade/provider/OSGiPrototypeDelegate.java @@ -1,10 +1,10 @@ package biz.aQute.bnd.facade.provider; -import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicBoolean; import org.osgi.framework.ServiceObjects; +import biz.aQute.bnd.facade.api.Binder; import biz.aQute.bnd.facade.api.Delegate; import biz.aQute.bnd.facade.api.Instance; import biz.aQute.bnd.facade.api.Memento; @@ -18,10 +18,10 @@ class InstanceImpl implements Instance { final D service = factory.getService(); final AtomicBoolean closed = new AtomicBoolean(false); - InstanceImpl(String description, WeakReference facade, Object state) { + InstanceImpl(Binder binder) { assert service != null; if (service instanceof Memento m) { - m.setState(state, facade); + m.setState(binder.getState(), binder.getFacade()); } } @@ -64,7 +64,7 @@ public String getId() { } @Override - public Instance create(String description, WeakReference facade, Object state) { - return new InstanceImpl(description, facade, state); + public Instance create(Binder binder) { + return new InstanceImpl(binder); } } \ No newline at end of file diff --git a/biz.aQute.bnd.facade.provider/test/biz/aQute/bnd/facade/provider/FacadeManagerProviderTest.java b/biz.aQute.bnd.facade.provider/test/biz/aQute/bnd/facade/provider/FacadeManagerProviderTest.java index 12190b0a87..742e93d1ce 100644 --- a/biz.aQute.bnd.facade.provider/test/biz/aQute/bnd/facade/provider/FacadeManagerProviderTest.java +++ b/biz.aQute.bnd.facade.provider/test/biz/aQute/bnd/facade/provider/FacadeManagerProviderTest.java @@ -70,7 +70,7 @@ void deactivate() { } public static class DomainFacade implements Domain { - final Binder binder = Binder.create(this, Domain.class, ID, "description"); + final Binder binder = Binder.create(this, Domain.class, ID, null); @Override public void set(String s) { diff --git a/biz.aQute.bnd.javagen/bnd.bnd b/biz.aQute.bnd.javagen/bnd.bnd index 3897c08659..a00ac9eb10 100644 --- a/biz.aQute.bnd.javagen/bnd.bnd +++ b/biz.aQute.bnd.javagen/bnd.bnd @@ -5,7 +5,8 @@ osgi.annotation,\ aQute.libg,\ biz.aQute.bnd.util;version=latest,\ - biz.aQute.bndlib;version=latest + biz.aQute.bndlib;version=latest,\ + org.eclipse.jdt.annotation -testpath: \ ${junit}, \ @@ -13,8 +14,7 @@ slf4j.simple -conditionalpackage: \ - aQute.lib.*, \ - aQute.libg.* + aQute.lib* # Tested in biz.aQute.bndall.tests/test/biz/aQute/bnd/project/ProjectGenerateTest.java diff --git a/biz.aQute.bnd.javagen/launch.bndrun b/biz.aQute.bnd.javagen/launch.bndrun deleted file mode 100644 index c2ad06c27b..0000000000 --- a/biz.aQute.bnd.javagen/launch.bndrun +++ /dev/null @@ -1,15 +0,0 @@ -#-runfw: org.apache.felix.framework;version=5 -#-runee: JavaSE-1.8 - --runprovidedcapabilities: ${native_capability} - --resolve.effective: active;skip:="osgi.service" - --runbundles:\ - org.apache.felix.gogo.runtime,\ - org.apache.felix.gogo.shell,\ - org.apache.felix.gogo.command - --runrequires:\ - osgi.identity;filter:='(osgi.identity=org.apache.felix.gogo.shell)',\ - osgi.identity;filter:='(osgi.identity=org.apache.felix.gogo.command)' diff --git a/biz.aQute.bnd.javagen/src/biz/aQute/bnd/javagen/util/JavaSourceBuilder.java b/biz.aQute.bnd.javagen/src/biz/aQute/bnd/javagen/util/JavaSourceBuilder.java index ba7ee9f6c0..12919380f1 100644 --- a/biz.aQute.bnd.javagen/src/biz/aQute/bnd/javagen/util/JavaSourceBuilder.java +++ b/biz.aQute.bnd.javagen/src/biz/aQute/bnd/javagen/util/JavaSourceBuilder.java @@ -1,14 +1,18 @@ package biz.aQute.bnd.javagen.util; +import java.io.IOException; import java.util.Collection; import java.util.Formatter; import java.util.HashSet; +import java.util.Objects; import java.util.Set; -import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; +import org.eclipse.jdt.annotation.Nullable; + +import aQute.bnd.exceptions.Exceptions; import aQute.bnd.osgi.Clazz; import aQute.bnd.osgi.Clazz.MethodDef; import aQute.bnd.osgi.Descriptors.Descriptor; @@ -21,11 +25,18 @@ import aQute.bnd.signatures.ReferenceTypeSignature; import aQute.bnd.signatures.Result; import aQute.bnd.signatures.SimpleClassTypeSignature; -import aQute.bnd.signatures.ThrowsSignature; import aQute.bnd.signatures.TypeArgument; import aQute.bnd.signatures.TypeParameter; import aQute.bnd.signatures.TypeVariableSignature; - +import aQute.bnd.signatures.VoidDescriptor; + +/** + * Utility class to build sources. It is simple and not complete. Please extend + * it as you run in new use cases. This class does not attempt to abstract the + * whole source code building in an AST and then print it. It is just a + * convenience but it is still based on text. It is supposed to be used in + * conjunction with the bnd Clazz and classfile library. + */ public class JavaSourceBuilder { final static Pattern PACKAGE_NAME_P = Pattern.compile("(.+)\\.([^.]+)"); final static Pattern PRIMITIVES_P = Pattern.compile("void|boolan|byte|char|short|int|long|float|double"); @@ -35,68 +46,161 @@ public class JavaSourceBuilder { int indent = 0; + /** + * Default constructor. The output goes to Formatter. + */ public JavaSourceBuilder() { app = new Formatter(); } + /** + * Format the output to an appendable. + * + * @param app the appendable + */ public JavaSourceBuilder(Appendable app) { this.app = new Formatter(app); } + /** + * Append the given object + * + * @param v the value to append + * @return this + */ public JavaSourceBuilder append(Object v) { - app.format("%s", v); - return this; + try { + app.out() + .append(Objects.toString(v)); + return this; + } catch (IOException e) { + throw Exceptions.duck(e); + } } - public JavaSourceBuilder body(Consumer body) { + /** + * Starts a body based on '{' and '}' + * + * @param body the code that will build the body + * @return this + */ + public JavaSourceBuilder body(Runnable body) { begin('{'); if (body != null) - body.accept(this); + body.run(); end('}'); return this; } - public JavaSourceBuilder interface_(String className) { - return append("interface ").append(className) - .append(" "); - } + /** + * Call a method + * + * @param m the method definition from Clazz + * @param expressions + * @return this + */ + public JavaSourceBuilder call(MethodDef m, String... expressions) { + if (m == null) + return this; + + format("%s(", m.getName()); + + String del = ""; + int n = m.getDescriptor() + .getPrototype().length; + for (int i = 0; i < n; i++) { + format("%s%s", del, argName(i, expressions)); + del = ","; + } + append(");").nl(); - public JavaSourceBuilder interface_(TypeRef facade) { - return class_(facade.getShortName()); + return this; } + /** + * Create a class declaration for the given className + * + * @param className the name of the class + * @return this + */ public JavaSourceBuilder class_(String className) { return append("class ").append(className) .append(" "); } - public JavaSourceBuilder class_(TypeRef facade) { - return class_(facade.getShortName()); - } - - public JavaSourceBuilder extends_(String... className) { - if (className == null || className.length == 0) + /** + * Create a class declaration for the given className + * + * @param className the name of the class + * @return this + */ + public JavaSourceBuilder class_(TypeRef className) { + return class_(className.getShortName()); + } + + /** + * Extend the class with the given class name. If the first parameter is + * null, this will be skipped. There are multiple parameters because + * interfaces can extend multiple other interfaces. + * + * @param classNames the names of the class + * @return this + */ + public JavaSourceBuilder extends_(String... classNames) { + if (classNames == null || classNames.length == 0 || classNames[0] == null) return this; format("extends "); - list(className); + list(classNames); append(" "); return this; } - public JavaSourceBuilder extends_(TypeRef... typeRef) { - if (typeRef == null) + /** + * Extend the class with the given class name. If the first parameter is + * null, this will be skipped. There are multiple parameters because + * interfaces can extend multiple other interfaces. + * + * @param classNames the names of the class + * @return this + */ + public JavaSourceBuilder extends_(TypeRef... classNames) { + if (classNames == null || classNames.length == 1 && classNames[0] == null) return this; - return extends_(adjust(typeRef)); + return extends_(adjust(classNames)); } + /** + * Append 'final ' + * + * @return this + */ public JavaSourceBuilder final_() { return append("final "); } + /** + * Generic format method. + * + * @param format the specification + * @param args the arguments + * @return this + */ + + public JavaSourceBuilder format(String format, Object... args) { + app.format(format, args); + return this; + } + + /** + * Add an implements of a set of interfaces + * + * @param interfaces the interfaces + * @return this + */ public JavaSourceBuilder implements_(String... interfaces) { - if (interfaces.length == 0) + if (interfaces == null || interfaces.length == 0) return this; append("implements "); list(interfaces); @@ -104,12 +208,27 @@ public JavaSourceBuilder implements_(String... interfaces) { return this; } - public JavaSourceBuilder implements_(TypeRef... implements_) { - return implements_(Stream.of(implements_) + /** + * Add an implements of a set of interfaces + * + * @param interfaces the interfaces + * @return this + */ + public JavaSourceBuilder implements_(TypeRef... interfaces) { + if (interfaces == null || interfaces.length == 0) + return this; + + return implements_(Stream.of(interfaces) .map(this::adjust) .toArray(String[]::new)); } + /** + * Import a set of type references + * + * @param imported the set + * @return this + */ public JavaSourceBuilder import_(Collection imported) { for (TypeRef r : imported) { import_(r); @@ -117,39 +236,87 @@ public JavaSourceBuilder import_(Collection imported) { return this; } - public JavaSourceBuilder import_(String s) { + /** + * Import a type references. This will print the reference if it is not a + * primitive, not an array, and has not been imported already. + * + * @param s the reference + * @return this + */ + public JavaSourceBuilder import_(@Nullable + String s) { if (s == null) return this; - if (ref.isJava()) { - return ref.getPackageRef() - .getFQN() - .equals("java.lang"); + if (s.endsWith("[]")) { + return import_(s.substring(0, s.length() - 2)); } - if (ref.isPrimitive()) - return true; - - if (ref.isArray()) - return true; Matcher m = PACKAGE_NAME_P.matcher(s); if (m.matches()) { String shortName = m.group(2); if (shortImports.contains(shortName)) return this; - } + if (!imports.add(s)) return this; return format("import %s;", s).nl(); } - public JavaSourceBuilder import_(TypeRef tr) { - import_(tr.toString()); + /** + * Import a type references. This will print the reference if it is not a + * primitive, not an array, and has not been imported already. + * + * @param s the reference + * @return this + */ + public JavaSourceBuilder import_(TypeRef s) { + import_(s.getFQN()); return this; } + /** + * Append an interface if the given className is not null + * + * @param className the class name of the interface or null + * @return this + */ + public JavaSourceBuilder interface_(@Nullable + String className) { + + if (className == null) + return this; + + return append("interface ").append(className) + .append(" "); + } + + /** + * Append an interface if the given className is not null + * + * @param className the class name of the interface or null + * @return this + */ + public JavaSourceBuilder interface_(@Nullable + TypeRef className) { + + if (className == null) + return this; + + return class_(className.getShortName()); + } + + /** + * Create the prototype of a method. This is without the modifiers and body. + * The prototype will show the shortened names for imported classes and + * include all generic information. + * + * @param mdef the method definition from Clazz + * @param argNames the actual argument names, missing names are calculated + * @return this + */ public JavaSourceBuilder method(MethodDef mdef, String... argNames) { if (mdef.isConstructor()) return this; @@ -209,139 +376,160 @@ public JavaSourceBuilder method(MethodDef mdef, String... argNames) { return this; } - public JavaSourceBuilder suppressWarnings(String... warnings) { - if (warnings.length == 0) - return this; - - append("@SuppressWarnings("); - if (warnings.length == 1) { - quoted(warnings[0]); - } else { - append("{ "); - String del = ""; - for (String w : warnings) { - append(del); - quoted(w); - del = ","; - } - append(" }"); - } - append(") "); + /** + * Add a new line, handling indent + * + * @return this + */ + public JavaSourceBuilder nl() { + append("\n"); + indent(); return this; } - JavaSourceBuilder quoted(String string) { - StringBuilder sb = new StringBuilder(string); - for ( int i=0; i "\\\""; - case '\n' -> "\\n"; - case '\t' -> "\\t"; - case '\r' -> "\\r"; - case '\b' -> "\\b"; - case '\f' -> "\\f"; - default -> null; - }; - if (repl != null) { - sb.delete(i, i + 1); - sb.insert(i, repl); - } + /** + * Append count number of new lines if positive + * + * @param count the number of new lines + * @return this + */ + public JavaSourceBuilder nl(int count) { + if (count <= 0) + return this; + while (count-- > 1) { + append("\n"); } - sb.insert(0, '"'); - sb.append('"'); - append(sb.toString()); - return this; + return nl(); } - public JavaSourceBuilder typeref(TypeRef type) { - append(adjust(type)); - return this; - } + /** + * Add an override annotation + * + * @return this + */ - public JavaSourceBuilder methodProlog(String name, Descriptor descriptor) { - append(descriptor.getType()); - format(" %s(", name); - String del = ""; - TypeRef[] prototype = descriptor.getPrototype(); - for (int arg = 0; arg < prototype.length; arg++) { - format("%s", del); - type(prototype[arg]); - format("arg%d", arg); - del = ","; - } - return this; - } - - public JavaSourceBuilder nl() { - append("\n"); - indent(); - return this; + public JavaSourceBuilder override() { + return append("@Override "); } + /** + * Append a package statement + * + * @param packageRef the package name + * @return this + */ public JavaSourceBuilder package_(PackageRef packageRef) { return package_(packageRef.toString()); } - public JavaSourceBuilder package_(String string) { - return format("package %s;", string).nl(); + /** + * Append a package statement + * + * @param packageRef the package name + * @return this + */ + public JavaSourceBuilder package_(String packageRef) { + return format("package %s;", packageRef).nl(); } + /** + * Append private + * + * @return this + */ public JavaSourceBuilder private_() { return append("private "); } + /** + * Append protected + * + * @return this + */ public JavaSourceBuilder protected_() { return append("protected "); } - public JavaSourceBuilder prototype(String name, MethodSignature ms) { - typeParameters(ms.typeParameters); - type(ms.resultType); - format("%s(", name); - String del = ""; - for (int arg = 0; arg < ms.parameterTypes.length; arg++) { - format("%s", del); - type(ms.parameterTypes[arg]); - format("arg%d", arg); - del = ","; - } - format(")"); - throwTypes(ms.throwTypes); - return this; - } - + /** + * Append public + * + * @return this + */ public JavaSourceBuilder public_() { return append("public "); } - public JavaSourceBuilder static_() { - return append("static "); + /** + * Append a return statement + * + * @return this + */ + public JavaSourceBuilder return_() { + append("return "); + return this; } - public JavaSourceBuilder synchronized_() { - return append("synchronized "); + /** + * Append static + * + * @return this + */ + public JavaSourceBuilder static_() { + return append("static "); } - public JavaSourceBuilder throwTypes(ThrowsSignature[] throwTypes) { - if (throwTypes == null || throwTypes.length == 0) { + /** + * Add a suppress warning annotation. + * + * @param warnings the warning string + * @return this + */ + public JavaSourceBuilder suppressWarnings(String... warnings) { + if (warnings.length == 0) return this; - } - format("throws "); - String del = ","; - for (ThrowsSignature throwsSignature : throwTypes) { - format(del); - type(throwsSignature); - } + append("@SuppressWarnings("); + if (warnings.length == 1) { + quoted(warnings[0]); + } else { + append("{ "); + String del = ""; + for (String w : warnings) { + append(del); + quoted(w); + del = ","; + } + append(" }"); + } + append(") "); return this; } + + /** + * Append synchronized + * + * @return this + */ + public JavaSourceBuilder synchronized_() { + return append("synchronized "); + } + + /** + * toString + */ @Override public String toString() { return app.toString(); } + /** + * Append a type parameter. Type parameters are part of a type declaration + * like {@code }. + * + * @param typeParameter the type parameter + * @return this + */ public JavaSourceBuilder typeParamater(TypeParameter typeParameter) { format("%s", typeParameter.identifier); @@ -358,6 +546,13 @@ public JavaSourceBuilder typeParamater(TypeParameter typeParameter) { return this; } + /** + * Append a set of type parameter. Type parameters are part of a type + * declaration like {@code }. + * + * @param typeParameters the type parameters + * @return this + */ public JavaSourceBuilder typeParameters(TypeParameter[] typeParameters) { if (typeParameters == null || typeParameters.length == 0) { return this; @@ -373,24 +568,60 @@ public JavaSourceBuilder typeParameters(TypeParameter[] typeParameters) { return this; } - public JavaSourceBuilder format(String format, Object... args) { - app.format(format, args); + /** + * Append a type type name, using the short name if possible. + * + * @param type the type to add + * @return this + */ + public JavaSourceBuilder typeref(TypeRef type) { + append(adjust(type)); return this; } + /* + * Append the current indent + */ void indent() { for (int i = 0; i < indent; i++) append(" "); } - private String adjust(TypeRef typeRef) { - if (typeRef.isJava() && typeRef.getPackageRef() - .toString() - .equals("java.lang")) - return typeRef.getShortName(); - return imports.contains(typeRef.getFQN()) ? typeRef.getShortName() : typeRef.toString(); + /** + * Add a quoted string, escaping characters that need to be escaped. + * + * @param string the string to quote + * @return this + */ + JavaSourceBuilder quoted(String string) { + StringBuilder sb = new StringBuilder(string); + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + String repl = switch (c) { + case '"' -> "\\\""; + case '\n' -> "\\n"; + case '\t' -> "\\t"; + case '\r' -> "\\r"; + case '\b' -> "\\b"; + case '\f' -> "\\f"; + default -> null; + }; + if (repl != null) { + sb.delete(i, i + 1); + sb.insert(i, repl); + } + } + sb.insert(0, '"'); + sb.append('"'); + append(sb.toString()); + return this; } + /* + * Adjust a type ref to a shorter name if it was imported. + * @param typeRef the type reference + * @return this + */ private String adjust(String typeRef) { Matcher m = PACKAGE_NAME_P.matcher(typeRef); if (m.matches()) { @@ -403,6 +634,19 @@ private String adjust(String typeRef) { return typeRef; } + /* + * Adjust a type ref to a shorter name if it was imported. + * @param typeRef the type reference + * @return this + */ + private String adjust(TypeRef typeRef) { + if (typeRef.isJava() && typeRef.getPackageRef() + .toString() + .equals("java.lang")) + return typeRef.getShortName(); + return imports.contains(typeRef.getFQN()) ? typeRef.getShortName() : typeRef.toString(); + } + private String[] adjust(TypeRef[] typeRef) { return Stream.of(typeRef) .map(this::adjust) @@ -471,6 +715,10 @@ private void type(Object type) { append(name); return; } + if (type instanceof VoidDescriptor ats) { + append("void"); + return; + } if (type instanceof ArrayTypeSignature ats) { type(ats.component); append("[]"); @@ -515,40 +763,4 @@ private void type(Object type) { } } } - - public JavaSourceBuilder return_() { - append("return "); - return this; - } - - public JavaSourceBuilder nl(int count) { - if (count <= 0) - return this; - while (count-- > 1) { - append("\n"); - } - return nl(); - } - - public JavaSourceBuilder call(MethodDef m, String... argNames) { - if (m == null) - return this; - - format("%s(", m.getName()); - - String del = ""; - int n = m.getDescriptor() - .getPrototype().length; - for (int i = 0; i < n; i++) { - format("%s%s", del, argName(i, argNames)); - del = ","; - } - append(");").nl(); - - return this; - } - - public JavaSourceBuilder override() { - return append("@Override "); - } } diff --git a/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/FacadeGenerator.java b/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/FacadeGenerator.java index 373d6ded06..ccfe1f4f58 100644 --- a/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/FacadeGenerator.java +++ b/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/FacadeGenerator.java @@ -13,8 +13,16 @@ import aQute.lib.io.IO; /** - * A -generate command to generate a Facade. This facade can participate in - * different facade scenarios. + * A -generate command to generate a Facade class. The class can create an + * instance of an object that optionally extends a class and implements 0 or + * more interfaces. Instance can bind to a controlling object when created. + * There is a public interface Delegate created that contains all the + * overridable methods in the extended class (if any) and which extends all the + * given interfaces. I.e. it declares all methods that need to be delegated. A + * second class is created that extends the given class (if any) and implements + * the interfaces. All overrideable methods are delegating to a bind object. + *

+ * A constructor is provided to let the binding happen. *

* It will create a class that extends a parameterizable base class * (-e/--extends). The package for the output class is specified with @@ -24,16 +32,16 @@ * definition which consists of a number of ':' separated names. *

* If only a single name is specified, it is assumed to be a the class to build - * a front for. The output name will be the simple name of the that class in the - * specified package. + * a front for. The output name will be the simple name of the that class, + * appended with 'Facade' and it will be placed in the -p specified package. *

- * If more than 1 name is specified, the first name is the output class. It can - * be a simple name. If not, then it must be in the same package as specified - * with -p. + * If more than one name is specified, the first name is the output class. It + * can be a simple name, it will then be placed then in the given package as + * specified with -p. *

* The second name may be an interface or a class name. If the name is a class * name, the Facade will extend it, otherwise it will implement the interface. - * Subsequent names must be valid interface names. + * Subsequent names must be valid interface names and will be implemented. *

* The class will hold a Delegate interface. This interface is supposed to be * implemented by the delegatee, e.g. a service. It specifies _all_ methods in diff --git a/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/FacadeSourceGenOptions.java b/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/FacadeSourceGenOptions.java index a0c31d4464..f3c10fbfba 100644 --- a/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/FacadeSourceGenOptions.java +++ b/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/FacadeSourceGenOptions.java @@ -5,11 +5,30 @@ import aQute.bnd.service.generate.Options; +/** + * The options for the FacdaeGenerator command. + */ public interface FacadeSourceGenOptions extends Options { + /** + * Define the output file + * + * @return the output file or empty + */ Optional output(); + /** + * The class to extend. This is a util class that handles the environmental + * interface. + * + * @return the extend class + */ String extend(); + /** + * The package the output classes should be defined in. + * + * @return the output package + */ String package_(); - + } diff --git a/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/Source.java b/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/Source.java index d7f019ea4f..0f4846218b 100644 --- a/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/Source.java +++ b/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/Source.java @@ -18,6 +18,9 @@ import aQute.bnd.osgi.Descriptors.TypeRef; import biz.aQute.bnd.javagen.util.JavaSourceBuilder; +/* + * Generate the source code + */ class Source { final Map methods = new LinkedHashMap<>(); @@ -28,11 +31,10 @@ class Source { final TypeRef extends_; final TypeRef facade; - - Source(Analyzer analyzer, TypeRef facade, TypeRef base, TypeRef... domains) throws Exception { + Source(Analyzer analyzer, TypeRef facadeClass, TypeRef baseClass, TypeRef... domains) throws Exception { if (domains.length == 0) - throw new IllegalArgumentException("you must specify at least one type to implement or extend"); + throw new IllegalArgumentException("you must specify at least one type to either implement or extend"); Clazz primary = analyzer.findClass(domains[0]); if (primary == null) @@ -53,56 +55,61 @@ class Source { } else { this.extends_ = domains[0]; this.implements_ = Arrays.copyOfRange(domains, 1, domains.length); + parse(extends_); } - this.facade = facade; + this.facade = facadeClass; this.analyzer = analyzer; - this.base = base; - } - - String source() throws Exception { - parse(extends_); + this.base = baseClass; for (TypeRef d : implements_) { parse(d); } + } + + String source() throws Exception { JavaSourceBuilder sb = new JavaSourceBuilder(); sb.package_(facade.getPackageRef()); - sb.nl(); + sb.nl(2); sb.import_(getImported()); - sb.nl(); + sb.nl(2); sb.public_() .class_(facade) .extends_(base) - .body(j -> { + .body(() -> { - j.public_() + sb.public_() .interface_("Delegate") .extends_(implements_) - .body(jj -> { + .body(() -> { getOverridableMethods(extends_).forEach(m -> { sb.method(m) .append(";") .nl(); + }); }) .nl(2); - j.public_() + sb.public_() + .format("%s(){ super(Delegate.class);}", facade.getShortName()) + .nl(2); + + sb.public_() .static_() .class_("Facade") .extends_(extends_) .implements_(implements_) - .body(jjj -> { + .body(() -> { sb.final_() .format("Supplier bind;") .nl(); sb.suppressWarnings("unchecked", "rawtypes"); sb.format( - "Facade(Function> binding) { this.bind = (Supplier) binding.apply(this) /* I know */; }") + "Facade(Function> binding) { this.bind = (Supplier) binding.apply(this) /* I know :-( */; }") .nl(2); methods.values() @@ -111,7 +118,7 @@ String source() throws Exception { sb.override() .public_(); sb.method(m) - .body(y -> { + .body(() -> { if (!isVoid(m.getType())) { sb.return_(); } @@ -162,12 +169,14 @@ private Set getImported() { result.add(base); result.add(analyzer.getTypeRefFromFQN(Function.class.getName())); result.add(analyzer.getTypeRefFromFQN(Supplier.class.getName())); - methods.keySet() + methods.values() .stream() - .map(nd -> nd.descriptor) - .forEach(d -> { - result.add(d.getType()); - for (TypeRef t : d.getPrototype()) { + .forEach(nd -> { + for (TypeRef tr : nd.getThrows()) { + result.add(tr); + } + result.add(nd.getDescriptor().getType()); + for (TypeRef t : nd.getDescriptor().getPrototype()) { result.add(t); } }); diff --git a/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/Spec.java b/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/Spec.java index b8f017f08c..66eb20292d 100644 --- a/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/Spec.java +++ b/biz.aQute.bnd.javagen/src/biz/aQute/bnd/proxy/generator/Spec.java @@ -5,6 +5,9 @@ import aQute.bnd.osgi.Descriptors.PackageRef; import aQute.bnd.osgi.Descriptors.TypeRef; +/** + * Analyzes the command line and turns it in a basic set of parameters + */ class Spec { final Analyzer analyzer; @@ -23,7 +26,7 @@ public Spec(Analyzer analyzer, String spec, String pack, String base) { this.domains = new TypeRef[] { domain }; - this.facade = toTypeRef(analyzer, this.pack, domain.getShortName()); + this.facade = toTypeRef(analyzer, this.pack, domain.getShortName() + "Facade"); } else { this.facade = toTypeRef(analyzer, this.pack, parts[0]); this.domains = new TypeRef[parts.length - 1]; diff --git a/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/FacadeInterface.java b/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/FacadeInterface.java index 1cebdfdbe9..04d2c8c0b3 100644 --- a/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/FacadeInterface.java +++ b/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/FacadeInterface.java @@ -1,9 +1,12 @@ package biz.aQute.bnd.proxy.generator; import java.net.NetworkInterface; +import java.util.Map; public interface FacadeInterface { + void clean(Map args) throws Exception; + void foo(); int fooint(); @@ -11,4 +14,6 @@ public interface FacadeInterface { String fooString(); void foo(String a1, NetworkInterface ni); + + } diff --git a/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/FacadeSourceGenTest.java b/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/FacadeSourceGenTest.java index eb1e7d2b0a..9ed1ed423e 100644 --- a/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/FacadeSourceGenTest.java +++ b/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/FacadeSourceGenTest.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; @@ -18,6 +19,9 @@ class FacadeSourceGenTest { + /* + * Helper to generate a test generated class + */ public static void main(String[] args) throws Exception { try (Analyzer a = new Analyzer()) { a.addClasspath(IO.getFile("bin_test")); @@ -29,7 +33,7 @@ public static void main(String[] args) throws Exception { TypeRef facade = a.getTypeRefFromFQN("biz.aQute.bnd.proxy.generator.TestFacade"); - Source fsg = new Source(a, facade, base, domain_fc, domain_fi); + Source fsg = new Source(a, facade, base, /* domain_fc, */ domain_fi); String source = fsg.source(); System.out.println(source); } @@ -75,6 +79,12 @@ public List generics(Class arg0, Set arg1) thr return Collections.emptyList(); } + @Override + public void clean(Map args) throws Exception { + // TODO Auto-generated method stub + + } + } TestFacade f = new TestFacade(); Facade d = f.createFacade((b) -> { @@ -88,7 +98,7 @@ public List generics(Class arg0, Set arg1) thr assertThat(events.remove(0)).isEqualTo("foo(String,NetworkInterface)"); d.bar(); - assertThat(events.remove(0)).isEqualTo("b"); + assertThat(events.remove(0)).isEqualTo("bar"); assertThat(events.isEmpty()); } diff --git a/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/JavaSourceBuilderTest.java b/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/JavaSourceBuilderTest.java index b18849827a..59c9eaa4c8 100644 --- a/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/JavaSourceBuilderTest.java +++ b/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/JavaSourceBuilderTest.java @@ -28,17 +28,21 @@ void test() throws Exception { .class_("Test") .extends_("OtherTest") .implements_("A", "B") - .body(j -> { + .body(() -> { clazz.methods() .filter(m -> !m.isConstructor()) .forEach(m -> { - j.public_() + sb.public_() .method(m) .body(null); }); }); System.out.println(sb); + + /** + * should test the output TODO + */ } } diff --git a/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/TestFacade.java b/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/TestFacade.java index 8537ced5f6..01ef646348 100644 --- a/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/TestFacade.java +++ b/biz.aQute.bnd.javagen/test/biz/aQute/bnd/proxy/generator/TestFacade.java @@ -2,6 +2,7 @@ import java.net.NetworkInterface; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; @@ -66,6 +67,12 @@ public void foo(String arg0, NetworkInterface arg1) { } + @Override + public void clean(Map args) throws Exception { + // TODO Auto-generated method stub + + } + } public Facade createFacade(Function> binder) { diff --git a/biz.aQute.bnd/src/aQute/bnd/main/MbrCommand.java b/biz.aQute.bnd/src/aQute/bnd/main/MbrCommand.java index 9355884b6c..d771751820 100644 --- a/biz.aQute.bnd/src/aQute/bnd/main/MbrCommand.java +++ b/biz.aQute.bnd/src/aQute/bnd/main/MbrCommand.java @@ -237,7 +237,7 @@ private List getRepositories(int[] repo) { List repositories = new ArrayList<>(); for (int n : repo) { - System.out.println("repo # =" + n); + System.out.println("repo # =§" + n); repositories.add(this.repositories.get(n)); } return repositories; diff --git a/biz.aQute.bndall.tests/test/biz/aQute/bnd/project/ProjectFacadeGenerateTest.java b/biz.aQute.bndall.tests/test/biz/aQute/bnd/project/ProjectFacadeGenerateTest.java new file mode 100644 index 0000000000..20ba5ff542 --- /dev/null +++ b/biz.aQute.bndall.tests/test/biz/aQute/bnd/project/ProjectFacadeGenerateTest.java @@ -0,0 +1,67 @@ +package biz.aQute.bnd.project; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import aQute.bnd.build.Project; +import aQute.bnd.build.Workspace; +import aQute.bnd.repository.fileset.FileSetRepository; +import aQute.bnd.test.jupiter.InjectTemporaryDirectory; +import aQute.lib.io.FileTree; +import aQute.lib.io.IO; + +public class ProjectFacadeGenerateTest { + @InjectTemporaryDirectory + File tmp; + + @Test + @SuppressWarnings({ + "unchecked", "rawtypes" + }) + public void testFacadeGenerator() throws Exception { + try (Workspace ws = getWorkspace("resources/ws-stalecheck")) { + getRepo(ws); + Project project = ws.getProject("p2"); + project.setProperty("-generate", + "bnd.bnd;output=src-gen/;generate='facadegen -o src-gen/ -e biz.aQute.bnd.project.TestBase -p biz.aQute.bnd.project Function:java.util.function.Function:java.util.function.Supplier'"); + + File outputdir = project.getFile("src-gen"); + + project.getGenerate() + .generate(true); + project.check(); + assertThat(IO.getFile(outputdir, "biz/aQute/bnd/project/Function.java")).isFile(); + } + } + + private Workspace getWorkspace(File file) throws Exception { + IO.copy(file, tmp); + return new Workspace(tmp); + } + + private Workspace getWorkspace(String dir) throws Exception { + return getWorkspace(new File(dir)); + } + + private void getRepo(Workspace ws) throws IOException, Exception { + System.out.println("current working dir " + IO.work); + FileTree tree = new FileTree(); + List files = tree.getFiles(IO.getFile("generated/"), "*.jar"); + File jar = IO.getFile("jar/") + .getCanonicalFile(); + File javagen = IO.getFile("../biz.aQute.bnd.javagen/generated/") + .getCanonicalFile(); + + files.addAll(tree.getFiles(jar, "*.jar")); + files.addAll(tree.getFiles(javagen, "*.jar")); + System.out.println("tmp repo " + files); + FileSetRepository repo = new FileSetRepository("test", files); + ws.addBasicPlugin(repo); + } + +} diff --git a/biz.aQute.bndall.tests/test/biz/aQute/bnd/project/ProjectGenerateTest.java b/biz.aQute.bndall.tests/test/biz/aQute/bnd/project/ProjectGenerateTest.java index 0f44f174b8..795a7e7328 100644 --- a/biz.aQute.bndall.tests/test/biz/aQute/bnd/project/ProjectGenerateTest.java +++ b/biz.aQute.bndall.tests/test/biz/aQute/bnd/project/ProjectGenerateTest.java @@ -108,26 +108,6 @@ public void testSimpleGenerator() throws Exception { } } - @Test - @SuppressWarnings({ - "unchecked", "rawtypes" - }) - public void testFacadeGenerator() throws Exception { - try (Workspace ws = getWorkspace("resources/ws-stalecheck")) { - getRepo(ws); - Project project = ws.getProject("p2"); - project.setProperty("-generate", - "bnd.bnd;output=src-gen/;generate='facadegen -o src-gen/ -e biz.aQute.bnd.project.TestBase -p biz.aQute.bnd.project Function:java.util.function.Function:java.util.function.Supplier'"); - - File outputdir = project.getFile("src-gen"); - - project.getGenerate() - .generate(true); - project.check(); - assertThat(IO.getFile(outputdir, "biz/aQute/bnd/project/Function.java")).isFile(); - } - } - @Test @SuppressWarnings({ "unchecked", "rawtypes" diff --git a/bndtools.facades/bnd.bnd b/bndtools.facades/bnd.bnd index f3e272f294..fe251aabc6 100644 --- a/bndtools.facades/bnd.bnd +++ b/bndtools.facades/bnd.bnd @@ -59,7 +59,19 @@ org.objenesis src: ${^src},src-gen -x-generate: \ + +# +# generate the facade classes +# + +-generate: \ bnd.bnd; \ output=src-gen; \ - generate="facadegen -o src-gen -e bndtools.facades.util.FacadeUtil -p bndtools.facades IPB:org.eclipse.core.resources.IncrementalProjectBuilder >tmp 2>err" + generate="facadegen \ + -o src-gen \ + --extend bndtools.facades.util.FacadeUtil \ + --package_ bndtools.facades \ + \ + org.eclipse.core.runtime.IAdapterFactory \ + IPB:org.eclipse.core.resources.IncrementalProjectBuilder:org.eclipse.core.resources.IIncrementalProjectBuilder2 \ + " diff --git a/bndtools.facades/src-gen/bndtools/facades/IAdapterFactoryFacade.java b/bndtools.facades/src-gen/bndtools/facades/IAdapterFactoryFacade.java new file mode 100644 index 0000000000..a95748bd7b --- /dev/null +++ b/bndtools.facades/src-gen/bndtools/facades/IAdapterFactoryFacade.java @@ -0,0 +1,38 @@ +package bndtools.facades; + + +import bndtools.facades.util.FacadeUtil; +import java.lang.Class; +import java.lang.Object; +import java.util.function.Function; +import java.util.function.Supplier; +import org.eclipse.core.runtime.IAdapterFactory; + + +public class IAdapterFactoryFacade extends FacadeUtil { + public interface Delegate extends IAdapterFactory { + + } + + + public IAdapterFactoryFacade(){ super(Delegate.class);} + + public static class Facade implements IAdapterFactory { + final Supplier bind; + @SuppressWarnings({ "unchecked","rawtypes" }) Facade(Function> binding) { this.bind = (Supplier) binding.apply(this) /* I know :-( */; } + + @Override public T getAdapter(Object arg0, Class arg1) { + return bind.get().getAdapter(arg0,arg1); + + } + + @Override public Class[] getAdapterList() { + return bind.get().getAdapterList(); + + } + + + } + + public Facade createFacade(Function> binder) { return new Facade(binder); } +} diff --git a/bndtools.facades/src/bndtools/facades/core/AdapterFactoryFacade.java b/bndtools.facades/src/bndtools/facades/core/AdapterFactoryFacade.java deleted file mode 100644 index 9675719d8b..0000000000 --- a/bndtools.facades/src/bndtools/facades/core/AdapterFactoryFacade.java +++ /dev/null @@ -1,25 +0,0 @@ -package bndtools.facades.core; - -import org.eclipse.core.runtime.IAdapterFactory; -import org.osgi.annotation.versioning.ConsumerType; - -import bndtools.facades.util.EclipseBinder; - -@ConsumerType -public class AdapterFactoryFacade extends EclipseBinder implements IAdapterFactory { - - public AdapterFactoryFacade() { - super(IAdapterFactory.class, null); - } - - @Override - public T getAdapter(Object adaptableObject, Class adapterType) { - return get().getAdapter(adaptableObject, adapterType); - } - - @Override - public Class[] getAdapterList() { - return get().getAdapterList(); - } - -} diff --git a/bndtools.facades/src/bndtools/facades/jdt/ClasspathContainerInitializerFacade.java b/bndtools.facades/src/bndtools/facades/jdt/ClasspathContainerInitializerFacade.java deleted file mode 100644 index 45d22a4f1c..0000000000 --- a/bndtools.facades/src/bndtools/facades/jdt/ClasspathContainerInitializerFacade.java +++ /dev/null @@ -1,33 +0,0 @@ -package bndtools.facades.jdt; - -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IConfigurationElement; -import org.eclipse.core.runtime.IExecutableExtension; -import org.eclipse.core.runtime.IPath; -import org.eclipse.jdt.core.ClasspathContainerInitializer; -import org.eclipse.jdt.core.IJavaProject; -import org.osgi.annotation.versioning.ConsumerType; - -import bndtools.facades.util.EclipseBinder; - -@ConsumerType -public class ClasspathContainerInitializerFacade extends ClasspathContainerInitializer implements IExecutableExtension -{ - final EclipseBinder binder = new EclipseBinder<>( - ClasspathContainerInitializer.class, - this); - - @Override - public void initialize(IPath containerPath, IJavaProject project) throws CoreException { - binder.get() - .initialize(containerPath, project); - - } - - @Override - public void setInitializationData(IConfigurationElement config, String propertyName, Object data) - throws CoreException { - binder.setInitializationData(config, propertyName, data); - } - -} diff --git a/bndtools.facades/src/bndtools/facades/jdt/IncrementalProjectBuilderFacade.java b/bndtools.facades/src/bndtools/facades/jdt/IncrementalProjectBuilderFacade.java deleted file mode 100644 index 0d7d700447..0000000000 --- a/bndtools.facades/src/bndtools/facades/jdt/IncrementalProjectBuilderFacade.java +++ /dev/null @@ -1,59 +0,0 @@ -package bndtools.facades.jdt; - -import java.lang.invoke.MethodHandle; -import java.util.Map; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.eclipse.core.resources.IIncrementalProjectBuilder2; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IncrementalProjectBuilder; -import org.eclipse.core.runtime.IConfigurationElement; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.jobs.ISchedulingRule; - -import bndtools.facades.util.FacadeUtil; - -public class IncrementalProjectBuilderFacade extends FacadeUtil { - - class Delegate extends IncrementalProjectBuilder implements IIncrementalProjectBuilder2 { - - final Supplier binder; - - Delegate(Function> bind) { - this.binder = bind.apply(this); - } - - static MethodHandle m1 = lookup(Delegate.class, "setInitializationData", void.class, - IConfigurationElement.class, String.class, Object.class); - @Override - public void setInitializationData(IConfigurationElement arg0, String arg1, Object arg2) { - invokeVoid(() -> m1.invoke(binder.get(), arg0, arg1, arg2)); - } - - static MethodHandle m2 = lookup(Delegate.class, "getRule", ISchedulingRule.class, int.class, Map.class); - @Override - public ISchedulingRule getRule(int arg0, Map arg1) { - return (ISchedulingRule) invokeReturn(() -> m2.invoke(binder.get(), arg0, arg1)); - } - - static MethodHandle m3 = lookup(Delegate.class, "build", IProject[].class, int.class, Map.class, - IProgressMonitor.class); - @Override - protected IProject[] build(int arg0, Map arg1, IProgressMonitor arg2) { - return (IProject[]) invokeReturn(() -> m3.invoke(binder.get(), arg0, arg1, arg2)); - } - - static MethodHandle m4 = lookup(Delegate.class, "clean", void.class, Map.class, IProgressMonitor.class); - @Override - public void clean(Map arg0, IProgressMonitor arg1) { - invokeVoid(() -> m4.invoke(binder.get(), arg0, arg1)); - } - } - - @Override - protected Object createDelegate(Function> bind) { - return new Delegate(bind); - } - -} diff --git a/bndtools.facades/src/bndtools/facades/jdt/package-info.java b/bndtools.facades/src/bndtools/facades/jdt/package-info.java deleted file mode 100644 index 41da1b82f1..0000000000 --- a/bndtools.facades/src/bndtools/facades/jdt/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -@Export -@org.osgi.annotation.versioning.Version("1.0.0") -package bndtools.facades.jdt; - -import org.osgi.annotation.bundle.Export; diff --git a/bndtools.facades/src/bndtools/facades/util/EclipseBinder.java b/bndtools.facades/src/bndtools/facades/util/EclipseBinder.java deleted file mode 100644 index b8acfbf9ec..0000000000 --- a/bndtools.facades/src/bndtools/facades/util/EclipseBinder.java +++ /dev/null @@ -1,92 +0,0 @@ -package bndtools.facades.util; - -import java.util.function.Supplier; - -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IConfigurationElement; -import org.eclipse.core.runtime.IExecutableExtension; -import org.eclipse.core.runtime.Status; -import org.osgi.annotation.versioning.ProviderType; - -import biz.aQute.bnd.facade.api.Binder; - -/** - * This is a special extension point (EP) binder. It is used in scenarios where - * there is an EP that needs a constant reference to an object. The EP should - * instance an XFacade, where X is the type required by the EP's attribute. The - * actual Binder ID (see FacadeManager FACADE_ID), is obtained via the - * {@link IExecutableExtension}. The EP defines the class name in an attribute - * and can add the facade ID after this class name, separating with a ':'. When - * the class is instantiated by the EP manager, it will call the EclipseBinder - * with extra information. For example: - * - *
- *  ... 
- * 
- * - * In the previous case, the Facade ID will be `bndtools.id.ido`. - *

- * There are the following cases: - *

    - *
  • The object is defined by an interface. In that case the facade class can - * extend the EclipseBinder. It will then automatically implement the - * {@link IExecutableExtension}. when the - * {@link IExecutableExtension#setInitializationData(IConfigurationElement, String, Object)} - * is called, a {@link Binder} is automatically created. - *
  • The executable object is a class type. In that case the facade class must - * implement {@link IExecutableExtension} and delegate the - * {@link IExecutableExtension#setInitializationData(IConfigurationElement, String, Object)} - * to a new Binder. - * - * @param the domain type - */ -@ProviderType -public class EclipseBinder implements IExecutableExtension, Supplier { - - final Class domainType; - final Object referent; - - Binder binder; - - /** - * Create a new Eclipse Binder. - * - * @param domainType the actual service type - * @param referent the object that refers to this binder. If null, this is - * assumed. - */ - public EclipseBinder(Class domainType, Object referent) { - this.domainType = domainType; - this.referent = referent == null ? this : referent; - } - - /** - * Creates a Binder with the initialization data. The assumption is that the - * data parameter contains the string after the `:` the class name. This - * must be the full FACADE ID. - */ - @Override - public void setInitializationData(IConfigurationElement config, String propertyName, Object data) - throws CoreException { - - assert binder == null; - - if (!(data instanceof String)) { - throw new CoreException(Status.error("must add the service id as parameter to the class name, like '" - + getClass().getName() + ":'")); - } - - binder = Binder.create(referent, domainType, (String) data, config.getName() + ":" + propertyName); - } - - /** - * Return the current delegatee - */ - - @Override - public D get() { - assert binder != null : "should not be called before setInitializationData has been done"; - return binder.get(); - } - -} diff --git a/bndtools.facades/src/bndtools/facades/util/EclipseFacadeProxy.java b/bndtools.facades/src/bndtools/facades/util/EclipseFacadeProxy.java deleted file mode 100644 index 7c43e7a339..0000000000 --- a/bndtools.facades/src/bndtools/facades/util/EclipseFacadeProxy.java +++ /dev/null @@ -1,220 +0,0 @@ -package bndtools.facades.util; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IConfigurationElement; -import org.eclipse.core.runtime.IExecutableExtension; -import org.eclipse.core.runtime.IExecutableExtensionFactory; -import org.eclipse.core.runtime.Status; -import org.eclipse.jdt.annotation.Nullable; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import aQute.bnd.exceptions.Exceptions; -import aQute.libg.glob.Glob; -import aQute.libg.parameters.Attributes; -import biz.aQute.bnd.facade.api.Binder; -import biz.aQute.bnd.facade.api.FacadeManager; - -/** - * Implements a helper for facades that are interfaces. Using this model, you - * can register an extension point with this class name. The parameters are then - * passed after the class name, separated by a colon (':'). - * - *
    - *  ... 
    - * 
    - *
    - * The attributes in the parameters are:
    - * 
      - *
    • id – The facade id, see {@link FacadeManager#FACADE_ID} - *
    • domain – The domain type, this is the type that the extension - * point class must implement. This must be an interface type - *
    • bundle A glob for the bundle symbolic names that should be - * searched for the domain type. This is optional, when not specified, it will - * search all bundles. - *
    - */ -@SuppressWarnings("rawtypes") -public class EclipseFacadeProxy implements IExecutableExtensionFactory, IExecutableExtension { - private static final String ATTR_BUNDLE = "bundle"; - private static final String ATTR_DOMAIN = "domain"; - private static final String ATTR_ID = "id"; - final static Logger logger = LoggerFactory.getLogger(EclipseFacadeProxy.class); - static BundleContext context; - - class ActiveFactory { - final String facadeId; - final Class facadeType; - final IConfigurationElement config; - final String propertyName; - final Object data; - - ActiveFactory(String facadeId, Class facadeType, IConfigurationElement config, String propertyName, - Object data) { - this.facadeId = facadeId; - this.facadeType = facadeType; - this.config = config; - this.propertyName = propertyName; - this.data = data; - } - - Object create() { - try { - /* - * Forwarder class is because we need a constant reference to - * pass to the proxy as invocation handler but the bunder needs - * access to the facade to detect when it is gc'ed. Make sure - * nobody holds a reference to the forwarder. - */ - class Forwarder implements InvocationHandler { - final Binder binder; - final Object facade; - - Forwarder() throws CoreException { - facade = Proxy.newProxyInstance(facadeType.getClassLoader(), new Class[] { - facadeType - }, this /* ! early escape */); - binder = Binder.create(facade, facadeType, facadeId, "Eclipse proxy on " + facadeType); - if (binder.get() instanceof IExecutableExtension iee) { - iee.setInitializationData(config, propertyName, data); - } - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - assert binder != null; - Object target = binder.get(); - return method.invoke(target, args); - } - } - Forwarder f = new Forwarder(); - return f.facade; - } catch (CoreException e) { - fatal("unexpected exception %s", e); - return null; // not called - } - } - - } - - ActiveFactory active; - - public EclipseFacadeProxy() { - if (context == null) { - Bundle bundle = FrameworkUtil.getBundle(EclipseFacadeProxy.class); - if (bundle != null) - context = bundle.getBundleContext(); - } - } - - @Override - public Object create() throws CoreException { - if (active == null) - fatal("proxy called but not yet initialized by " + IExecutableExtension.class.getSimpleName()); - - return active.create(); - } - - @Override - public void setInitializationData(IConfigurationElement config, String propertyName, Object data) - throws CoreException { - try { - String name = config.getName(); - - Map parameters = new Attributes((String) data); - String id = parameters.get(ATTR_ID); - String domain = parameters.get(ATTR_DOMAIN); - String bundle = parameters.get(ATTR_BUNDLE); - - logger.debug("intitialize factory <{} {}='...:{}' ...>", name, propertyName, data); - - if (id == null) - fatal("missing %s, EP <%s %s='%s' ... >", ATTR_ID, name, propertyName, data); - if (domain == null) - fatal("missing %s, EP <%s %s='%s' ... >", ATTR_DOMAIN, name, propertyName, data); - - Class domainType = findType(domain, bundle); - - this.active = new ActiveFactory(id, domainType, config, propertyName, data); - } catch (Exception e) { - fatal("failed to initialize " + e); - } - } - - private Class findType(String proxy, @Nullable - String bundle) throws CoreException { - - assert proxy != null; - - Glob glob = Glob.ALL; - if (bundle != null) { - glob = new Glob(bundle); - } - - List interfaces = new ArrayList<>(); - if (context != null) { - for (Bundle b : context.getBundles()) { - if (bundle == null) { - String bsn = b.getSymbolicName(); - if (glob.matches(bsn)) { - try { - Class proxyClass = b.loadClass(proxy); - if (proxyClass.isInterface()) { - interfaces.add(proxyClass); - } - } catch (ClassNotFoundException e) { - // ignore - } - } - } - } - } else { - logger.warn("no bundle context"); - Class proxyClass; - try { - proxyClass = Class.forName(proxy); - if (proxyClass.isInterface()) { - interfaces.add(proxyClass); - } - } catch (ClassNotFoundException e) { - // ignore - } - } - if (interfaces.isEmpty()) - fatal("cannot find proxy class " + proxy + " in bundles matching " + glob); - logger.debug("found interfaces ", interfaces); - return interfaces.get(0); - } - - private void fatal(String message, Object... data) { - String msg = String.format(""" - an %s is an %s. After its class name it expects a ':' - and the parameters. The parameter are: - - id ::= the facade id - domain ::= - bundle ::= [optional] a glob of bundle symbolic names to load the domain type from - - parameters must be separated by a comma (','). - """, EclipseFacadeProxy.class.getSimpleName(), IExecutableExtension.class.getName()); - - String plain = String.format(message + "\n" + msg, data); - - logger.error(plain); - - throw Exceptions.duck(new CoreException(Status.error(plain))); - } - -} diff --git a/bndtools.facades/src/bndtools/facades/util/FacadeUtil.java b/bndtools.facades/src/bndtools/facades/util/FacadeUtil.java index 69bf9e7cbc..5f91be4bb8 100644 --- a/bndtools.facades/src/bndtools/facades/util/FacadeUtil.java +++ b/bndtools.facades/src/bndtools/facades/util/FacadeUtil.java @@ -1,11 +1,6 @@ package bndtools.facades.util; -import java.lang.invoke.MethodHandle; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.List; +import java.util.Formatter; import java.util.Map; import java.util.function.Function; import java.util.function.Supplier; @@ -14,231 +9,152 @@ import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExecutableExtension; import org.eclipse.core.runtime.IExecutableExtensionFactory; +import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.Status; -import org.eclipse.jdt.annotation.Nullable; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import aQute.bnd.exceptions.Exceptions; -import aQute.libg.glob.Glob; import aQute.libg.parameters.Attributes; import biz.aQute.bnd.facade.api.Binder; import biz.aQute.bnd.facade.api.FacadeManager; /** - * Implements a helper for facades that are interfaces. Using this model, you - * can register an extension point with this class name. The parameters are then - * passed after the class name, separated by a colon (':'). + * Implements a helper for generated facades in Eclipse. This base class is an + * {@link IExecutableExtensionFactory}. This means that after instantiation, + * Eclipse will call {@link IExecutableExtensionFactory#create()} to get an + * actual instance. Since we also implement {@link IExecutableExtension},we are + * first called with the parameters, like the id of the service facade, + * specified in the XML. * *
    - *  ... 
    - * 
    + * 
    + * ... 
      *
    + * 
      * The attributes in the parameters are:
      * 
      *
    • id – The facade id, see {@link FacadeManager#FACADE_ID} - *
    • domain – The domain type, this is the type that the extension - * point class must implement. This must be an interface type - *
    • bundle A glob for the bundle symbolic names that should be - * searched for the domain type. This is optional, when not specified, it will - * search all bundles. + *
    • timeout – The timeout in seconds the binding should wait for the + * facade service + *
    • description – A description *
    */ @SuppressWarnings("rawtypes") public abstract class FacadeUtil implements IExecutableExtensionFactory, IExecutableExtension { - private static final String ATTR_BUNDLE = "bundle"; - private static final String ATTR_DOMAIN = "domain"; - private static final String ATTR_ID = "id"; - final static Logger logger = LoggerFactory.getLogger(FacadeUtil.class); - static BundleContext context; - + final static Logger logger = LoggerFactory.getLogger(FacadeUtil.class); + public static final String ATTR_ID = "id"; + public static final String ATTR_TIMEOUT = "timeout"; + public static final String ATTR_DESCRIPTION = "description"; + + /* + * Helper to maintain the data we get from the IExecutableExtension in one + * place + */ class ActiveFactory { final String facadeId; - final Class facadeType; final IConfigurationElement config; final String propertyName; final Object data; + final Map parameters; + final long to; + + ActiveFactory(IConfigurationElement config, String propertyName, Object data) { + Map parameters = new Attributes((String) data); + this.facadeId = parameters.get(ATTR_ID); + String timeout = parameters.get(ATTR_TIMEOUT); + + if (facadeId == null) + fatal("missing mandatory attribute %s in parameters", ATTR_ID); + + long to = -1; + if (timeout != null && timeout.matches("\\d+")) { + to = Long.parseLong(timeout) * 1000; + } + this.to = to; - ActiveFactory(String facadeId, Class facadeType, IConfigurationElement config, String propertyName, - Object data) { - this.facadeId = facadeId; - this.facadeType = facadeType; this.config = config; this.propertyName = propertyName; this.data = data; + this.parameters = parameters; } - Object create() { - try { - /* - * Forwarder class is because we need a constant reference to - * pass to the proxy as invocation handler but the bunder needs - * access to the facade to detect when it is gc'ed. Make sure - * nobody holds a reference to the forwarder. - */ - class Forwarder implements InvocationHandler { - final Binder binder; - final Object facade; - - Forwarder() throws CoreException { - facade = Proxy.newProxyInstance(facadeType.getClassLoader(), new Class[] { - facadeType - }, this /* ! early escape */); - binder = Binder.create(facade, facadeType, facadeId, "Eclipse proxy on " + facadeType); - if (binder.get() instanceof IExecutableExtension iee) { - iee.setInitializationData(config, propertyName, data); - } - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - assert binder != null; - Object target = binder.get(); - return method.invoke(target, args); - } - } - Forwarder f = new Forwarder(); - return f.facade; - } catch (CoreException e) { - fatal("unexpected exception %s", e); - return null; // not called - } + Binder bind(Object facade) { + Binder binder = Binder.create(facade, delegateType, facadeId, parameters); + if (to > 0) + binder.setTimeout(to); + return binder; } - } + @Override + public String toString() { + try (Formatter sb = new Formatter()) { - ActiveFactory active; + IExtension extension = config.getDeclaringExtension(); + if (extension != null) { + sb.format("ep=%s\n", extension.getLabel()); + } + sb.format("<%s %s='%s:%s' ...>\n", config.getName(), propertyName, FacadeUtil.this.getClass() + .getName(), data); - public FacadeUtil() { - if (context == null) { - Bundle bundle = FrameworkUtil.getBundle(FacadeUtil.class); - if (bundle != null) - context = bundle.getBundleContext(); + return sb.toString(); + } catch (Exception e) { + return e.toString(); + } } } + final Class delegateType; + ActiveFactory active; + + /** + * Called by the subclass to provide the service under which the facade + * component will register. + * + * @param delegateServiceInterface the interface (not class) of the service + */ + public FacadeUtil(Class delegateServiceInterface) { + assert delegateServiceInterface.isInterface(); + this.delegateType = delegateServiceInterface; + } + + /** + * Create an instance of the facade that is bound to the designated delegate + */ @Override public Object create() throws CoreException { - if (active == null) - fatal("proxy called but not yet initialized by " + IExecutableExtension.class.getSimpleName()); - - return active.create(); + assert active != null; + return createFacade(active::bind); } + /** + * parse the initialization data + */ @Override public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException { + assert active == null : "one time initialization expected"; try { String name = config.getName(); - - Map parameters = new Attributes((String) data); - String id = parameters.get(ATTR_ID); - String domain = parameters.get(ATTR_DOMAIN); - String bundle = parameters.get(ATTR_BUNDLE); - - logger.debug("intitialize factory <{} {}='...:{}' ...>", name, propertyName, data); - - if (id == null) - fatal("missing %s, EP <%s %s='%s' ... >", ATTR_ID, name, propertyName, data); - if (domain == null) - fatal("missing %s, EP <%s %s='%s' ... >", ATTR_DOMAIN, name, propertyName, data); - - Class domainType = findType(domain, bundle); - - this.active = new ActiveFactory(id, domainType, config, propertyName, data); + this.active = new ActiveFactory(config, propertyName, data); + logger.debug("intitialize factory %s", this.active); } catch (Exception e) { fatal("failed to initialize " + e); } } - - public static MethodHandle lookup(Class target, String name, Class... parameterTypes) { - return null; - } - - public interface FE { - Object apply() throws Throwable; - } - - public interface CE { - void apply() throws Throwable; - } - - public Object invokeReturn(FE f) { - return null; - } - - public Object invokeVoid(CE f) { - return null; - } - - protected abstract Object createDelegate(Function> bindFunction); - - - private Class findType(String proxy, @Nullable - String bundle) throws CoreException { - - assert proxy != null; - - Glob glob = Glob.ALL; - if (bundle != null) { - glob = new Glob(bundle); - } - - List interfaces = new ArrayList<>(); - if (context != null) { - for (Bundle b : context.getBundles()) { - if (bundle == null) { - String bsn = b.getSymbolicName(); - if (glob.matches(bsn)) { - try { - Class proxyClass = b.loadClass(proxy); - if (proxyClass.isInterface()) { - interfaces.add(proxyClass); - } - } catch (ClassNotFoundException e) { - // ignore - } - } - } - } - } else { - logger.warn("no bundle context"); - Class proxyClass; - try { - proxyClass = Class.forName(proxy); - if (proxyClass.isInterface()) { - interfaces.add(proxyClass); - } - } catch (ClassNotFoundException e) { - // ignore - } - } - if (interfaces.isEmpty()) - fatal("cannot find proxy class " + proxy + " in bundles matching " + glob); - logger.debug("found interfaces ", interfaces); - return interfaces.get(0); - } + protected abstract Object createFacade(Function> bindFunction); private void fatal(String message, Object... data) { String msg = String.format(""" - an %s is an %s. After its class name it expects a ':' - and the parameters. The parameter are: + Failed extension point with facade class %s. + """, this.getClass()); - id ::= the facade id - domain ::= - bundle ::= [optional] a glob of bundle symbolic names to load the domain type from - - parameters must be separated by a comma (','). - """, FacadeUtil.class.getSimpleName(), IExecutableExtension.class.getName()); + if (active != null) + msg = "\n" + active.toString(); String plain = String.format(message + "\n" + msg, data); - logger.error(plain); throw Exceptions.duck(new CoreException(Status.error(plain))); diff --git a/bndtools.facades/test/bndtools/facades/JTAGFacades.java b/bndtools.facades/test/bndtools/facades/JTAGFacades.java new file mode 100644 index 0000000000..caa96f6c81 --- /dev/null +++ b/bndtools.facades/test/bndtools/facades/JTAGFacades.java @@ -0,0 +1,12 @@ +package bndtools.facades; + +import java.util.function.Supplier; + +import bndtools.facades.IAdapterFactoryFacade.Delegate; + +public interface JTAGFacades { + + static Supplier facade(IAdapterFactoryFacade.Facade f) { + return f.bind; + } +} diff --git a/bndtools.facades/test/bndtools/facades/util/EclipseFacadeProxyTest.java b/bndtools.facades/test/bndtools/facades/util/EclipseFacadeProxyTest.java deleted file mode 100644 index 94b58b90fe..0000000000 --- a/bndtools.facades/test/bndtools/facades/util/EclipseFacadeProxyTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package bndtools.facades.util; - -import static org.junit.jupiter.api.Assertions.fail; - -import org.junit.jupiter.api.Test; - -class EclipseFacadeProxyTest { - - @Test - void test() { - EclipseFacadeProxy e = new EclipseFacadeProxy(); - - fail("Not yet implemented"); - } - -} diff --git a/bndtools.facades/test/bndtools/facades/util/EclipseFacadeTest.java b/bndtools.facades/test/bndtools/facades/util/EclipseFacadeTest.java new file mode 100644 index 0000000000..edb407a425 --- /dev/null +++ b/bndtools.facades/test/bndtools/facades/util/EclipseFacadeTest.java @@ -0,0 +1,47 @@ +package bndtools.facades.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.Supplier; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdapterFactory; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExecutableExtensionFactory; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import biz.aQute.bnd.facade.api.Binder; +import bndtools.facades.IAdapterFactoryFacade; +import bndtools.facades.IAdapterFactoryFacade.Delegate; +import bndtools.facades.IAdapterFactoryFacade.Facade; +import bndtools.facades.JTAGFacades; + +class EclipseFacadeTest { + + @Test + void testIAdapterFactoryFacade() throws CoreException { + IAdapterFactoryFacade a = new IAdapterFactoryFacade(); + assertThat(a).isInstanceOf(IExecutableExtensionFactory.class); + + IConfigurationElement config = Mockito.mock(IConfigurationElement.class); + Mockito.when(config.getName()) + .thenReturn("test"); + + a.setInitializationData(config, "property", "id=foo.bar,timeout=30,description='hello world'"); + + IAdapterFactoryFacade.Facade c = (Facade) a.create(); + assertThat(c).isInstanceOf(IAdapterFactory.class); + + Supplier supplier = JTAGFacades.facade(c); + assertThat(supplier).isInstanceOf(Binder.class); + @SuppressWarnings("resource") + Binder binder = (Binder) supplier; + assertThat(binder.getId()).isEqualTo("foo.bar"); + assertThat(binder.getTimeout()).isEqualTo(30_000_000_000L); + assertThat(binder.getFacade() + .get()).isEqualTo(c); + assertThat(binder.getDomainType()).isEqualTo(IAdapterFactoryFacade.Delegate.class); + } + +}