diff --git a/src/main/java/org/cryptomator/integrations/common/DisplayName.java b/src/main/java/org/cryptomator/integrations/common/DisplayName.java new file mode 100644 index 0000000..7389220 --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/common/DisplayName.java @@ -0,0 +1,25 @@ +package org.cryptomator.integrations.common; + +import org.jetbrains.annotations.ApiStatus; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A humanreadable name of the annotated class. + *
+ * Checked in the default implementation of the {@link NamedServiceProvider#getName()} with lower priority. + * + * @see NamedServiceProvider + * @see LocalizedDisplayName + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@ApiStatus.Experimental +public @interface DisplayName { + String value(); +} diff --git a/src/main/java/org/cryptomator/integrations/common/LocalizedDisplayName.java b/src/main/java/org/cryptomator/integrations/common/LocalizedDisplayName.java new file mode 100644 index 0000000..3a05603 --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/common/LocalizedDisplayName.java @@ -0,0 +1,39 @@ +package org.cryptomator.integrations.common; + +import org.jetbrains.annotations.ApiStatus; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A humanreadable, localized name of the annotated class. + *
+ * Checked in the default implementation of the {@link NamedServiceProvider#getName()} with highest priority. + * + * @see NamedServiceProvider + * @see DisplayName + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@ApiStatus.Experimental +public @interface LocalizedDisplayName { + + /** + * Name of the localization bundle, where the display name is loaded from. + * + * @return Name of the localization bundle + */ + String bundle(); + + /** + * The localization key containing the display name. + * + * @return Localization key to use + */ + String key(); + +} diff --git a/src/main/java/org/cryptomator/integrations/common/NamedServiceProvider.java b/src/main/java/org/cryptomator/integrations/common/NamedServiceProvider.java new file mode 100644 index 0000000..692192b --- /dev/null +++ b/src/main/java/org/cryptomator/integrations/common/NamedServiceProvider.java @@ -0,0 +1,41 @@ +package org.cryptomator.integrations.common; + +import org.slf4j.LoggerFactory; + +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * A service provider with a human-readable, possibly localized name. + */ +public interface NamedServiceProvider { + + /** + * Get the name of this service provider. + * + * @return The name of the service provider + * @implNote The default implementation looks first for a {@link LocalizedDisplayName} and loads the name from the specified resource bundle/key. If the annotation is not present or loading the resource throws an exception, the code looks for {@link DisplayName} and uses its value. If none of the former annotations are present, it falls back to the qualified class name. + * @see DisplayName + * @see LocalizedDisplayName + */ + default String getName() { + var localizedDisplayName = this.getClass().getAnnotation(LocalizedDisplayName.class); + if (localizedDisplayName != null) { + try { + return ResourceBundle.getBundle(localizedDisplayName.bundle()) // + .getString(localizedDisplayName.key()); + } catch (MissingResourceException e) { + var clazz = this.getClass(); + var logger = LoggerFactory.getLogger(clazz); + logger.warn("Failed to load localized display name for {}. Falling back to not-localized display name/class name.", clazz.getName(), e); + } + } + + var displayName = this.getClass().getAnnotation(DisplayName.class); + if (displayName != null) { + return displayName.value(); + } else { + return this.getClass().getName(); + } + } +}