Skip to content

Commit 3bf764f

Browse files
authored
feat: Add OSGi support
1 parent 6edecf3 commit 3bf764f

File tree

311 files changed

+959
-229
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

311 files changed

+959
-229
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ jdks
1515
.bach/workspace
1616
.bach/external-tools
1717
.bach/external-modules
18+
bin
19+
.settings

build.gradle

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* SPDX-License-Identifier: Apache-2.0
33
*
4-
* Copyright 2015-2024 Andres Almiray
4+
* Copyright 2015-2025 Andres Almiray
55
*
66
* Licensed under the Apache License, Version 2.0 (the "License");
77
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
*/
1818
plugins {
1919
id 'com.google.osdetector'
20+
id 'biz.aQute.bnd.builder'
2021
}
2122

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

84+
jar {
85+
bundle {
86+
bnd '''
87+
Bundle-SymbolicName: ${project.name}
88+
Bundle-Name: ${project.name}
89+
Bundle-Version: ${project.version}
90+
91+
Import-Package: !javafx.*, *;
92+
Export-Package: *;
93+
Bundle-ActivationPolicy: lazy
94+
95+
# Remove private packages to prevent embedding
96+
Private-Package: !*
97+
98+
-nouses: true
99+
-noimportjava: true
100+
'''
101+
}
102+
}
103+
82104
dependencies {
105+
compileOnly 'org.osgi:osgi.core:8.0.0'
106+
compileOnly 'org.osgi:org.osgi.service.component.annotations:1.5.1'
83107
testImplementation 'junit:junit:4.13.2'
84108
}
85109

core/ikonli-core/src/main/java/module-info.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
exports org.kordamp.ikonli;
2020
requires static org.kordamp.jipsy.annotations;
2121
requires java.logging;
22+
requires static org.osgi.service.component.annotations;
23+
requires static osgi.core;
2224

2325
uses org.kordamp.ikonli.IkonHandler;
2426

core/ikonli-core/src/main/java/org/kordamp/ikonli/AbstractIkonResolver.java

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,88 +17,87 @@
1717
*/
1818
package org.kordamp.ikonli;
1919

20+
import static java.util.Objects.requireNonNull;
21+
2022
import java.util.Arrays;
2123
import java.util.ServiceLoader;
2224
import java.util.Set;
25+
import java.util.concurrent.CopyOnWriteArraySet;
2326
import java.util.logging.Logger;
2427

