use annotations processor to create extensions
This commit is contained in:
parent
da65960fd1
commit
bfa61d56af
|
@ -47,6 +47,9 @@ dependencies {
|
||||||
|
|
||||||
def room_version = "2.5.0"
|
def room_version = "2.5.0"
|
||||||
|
|
||||||
|
implementation project(':libs:annotation')
|
||||||
|
annotationProcessor project(':libs:annotation-processor')
|
||||||
|
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8'
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:$room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
|
|
21
libs/annotation-processor/build.gradle
Normal file
21
libs/annotation-processor/build.gradle
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
implementation project(':libs:annotation')
|
||||||
|
|
||||||
|
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc5'
|
||||||
|
compileOnly 'com.google.auto.service:auto-service:1.0-rc5'
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
package im.conversations.android.annotation.processor;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.annotation.XmlPackage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.annotation.processing.AbstractProcessor;
|
||||||
|
import javax.annotation.processing.Processor;
|
||||||
|
import javax.annotation.processing.RoundEnvironment;
|
||||||
|
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||||
|
import javax.annotation.processing.SupportedSourceVersion;
|
||||||
|
import javax.lang.model.SourceVersion;
|
||||||
|
import javax.lang.model.element.Element;
|
||||||
|
import javax.lang.model.element.PackageElement;
|
||||||
|
import javax.lang.model.element.TypeElement;
|
||||||
|
import javax.tools.JavaFileObject;
|
||||||
|
|
||||||
|
@AutoService(Processor.class)
|
||||||
|
@SupportedSourceVersion(SourceVersion.RELEASE_11)
|
||||||
|
@SupportedAnnotationTypes("im.conversations.android.annotation.XmlElement")
|
||||||
|
public class XmlElementProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
|
||||||
|
final Set<? extends Element> elements =
|
||||||
|
roundEnvironment.getElementsAnnotatedWith(XmlElement.class);
|
||||||
|
final ImmutableMap.Builder<Id, String> builder = ImmutableMap.<Id, String>builder();
|
||||||
|
for (final Element element : elements) {
|
||||||
|
if (element instanceof TypeElement) {
|
||||||
|
final TypeElement typeElement = (TypeElement) element;
|
||||||
|
final Id id = of(typeElement);
|
||||||
|
builder.put(id, typeElement.getQualifiedName().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final ImmutableMap<Id, String> maps = builder.build();
|
||||||
|
if (maps.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final JavaFileObject extensionFile;
|
||||||
|
try {
|
||||||
|
extensionFile =
|
||||||
|
processingEnv
|
||||||
|
.getFiler()
|
||||||
|
.createSourceFile("im.conversations.android.xmpp.Extensions");
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
try (final PrintWriter out = new PrintWriter(extensionFile.openWriter())) {
|
||||||
|
out.println("package im.conversations.android.xmpp;");
|
||||||
|
out.println("import com.google.common.collect.BiMap;");
|
||||||
|
out.println("import com.google.common.collect.ImmutableBiMap;");
|
||||||
|
out.println("import im.conversations.android.xmpp.ExtensionFactory;");
|
||||||
|
out.println("import im.conversations.android.xmpp.model.Extension;");
|
||||||
|
out.print("\n");
|
||||||
|
out.println("public final class Extensions {");
|
||||||
|
out.println(
|
||||||
|
"public static final BiMap<ExtensionFactory.Id, Class<? extends Extension>>"
|
||||||
|
+ " EXTENSION_CLASS_MAP;");
|
||||||
|
out.println("static {");
|
||||||
|
out.println(
|
||||||
|
"final var builder = new ImmutableBiMap.Builder<ExtensionFactory.Id, Class<?"
|
||||||
|
+ " extends Extension>>();");
|
||||||
|
for (final Map.Entry<Id, String> entry : maps.entrySet()) {
|
||||||
|
Id id = entry.getKey();
|
||||||
|
String clazz = entry.getValue();
|
||||||
|
out.format(
|
||||||
|
"builder.put(new ExtensionFactory.Id(\"%s\",\"%s\"),%s.class);",
|
||||||
|
id.name, id.namespace, clazz);
|
||||||
|
out.print("\n");
|
||||||
|
}
|
||||||
|
out.println("EXTENSION_CLASS_MAP = builder.build();");
|
||||||
|
out.println("}");
|
||||||
|
out.println(" private Extensions() {}");
|
||||||
|
out.println("}");
|
||||||
|
// writing generated file to out …
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Id of(final TypeElement typeElement) {
|
||||||
|
final XmlElement xmlElement = typeElement.getAnnotation(XmlElement.class);
|
||||||
|
PackageElement packageElement = (PackageElement) typeElement.getEnclosingElement();
|
||||||
|
XmlPackage xmlPackage = packageElement.getAnnotation(XmlPackage.class);
|
||||||
|
if (xmlElement == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format(
|
||||||
|
"%s is not annotated as @XmlElement",
|
||||||
|
typeElement.getQualifiedName().toString()));
|
||||||
|
}
|
||||||
|
final String packageNamespace = xmlPackage == null ? null : xmlPackage.namespace();
|
||||||
|
final String elementName = xmlElement.name();
|
||||||
|
final String elementNamespace = xmlElement.namespace();
|
||||||
|
final String namespace;
|
||||||
|
if (!Strings.isNullOrEmpty(elementNamespace)) {
|
||||||
|
namespace = elementNamespace;
|
||||||
|
} else if (!Strings.isNullOrEmpty(packageNamespace)) {
|
||||||
|
namespace = packageNamespace;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format(
|
||||||
|
"%s does not declare a namespace",
|
||||||
|
typeElement.getQualifiedName().toString()));
|
||||||
|
}
|
||||||
|
final String name;
|
||||||
|
if (Strings.isNullOrEmpty(elementName)) {
|
||||||
|
name = typeElement.getSimpleName().toString().toLowerCase(Locale.ROOT);
|
||||||
|
} else {
|
||||||
|
name = elementName;
|
||||||
|
}
|
||||||
|
return new Id(name, namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Id {
|
||||||
|
public final String name;
|
||||||
|
public final String namespace;
|
||||||
|
|
||||||
|
public Id(String name, String namespace) {
|
||||||
|
this.name = name;
|
||||||
|
this.namespace = namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Id id = (Id) o;
|
||||||
|
return Objects.equal(name, id.name) && Objects.equal(namespace, id.namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(name, namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
libs/annotation/build.gradle
Normal file
8
libs/annotation/build.gradle
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_7
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_7
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Target({ElementType.TYPE})
|
@Target({ElementType.TYPE})
|
||||||
public @interface XmlElement {
|
public @interface XmlElement {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Target(ElementType.PACKAGE)
|
@Target(ElementType.PACKAGE)
|
||||||
public @interface XmlPackage {
|
public @interface XmlPackage {
|
||||||
String namespace();
|
String namespace();
|
|
@ -1 +1,3 @@
|
||||||
|
include ':libs:annotation'
|
||||||
|
include ':libs:annotation-processor'
|
||||||
rootProject.name = 'Conversations'
|
rootProject.name = 'Conversations'
|
||||||
|
|
|
@ -3,7 +3,7 @@ package eu.siacs.conversations.xml;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Xml;
|
import android.util.Xml;
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import im.conversations.android.xmpp.Extensions;
|
import im.conversations.android.xmpp.ExtensionFactory;
|
||||||
import im.conversations.android.xmpp.model.Extension;
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -106,7 +106,7 @@ public class XmlReader implements Closeable {
|
||||||
final var attributes = currentTag.getAttributes();
|
final var attributes = currentTag.getAttributes();
|
||||||
final var namespace = attributes.get("xmlns");
|
final var namespace = attributes.get("xmlns");
|
||||||
final var name = currentTag.getName();
|
final var name = currentTag.getName();
|
||||||
final Element element = Extensions.create(name, namespace);
|
final Element element = ExtensionFactory.create(name, namespace);
|
||||||
element.setAttributes(currentTag.getAttributes());
|
element.setAttributes(currentTag.getAttributes());
|
||||||
Tag nextTag = this.readTag();
|
Tag nextTag = this.readTag();
|
||||||
if (nextTag == null) {
|
if (nextTag == null) {
|
||||||
|
|
|
@ -201,7 +201,8 @@ public class CredentialStore {
|
||||||
return getEncryptedFile(getCredentialStoreFile());
|
return getEncryptedFile(getCredentialStoreFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
private EncryptedFile getEncryptedFile(final File file) throws GeneralSecurityException, IOException {
|
private EncryptedFile getEncryptedFile(final File file)
|
||||||
|
throws GeneralSecurityException, IOException {
|
||||||
final KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
|
final KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
|
||||||
final String mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);
|
final String mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);
|
||||||
return new EncryptedFile.Builder(
|
return new EncryptedFile.Builder(
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
public final class ExtensionFactory {
|
||||||
|
|
||||||
|
public static Element create(final String name, final String namespace) {
|
||||||
|
final Class<? extends Element> clazz = of(name, namespace);
|
||||||
|
if (clazz == null) {
|
||||||
|
return new Element(name, namespace);
|
||||||
|
}
|
||||||
|
final Constructor<? extends Element> constructor;
|
||||||
|
try {
|
||||||
|
constructor = clazz.getDeclaredConstructor();
|
||||||
|
} catch (final NoSuchMethodException e) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format("%s has no default constructor", clazz.getName()));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return constructor.newInstance();
|
||||||
|
} catch (final IllegalAccessException
|
||||||
|
| InstantiationException
|
||||||
|
| InvocationTargetException e) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format("%s has inaccessible default constructor", clazz.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<? extends Element> of(final String name, final String namespace) {
|
||||||
|
return Extensions.EXTENSION_CLASS_MAP.get(new Id(name, namespace));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Id id(final Class<? extends Extension> clazz) {
|
||||||
|
return Extensions.EXTENSION_CLASS_MAP.inverse().get(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExtensionFactory() {}
|
||||||
|
|
||||||
|
public static class Id {
|
||||||
|
public final String name;
|
||||||
|
public final String namespace;
|
||||||
|
|
||||||
|
public Id(String name, String namespace) {
|
||||||
|
this.name = name;
|
||||||
|
this.namespace = namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Id id = (Id) o;
|
||||||
|
return Objects.equal(name, id.name) && Objects.equal(namespace, id.namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(name, namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,135 +0,0 @@
|
||||||
package im.conversations.android.xmpp;
|
|
||||||
|
|
||||||
import com.google.common.base.Objects;
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.google.common.collect.BiMap;
|
|
||||||
import com.google.common.collect.ImmutableBiMap;
|
|
||||||
import eu.siacs.conversations.xml.Element;
|
|
||||||
import im.conversations.android.annotation.XmlElement;
|
|
||||||
import im.conversations.android.annotation.XmlPackage;
|
|
||||||
import im.conversations.android.xmpp.model.Extension;
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public final class Extensions {
|
|
||||||
|
|
||||||
// TODO these two maps can easily be generated by an annotation processor
|
|
||||||
private static final List<Class<? extends Extension>> ELEMENTS =
|
|
||||||
Arrays.asList(
|
|
||||||
im.conversations.android.xmpp.model.blocking.Item.class,
|
|
||||||
im.conversations.android.xmpp.model.blocking.Block.class,
|
|
||||||
im.conversations.android.xmpp.model.blocking.Blocklist.class,
|
|
||||||
im.conversations.android.xmpp.model.blocking.Unblock.class,
|
|
||||||
im.conversations.android.xmpp.model.capabilties.LegacyCapabilities.class,
|
|
||||||
im.conversations.android.xmpp.model.capabilties.Capabilities.class,
|
|
||||||
im.conversations.android.xmpp.model.data.Data.class,
|
|
||||||
im.conversations.android.xmpp.model.data.Field.class,
|
|
||||||
im.conversations.android.xmpp.model.data.Value.class,
|
|
||||||
im.conversations.android.xmpp.model.disco.info.Feature.class,
|
|
||||||
im.conversations.android.xmpp.model.disco.info.Identity.class,
|
|
||||||
im.conversations.android.xmpp.model.disco.info.InfoQuery.class,
|
|
||||||
im.conversations.android.xmpp.model.disco.items.Item.class,
|
|
||||||
im.conversations.android.xmpp.model.disco.items.ItemsQuery.class,
|
|
||||||
im.conversations.android.xmpp.model.roster.Query.class,
|
|
||||||
im.conversations.android.xmpp.model.roster.Item.class,
|
|
||||||
im.conversations.android.xmpp.model.streams.Features.class,
|
|
||||||
im.conversations.android.xmpp.model.Hash.class);
|
|
||||||
|
|
||||||
private static final BiMap<Id, Class<? extends Extension>> EXTENSION_CLASS_MAP;
|
|
||||||
|
|
||||||
static {
|
|
||||||
final var builder = new ImmutableBiMap.Builder<Id, Class<? extends Extension>>();
|
|
||||||
for (final Class<? extends Extension> clazz : ELEMENTS) {
|
|
||||||
builder.put(of(clazz), clazz);
|
|
||||||
}
|
|
||||||
EXTENSION_CLASS_MAP = builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Id of(final Class<? extends Extension> clazz) {
|
|
||||||
final XmlElement xmlElement = clazz.getAnnotation(XmlElement.class);
|
|
||||||
final Package clazzPackage = clazz.getPackage();
|
|
||||||
final XmlPackage xmlPackage =
|
|
||||||
clazzPackage == null ? null : clazzPackage.getAnnotation(XmlPackage.class);
|
|
||||||
if (xmlElement == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
String.format("%s is not annotated as @XmlElement", clazz.getName()));
|
|
||||||
}
|
|
||||||
final String packageNamespace = xmlPackage == null ? null : xmlPackage.namespace();
|
|
||||||
final String elementName = xmlElement.name();
|
|
||||||
final String elementNamespace = xmlElement.namespace();
|
|
||||||
final String namespace;
|
|
||||||
if (!Strings.isNullOrEmpty(elementNamespace)) {
|
|
||||||
namespace = elementNamespace;
|
|
||||||
} else if (!Strings.isNullOrEmpty(packageNamespace)) {
|
|
||||||
namespace = packageNamespace;
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
String.format("%s does not declare a namespace", clazz.getName()));
|
|
||||||
}
|
|
||||||
final String name;
|
|
||||||
if (Strings.isNullOrEmpty(elementName)) {
|
|
||||||
name = clazz.getSimpleName().toLowerCase(Locale.ROOT);
|
|
||||||
} else {
|
|
||||||
name = elementName;
|
|
||||||
}
|
|
||||||
return new Id(name, namespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Element create(final String name, final String namespace) {
|
|
||||||
final Class<? extends Element> clazz = of(name, namespace);
|
|
||||||
if (clazz == null) {
|
|
||||||
return new Element(name, namespace);
|
|
||||||
}
|
|
||||||
final Constructor<? extends Element> constructor;
|
|
||||||
try {
|
|
||||||
constructor = clazz.getDeclaredConstructor();
|
|
||||||
} catch (final NoSuchMethodException e) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
String.format("%s has no default constructor", clazz.getName()));
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return constructor.newInstance();
|
|
||||||
} catch (final IllegalAccessException
|
|
||||||
| InstantiationException
|
|
||||||
| InvocationTargetException e) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
String.format("%s has inaccessible default constructor", clazz.getName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Class<? extends Element> of(final String name, final String namespace) {
|
|
||||||
return EXTENSION_CLASS_MAP.get(new Id(name, namespace));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Id id(final Class<? extends Extension> clazz) {
|
|
||||||
return EXTENSION_CLASS_MAP.inverse().get(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Extensions() {}
|
|
||||||
|
|
||||||
public static class Id {
|
|
||||||
public final String name;
|
|
||||||
public final String namespace;
|
|
||||||
|
|
||||||
public Id(String name, String namespace) {
|
|
||||||
this.name = name;
|
|
||||||
this.namespace = namespace;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
Id id = (Id) o;
|
|
||||||
return Objects.equal(name, id.name) && Objects.equal(namespace, id.namespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hashCode(name, namespace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +1,15 @@
|
||||||
package im.conversations.android.xmpp.model;
|
package im.conversations.android.xmpp.model;
|
||||||
|
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import im.conversations.android.xmpp.Extensions;
|
import im.conversations.android.xmpp.ExtensionFactory;
|
||||||
|
|
||||||
public class Extension extends Element {
|
public class Extension extends Element {
|
||||||
|
|
||||||
private Extension(final Extensions.Id id) {
|
private Extension(final ExtensionFactory.Id id) {
|
||||||
super(id.name, id.namespace);
|
super(id.name, id.namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Extension(final Class<? extends Extension> clazz) {
|
public Extension(final Class<? extends Extension> clazz) {
|
||||||
this(Extensions.id(clazz));
|
this(ExtensionFactory.id(clazz));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue