From b8f3fcca022295700464d59133e2d64db19476f4 Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Fri, 23 Mar 2018 23:26:35 +0000 Subject: [PATCH] move rnd logic to RandomContext git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1144 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../jackcess/expr/EvalContext.java | 2 +- .../jackcess/impl/DatabaseImpl.java | 9 +- .../jackcess/impl/SimpleCache.java | 46 ++++++ .../impl/expr/DefaultNumberFunctions.java | 2 +- .../jackcess/impl/expr/RandomContext.java | 140 ++++++++++++++++++ .../impl/expr/ExpressionatorTest.java | 19 +-- 6 files changed, 192 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/healthmarketscience/jackcess/impl/SimpleCache.java create mode 100644 src/main/java/com/healthmarketscience/jackcess/impl/expr/RandomContext.java diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java b/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java index c140594..caec4c2 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java @@ -36,5 +36,5 @@ public interface EvalContext public Value getRowValue(String collectionName, String objName, String colName); - public Random getRandom(Integer seed); + public float getRandom(Integer seed); } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java index a94d127..a3b3701 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java @@ -39,7 +39,6 @@ import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -266,13 +265,7 @@ public class DatabaseImpl implements Database * MAX_CACHED_LOOKUP_TABLES). */ private final Map _tableLookup = - new LinkedHashMap() { - private static final long serialVersionUID = 0L; - @Override - protected boolean removeEldestEntry(Map.Entry e) { - return(size() > MAX_CACHED_LOOKUP_TABLES); - } - }; + new SimpleCache(MAX_CACHED_LOOKUP_TABLES); /** set of table names as stored in the mdb file, created on demand */ private Set _tableNames; /** Reads and writes database pages */ diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/SimpleCache.java b/src/main/java/com/healthmarketscience/jackcess/impl/SimpleCache.java new file mode 100644 index 0000000..fef2f68 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/impl/SimpleCache.java @@ -0,0 +1,46 @@ +/* +Copyright (c) 2018 James Ahlborn + +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.healthmarketscience.jackcess.impl; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Simple LRU cache implementation which keeps at most the configured maximum + * number of elements. + * @author James Ahlborn + */ +public class SimpleCache extends LinkedHashMap +{ + private static final long serialVersionUID = 20180313L; + + private final int _maxSize; + + public SimpleCache(int maxSize) { + super(16, 0.75f, true); + _maxSize = maxSize; + } + + protected int getMaxSize() { + return _maxSize; + } + + @Override + protected boolean removeEldestEntry(Map.Entry e) { + return(size() > _maxSize); + } +} diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java index 0195210..0f8ebf2 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java @@ -117,7 +117,7 @@ public class DefaultNumberFunctions @Override protected Value evalVar(EvalContext ctx, Value[] params) { Integer seed = ((params.length > 0) ? params[0].getAsLongInt() : null); - return BuiltinOperators.toValue(ctx.getRandom(seed).nextFloat()); + return BuiltinOperators.toValue(ctx.getRandom(seed)); } }); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/RandomContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/RandomContext.java new file mode 100644 index 0000000..b3a21c2 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/RandomContext.java @@ -0,0 +1,140 @@ +/* +Copyright (c) 2018 James Ahlborn + +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.healthmarketscience.jackcess.impl.expr; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +/** + * This class effectively encapsulates the stateful logic of the "Rnd" + * function. + * + * @author James Ahlborn + */ +public class RandomContext +{ + private Source _defRnd; + private Map _rnds; + // default to the value access uses for "last val" when none has been + // returned yet + private float _lastVal = 1.953125E-02f; + + public RandomContext() + { + } + + public float getRandom(Integer seed) { + + if(seed == null) { + if(_defRnd == null) { + _defRnd = new SimpleSource(createRandom(System.currentTimeMillis())); + } + return _defRnd.get(); + } + + if(_rnds == null) { + // note, we don't use a SimpleCache here because if we discard a Random + // instance, that will cause the values to be reset + _rnds = new HashMap(); + } + + Source rnd = _rnds.get(seed); + if(rnd == null) { + + int seedInt = seed; + if(seedInt > 0) { + // normal random with a user specified seed + rnd = new SimpleSource(createRandom(seedInt)); + } else if(seedInt < 0) { + // returns the same value every time and resets all randoms + rnd = new ResetSource(createRandom(seedInt)); + } else { + // returns the last random value returned + rnd = new LastValSource(); + } + + _rnds.put(seed, rnd); + } + return rnd.get(); + } + + private float setLast(float lastVal) { + _lastVal = lastVal; + return lastVal; + } + + private void reset() { + if(_rnds != null) { + _rnds.clear(); + } + } + + private static Random createRandom(long seed) { + // FIXME, support SecureRandom? + return new Random(seed); + } + + private abstract class Source + { + public float get() { + return setLast(getImpl()); + } + + protected abstract float getImpl(); + } + + private class SimpleSource extends Source + { + private final Random _rnd; + + private SimpleSource(Random rnd) { + _rnd = rnd; + } + + @Override + protected float getImpl() { + return _rnd.nextFloat(); + } + } + + private class ResetSource extends Source + { + private final float _val; + + private ResetSource(Random rnd) { + _val = rnd.nextFloat(); + } + + @Override + protected float getImpl() { + reset(); + return _val; + } + } + + private class LastValSource extends Source + { + private LastValSource() { + } + + @Override + protected float getImpl() { + return _lastVal; + } + } +} diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java index c6bda5b..a3eb46a 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java @@ -19,7 +19,6 @@ package com.healthmarketscience.jackcess.impl.expr; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.Random; import com.healthmarketscience.jackcess.DatabaseBuilder; import com.healthmarketscience.jackcess.TestUtil; @@ -382,9 +381,7 @@ public class ExpressionatorTest extends TestCase private static final class TestEvalContext implements EvalContext { private final Value _thisVal; - private Random _defRnd; - private Random _rnd; - private long _rndSeed; + private final RandomContext _rndCtx = new RandomContext(); private TestEvalContext(Value thisVal) { _thisVal = thisVal; @@ -416,18 +413,8 @@ public class ExpressionatorTest extends TestCase throw new UnsupportedOperationException(); } - public Random getRandom(Integer seed) { - if(seed == null) { - if(_defRnd == null) { - _defRnd = new Random(System.currentTimeMillis()); - } - return _defRnd; - } - if((_rnd == null) || (seed != _rndSeed)) { - _rndSeed = seed; - _rnd = new Random(_rndSeed); - } - return _rnd; + public float getRandom(Integer seed) { + return _rndCtx.getRandom(seed); } } } -- 2.39.5