diff --git a/src/io/nayuki/fastqrcodegen/Memoizer.java b/src/io/nayuki/fastqrcodegen/Memoizer.java new file mode 100644 index 0000000..cbb7455 --- /dev/null +++ b/src/io/nayuki/fastqrcodegen/Memoizer.java @@ -0,0 +1,88 @@ +/* + * Fast QR Code generator library + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/fast-qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +package io.nayuki.fastqrcodegen; + +import java.lang.ref.SoftReference; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + + +// A thread-safe cache based on soft references. +final class Memoizer { + + private final Function function; + private Map> cache = new HashMap<>(); + private Set pending = new HashSet<>(); + + + // Creates a memoizer based on the given function that takes one input to compute an output. + public Memoizer(Function func) { + function = func; + } + + + // Computes function.apply(arg) or returns a cached copy of a previous call. + public R get(T arg) { + while (true) { + synchronized(this) { + if (cache.containsKey(arg)) { + SoftReference ref = cache.get(arg); + R result = ref.get(); + if (result != null) + return result; + cache.remove(arg); + } + // Now cache.containsKey(arg) == false + + if (!pending.contains(arg)) { + pending.add(arg); + break; + } + + try { + this.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + try { + R result = function.apply(arg); + synchronized(this) { + cache.put(arg, new SoftReference<>(result)); + } + return result; + } finally { + synchronized(this) { + pending.remove(arg); + this.notifyAll(); + } + } + } + +} diff --git a/src/io/nayuki/fastqrcodegen/QrCode.java b/src/io/nayuki/fastqrcodegen/QrCode.java index 7825632..e25f377 100644 --- a/src/io/nayuki/fastqrcodegen/QrCode.java +++ b/src/io/nayuki/fastqrcodegen/QrCode.java @@ -248,7 +248,7 @@ public final class QrCode { errorCorrectionLevel = Objects.requireNonNull(ecl); Objects.requireNonNull(dataCodewords); - QrTemplate tpl = QrTemplate.getInstance(ver); + QrTemplate tpl = QrTemplate.MEMOIZER.get(ver); modules = tpl.template.clone(); // Compute ECC, draw modules, do masking @@ -407,7 +407,7 @@ public final class QrCode { // Split data into blocks, calculate ECC, and interleave // (not concatenate) the bytes into a single sequence byte[] result = new byte[rawCodewords]; - ReedSolomonGenerator rs = ReedSolomonGenerator.getInstance(blockEccLen); + ReedSolomonGenerator rs = ReedSolomonGenerator.MEMOIZER.get(blockEccLen); byte[] ecc = new byte[blockEccLen]; // Temporary storage per iteration for (int i = 0, k = 0; i < numBlocks; i++) { int datLen = shortBlockDataLen + (i < numShortBlocks ? 0 : 1); diff --git a/src/io/nayuki/fastqrcodegen/QrTemplate.java b/src/io/nayuki/fastqrcodegen/QrTemplate.java index f933bd7..b2e435a 100644 --- a/src/io/nayuki/fastqrcodegen/QrTemplate.java +++ b/src/io/nayuki/fastqrcodegen/QrTemplate.java @@ -25,59 +25,12 @@ package io.nayuki.fastqrcodegen; import static io.nayuki.fastqrcodegen.QrCode.MAX_VERSION; import static io.nayuki.fastqrcodegen.QrCode.MIN_VERSION; -import java.lang.ref.SoftReference; final class QrTemplate { - /*---- Factory members ----*/ - - public static QrTemplate getInstance(int version) { - if (version < MIN_VERSION || version > MAX_VERSION) - throw new IllegalArgumentException("Version out of range"); - - while (true) { - synchronized(cache) { - SoftReference ref = cache[version]; - if (ref != null) { - QrTemplate result = ref.get(); - if (result != null) - return result; - cache[version] = null; - } - - if (!isPending[version]) { - isPending[version] = true; - break; - } - - try { - cache.wait(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - try { - QrTemplate tpl = new QrTemplate(version); - synchronized(cache) { - cache[version] = new SoftReference<>(tpl); - } - return tpl; - } finally { - synchronized(cache) { - isPending[version] = false; - cache.notifyAll(); - } - } - } - - - @SuppressWarnings("unchecked") - private static final SoftReference[] cache = new SoftReference[MAX_VERSION + 1]; - - private static final boolean[] isPending = new boolean[MAX_VERSION + 1]; + public static final Memoizer MEMOIZER + = new Memoizer<>(QrTemplate::new); diff --git a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java index aef45c0..697bc81 100644 --- a/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java +++ b/src/io/nayuki/fastqrcodegen/ReedSolomonGenerator.java @@ -23,63 +23,14 @@ package io.nayuki.fastqrcodegen; -import java.lang.ref.SoftReference; import java.util.Arrays; import java.util.Objects; final class ReedSolomonGenerator { - /*---- Factory members ----*/ - - public static ReedSolomonGenerator getInstance(int degree) { - if (degree < 1 || degree > MAX_DEGREE) - throw new IllegalArgumentException("Degree out of range"); - - while (true) { - synchronized(cache) { - SoftReference ref = cache[degree]; - if (ref != null) { - ReedSolomonGenerator result = ref.get(); - if (result != null) - return result; - cache[degree] = null; - } - - if (!isPending[degree]) { - isPending[degree] = true; - break; - } - - try { - cache.wait(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - try { - ReedSolomonGenerator rs = new ReedSolomonGenerator(degree); - synchronized(cache) { - cache[degree] = new SoftReference<>(rs); - } - return rs; - } finally { - synchronized(cache) { - isPending[degree] = false; - cache.notifyAll(); - } - } - } - - - private static final int MAX_DEGREE = 30; - - @SuppressWarnings("unchecked") - private static final SoftReference[] cache = new SoftReference[MAX_DEGREE + 1]; - - private static final boolean[] isPending = new boolean[MAX_DEGREE + 1]; + public static final Memoizer MEMOIZER + = new Memoizer<>(ReedSolomonGenerator::new);