Skip to content

Commit

Permalink
feat: Add OSGi support
Browse files Browse the repository at this point in the history
  • Loading branch information
konczdev authored Jan 27, 2025
1 parent 6edecf3 commit 3bf764f
Show file tree
Hide file tree
Showing 311 changed files with 959 additions and 229 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ jdks
.bach/workspace
.bach/external-tools
.bach/external-modules
bin
.settings
26 changes: 25 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2015-2024 Andres Almiray
* Copyright 2015-2025 Andres Almiray
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@
*/
plugins {
id 'com.google.osdetector'
id 'biz.aQute.bnd.builder'
}

ext.javafx_platform = osdetector.os == 'osx' ? 'mac' : osdetector.os == 'windows' ? 'win' : osdetector.os
Expand Down Expand Up @@ -78,8 +79,31 @@ idea {
subprojects { subproj ->
if (!subproj.name.contains('guide') && !subproj.name.contains('bom')) {
apply plugin: 'java-library'
apply plugin: 'biz.aQute.bnd.builder' // Apply BND to subprojects

jar {
bundle {
bnd '''
Bundle-SymbolicName: ${project.name}
Bundle-Name: ${project.name}
Bundle-Version: ${project.version}
Import-Package: !javafx.*, *;
Export-Package: *;
Bundle-ActivationPolicy: lazy
# Remove private packages to prevent embedding
Private-Package: !*
-nouses: true
-noimportjava: true
'''
}
}

dependencies {
compileOnly 'org.osgi:osgi.core:8.0.0'
compileOnly 'org.osgi:org.osgi.service.component.annotations:1.5.1'
testImplementation 'junit:junit:4.13.2'
}

Expand Down
2 changes: 2 additions & 0 deletions core/ikonli-core/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
exports org.kordamp.ikonli;
requires static org.kordamp.jipsy.annotations;
requires java.logging;
requires static org.osgi.service.component.annotations;
requires static osgi.core;

uses org.kordamp.ikonli.IkonHandler;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,88 +17,87 @@
*/
package org.kordamp.ikonli;

import static java.util.Objects.requireNonNull;

import java.util.Arrays;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Logger;

import static java.util.Objects.requireNonNull;

/**
* @author Andres Almiray
*/
public class AbstractIkonResolver {
public class AbstractIkonResolver implements IkonResolver {
private static final String ORG_KORDAMP_IKONLI_STRICT = "org.kordamp.ikonli.strict";
private final static Logger LOGGER = Logger.getLogger(AbstractIkonResolver.class.getName());

protected boolean registerHandler(IkonHandler handler, Set<IkonHandler> handlers, Set<IkonHandler> customHandlers) {
// check whether handler for this font is already loaded via classpath
if (isLoadedViaClasspath(handler, handlers)) {
protected final Set<IkonHandler> handlers = new CopyOnWriteArraySet<>();
protected final Set<IkonHandler> customHandlers = new CopyOnWriteArraySet<>();

@Override
public boolean registerHandler(IkonHandler handler) {
requireNonNull(handler, "Handler must not be null");
if (isHandlerLoadedViaClasspath(handler)) {
throwOrWarn(String.format("IkonHandler for %s is already loaded via classpath", handler.getFontFamily()));
return false;
}
return customHandlers.add(handler);
}

protected boolean unregisterHandler(IkonHandler handler, Set<IkonHandler> handlers, Set<IkonHandler> customHandlers) {
// check whether handler for this font is loaded via classpath
if (isLoadedViaClasspath(handler, handlers)) {
@Override
public boolean unregisterHandler(IkonHandler handler) {
requireNonNull(handler, "Handler must not be null");
if (isHandlerLoadedViaClasspath(handler)) {
throwOrWarn(String.format("IkonHandler for %s was loaded via classpath and can't be unregistered", handler.getFontFamily()));
return false;
}
return customHandlers.remove(handler);
}

protected IkonHandler resolve(String value, Set<IkonHandler> handlers, Set<IkonHandler> customHandlers) {
@Override
public IkonHandler resolve(String value) {
requireNonNull(value, "Ikon description must not be null");
for (Set<IkonHandler> hs : Arrays.asList(handlers, customHandlers)) {
for (IkonHandler handler : hs) {
if (handler.supports(value)) {
return handler;
}
}
}
throw new UnsupportedOperationException("Cannot resolve '" + value + "'");
return findHandler(value)
.orElseThrow(() -> new UnsupportedOperationException("Cannot resolve '" + value + "'"));
}

private boolean isLoadedViaClasspath(IkonHandler handler, Set<IkonHandler> handlers) {
String fontFamily = handler.getFontFamily();
for (IkonHandler classpathHandler : handlers) {
if (classpathHandler.getFontFamily().equals(fontFamily)) {
return true;
}
}
return false;
protected java.util.Optional<IkonHandler> findHandler(String value) {
return Arrays.asList(handlers, customHandlers).stream()
.flatMap(Set::stream)
.filter(handler -> handler.supports(value))
.findFirst();
}

private boolean isHandlerLoadedViaClasspath(IkonHandler handler) {
return handlers.stream()
.anyMatch(h -> h.getFontFamily().equals(handler.getFontFamily()));
}

private void throwOrWarn(String message) {
if (strictChecksEnabled()) {
if (Boolean.getBoolean(ORG_KORDAMP_IKONLI_STRICT)) {
throw new IllegalArgumentException(message);
} else {
LOGGER.warning(message);
}
}

private boolean strictChecksEnabled() {
return System.getProperty(ORG_KORDAMP_IKONLI_STRICT) == null || Boolean.getBoolean(ORG_KORDAMP_IKONLI_STRICT);
LOGGER.warning(message);
}

public static ServiceLoader<IkonHandler> resolveServiceLoader() {
// Check if handlers must be loaded from a ModuleLayer
if (null != IkonHandler.class.getModule().getLayer()) {
ServiceLoader<IkonHandler> handlers = ServiceLoader.load(IkonHandler.class.getModule().getLayer(), IkonHandler.class);
if (handlers.stream().count() > 0) {
return handlers;
ServiceLoader<IkonHandler> ikonHandlerServiceLoaders = ServiceLoader.load(
IkonHandler.class.getModule().getLayer(),
IkonHandler.class
);
if (ikonHandlerServiceLoaders.findFirst().isPresent()) {
return ikonHandlerServiceLoaders;
}
}

// Check if the IkonHandler.class.classLoader works
ServiceLoader<IkonHandler> handlers = ServiceLoader.load(IkonHandler.class, IkonHandler.class.getClassLoader());
if (handlers.stream().count() > 0) {
return handlers;
}

// If *nothing* else works
return ServiceLoader.load(IkonHandler.class);
ServiceLoader<IkonHandler> handlers = ServiceLoader.load(
IkonHandler.class,
IkonHandler.class.getClassLoader()
);
// Return if the IkonHandler.class.classLoader works or if *nothing* else works
return handlers.findFirst().isPresent() ? handlers : ServiceLoader.load(IkonHandler.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2015-2025 Andres Almiray
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kordamp.ikonli;

public class DefaultIkonResolver extends AbstractIkonResolver implements IkonResolver {
private final FontLoader fontLoader;
private static volatile DefaultIkonResolver instance;

protected DefaultIkonResolver(FontLoader fontLoader) {
this.fontLoader = fontLoader;
resolveServiceLoader().forEach(handler -> {
handlers.add(handler);
fontLoader.loadFont(handler);
});
}

@Override
public boolean registerHandler(IkonHandler handler) {
boolean result = super.registerHandler(handler);
if (result) {
fontLoader.loadFont(handler);
}
return result;
}

public static IkonResolver getInstance(FontLoader fontLoader) {
if (instance == null) {
synchronized (DefaultIkonResolver.class) {
if (instance == null) {
instance = new DefaultIkonResolver(fontLoader);
}
}
}
return instance;
}
}
24 changes: 24 additions & 0 deletions core/ikonli-core/src/main/java/org/kordamp/ikonli/FontLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2015-2025 Andres Almiray
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kordamp.ikonli;

public interface FontLoader {

void loadFont(IkonHandler handler);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2015-2025 Andres Almiray
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kordamp.ikonli;

public interface IkonResolver {

IkonHandler resolve(String value);

boolean registerHandler(IkonHandler handler);

boolean unregisterHandler(IkonHandler handler);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2015-2025 Andres Almiray
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kordamp.ikonli;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;

public class IkonResolverProvider {
private static volatile IkonResolver instance;

private IkonResolverProvider() {
}

public static IkonResolver getInstance(FontLoader fontLoader) {
IkonResolver localInstance = instance;
if (localInstance == null) {
synchronized (IkonResolverProvider.class) {
localInstance = instance;
if (localInstance == null) {
instance = localInstance = createIkonResolver(fontLoader);
}
}
}
return localInstance;
}

private static IkonResolver createIkonResolver(FontLoader fontLoader) {
return tryGetOSGiResolver().orElseGet(() -> DefaultIkonResolver.getInstance(fontLoader));
}

private static java.util.Optional<IkonResolver> tryGetOSGiResolver() {
try {
if (!isOSGiAvailable()) {
return java.util.Optional.empty();
}

Bundle bundle = FrameworkUtil.getBundle(IkonResolverProvider.class);
if (bundle == null) {
return java.util.Optional.empty();
}

BundleContext context = bundle.getBundleContext();
if (context == null) {
return java.util.Optional.empty();
}

ServiceReference<IkonResolver> ref = context.getServiceReference(IkonResolver.class);
if (ref == null) {
return java.util.Optional.empty();
}

IkonResolver resolver = context.getService(ref);
return java.util.Optional.ofNullable(resolver);

} catch (NoClassDefFoundError | IllegalStateException e) {
return java.util.Optional.empty();
}
}

private static boolean isOSGiAvailable() {
try {
Class.forName("org.osgi.framework.BundleContext");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
Loading

0 comments on commit 3bf764f

Please sign in to comment.