Skip to content

Commit

Permalink
Merge pull request #26 from Fenrur/master
Browse files Browse the repository at this point in the history
Change some codes for to be Thread Safe
  • Loading branch information
Dansoftowner authored Jul 16, 2022
2 parents 7b726b8 + 8f2db06 commit 48fe772
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 32 deletions.
Empty file modified gradlew
100644 → 100755
Empty file.
15 changes: 8 additions & 7 deletions src/main/java/com/jthemedetecor/GnomeThemeDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package com.jthemedetecor;

import com.jthemedetecor.util.ConcurrentHashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
Expand All @@ -22,8 +23,6 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
Expand All @@ -42,10 +41,10 @@ class GnomeThemeDetector extends OsThemeDetector {
private static final String MONITORING_CMD = "gsettings monitor org.gnome.desktop.interface gtk-theme";
private static final String GET_CMD = "gsettings get org.gnome.desktop.interface gtk-theme";

private final Set<Consumer<Boolean>> listeners = Collections.synchronizedSet(new HashSet<>());
private final Set<Consumer<Boolean>> listeners = new ConcurrentHashSet<>();
private final Pattern darkThemeNamePattern = Pattern.compile(".*dark.*", Pattern.CASE_INSENSITIVE);

private DetectorThread detectorThread;
private volatile DetectorThread detectorThread;

@Override
public boolean isDark() {
Expand Down Expand Up @@ -74,11 +73,13 @@ public synchronized void registerListener(@NotNull Consumer<Boolean> darkThemeLi
Objects.requireNonNull(darkThemeListener);
final boolean listenerAdded = listeners.add(darkThemeListener);
final boolean singleListener = listenerAdded && listeners.size() == 1;
final boolean threadInterrupted = detectorThread != null && detectorThread.isInterrupted();
final DetectorThread currentDetectorThread = detectorThread;
final boolean threadInterrupted = currentDetectorThread != null && currentDetectorThread.isInterrupted();

if (singleListener || threadInterrupted) {
this.detectorThread = new DetectorThread(this);
this.detectorThread.start();
final DetectorThread newDetectorThread = new DetectorThread(this);
this.detectorThread = newDetectorThread;
newDetectorThread.start();
}
}

Expand Down
5 changes: 2 additions & 3 deletions src/main/java/com/jthemedetecor/MacOSThemeDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package com.jthemedetecor;

import com.jthemedetecor.util.ConcurrentHashSet;
import com.sun.jna.Callback;
import de.jangassen.jfa.foundation.Foundation;
import de.jangassen.jfa.foundation.ID;
Expand All @@ -22,8 +23,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
Expand All @@ -39,7 +38,7 @@ class MacOSThemeDetector extends OsThemeDetector {

private static final Logger logger = LoggerFactory.getLogger(MacOSThemeDetector.class);

private final Set<Consumer<Boolean>> listeners = Collections.synchronizedSet(new HashSet<>());
private final Set<Consumer<Boolean>> listeners = new ConcurrentHashSet<>();
private final Pattern themeNamePattern = Pattern.compile(".*dark.*", Pattern.CASE_INSENSITIVE);
private final ExecutorService callbackExecutor = Executors.newSingleThreadExecutor(DetectorThread::new);

Expand Down
38 changes: 29 additions & 9 deletions src/main/java/com/jthemedetecor/OsThemeDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import oshi.annotation.concurrent.ThreadSafe;

import java.util.function.Consumer;

Expand All @@ -31,28 +32,43 @@ public abstract class OsThemeDetector {

private static final Logger logger = LoggerFactory.getLogger(OsThemeDetector.class);

private static OsThemeDetector osThemeDetector;
private static volatile OsThemeDetector osThemeDetector;

OsThemeDetector() {
}

@NotNull
public static synchronized OsThemeDetector getDetector() {
if (osThemeDetector != null) {
return osThemeDetector;
} else if (OsInfo.isWindows10OrLater()) {
@ThreadSafe
public static OsThemeDetector getDetector() {
OsThemeDetector instance = osThemeDetector;

if (instance == null) {
synchronized (OsThemeDetector.class) {
instance = osThemeDetector;

if (instance == null) {
osThemeDetector = instance = createDetector();
}
}
}

return instance;
}

private static OsThemeDetector createDetector() {
if (OsInfo.isWindows10OrLater()) {
logDetection("Windows 10", WindowsThemeDetector.class);
return osThemeDetector = new WindowsThemeDetector();
return new WindowsThemeDetector();
} else if (OsInfo.isGnome()) {
logDetection("Gnome", GnomeThemeDetector.class);
return osThemeDetector = new GnomeThemeDetector();
return new GnomeThemeDetector();
} else if (OsInfo.isMacOsMojaveOrLater()) {
logDetection("MacOS", MacOSThemeDetector.class);
return osThemeDetector = new MacOSThemeDetector();
return new MacOSThemeDetector();
} else {
logger.debug("Theme detection is not supported on the system: {} {}", OsInfo.getFamily(), OsInfo.getVersion());
logger.debug("Creating empty detector...");
return osThemeDetector = new EmptyDetector();
return new EmptyDetector();
}
}

Expand All @@ -66,6 +82,7 @@ private static void logDetection(String desktop, Class<? extends OsThemeDetector
*
* @return {@code true} if the os uses dark theme; {@code false} otherwise.
*/
@ThreadSafe
public abstract boolean isDark();

/**
Expand All @@ -74,13 +91,16 @@ private static void logDetection(String desktop, Class<? extends OsThemeDetector
* @param darkThemeListener the {@link Consumer} that accepts a {@link Boolean} that represents
* that the os using a dark theme or not
*/
@ThreadSafe
public abstract void registerListener(@NotNull Consumer<Boolean> darkThemeListener);

/**
* Removes the listener.
*/
@ThreadSafe
public abstract void removeListener(@Nullable Consumer<Boolean> darkThemeListener);

@ThreadSafe
public static boolean isSupported() {
return OsInfo.isWindows10OrLater() || OsInfo.isMacOsMojaveOrLater() || OsInfo.isGnome();
}
Expand Down
22 changes: 9 additions & 13 deletions src/main/java/com/jthemedetecor/WindowsThemeDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,13 @@

package com.jthemedetecor;

import com.sun.jna.platform.win32.Advapi32;
import com.sun.jna.platform.win32.Advapi32Util;
import com.sun.jna.platform.win32.W32Errors;
import com.sun.jna.platform.win32.Win32Exception;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.platform.win32.WinReg;
import com.jthemedetecor.util.ConcurrentHashSet;
import com.sun.jna.platform.win32.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
Expand All @@ -45,8 +39,8 @@ class WindowsThemeDetector extends OsThemeDetector {
private static final String REGISTRY_PATH = "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
private static final String REGISTRY_VALUE = "AppsUseLightTheme";

private final Set<Consumer<Boolean>> listeners = Collections.synchronizedSet(new HashSet<>());
private DetectorThread detectorThread;
private final Set<Consumer<Boolean>> listeners = new ConcurrentHashSet<>();
private volatile DetectorThread detectorThread;

WindowsThemeDetector() {
}
Expand All @@ -63,11 +57,13 @@ public synchronized void registerListener(@NotNull Consumer<Boolean> darkThemeLi
Objects.requireNonNull(darkThemeListener);
final boolean listenerAdded = listeners.add(darkThemeListener);
final boolean singleListener = listenerAdded && listeners.size() == 1;
final boolean threadInterrupted = detectorThread != null && detectorThread.isInterrupted();
final DetectorThread currentDetectorThread = detectorThread;
final boolean threadInterrupted = currentDetectorThread != null && currentDetectorThread.isInterrupted();

if (singleListener || threadInterrupted) {
this.detectorThread = new DetectorThread(this);
this.detectorThread.start();
final DetectorThread newDetectorThread = new DetectorThread(this);
this.detectorThread = newDetectorThread;
newDetectorThread.start();
}
}

Expand Down
103 changes: 103 additions & 0 deletions src/main/java/com/jthemedetecor/util/ConcurrentHashSet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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 com.jthemedetecor.util;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashSet<E> implements Set<E> {

private final Map<E, Object> map;

private static final Object OBJ = new Object();

public ConcurrentHashSet() {
map = new ConcurrentHashMap<>();
}

@Override
public int size() {
return map.size();
}

@Override
public boolean isEmpty() {
return map.isEmpty();
}

@Override
public boolean contains(Object o) {
return map.containsKey(o);
}

@Override
public Iterator<E> iterator() {
return map.keySet().iterator();
}

@Override
public Object[] toArray() {
return map.keySet().toArray();
}

@Override
public <T> T[] toArray(T[] a) {
return map.keySet().toArray(a);
}

@Override
public boolean add(E e) {
return map.put(e, OBJ) == null;
}

@Override
public boolean remove(Object o) {
return map.remove(o) != null;
}

@Override
public boolean containsAll(Collection<?> c) {
return map.keySet().containsAll(c);
}

@Override
public boolean addAll(Collection<? extends E> c) {
boolean changed = false;
for (E e: c) {
if (map.put(e, OBJ) == null) {
changed = true;
}
}
return changed;
}

@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException();
}

@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException();
}

@Override
public void clear() {
map.clear();
}
}

0 comments on commit 48fe772

Please sign in to comment.