25-
import static java.util.Objects.requireNonNull;
26-
2728
/**
2829
* @author Andres Almiray
2930
*/
30-
public class AbstractIkonResolver {
31+
public class AbstractIkonResolver implements IkonResolver {
3132
private static final String ORG_KORDAMP_IKONLI_STRICT = "org.kordamp.ikonli.strict";
3233
private final static Logger LOGGER = Logger.getLogger(AbstractIkonResolver.class.getName());
3334

34-
protected boolean registerHandler(IkonHandler handler, Set<IkonHandler> handlers, Set<IkonHandler> customHandlers) {
35-
// check whether handler for this font is already loaded via classpath
36-
if (isLoadedViaClasspath(handler, handlers)) {
35+
protected final Set<IkonHandler> handlers = new CopyOnWriteArraySet<>();
36+
protected final Set<IkonHandler> customHandlers = new CopyOnWriteArraySet<>();
37+
38+
@Override
39+
public boolean registerHandler(IkonHandler handler) {
40+
requireNonNull(handler, "Handler must not be null");
41+
if (isHandlerLoadedViaClasspath(handler)) {
3742
throwOrWarn(String.format("IkonHandler for %s is already loaded via classpath", handler.getFontFamily()));
3843
return false;
3944
}
4045
return customHandlers.add(handler);
4146
}
4247

43-
protected boolean unregisterHandler(IkonHandler handler, Set<IkonHandler> handlers, Set<IkonHandler> customHandlers) {
44-
// check whether handler for this font is loaded via classpath
45-
if (isLoadedViaClasspath(handler, handlers)) {
48+
@Override
49+
public boolean unregisterHandler(IkonHandler handler) {
50+
requireNonNull(handler, "Handler must not be null");
51+
if (isHandlerLoadedViaClasspath(handler)) {
4652
throwOrWarn(String.format("IkonHandler for %s was loaded via classpath and can't be unregistered", handler.getFontFamily()));
4753
return false;
4854
}
4955
return customHandlers.remove(handler);
5056
}
5157

52-
protected IkonHandler resolve(String value, Set<IkonHandler> handlers, Set<IkonHandler> customHandlers) {
58+
@Override
59+
public IkonHandler resolve(String value) {
5360
requireNonNull(value, "Ikon description must not be null");
54-
for (Set<IkonHandler> hs : Arrays.asList(handlers, customHandlers)) {
55-
for (IkonHandler handler : hs) {
56-
if (handler.supports(value)) {
57-
return handler;
58-
}
59-
}
60-
}
61-
throw new UnsupportedOperationException("Cannot resolve '" + value + "'");
61+
return findHandler(value)
62+
.orElseThrow(() -> new UnsupportedOperationException("Cannot resolve '" + value + "'"));
6263
}
6364

64-
private boolean isLoadedViaClasspath(IkonHandler handler, Set<IkonHandler> handlers) {
65-
String fontFamily = handler.getFontFamily();
66-
for (IkonHandler classpathHandler : handlers) {
67-
if (classpathHandler.getFontFamily().equals(fontFamily)) {
68-
return true;
69-
}
70-
}
71-
return false;
65+
protected java.util.Optional<IkonHandler> findHandler(String value) {
66+
return Arrays.asList(handlers, customHandlers).stream()
67+
.flatMap(Set::stream)
68+
.filter(handler -> handler.supports(value))
69+
.findFirst();
70+
}
71+
72+
private boolean isHandlerLoadedViaClasspath(IkonHandler handler) {
73+
return handlers.stream()
74+
.anyMatch(h -> h.getFontFamily().equals(handler.getFontFamily()));
7275
}
7376

7477
private void throwOrWarn(String message) {
75-
if (strictChecksEnabled()) {
78+
if (Boolean.getBoolean(ORG_KORDAMP_IKONLI_STRICT)) {
7679
throw new IllegalArgumentException(message);
77-
} else {
78-
LOGGER.warning(message);
7980
}
80-
}
81-
82-
private boolean strictChecksEnabled() {
83-
return System.getProperty(ORG_KORDAMP_IKONLI_STRICT) == null || Boolean.getBoolean(ORG_KORDAMP_IKONLI_STRICT);
81+
LOGGER.warning(message);
8482
}
8583

8684
public static ServiceLoader<IkonHandler> resolveServiceLoader() {
8785
// Check if handlers must be loaded from a ModuleLayer
8886
if (null != IkonHandler.class.getModule().getLayer()) {
89-
ServiceLoader<IkonHandler> handlers = ServiceLoader.load(IkonHandler.class.getModule().getLayer(), IkonHandler.class);
90-
if (handlers.stream().count() > 0) {
91-
return handlers;
87+
ServiceLoader<IkonHandler> ikonHandlerServiceLoaders = ServiceLoader.load(
88+
IkonHandler.class.getModule().getLayer(),
89+
IkonHandler.class
90+
);
91+
if (ikonHandlerServiceLoaders.findFirst().isPresent()) {
92+
return ikonHandlerServiceLoaders;
9293
}
9394
}
9495

95-
// Check if the IkonHandler.class.classLoader works
96-
ServiceLoader<IkonHandler> handlers = ServiceLoader.load(IkonHandler.class, IkonHandler.class.getClassLoader());
97-
if (handlers.stream().count() > 0) {
98-
return handlers;
99-
}
100-
101-
// If *nothing* else works
102-
return ServiceLoader.load(IkonHandler.class);
96+
ServiceLoader<IkonHandler> handlers = ServiceLoader.load(
97+
IkonHandler.class,
98+
IkonHandler.class.getClassLoader()
99+
);
100+
// Return if the IkonHandler.class.classLoader works or if *nothing* else works
101+
return handlers.findFirst().isPresent() ? handlers : ServiceLoader.load(IkonHandler.class);
103102
}
104103
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* Copyright 2015-2025 Andres Almiray
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.kordamp.ikonli;
19+
20+
public class DefaultIkonResolver extends AbstractIkonResolver implements IkonResolver {
21+
private final FontLoader fontLoader;
22+
private static volatile DefaultIkonResolver instance;
23+
24+
protected DefaultIkonResolver(FontLoader fontLoader) {
25+
this.fontLoader = fontLoader;
26+
resolveServiceLoader().forEach(handler -> {
27+
handlers.add(handler);
28+
fontLoader.loadFont(handler);
29+
});
30+
}
31+
32+
@Override
33+
public boolean registerHandler(IkonHandler handler) {
34+
boolean result = super.registerHandler(handler);
35+
if (result) {
36+
fontLoader.loadFont(handler);
37+
}
38+
return result;
39+
}
40+
41+
public static IkonResolver getInstance(FontLoader fontLoader) {
42+
if (instance == null) {
43+
synchronized (DefaultIkonResolver.class) {
44+
if (instance == null) {
45+
instance = new DefaultIkonResolver(fontLoader);
46+
}
47+
}
48+
}
49+
return instance;
50+
}
51+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* Copyright 2015-2025 Andres Almiray
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.kordamp.ikonli;
19+
20+
public interface FontLoader {
21+
22+
void loadFont(IkonHandler handler);
23+
24+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* Copyright 2015-2025 Andres Almiray
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.kordamp.ikonli;
19+
20+
public interface IkonResolver {
21+
22+
IkonHandler resolve(String value);
23+
24+
boolean registerHandler(IkonHandler handler);
25+
26+
boolean unregisterHandler(IkonHandler handler);
27+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* Copyright 2015-2025 Andres Almiray
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.kordamp.ikonli;
19+
20+
import org.osgi.framework.Bundle;
21+
import org.osgi.framework.BundleContext;
22+
import org.osgi.framework.FrameworkUtil;
23+
import org.osgi.framework.ServiceReference;
24+
25+
public class IkonResolverProvider {
26+
private static volatile IkonResolver instance;
27+
28+
private IkonResolverProvider() {
29+
}
30+
31+
public static IkonResolver getInstance(FontLoader fontLoader) {
32+
IkonResolver localInstance = instance;
33+
if (localInstance == null) {
34+
synchronized (IkonResolverProvider.class) {
35+
localInstance = instance;
36+
if (localInstance == null) {
37+
instance = localInstance = createIkonResolver(fontLoader);
38+
}
39+
}
40+
}
41+
return localInstance;
42+
}
43+
44+
private static IkonResolver createIkonResolver(FontLoader fontLoader) {
45+
return tryGetOSGiResolver().orElseGet(() -> DefaultIkonResolver.getInstance(fontLoader));
46+
}
47+
48+
private static java.util.Optional<IkonResolver> tryGetOSGiResolver() {
49+
try {
50+
if (!isOSGiAvailable()) {
51+
return java.util.Optional.empty();
52+
}
53+
54+
Bundle bundle = FrameworkUtil.getBundle(IkonResolverProvider.class);
55+
if (bundle == null) {
56+
return java.util.Optional.empty();
57+
}
58+
59+
BundleContext context = bundle.getBundleContext();
60+
if (context == null) {
61+
return java.util.Optional.empty();
62+
}
63+
64+
ServiceReference<IkonResolver> ref = context.getServiceReference(IkonResolver.class);
65+
if (ref == null) {
66+
return java.util.Optional.empty();
67+
}
68+
69+
IkonResolver resolver = context.getService(ref);
70+
return java.util.Optional.ofNullable(resolver);
71+
72+
} catch (NoClassDefFoundError | IllegalStateException e) {
73+
return java.util.Optional.empty();
74+
}
75+
}
76+
77+
private static boolean isOSGiAvailable() {
78+
try {
79+
Class.forName("org.osgi.framework.BundleContext");
80+
return true;
81+
} catch (ClassNotFoundException e) {
82+
return false;
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)