From 707ad2da70e1e359c142180776020142c53dc561 Mon Sep 17 00:00:00 2001 From: redkale Date: Thu, 31 Aug 2023 22:30:55 +0800 Subject: [PATCH] =?UTF-8?q?convert=E4=BC=98=E5=8C=96features?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/redkale/annotation/Scheduled.java | 39 ++ .../java/org/redkale/boot/ApiDocCommand.java | 4 +- .../org/redkale/convert/BinaryConvert.java | 4 +- .../java/org/redkale/convert/Convert.java | 5 +- .../org/redkale/convert/ConvertFactory.java | 53 +- .../java/org/redkale/convert/TextConvert.java | 4 +- src/main/java/org/redkale/convert/Writer.java | 30 +- .../convert/bson/BsonByteBufferWriter.java | 17 +- .../org/redkale/convert/bson/BsonConvert.java | 22 +- .../org/redkale/convert/bson/BsonFactory.java | 28 +- .../convert/bson/BsonStreamWriter.java | 4 +- .../org/redkale/convert/bson/BsonWriter.java | 24 +- .../convert/json/JsonByteBufferWriter.java | 19 +- .../redkale/convert/json/JsonBytesWriter.java | 9 +- .../org/redkale/convert/json/JsonConvert.java | 23 +- .../redkale/convert/json/JsonDynEncoder.java | 16 +- .../org/redkale/convert/json/JsonFactory.java | 41 +- .../convert/json/JsonStreamWriter.java | 8 +- .../org/redkale/convert/json/JsonWriter.java | 23 +- src/main/java/org/redkale/net/http/Rest.java | 15 +- .../org/redkale/net/http/RestConvert.java | 8 +- .../redkale/source/CacheEventListener.java | 18 + .../org/redkale/source/CacheMemorySource.java | 20 + .../java/org/redkale/source/CacheSource.java | 76 ++- .../java/org/redkale/util/CronExpression.java | 643 ++++++++++++++++++ .../org/redkale/test/convert/Json5Test.java | 3 +- .../redkale/test/convert/JsonMainTest.java | 5 +- .../org/redkale/test/convert/TinyTest.java | 13 +- 28 files changed, 976 insertions(+), 198 deletions(-) create mode 100644 src/main/java/org/redkale/annotation/Scheduled.java create mode 100644 src/main/java/org/redkale/source/CacheEventListener.java create mode 100644 src/main/java/org/redkale/util/CronExpression.java diff --git a/src/main/java/org/redkale/annotation/Scheduled.java b/src/main/java/org/redkale/annotation/Scheduled.java new file mode 100644 index 000000000..72aaa8b83 --- /dev/null +++ b/src/main/java/org/redkale/annotation/Scheduled.java @@ -0,0 +1,39 @@ +/* + * + */ +package org.redkale.annotation; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + +/** + * 定时任务标记,只能作用于Service的方法上, 功能类似Spring里的Scheduled注解 + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * @since 2.8.0 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Scheduled { + + String cron() default ""; + + String zone() default ""; + + long fixedDelay() default -1; + + String fixedDelayString() default ""; + + long fixedRate() default -1; + + String fixedRateString() default ""; + + long initialDelay() default -1; + + String initialDelayString() default ""; + + TimeUnit timeUnit() default TimeUnit.MILLISECONDS; +} diff --git a/src/main/java/org/redkale/boot/ApiDocCommand.java b/src/main/java/org/redkale/boot/ApiDocCommand.java index 1d3ec1851..ab0e01a2d 100644 --- a/src/main/java/org/redkale/boot/ApiDocCommand.java +++ b/src/main/java/org/redkale/boot/ApiDocCommand.java @@ -338,7 +338,7 @@ public final class ApiDocCommand { swaggerOperatMap.put("deprecated", true); } Map respSchemaMap = new LinkedHashMap<>(); - JsonFactory returnFactory = Rest.createJsonFactory(false, method.getAnnotationsByType(RestConvert.class), method.getAnnotationsByType(RestConvertCoder.class)); + JsonFactory returnFactory = Rest.createJsonFactory(0, method.getAnnotationsByType(RestConvert.class), method.getAnnotationsByType(RestConvertCoder.class)); simpleSchemaType(returnFactory, node.getLogger(), swaggerComponentsMap, action.result(), resultType, respSchemaMap, true); Map respMap = new LinkedHashMap<>(); @@ -762,6 +762,6 @@ public final class ApiDocCommand { return example; } - private static final JsonFactory exampleFactory = JsonFactory.create().tiny(false).nullable(false); + private static final JsonFactory exampleFactory = JsonFactory.create().features(0); } diff --git a/src/main/java/org/redkale/convert/BinaryConvert.java b/src/main/java/org/redkale/convert/BinaryConvert.java index d4a3a18be..9602cca42 100644 --- a/src/main/java/org/redkale/convert/BinaryConvert.java +++ b/src/main/java/org/redkale/convert/BinaryConvert.java @@ -19,8 +19,8 @@ import java.lang.reflect.Type; */ public abstract class BinaryConvert extends Convert { - protected BinaryConvert(ConvertFactory factory) { - super(factory); + protected BinaryConvert(ConvertFactory factory, int features) { + super(factory, features); } @Override diff --git a/src/main/java/org/redkale/convert/Convert.java b/src/main/java/org/redkale/convert/Convert.java index 00e7ed73a..caec50d66 100644 --- a/src/main/java/org/redkale/convert/Convert.java +++ b/src/main/java/org/redkale/convert/Convert.java @@ -24,8 +24,11 @@ public abstract class Convert { protected final ConvertFactory factory; - protected Convert(ConvertFactory factory) { + protected final int features; + + protected Convert(ConvertFactory factory, int features) { this.factory = factory; + this.features = features; } public ConvertFactory getFactory() { diff --git a/src/main/java/org/redkale/convert/ConvertFactory.java b/src/main/java/org/redkale/convert/ConvertFactory.java index 8425771d0..7cf0ce655 100644 --- a/src/main/java/org/redkale/convert/ConvertFactory.java +++ b/src/main/java/org/redkale/convert/ConvertFactory.java @@ -36,6 +36,12 @@ import org.redkale.util.*; @SuppressWarnings("unchecked") public abstract class ConvertFactory { + //值为true时 String类型值为"",Boolean类型值为false时不会输出,默认为false + public static final int FEATURE_TINY = 1 << 1; + + //值为true时 字段值为null时会输出,默认为false + public static final int FEATURE_NULLABLE = 1 << 2; + private static final AtomicBoolean loaderInited = new AtomicBoolean(); private static Convert defProtobufConvert; @@ -44,9 +50,8 @@ public abstract class ConvertFactory { protected Convert convert; - protected boolean tiny; //值为true时 String类型值为"",Boolean类型值为false时不会输出,默认为false - - protected boolean nullable; ///值为true时 字段值为null时会输出,默认为false + //配置属性集合, 1<<1至1<<10为系统内置 + protected int features; private final Encodeable anyEncoder = new AnyEncoder(this); @@ -74,9 +79,8 @@ public abstract class ConvertFactory { private boolean skipAllIgnore = false; - protected ConvertFactory(ConvertFactory parent, boolean tiny, boolean nullable) { - this.tiny = tiny; - this.nullable = nullable; + protected ConvertFactory(ConvertFactory parent, int features) { + this.features = features; this.parent = parent; if (parent == null) { //--------------------------------------------------------- @@ -209,6 +213,17 @@ public abstract class ConvertFactory { } } + public final int features() { + return this.features; + } + + public ConvertFactory features(int features) { + if (features > -1) { + this.features = features; + } + return this; + } + public ConvertFactory parent() { return this.parent; } @@ -242,8 +257,8 @@ public abstract class ConvertFactory { return type == ConvertType.PROTOBUF ? defProtobufConvert : null; } - protected static boolean getSystemPropertyBoolean(String key, String parentkey, boolean defvalue) { - return Boolean.parseBoolean(System.getProperty(key, System.getProperty(parentkey, String.valueOf(defvalue)))); + protected static int getSystemPropertyInt(String key, String parentkey, boolean defvalue, int feature) { + return Boolean.parseBoolean(System.getProperty(key, System.getProperty(parentkey, String.valueOf(defvalue)))) ? feature : 0; } public abstract ConvertType getConvertType(); @@ -254,7 +269,7 @@ public abstract class ConvertFactory { public abstract ConvertFactory createChild(); - public abstract ConvertFactory createChild(boolean tiny, boolean nullable); + public abstract ConvertFactory createChild(int features); protected SimpledCoder createEnumSimpledCoder(Class enumClass) { return new EnumSimpledCoder(this, enumClass); @@ -333,13 +348,29 @@ public abstract class ConvertFactory { return convert; } + public static boolean tinyFeature(int features) { + return (features & FEATURE_TINY) > 0; + } + + public static boolean nullableFeature(int features) { + return (features & FEATURE_NULLABLE) > 0; + } + public ConvertFactory tiny(boolean tiny) { - this.tiny = tiny; + if (tiny) { + this.features |= FEATURE_TINY; + } else { + this.features = this.features & ~FEATURE_TINY; + } return this; } public ConvertFactory nullable(boolean nullable) { - this.nullable = nullable; + if (nullable) { + this.features |= FEATURE_NULLABLE; + } else { + this.features = this.features & ~FEATURE_NULLABLE; + } return this; } diff --git a/src/main/java/org/redkale/convert/TextConvert.java b/src/main/java/org/redkale/convert/TextConvert.java index b33a375bf..82f043eec 100644 --- a/src/main/java/org/redkale/convert/TextConvert.java +++ b/src/main/java/org/redkale/convert/TextConvert.java @@ -19,8 +19,8 @@ import java.lang.reflect.Type; */ public abstract class TextConvert extends Convert { - protected TextConvert(ConvertFactory factory) { - super(factory); + protected TextConvert(ConvertFactory factory, int features) { + super(factory, features); } @Override diff --git a/src/main/java/org/redkale/convert/Writer.java b/src/main/java/org/redkale/convert/Writer.java index 7aedcb6e6..ef5febc4b 100644 --- a/src/main/java/org/redkale/convert/Writer.java +++ b/src/main/java/org/redkale/convert/Writer.java @@ -35,6 +35,9 @@ public abstract class Writer { //对某个对象进行动态扩展字段值处理 protected Function objExtFunc; + //配置项 + protected int features; + /** * 设置specificObjectType * @@ -69,18 +72,27 @@ public abstract class Writer { } /** - * 当tiny=true时, 字符串为空、boolean为false的字段值都会被跳过, 不会输出。 + * 获取配置属性 * - * @return 是否简化 */ - public abstract boolean tiny(); + public final int features() { + return features; + } - /** - * 当nullable=true时, 字段值为null时会输出该字段 - * - * @return 是否简化 - */ - public abstract boolean nullable(); + public Writer features(int features) { + if (features > -1) { + this.features = features; + } + return this; + } + + protected final boolean tiny() { + return ConvertFactory.tinyFeature(features); + } + + protected final boolean nullable() { + return ConvertFactory.nullableFeature(features); + } /** * 输出null值 diff --git a/src/main/java/org/redkale/convert/bson/BsonByteBufferWriter.java b/src/main/java/org/redkale/convert/bson/BsonByteBufferWriter.java index 1253ffac4..c0a179316 100644 --- a/src/main/java/org/redkale/convert/bson/BsonByteBufferWriter.java +++ b/src/main/java/org/redkale/convert/bson/BsonByteBufferWriter.java @@ -26,13 +26,12 @@ public class BsonByteBufferWriter extends BsonWriter { private int index; public BsonByteBufferWriter(Supplier supplier) { - this(false, false, supplier); + this(0, supplier); } - protected BsonByteBufferWriter(boolean tiny, boolean nullable, Supplier supplier) { + protected BsonByteBufferWriter(int features, Supplier supplier) { super((byte[]) null); - this.tiny = tiny; - this.nullable = nullable; + this.features = features; this.supplier = supplier; } @@ -72,14 +71,8 @@ public class BsonByteBufferWriter extends BsonWriter { } @Override - public BsonByteBufferWriter tiny(boolean tiny) { - this.tiny = tiny; - return this; - } - - @Override - public BsonByteBufferWriter nullable(boolean nullable) { - this.nullable = nullable; + public BsonByteBufferWriter features(int features) { + this.features = features; return this; } diff --git a/src/main/java/org/redkale/convert/bson/BsonConvert.java b/src/main/java/org/redkale/convert/bson/BsonConvert.java index 58796317f..c9c7bee70 100644 --- a/src/main/java/org/redkale/convert/bson/BsonConvert.java +++ b/src/main/java/org/redkale/convert/bson/BsonConvert.java @@ -46,14 +46,8 @@ public class BsonConvert extends BinaryConvert { private final ThreadLocal readerPool = ThreadLocal.withInitial(BsonReader::new); - private final boolean tiny; - - private final boolean nullable; - - protected BsonConvert(ConvertFactory factory, boolean tiny, boolean nullable) { - super(factory); - this.tiny = tiny; - this.nullable = nullable; + protected BsonConvert(ConvertFactory factory, int features) { + super(factory, features); } @Override @@ -82,7 +76,7 @@ public class BsonConvert extends BinaryConvert { @Override public BsonConvert newConvert(final BiFunction fieldFunc, BiFunction mapFieldFunc, Function objExtFunc) { - return new BsonConvert(getFactory(), tiny, nullable) { + return new BsonConvert(getFactory(), features) { @Override protected S configWrite(S writer) { return fieldFunc(writer, fieldFunc, mapFieldFunc, objExtFunc); @@ -120,11 +114,11 @@ public class BsonConvert extends BinaryConvert { //------------------------------ writer ----------------------------------------------------------- public BsonByteBufferWriter pollWriter(final Supplier supplier) { - return configWrite(new BsonByteBufferWriter(tiny, nullable, supplier)); + return configWrite(new BsonByteBufferWriter(features, supplier)); } protected BsonWriter pollWriter(final OutputStream out) { - return configWrite(new BsonStreamWriter(tiny, nullable, out)); + return configWrite(new BsonStreamWriter(features, out)); } @Override @@ -135,7 +129,7 @@ public class BsonConvert extends BinaryConvert { } else { writerPool.set(null); } - return configWrite(writer.tiny(tiny).nullable(nullable)); + return configWrite(writer.features(features)); } @Override @@ -240,7 +234,7 @@ public class BsonConvert extends BinaryConvert { @Override public void convertToBytes(final ByteArray array, final Type type, final Object value) { Objects.requireNonNull(array); - final BsonWriter writer = configWrite(new BsonWriter(array).tiny(tiny).nullable(nullable)); + final BsonWriter writer = configWrite(new BsonWriter(array).features(features)); if (value == null) { writer.writeNull(); } else { @@ -286,7 +280,7 @@ public class BsonConvert extends BinaryConvert { if (value == null) { return null; } - final BsonWriter writer = writerPool.get().tiny(tiny).nullable(nullable); + final BsonWriter writer = writerPool.get().features(features); factory.loadEncoder(type == null ? value.getClass() : type).convertTo(writer, value); return writer; } diff --git a/src/main/java/org/redkale/convert/bson/BsonFactory.java b/src/main/java/org/redkale/convert/bson/BsonFactory.java index 1c23d427a..1e3bdd322 100644 --- a/src/main/java/org/redkale/convert/bson/BsonFactory.java +++ b/src/main/java/org/redkale/convert/bson/BsonFactory.java @@ -25,8 +25,9 @@ import org.redkale.util.TypeToken; public final class BsonFactory extends ConvertFactory { private static final BsonFactory instance = new BsonFactory(null, - getSystemPropertyBoolean("redkale.convert.bson.tiny", "redkale.convert.tiny", true), - getSystemPropertyBoolean("redkale.convert.bson.nullable", "redkale.convert.nullable", false)); + getSystemPropertyInt("redkale.convert.bson.tiny", "redkale.convert.tiny", true, FEATURE_TINY) + | getSystemPropertyInt("redkale.convert.bson.nullable", "redkale.convert.nullable", false, FEATURE_NULLABLE) + ); static final Decodeable objectDecoder = instance.loadDecoder(Object.class); @@ -50,28 +51,28 @@ public final class BsonFactory extends ConvertFactory { //instance.register(AnyValue.class, instance.loadEncoder(AnyValue.DefaultAnyValue.class)); } - private BsonFactory(BsonFactory parent, boolean tiny, boolean nullable) { - super(parent, tiny, nullable); + private BsonFactory(BsonFactory parent, int features) { + super(parent, features); } @Override public BsonFactory tiny(boolean tiny) { - this.tiny = tiny; + super.tiny(tiny); return this; } protected boolean tiny() { - return this.tiny; + return (this.features & FEATURE_TINY) > 0; } @Override public BsonFactory nullable(boolean nullable) { - this.nullable = nullable; + super.nullable(nullable); return this; } protected boolean nullable() { - return this.nullable; + return (this.features & FEATURE_NULLABLE) > 0; } @Override @@ -85,26 +86,25 @@ public final class BsonFactory extends ConvertFactory { } public static BsonFactory create() { - return new BsonFactory(null, getSystemPropertyBoolean("redkale.convert.bson.tiny", "redkale.convert.tiny", true), - getSystemPropertyBoolean("redkale.convert.bson.nullable", "redkale.convert.nullable", false)); + return new BsonFactory(null, instance.features); } @Override public final BsonConvert getConvert() { if (convert == null) { - convert = new BsonConvert(this, tiny, nullable); + convert = new BsonConvert(this, features); } return (BsonConvert) convert; } @Override public BsonFactory createChild() { - return new BsonFactory(this, this.tiny, this.nullable); + return new BsonFactory(this, features); } @Override - public BsonFactory createChild(boolean tiny, boolean nullable) { - return new BsonFactory(this, tiny, nullable); + public BsonFactory createChild(int features) { + return new BsonFactory(this, features); } @Override diff --git a/src/main/java/org/redkale/convert/bson/BsonStreamWriter.java b/src/main/java/org/redkale/convert/bson/BsonStreamWriter.java index 2d7a9b50f..d3c756d32 100644 --- a/src/main/java/org/redkale/convert/bson/BsonStreamWriter.java +++ b/src/main/java/org/redkale/convert/bson/BsonStreamWriter.java @@ -18,8 +18,8 @@ class BsonStreamWriter extends BsonByteBufferWriter { private OutputStream out; - protected BsonStreamWriter(boolean tiny, boolean nullable, OutputStream out) { - super(tiny, nullable, null); + protected BsonStreamWriter(int features, OutputStream out) { + super(features, null); this.out = out; } diff --git a/src/main/java/org/redkale/convert/bson/BsonWriter.java b/src/main/java/org/redkale/convert/bson/BsonWriter.java index 4d7227802..54bb7415b 100644 --- a/src/main/java/org/redkale/convert/bson/BsonWriter.java +++ b/src/main/java/org/redkale/convert/bson/BsonWriter.java @@ -27,10 +27,6 @@ public class BsonWriter extends Writer implements ByteTuple { protected int count; - protected boolean tiny = BsonFactory.root().tiny(); - - protected boolean nullable = BsonFactory.root().nullable(); - public static ObjectPool createPool(int max) { return ObjectPool.createSafePool(max, (Object... params) -> new BsonWriter(), null, (t) -> t.recycle()); } @@ -86,6 +82,7 @@ public class BsonWriter extends Writer implements ByteTuple { public BsonWriter() { this(defaultSize); + this.features = BsonFactory.root().features(); } public BsonWriter(int size) { @@ -97,23 +94,8 @@ public class BsonWriter extends Writer implements ByteTuple { this.count = array.length(); } - @Override - public final boolean tiny() { - return tiny; - } - - public BsonWriter tiny(boolean tiny) { - this.tiny = tiny; - return this; - } - - @Override - public final boolean nullable() { - return nullable; - } - - public BsonWriter nullable(boolean nullable) { - this.nullable = nullable; + public BsonWriter features(int features) { + super.features(features); return this; } diff --git a/src/main/java/org/redkale/convert/json/JsonByteBufferWriter.java b/src/main/java/org/redkale/convert/json/JsonByteBufferWriter.java index 6d67fa48e..a9077931d 100644 --- a/src/main/java/org/redkale/convert/json/JsonByteBufferWriter.java +++ b/src/main/java/org/redkale/convert/json/JsonByteBufferWriter.java @@ -34,23 +34,16 @@ public class JsonByteBufferWriter extends JsonWriter { private int index; - public JsonByteBufferWriter(boolean tiny, boolean nullable, Supplier supplier) { - this(tiny, nullable, null, supplier); + public JsonByteBufferWriter(int features, Supplier supplier) { + this(features, null, supplier); } - public JsonByteBufferWriter(boolean tiny, boolean nullable, Charset charset, Supplier supplier) { - this.tiny = tiny; - this.nullable = nullable; + public JsonByteBufferWriter(int features, Charset charset, Supplier supplier) { + this.features = features; this.charset = charset; this.supplier = supplier; } - @Override - public JsonByteBufferWriter tiny(boolean tiny) { - this.tiny = tiny; - return this; - } - @Override protected boolean recycle() { super.recycle(); @@ -575,7 +568,7 @@ public class JsonByteBufferWriter extends JsonWriter { writeTo('}'); return; } - if (value == null || (tiny && value.isEmpty())) { + if (value == null || (tiny() && value.isEmpty())) { expand(1); this.buffers[index].put((byte) '}'); } else { @@ -634,7 +627,7 @@ public class JsonByteBufferWriter extends JsonWriter { writeTo('}'); return; } - if (value == null || (tiny && value.isEmpty())) { + if (value == null || (tiny() && value.isEmpty())) { int expandsize = expand(2); if (expandsize == 0) { // 只需要一个buffer ByteBuffer bb = this.buffers[index]; diff --git a/src/main/java/org/redkale/convert/json/JsonBytesWriter.java b/src/main/java/org/redkale/convert/json/JsonBytesWriter.java index 33382e2a2..7856f4815 100644 --- a/src/main/java/org/redkale/convert/json/JsonBytesWriter.java +++ b/src/main/java/org/redkale/convert/json/JsonBytesWriter.java @@ -55,9 +55,8 @@ public class JsonBytesWriter extends JsonWriter implements ByteTuple { this.count = array.length(); } - public JsonBytesWriter(boolean tiny, boolean nullable, ByteArray array) { - this.tiny = tiny; - this.nullable = nullable; + public JsonBytesWriter(int features, ByteArray array) { + this.features = features; this.content = array.content(); this.count = array.length(); } @@ -282,7 +281,7 @@ public class JsonBytesWriter extends JsonWriter implements ByteTuple { writeTo('}'); return; } - if (value == null || (tiny && value.isEmpty())) { + if (value == null || (tiny() && value.isEmpty())) { expand(1); content[count++] = '}'; } else { @@ -312,7 +311,7 @@ public class JsonBytesWriter extends JsonWriter implements ByteTuple { writeTo('}'); return; } - if (value == null || (tiny && value.isEmpty())) { + if (value == null || (tiny() && value.isEmpty())) { expand(2); content[count++] = '{'; content[count++] = '}'; diff --git a/src/main/java/org/redkale/convert/json/JsonConvert.java b/src/main/java/org/redkale/convert/json/JsonConvert.java index 497b9c79d..73896f1c1 100644 --- a/src/main/java/org/redkale/convert/json/JsonConvert.java +++ b/src/main/java/org/redkale/convert/json/JsonConvert.java @@ -40,18 +40,13 @@ public class JsonConvert extends TextConvert { private final ThreadLocal readerPool = ThreadLocal.withInitial(JsonReader::new); - private final boolean tiny; - - private final boolean nullable; private Encodeable lastConvertEncodeable; private Decodeable lastConvertDecodeable; - protected JsonConvert(JsonFactory factory, boolean tiny, boolean nullable) { - super(factory); - this.tiny = tiny; - this.nullable = nullable; + protected JsonConvert(JsonFactory factory, int features) { + super(factory, features); } @Override @@ -80,7 +75,7 @@ public class JsonConvert extends TextConvert { @Override public JsonConvert newConvert(final BiFunction objFieldFunc, BiFunction mapFieldFunc, Function objExtFunc) { - return new JsonConvert(getFactory(), tiny, nullable) { + return new JsonConvert(getFactory(), features) { @Override protected S configWrite(S writer) { return fieldFunc(writer, objFieldFunc, mapFieldFunc, objExtFunc); @@ -115,7 +110,7 @@ public class JsonConvert extends TextConvert { } else { bytesWriterPool.set(null); } - return configWrite((JsonBytesWriter) writer.tiny(tiny).nullable(nullable)); + return configWrite((JsonBytesWriter) writer.features(features)); } @Override @@ -135,7 +130,7 @@ public class JsonConvert extends TextConvert { } else { bytesWriterPool.set(null); } - return configWrite((JsonBytesWriter) writer.tiny(tiny).nullable(nullable)); + return configWrite((JsonBytesWriter) writer.features(features)); } private void offerJsonBytesWriter(final JsonBytesWriter writer) { @@ -395,7 +390,7 @@ public class JsonConvert extends TextConvert { @Override public void convertToBytes(final ByteArray array, final Type type, final Object value) { Objects.requireNonNull(array); - JsonBytesWriter writer = configWrite(new JsonBytesWriter(tiny, nullable, array)); + JsonBytesWriter writer = configWrite(new JsonBytesWriter(features, array)); if (value == null) { writer.writeNull(); } else { @@ -415,10 +410,10 @@ public class JsonConvert extends TextConvert { public void convertTo(final OutputStream out, final Type type, final Object value) { if (value == null) { - configWrite(new JsonStreamWriter(tiny, nullable, out)).writeNull(); + configWrite(new JsonStreamWriter(features, out)).writeNull(); } else { final Type t = type == null ? value.getClass() : type; - JsonStreamWriter writer = configWrite(new JsonStreamWriter(tiny, nullable, out)); + JsonStreamWriter writer = configWrite(new JsonStreamWriter(features, out)); Encodeable encoder = this.lastConvertEncodeable; if (encoder == null || encoder.getType() != t) { encoder = factory.loadEncoder(t); @@ -434,7 +429,7 @@ public class JsonConvert extends TextConvert { @Override public ByteBuffer[] convertTo(final Supplier supplier, final Type type, final Object value) { Objects.requireNonNull(supplier); - JsonByteBufferWriter out = configWrite(new JsonByteBufferWriter(tiny, nullable, supplier)); + JsonByteBufferWriter out = configWrite(new JsonByteBufferWriter(features, supplier)); if (value == null) { out.writeNull(); } else { diff --git a/src/main/java/org/redkale/convert/json/JsonDynEncoder.java b/src/main/java/org/redkale/convert/json/JsonDynEncoder.java index 99383518f..a8b94ae85 100644 --- a/src/main/java/org/redkale/convert/json/JsonDynEncoder.java +++ b/src/main/java/org/redkale/convert/json/JsonDynEncoder.java @@ -386,7 +386,7 @@ public abstract class JsonDynEncoder implements Encodeable { final Map mixedNames = mixedNames0; final ClassLoader loader = Thread.currentThread().getContextClassLoader(); final String newDynName = "org/redkaledyn/json/_Dyn" + JsonDynEncoder.class.getSimpleName() - + "__" + clazz.getName().replace('.', '_').replace('$', '_') + "_" + factory.tiny() + "_" + factory.nullable() + "_" + Utility.md5Hex(memberb.toString()); //tiny必须要加上, 同一个类会有多个字段定制Convert + + "__" + clazz.getName().replace('.', '_').replace('$', '_') + "_" + factory.features() + "_" + Utility.md5Hex(memberb.toString()); //tiny必须要加上, 同一个类会有多个字段定制Convert try { Class clz = RedkaleClassLoader.findDynClass(newDynName.replace('/', '.')); Class newClazz = clz == null ? loader.loadClass(newDynName.replace('/', '.')) : clz; @@ -511,8 +511,10 @@ public abstract class JsonDynEncoder implements Encodeable { int maxLocals = 4; int elementIndex = -1; + final boolean tiny = ConvertFactory.tinyFeature(factory.features()); + final boolean nullable = ConvertFactory.nullableFeature(factory.features()); final Class firstType = readGetSetFieldType(members.get(0)); - final boolean mustHadComma = firstType.isPrimitive() && (firstType != boolean.class || !factory.tiny() || factory.nullable()); //byte/short/char/int/float/long/double + final boolean mustHadComma = firstType.isPrimitive() && (firstType != boolean.class || !tiny || nullable); //byte/short/char/int/float/long/double if (onlyOneLatin1FieldObjectFlag) { //out.writeObjectByOnlyOneLatin1FieldValue(messageFirstFieldBytes, value.getMessage());elementIndex++; @@ -670,16 +672,16 @@ public abstract class JsonDynEncoder implements Encodeable { } } Label msgnotemptyif = null; - if (!fieldtype.isPrimitive() && !factory.nullable()) { //if (message != null) { start + if (!fieldtype.isPrimitive() && !nullable) { //if (message != null) { start mv.visitVarInsn(loadid, maxLocals); msgnotemptyif = new Label(); mv.visitJumpInsn(IFNULL, msgnotemptyif); - if (factory.tiny() && fieldtype == String.class) { + if (tiny && fieldtype == String.class) { mv.visitVarInsn(loadid, maxLocals); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "isEmpty", "()Z", false); mv.visitJumpInsn(IFNE, msgnotemptyif); } - } else if (fieldtype == boolean.class && factory.tiny()) { + } else if (fieldtype == boolean.class && tiny) { mv.visitVarInsn(loadid, maxLocals); msgnotemptyif = new Label(); mv.visitJumpInsn(IFEQ, msgnotemptyif); @@ -794,10 +796,10 @@ public abstract class JsonDynEncoder implements Encodeable { mv.visitVarInsn(loadid, maxLocals); mv.visitMethodInsn(INVOKEINTERFACE, encodeableName, "convertTo", "(" + writerDesc + "Ljava/lang/Object;)V", true); } - if (!fieldtype.isPrimitive() && !factory.nullable()) { //if (message != null) } end + if (!fieldtype.isPrimitive() && !nullable) { //if (message != null) } end mv.visitLabel(msgnotemptyif); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - } else if (fieldtype == boolean.class && factory.tiny()) { + } else if (fieldtype == boolean.class && tiny) { mv.visitLabel(msgnotemptyif); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); } diff --git a/src/main/java/org/redkale/convert/json/JsonFactory.java b/src/main/java/org/redkale/convert/json/JsonFactory.java index 43a94faf1..118911818 100644 --- a/src/main/java/org/redkale/convert/json/JsonFactory.java +++ b/src/main/java/org/redkale/convert/json/JsonFactory.java @@ -25,8 +25,9 @@ import org.redkale.util.Uint128; public final class JsonFactory extends ConvertFactory { private static final JsonFactory instance = new JsonFactory(null, - getSystemPropertyBoolean("redkale.convert.json.tiny", "redkale.convert.tiny", false), - getSystemPropertyBoolean("redkale.convert.json.nullable", "redkale.convert.nullable", false)); + getSystemPropertyInt("redkale.convert.json.tiny", "redkale.convert.tiny", false, FEATURE_TINY) + | getSystemPropertyInt("redkale.convert.json.nullable", "redkale.convert.nullable", false, FEATURE_NULLABLE) + ); static { instance.register(Serializable.class, instance.loadEncoder(Object.class)); @@ -35,8 +36,8 @@ public final class JsonFactory extends ConvertFactory { //instance.register(AnyValue.class, instance.loadEncoder(AnyValue.DefaultAnyValue.class)); } - private JsonFactory(JsonFactory parent, boolean tiny, boolean nullable) { - super(parent, tiny, nullable); + private JsonFactory(JsonFactory parent, int features) { + super(parent, features); if (parent == null) { this.register(InetAddress.class, InetAddressSimpledCoder.InetAddressJsonSimpledCoder.instance); this.register(InetSocketAddress.class, InetAddressSimpledCoder.InetSocketAddressJsonSimpledCoder.instance); @@ -55,15 +56,8 @@ public final class JsonFactory extends ConvertFactory { } } - @Override - public JsonFactory tiny(boolean tiny) { - this.tiny = tiny; - return this; - } - - @Override - public JsonFactory nullable(boolean nullable) { - this.nullable = nullable; + public JsonFactory features(int features) { + this.features = features; return this; } @@ -78,9 +72,8 @@ public final class JsonFactory extends ConvertFactory { } public static JsonFactory create() { - return new JsonFactory(null, getSystemPropertyBoolean("redkale.convert.json.tiny", "redkale.convert.tiny", false), - getSystemPropertyBoolean("redkale.convert.json.nullable", "redkale.convert.nullable", false)); - } + return new JsonFactory(null, instance.features()); + } @Override protected Encodeable createDyncEncoder(Type type) { @@ -97,30 +90,22 @@ public final class JsonFactory extends ConvertFactory { return new JsonMultiImplDecoder(this, types); } - protected boolean tiny() { - return this.tiny; - } - - protected boolean nullable() { - return this.nullable; - } - @Override public final JsonConvert getConvert() { if (convert == null) { - convert = new JsonConvert(this, tiny, nullable); + convert = new JsonConvert(this, features); } return (JsonConvert) convert; } @Override public JsonFactory createChild() { - return new JsonFactory(this, this.tiny, this.nullable); + return new JsonFactory(this, this.features); } @Override - public JsonFactory createChild(boolean tiny, boolean nullable) { - return new JsonFactory(this, tiny, nullable); + public JsonFactory createChild(int features) { + return new JsonFactory(this, features); } @Override diff --git a/src/main/java/org/redkale/convert/json/JsonStreamWriter.java b/src/main/java/org/redkale/convert/json/JsonStreamWriter.java index c1de22503..b725ed383 100644 --- a/src/main/java/org/redkale/convert/json/JsonStreamWriter.java +++ b/src/main/java/org/redkale/convert/json/JsonStreamWriter.java @@ -21,12 +21,12 @@ class JsonStreamWriter extends JsonByteBufferWriter { private OutputStream out; - protected JsonStreamWriter(boolean tiny, boolean nullable, OutputStream out) { - this(tiny, nullable, null, out); + protected JsonStreamWriter(int features, OutputStream out) { + this(features, null, out); } - protected JsonStreamWriter(boolean tiny, boolean nullable, Charset charset, OutputStream out) { - super(tiny, nullable, charset, null); + protected JsonStreamWriter(int features, Charset charset, OutputStream out) { + super(features, charset, null); this.out = out; } diff --git a/src/main/java/org/redkale/convert/json/JsonWriter.java b/src/main/java/org/redkale/convert/json/JsonWriter.java index c903262dc..4e9c58fce 100644 --- a/src/main/java/org/redkale/convert/json/JsonWriter.java +++ b/src/main/java/org/redkale/convert/json/JsonWriter.java @@ -21,17 +21,12 @@ public abstract class JsonWriter extends Writer { protected static final int defaultSize = Integer.getInteger("redkale.convert.json.writer.buffer.defsize", Integer.getInteger("redkale.convert.writer.buffer.defsize", 1024)); - protected boolean tiny = JsonFactory.root().tiny(); - - protected boolean nullable = JsonFactory.root().nullable(); - - @Override - public boolean tiny() { - return tiny; + protected JsonWriter() { + this.features = JsonFactory.root().features(); } - public JsonWriter tiny(boolean tiny) { - this.tiny = tiny; + public JsonWriter features(int features) { + super.features(features); return this; } @@ -40,16 +35,6 @@ public abstract class JsonWriter extends Writer { return this.objExtFunc == null && this.objFieldFunc == null; } - @Override - public boolean nullable() { - return nullable; - } - - public JsonWriter nullable(boolean nullable) { - this.nullable = nullable; - return this; - } - //----------------------------------------------------------------------- public abstract void writeTo(final char ch); //只能是 0 - 127 的字符 diff --git a/src/main/java/org/redkale/net/http/Rest.java b/src/main/java/org/redkale/net/http/Rest.java index 34206da0f..feaf22f7d 100644 --- a/src/main/java/org/redkale/net/http/Rest.java +++ b/src/main/java/org/redkale/net/http/Rest.java @@ -172,14 +172,17 @@ public final class Rest { } public static JsonFactory createJsonFactory(RestConvert[] converts, RestConvertCoder[] coders) { - return createJsonFactory(true, converts, coders); + return createJsonFactory(-1, converts, coders); } - public static JsonFactory createJsonFactory(boolean tiny, RestConvert[] converts, RestConvertCoder[] coders) { + public static JsonFactory createJsonFactory(int features, RestConvert[] converts, RestConvertCoder[] coders) { if ((converts == null || converts.length < 1) && (coders == null || coders.length < 1)) { return JsonFactory.root(); } - final JsonFactory childFactory = JsonFactory.create().tiny(tiny); + final JsonFactory childFactory = JsonFactory.create(); + if (features > -1) { + childFactory.features(features); + } List types = new ArrayList<>(); Set reloadTypes = new HashSet<>(); if (coders != null) { @@ -208,8 +211,8 @@ public final class Rest { childFactory.reloadCoder(rc.type()); } types.add(rc.type()); - if (tiny) { - childFactory.tiny(rc.tiny()); + if (rc.features() > -1) { + childFactory.features(rc.features()); } } } @@ -2287,7 +2290,7 @@ public final class Rest { //设置 RestConvert for (RestConvert rc : rcs) { AnnotationVisitor av2 = av1.visitAnnotation(null, restConvertDesc); - av2.visit("tiny", rc.tiny()); + av2.visit("features", rc.features()); av2.visit("skipIgnore", rc.skipIgnore()); av2.visit("type", Type.getType(Type.getDescriptor(rc.type()))); AnnotationVisitor av3 = av2.visitArray("onlyColumns"); diff --git a/src/main/java/org/redkale/net/http/RestConvert.java b/src/main/java/org/redkale/net/http/RestConvert.java index 4e259c0ad..eb1d0b2de 100644 --- a/src/main/java/org/redkale/net/http/RestConvert.java +++ b/src/main/java/org/redkale/net/http/RestConvert.java @@ -5,9 +5,9 @@ */ package org.redkale.net.http; +import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; -import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 只能依附在Service实现类的public方法上, 当方法的返回值以JSON输出时对指定类型的转换设定。
@@ -26,11 +26,11 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; public @interface RestConvert { /** - * 是否输出空字符串,0数值 + * 配置项 * - * @return boolean + * @return int */ - boolean tiny() default true; + int features() default -1; /** * 是否忽略ConvertColumn.ignore=true的设置, 优先级最高 diff --git a/src/main/java/org/redkale/source/CacheEventListener.java b/src/main/java/org/redkale/source/CacheEventListener.java new file mode 100644 index 000000000..6d004789a --- /dev/null +++ b/src/main/java/org/redkale/source/CacheEventListener.java @@ -0,0 +1,18 @@ +/* + * + */ +package org.redkale.source; + +/** + * CacheSource订阅频道的消费监听器 + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * @since 2.8.0 + */ +public interface CacheEventListener { + + public void onMessage(String topic, T message); +} diff --git a/src/main/java/org/redkale/source/CacheMemorySource.java b/src/main/java/org/redkale/source/CacheMemorySource.java index 397272ee6..5ae61a008 100644 --- a/src/main/java/org/redkale/source/CacheMemorySource.java +++ b/src/main/java/org/redkale/source/CacheMemorySource.java @@ -161,6 +161,26 @@ public final class CacheMemorySource extends AbstractCacheSource { return CompletableFuture.completedFuture(true); } + //------------------------ 订阅发布 SUB/PUB ------------------------ + @Override + public CompletableFuture> pubsubChannelsAsync(@Nullable String pattern){ + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CompletableFuture subscribeAsync(CacheEventListener consumer, String... topics) { + Objects.requireNonNull(consumer); + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CompletableFuture publishAsync(String topic, byte[] message) { + Objects.requireNonNull(topic); + Objects.requireNonNull(message); + throw new UnsupportedOperationException("Not supported yet."); + } + + //------------------------ 字符串 String ------------------------ @Override public CompletableFuture msetAsync(Serializable... keyVals) { return runFuture(() -> { diff --git a/src/main/java/org/redkale/source/CacheSource.java b/src/main/java/org/redkale/source/CacheSource.java index 9d65fb055..fd140bc6d 100644 --- a/src/main/java/org/redkale/source/CacheSource.java +++ b/src/main/java/org/redkale/source/CacheSource.java @@ -10,8 +10,9 @@ import java.lang.reflect.Type; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicLong; -import org.redkale.annotation.Component; +import org.redkale.annotation.*; import org.redkale.convert.Convert; +import org.redkale.convert.json.JsonConvert; import org.redkale.util.*; /** @@ -36,6 +37,79 @@ public interface CacheSource extends Resourcable { return isOpenAsync().join(); } + //------------------------ 订阅发布 SUB/PUB ------------------------ + default List pubsubChannels(@Nullable String pattern) { + return pubsubChannelsAsync(pattern).join(); + } + + public CompletableFuture> pubsubChannelsAsync(@Nullable String pattern); + + //------------------------ 订阅 SUB ------------------------ + default void subscribe(Type messageType, CacheEventListener listener, String... topics) { + subscribe(JsonConvert.root(), messageType, listener, topics); + } + + default void subscribe(Convert convert, Type messageType, CacheEventListener listener, String... topics) { + final Convert c = convert == null ? JsonConvert.root() : convert; + subscribe((t, bs) -> listener.onMessage(t, bs == null ? null : (T) c.convertFrom(messageType, bs)), topics); + } + + default void subscribe(CacheEventListener listener, String... topics) { + subscribeAsync(listener, topics).join(); + } + + default CompletableFuture subscribeAsync(Type messageType, CacheEventListener listener, String... topics) { + return subscribeAsync(JsonConvert.root(), messageType, listener, topics); + } + + default CompletableFuture subscribeAsync(Convert convert, Type messageType, CacheEventListener listener, String... topics) { + final Convert c = convert == null ? JsonConvert.root() : convert; + return subscribeAsync((t, bs) -> listener.onMessage(t, bs == null ? null : (T) c.convertFrom(messageType, bs)), topics); + } + + public CompletableFuture subscribeAsync(CacheEventListener listener, String... topics); + + //------------------------ 发布 PUB ------------------------ + default int publish(String topic, T message) { + return publish(topic, JsonConvert.root(), message.getClass(), message); + } + + default int publish(String topic, Convert convert, T message) { + return publish(topic, convert, message.getClass(), message); + } + + default int publish(String topic, Type messageType, T message) { + return publish(topic, JsonConvert.root(), messageType, message); + } + + default int publish(String topic, Convert convert, Type messageType, T message) { + final Convert c = convert == null ? JsonConvert.root() : convert; + return publish(topic, c.convertToBytes(messageType, message)); + } + + default int publish(String topic, byte[] message) { + return publishAsync(topic, message).join(); + } + + default CompletableFuture publishAsync(String topic, T message) { + return publishAsync(topic, JsonConvert.root(), message.getClass(), message); + } + + default CompletableFuture publishAsync(String topic, Convert convert, T message) { + return publishAsync(topic, convert, message.getClass(), message); + } + + default CompletableFuture publishAsync(String topic, Type messageType, T message) { + return publishAsync(topic, JsonConvert.root(), messageType, message); + } + + default CompletableFuture publishAsync(String topic, Convert convert, Type messageType, T message) { + final Convert c = convert == null ? JsonConvert.root() : convert; + return publishAsync(topic, c.convertToBytes(messageType, message)); + } + + public CompletableFuture publishAsync(String topic, byte[] message); + //------------------------ 字符串 String ------------------------ default long incr(String key) { return incrAsync(key).join(); diff --git a/src/main/java/org/redkale/util/CronExpression.java b/src/main/java/org/redkale/util/CronExpression.java new file mode 100644 index 000000000..0eb1befcd --- /dev/null +++ b/src/main/java/org/redkale/util/CronExpression.java @@ -0,0 +1,643 @@ +/* + * + */ +package org.redkale.util; + +import java.time.DateTimeException; +import java.time.temporal.*; +import java.util.*; +import org.redkale.annotation.Nullable; + +/** + * cron定时表达式解析器
代码复制于org.springframework.scheduling.support.CronExpression + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * @since 2.8.0 + */ +public class CronExpression { + + static final int MAX_ATTEMPTS = 366; + + private static final String[] MACROS = new String[]{ + "@yearly", "0 0 0 1 1 *", + "@annually", "0 0 0 1 1 *", + "@monthly", "0 0 0 1 * *", + "@weekly", "0 0 0 * * 0", + "@daily", "0 0 0 * * *", + "@midnight", "0 0 0 * * *", + "@hourly", "0 0 * * * *" + }; + + private final CronField[] fields; + + private final String expression; + + private CronExpression(CronField seconds, CronField minutes, CronField hours, + CronField daysOfMonth, CronField months, CronField daysOfWeek, String expression) { + this.fields = new CronField[]{daysOfWeek, months, daysOfMonth, hours, minutes, seconds, CronField.zeroNanos()}; + this.expression = expression; + } + + public static CronExpression parse(String expression) { + if (Utility.isBlank(expression)) { + throw new RedkaleException("Expression string must not be empty"); + } + expression = resolveMacros(expression); + String[] fields = expression.split("\\s+"); + if (fields.length != 6) { + throw new RedkaleException(String.format("Cron expression must consist of 6 fields (found %d in \"%s\")", fields.length, expression)); + } + try { + CronField seconds = CronField.parseSeconds(fields[0]); + CronField minutes = CronField.parseMinutes(fields[1]); + CronField hours = CronField.parseHours(fields[2]); + CronField daysOfMonth = CronField.parseDaysOfMonth(fields[3]); + CronField months = CronField.parseMonth(fields[4]); + CronField daysOfWeek = CronField.parseDaysOfWeek(fields[5]); + return new CronExpression(seconds, minutes, hours, daysOfMonth, months, daysOfWeek, expression); + } catch (IllegalArgumentException ex) { + String msg = ex.getMessage() + " in cron expression \"" + expression + "\""; + throw new RedkaleException(msg, ex); + } + } + + private static String resolveMacros(String expression) { + expression = expression.trim(); + for (int i = 0; i < MACROS.length; i = i + 2) { + if (MACROS[i].equalsIgnoreCase(expression)) { + return MACROS[i + 1]; + } + } + return expression; + } + + @Nullable + public > T next(T temporal) { + return nextOrSame(ChronoUnit.NANOS.addTo(temporal, 1)); + } + + @Nullable + private > T nextOrSame(T temporal) { + for (int i = 0; i < MAX_ATTEMPTS; i++) { + T result = nextOrSameInternal(temporal); + if (result == null || result.equals(temporal)) { + return result; + } + temporal = result; + } + return null; + } + + @Nullable + private > T nextOrSameInternal(T temporal) { + for (CronField field : this.fields) { + temporal = field.nextOrSame(temporal); + if (temporal == null) { + return null; + } + } + return temporal; + } + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (!(other instanceof CronExpression)) { + return false; + } + CronExpression that = (CronExpression) other; + return Arrays.equals(this.fields, that.fields); + } + + @Override + public int hashCode() { + return Arrays.hashCode(this.fields); + } + + @Override + public String toString() { + return this.expression; + } + + abstract static class CronField { + + private static final String[] MONTHS = new String[]{"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", + "OCT", "NOV", "DEC"}; + + private static final String[] DAYS = new String[]{"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"}; + + private final Type type; + + protected CronField(Type type) { + this.type = type; + } + + public static CronField zeroNanos() { + return BitsCronField.zeroNanos(); + } + + public static CronField parseSeconds(String value) { + return BitsCronField.parseSeconds(value); + } + + public static CronField parseMinutes(String value) { + return BitsCronField.parseMinutes(value); + } + + public static CronField parseHours(String value) { + return BitsCronField.parseHours(value); + } + + public static CronField parseDaysOfMonth(String value) { + return BitsCronField.parseDaysOfMonth(value); + } + + public static CronField parseMonth(String value) { + value = replaceOrdinals(value, MONTHS); + return BitsCronField.parseMonth(value); + } + + public static CronField parseDaysOfWeek(String value) { + value = replaceOrdinals(value, DAYS); + return BitsCronField.parseDaysOfWeek(value); + } + + private static String replaceOrdinals(String value, String[] list) { + value = value.toUpperCase(); + for (int i = 0; i < list.length; i++) { + String replacement = Integer.toString(i + 1); + value = replace(value, list[i], replacement); + } + return value; + } + + private static String replace(String inString, String oldPattern, @Nullable String newPattern) { + if (Utility.isEmpty(inString) || Utility.isEmpty(oldPattern) || newPattern == null) { + return inString; + } + int index = inString.indexOf(oldPattern); + if (index == -1) { + // no occurrence -> can return input as-is + return inString; + } + + int capacity = inString.length(); + if (newPattern.length() > oldPattern.length()) { + capacity += 16; + } + StringBuilder sb = new StringBuilder(capacity); + + int pos = 0; // our position in the old string + int patLen = oldPattern.length(); + while (index >= 0) { + sb.append(inString, pos, index); + sb.append(newPattern); + pos = index + patLen; + index = inString.indexOf(oldPattern, pos); + } + + // append any characters to the right of a match + sb.append(inString, pos, inString.length()); + return sb.toString(); + } + + private static String[] delimitedListToStringArray(@Nullable String str, @Nullable String delimiter) { + return delimitedListToStringArray(str, delimiter, null); + } + + private static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) { + + if (str == null) { + return new String[0]; + } + if (delimiter == null) { + return new String[]{str}; + } + + List result = new ArrayList<>(); + if (delimiter.isEmpty()) { + for (int i = 0; i < str.length(); i++) { + result.add(deleteAny(str.substring(i, i + 1), charsToDelete)); + } + } else { + int pos = 0; + int delPos; + while ((delPos = str.indexOf(delimiter, pos)) != -1) { + result.add(deleteAny(str.substring(pos, delPos), charsToDelete)); + pos = delPos + delimiter.length(); + } + if (str.length() > 0 && pos <= str.length()) { + // Add rest of String, but not in case of empty input. + result.add(deleteAny(str.substring(pos), charsToDelete)); + } + } + return result.toArray(new String[result.size()]); + } + + private static String deleteAny(String inString, @Nullable String charsToDelete) { + if (Utility.isEmpty(inString) || Utility.isEmpty(charsToDelete)) { + return inString; + } + int lastCharIndex = 0; + char[] result = new char[inString.length()]; + for (int i = 0; i < inString.length(); i++) { + char c = inString.charAt(i); + if (charsToDelete.indexOf(c) == -1) { + result[lastCharIndex++] = c; + } + } + if (lastCharIndex == inString.length()) { + return inString; + } + return new String(result, 0, lastCharIndex); + } + + @Nullable + public abstract > T nextOrSame(T temporal); + + protected Type type() { + return this.type; + } + + @SuppressWarnings("unchecked") + protected static > T cast(Temporal temporal) { + return (T) temporal; + } + + protected enum Type { + NANO(ChronoField.NANO_OF_SECOND, ChronoUnit.SECONDS), + SECOND(ChronoField.SECOND_OF_MINUTE, ChronoUnit.MINUTES, ChronoField.NANO_OF_SECOND), + MINUTE(ChronoField.MINUTE_OF_HOUR, ChronoUnit.HOURS, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND), + HOUR(ChronoField.HOUR_OF_DAY, ChronoUnit.DAYS, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND), + DAY_OF_MONTH(ChronoField.DAY_OF_MONTH, ChronoUnit.MONTHS, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND), + MONTH(ChronoField.MONTH_OF_YEAR, ChronoUnit.YEARS, ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND), + DAY_OF_WEEK(ChronoField.DAY_OF_WEEK, ChronoUnit.WEEKS, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND); + + private final ChronoField field; + + private final ChronoUnit higherOrder; + + private final ChronoField[] lowerOrders; + + Type(ChronoField field, ChronoUnit higherOrder, ChronoField... lowerOrders) { + this.field = field; + this.higherOrder = higherOrder; + this.lowerOrders = lowerOrders; + } + + public int get(Temporal date) { + return date.get(this.field); + } + + public ValueRange range() { + return this.field.range(); + } + + public int checkValidValue(int value) { + if (this == DAY_OF_WEEK && value == 0) { + return value; + } else { + try { + return this.field.checkValidIntValue(value); + } catch (DateTimeException ex) { + throw new IllegalArgumentException(ex.getMessage(), ex); + } + } + } + + public > T elapseUntil(T temporal, int goal) { + int current = get(temporal); + ValueRange range = temporal.range(this.field); + if (current < goal) { + if (range.isValidIntValue(goal)) { + return cast(temporal.with(this.field, goal)); + } else { + // goal is invalid, eg. 29th Feb, so roll forward + long amount = range.getMaximum() - current + 1; + return this.field.getBaseUnit().addTo(temporal, amount); + } + } else { + long amount = goal + range.getMaximum() - current + 1 - range.getMinimum(); + return this.field.getBaseUnit().addTo(temporal, amount); + } + } + + public > T rollForward(T temporal) { + T result = this.higherOrder.addTo(temporal, 1); + ValueRange range = result.range(this.field); + return this.field.adjustInto(result, range.getMinimum()); + } + + public T reset(T temporal) { + for (ChronoField lowerOrder : this.lowerOrders) { + if (temporal.isSupported(lowerOrder)) { + temporal = lowerOrder.adjustInto(temporal, temporal.range(lowerOrder).getMinimum()); + } + } + return temporal; + } + + @Override + public String toString() { + return this.field.toString(); + } + } + + } + + static class BitsCronField extends CronField { + + private static final long MASK = 0xFFFFFFFFFFFFFFFFL; + + @Nullable + private static BitsCronField zeroNanos = null; + + // we store at most 60 bits, for seconds and minutes, so a 64-bit long suffices + private long bits; + + private BitsCronField(Type type) { + super(type); + } + + public static BitsCronField zeroNanos() { + if (zeroNanos == null) { + BitsCronField field = new BitsCronField(Type.NANO); + field.setBit(0); + zeroNanos = field; + } + return zeroNanos; + } + + public static BitsCronField parseSeconds(String value) { + return parseField(value, Type.SECOND); + } + + public static BitsCronField parseMinutes(String value) { + return BitsCronField.parseField(value, Type.MINUTE); + } + + public static BitsCronField parseHours(String value) { + return BitsCronField.parseField(value, Type.HOUR); + } + + public static BitsCronField parseDaysOfMonth(String value) { + return parseDate(value, Type.DAY_OF_MONTH); + } + + public static BitsCronField parseMonth(String value) { + return BitsCronField.parseField(value, Type.MONTH); + } + + public static BitsCronField parseDaysOfWeek(String value) { + BitsCronField result = parseDate(value, Type.DAY_OF_WEEK); + if (result.getBit(0)) { + // cron supports 0 for Sunday; we use 7 like java.time + result.setBit(7); + result.clearBit(0); + } + return result; + } + + private static BitsCronField parseDate(String value, BitsCronField.Type type) { + if (value.equals("?")) { + value = "*"; + } + return BitsCronField.parseField(value, type); + } + + private static BitsCronField parseField(String value, Type type) { + if (Utility.isBlank(value)) { + throw new RedkaleException("Value must not be empty"); + } + if (type == null) { + throw new RedkaleException("Type must not be null"); + } + try { + BitsCronField result = new BitsCronField(type); + String[] fields = CronField.delimitedListToStringArray(value, ","); + for (String field : fields) { + int slashPos = field.indexOf('/'); + if (slashPos == -1) { + ValueRange range = parseRange(field, type); + result.setBits(range); + } else { + String rangeStr = field.substring(0, slashPos); + String deltaStr = field.substring(slashPos + 1); + ValueRange range = parseRange(rangeStr, type); + if (rangeStr.indexOf('-') == -1) { + range = ValueRange.of(range.getMinimum(), type.range().getMaximum()); + } + int delta = Integer.parseInt(deltaStr); + if (delta <= 0) { + throw new IllegalArgumentException("Incrementer delta must be 1 or higher"); + } + result.setBits(range, delta); + } + } + return result; + } catch (DateTimeException | IllegalArgumentException ex) { + String msg = ex.getMessage() + " '" + value + "'"; + throw new IllegalArgumentException(msg, ex); + } + } + + private static ValueRange parseRange(String value, Type type) { + if (value.equals("*")) { + return type.range(); + } else { + int hyphenPos = value.indexOf('-'); + if (hyphenPos == -1) { + int result = type.checkValidValue(Integer.parseInt(value)); + return ValueRange.of(result, result); + } else { + int min = Integer.parseInt(value, 0, hyphenPos, 10); + int max = Integer.parseInt(value, hyphenPos + 1, value.length(), 10); + min = type.checkValidValue(min); + max = type.checkValidValue(max); + if (type == Type.DAY_OF_WEEK && min == 7) { + // If used as a minimum in a range, Sunday means 0 (not 7) + min = 0; + } + return ValueRange.of(min, max); + } + } + } + + @Nullable + @Override + public > T nextOrSame(T temporal) { + int current = type().get(temporal); + int next = nextSetBit(current); + if (next == -1) { + temporal = type().rollForward(temporal); + next = nextSetBit(0); + } + if (next == current) { + return temporal; + } else { + int count = 0; + current = type().get(temporal); + while (current != next && count++ < CronExpression.MAX_ATTEMPTS) { + temporal = type().elapseUntil(temporal, next); + current = type().get(temporal); + next = nextSetBit(current); + if (next == -1) { + temporal = type().rollForward(temporal); + next = nextSetBit(0); + } + } + if (count >= CronExpression.MAX_ATTEMPTS) { + return null; + } + return type().reset(temporal); + } + } + + boolean getBit(int index) { + return (this.bits & (1L << index)) != 0; + } + + private int nextSetBit(int fromIndex) { + long result = this.bits & (MASK << fromIndex); + if (result != 0) { + return Long.numberOfTrailingZeros(result); + } else { + return -1; + } + + } + + private void setBits(ValueRange range) { + if (range.getMinimum() == range.getMaximum()) { + setBit((int) range.getMinimum()); + } else { + long minMask = MASK << range.getMinimum(); + long maxMask = MASK >>> -(range.getMaximum() + 1); + this.bits |= (minMask & maxMask); + } + } + + private void setBits(ValueRange range, int delta) { + if (delta == 1) { + setBits(range); + } else { + for (int i = (int) range.getMinimum(); i <= range.getMaximum(); i += delta) { + setBit(i); + } + } + } + + private void setBit(int index) { + this.bits |= (1L << index); + } + + private void clearBit(int index) { + this.bits &= ~(1L << index); + } + + @Override + public int hashCode() { + return Long.hashCode(this.bits); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BitsCronField)) { + return false; + } + BitsCronField other = (BitsCronField) o; + return type() == other.type() && this.bits == other.bits; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(type().toString()); + builder.append(" {"); + int i = nextSetBit(0); + if (i != -1) { + builder.append(i); + i = nextSetBit(i + 1); + while (i != -1) { + builder.append(", "); + builder.append(i); + i = nextSetBit(i + 1); + } + } + builder.append('}'); + return builder.toString(); + } + + } + + static class CompositeCronField extends CronField { + + private final CronField[] fields; + + private final String value; + + private CompositeCronField(Type type, CronField[] fields, String value) { + super(type); + this.fields = fields; + this.value = value; + } + + public static CronField compose(CronField[] fields, Type type, String value) { + if (fields == null || fields.length < 1) { + throw new RedkaleException("Fields must not be empty"); + } + if (Utility.isBlank(value)) { + throw new RedkaleException("Value must not be empty"); + } + if (fields.length == 1) { + return fields[0]; + } else { + return new CompositeCronField(type, fields, value); + } + } + + @Nullable + @Override + public > T nextOrSame(T temporal) { + T result = null; + for (CronField field : this.fields) { + T candidate = field.nextOrSame(temporal); + if (result == null + || candidate != null && candidate.compareTo(result) < 0) { + result = candidate; + } + } + return result; + } + + @Override + public int hashCode() { + return this.value.hashCode(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CompositeCronField)) { + return false; + } + CompositeCronField other = (CompositeCronField) o; + return type() == other.type() && this.value.equals(other.value); + } + + @Override + public String toString() { + return type() + " '" + this.value + "'"; + } + } + +} diff --git a/src/test/java/org/redkale/test/convert/Json5Test.java b/src/test/java/org/redkale/test/convert/Json5Test.java index 4302ecd85..e7775fb29 100644 --- a/src/test/java/org/redkale/test/convert/Json5Test.java +++ b/src/test/java/org/redkale/test/convert/Json5Test.java @@ -4,6 +4,7 @@ package org.redkale.test.convert; import java.util.*; import org.junit.jupiter.api.*; +import org.redkale.convert.ConvertFactory; import org.redkale.convert.json.*; /** @@ -23,7 +24,7 @@ public class Json5Test { @Test public void run1() throws Exception { - JsonFactory factory = JsonFactory.root().tiny(true).nullable(true); + JsonFactory factory = JsonFactory.root().features(ConvertFactory.FEATURE_TINY | ConvertFactory.FEATURE_NULLABLE); final JsonConvert convert = factory.getConvert(); Json5Bean bean = new Json5Bean(); bean.id = 60; diff --git a/src/test/java/org/redkale/test/convert/JsonMainTest.java b/src/test/java/org/redkale/test/convert/JsonMainTest.java index aca35ba2c..f87cdf4bd 100644 --- a/src/test/java/org/redkale/test/convert/JsonMainTest.java +++ b/src/test/java/org/redkale/test/convert/JsonMainTest.java @@ -9,6 +9,7 @@ import java.io.*; import java.nio.ByteBuffer; import java.util.Map; import org.junit.jupiter.api.*; +import org.redkale.convert.ConvertFactory; import org.redkale.convert.json.*; /** @@ -32,7 +33,7 @@ public class JsonMainTest { @Test public void run1() throws Throwable { - JsonFactory factory = JsonFactory.root().tiny(true); + JsonFactory factory = JsonFactory.root().features(ConvertFactory.FEATURE_TINY); final JsonConvert convert = JsonConvert.root(); String json = "{\"access_token\":\"null\",\"priv\":null, vvv:nulla,\"priv2\":\"nulla\",\"expires_in\":7200, \"aa\":\"\"}"; Map map = convert.convertFrom(JsonConvert.TYPE_MAP_STRING_STRING, json); @@ -53,7 +54,7 @@ public class JsonMainTest { SimpleChildEntity entry = SimpleChildEntity.create(); String json = convert.convertTo(SimpleEntity.class, entry); System.out.println("长度: " + json.length()); - JsonByteBufferWriter writer = new JsonByteBufferWriter(false, false, () -> ByteBuffer.allocate(1)) { + JsonByteBufferWriter writer = new JsonByteBufferWriter(0, () -> ByteBuffer.allocate(1)) { }; convert.convertTo(writer, SimpleEntity.class, entry); ByteBuffer[] buffers = writer.toBuffers(); diff --git a/src/test/java/org/redkale/test/convert/TinyTest.java b/src/test/java/org/redkale/test/convert/TinyTest.java index 89274d5e4..96471ada0 100644 --- a/src/test/java/org/redkale/test/convert/TinyTest.java +++ b/src/test/java/org/redkale/test/convert/TinyTest.java @@ -3,6 +3,7 @@ package org.redkale.test.convert; import org.junit.jupiter.api.*; +import org.redkale.convert.ConvertFactory; import org.redkale.convert.json.*; /** @@ -24,17 +25,21 @@ public class TinyTest { TinyRecord record = new TinyRecord(); record.id = 5; { - JsonFactory factory = JsonFactory.create().tiny(true); + JsonFactory factory = JsonFactory.create().features(ConvertFactory.FEATURE_TINY); JsonConvert convert = factory.getConvert(); String json = "{\"id\":5}"; - if (!main) Assertions.assertEquals(json, convert.convertTo(record)); + if (!main) { + Assertions.assertEquals(json, convert.convertTo(record)); + } System.out.println(convert.convertTo(record)); } { - JsonFactory factory = JsonFactory.create().tiny(false); + JsonFactory factory = JsonFactory.create().features(0); JsonConvert convert = factory.getConvert(); String json = "{\"id\":5,\"name\":\"\"}"; - if (!main) Assertions.assertEquals(json, convert.convertTo(record)); + if (!main) { + Assertions.assertEquals(json, convert.convertTo(record)); + } System.out.println(convert.convertTo(record)); } }